From 208c2c83a4c38f6baf1298c032194eeda272e61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20W=C3=B6lfer?= Date: Mon, 8 Apr 2019 19:54:16 +0200 Subject: [PATCH] Implemented actions --- board.py | 19 +++---- board_actions.py | 116 ++++++++++++++++++++++++++++++++++++++--- board_possibilities.py | 46 +++++++++------- 3 files changed, 144 insertions(+), 37 deletions(-) diff --git a/board.py b/board.py index 28d78b1..1526ef8 100644 --- a/board.py +++ b/board.py @@ -1,6 +1,6 @@ """Contains board class""" import enum -from typing import Union, Tuple, List +from typing import Union, List, Dict, Optional, NewType from dataclasses import dataclass @@ -34,19 +34,14 @@ class Position(enum.Enum): Goal = enum.auto() -class BunkerStatus(enum.Enum): - """States a bunker can be in, if it not holding a card""" - Empty = enum.auto() - Dragon = enum.auto() - +KilledDragon = NewType('KilledDragon', SpecialCard) @dataclass class Board: """Solitaire board""" - field: List[List[Card]] = [] - bunker: Tuple[Union[BunkerStatus, Card], - Union[BunkerStatus, Card], - Union[BunkerStatus, Card]] = (BunkerStatus.Empty, - BunkerStatus.Empty, BunkerStatus.Empty,) - goal: List[Tuple[NumberCard.Suit, int]] = [] + field: List[List[Card]] = [[]] * 8 + bunker: List[Union[KilledDragon, Optional[Card]]] = [None] * 3 + goal: Dict[NumberCard.Suit, int] = {NumberCard.Suit.Red: 0, + NumberCard.Suit.Green: 0, + NumberCard.Suit.Black: 0} flowerGone: bool = False diff --git a/board_actions.py b/board_actions.py index fcb83fd..7005b29 100644 --- a/board_actions.py +++ b/board_actions.py @@ -5,14 +5,72 @@ import board @dataclass -class MoveAction: - """Moving a card from one stack to another""" - card: board.Card - source_position: board.Position +class GoalAction: + """Move card from field to goal""" + card: board.NumberCard + source_id: int + source_position: board.Position + + def apply(self, action_board: board.Board) -> None: + """Do action""" + if self.source_position == board.Position.Field: + assert action_board.field[self.source_id][-1] == self.card + assert action_board.goal[self.card.suit] + 1 == self.card.number + action_board.field[self.source_id].pop() + action_board.goal[self.card.suit] += 1 + elif self.source_position == board.Position.Bunker: + assert action_board.bunker[self.source_id] == self.card + assert action_board.goal[self.card.suit] + 1 == self.card.number + action_board.bunker[self.source_id] = None + action_board.goal[self.card.suit] += 1 + else: + raise RuntimeError("Unknown position") + + def undo(self, action_board: board.Board) -> None: + """Undo action""" + assert action_board.goal[self.card.suit] == self.card.number + if self.source_position == board.Position.Field: + action_board.field[self.source_id].append(self.card) + elif self.source_position == board.Position.Bunker: + assert action_board.bunker[self.source_id] is None + action_board.bunker[self.source_id] = self.card + else: + raise RuntimeError("Unknown position") + action_board.goal[self.card.suit] -= 1 + + +@dataclass +class RestoreAction: + """Move card from bunker to field""" + card: board.Card source_id: int - destination_position: board.Position destination_id: int + def apply(self, action_board: board.Board) -> None: + """Do action""" + assert action_board.bunker[self.source_id] == self.card + action_board.bunker[self.source_id] = None + + + +@dataclass +class StoreAction: + """Move card from field to bunker""" + card: board.Card + source_id: int + destination_id: int + + +@dataclass +class MoveAction: + """Moving a card from one field stack to another""" + card: board.Card + source_id: int + destination_id: int + + def apply(self, action_board: board.Board) -> None: + """Do action""" + @dataclass class DragonKillAction: @@ -21,11 +79,57 @@ class DragonKillAction: source_stacks: List[Tuple[board.Position, int]] destination_bunker_id: int + def apply(self, action_board: board.Board) -> None: + """Do action""" + assert (action_board.bunker[self.destination_bunker_id] is None or + action_board.bunker[self.destination_bunker_id] == self.dragon) + assert len(self.source_stacks) == 4 + for position, index in self.source_stacks: + if position == board.Position.Field: + assert action_board.field[index] + assert action_board.field[index][-1] == self.dragon + action_board.field[index].pop() + elif position == board.Position.Bunker: + assert action_board.bunker[index] == self.dragon + action_board.bunker[index] = None + else: + raise RuntimeError("Can only kill dragons in field and bunker") + action_board.bunker[self.destination_bunker_id] = board.KilledDragon( + self.dragon) + + def undo(self, action_board: board.Board) -> None: + """Undo action""" + assert action_board.bunker[self.destination_bunker_id] == board.KilledDragon( + self.dragon) + assert len(self.source_stacks) == 4 + for position, index in self.source_stacks: + if position == board.Position.Field: + action_board.field[index].append(self.dragon) + elif position == board.Position.Bunker: + action_board.bunker[index] = self.dragon + else: + raise RuntimeError("Can only kill dragons in field and bunker") + action_board.bunker[self.destination_bunker_id] = None + @dataclass class HuaKillAction: """Remove the flower card""" source_field_id: int + def apply(self, action_board: board.Board) -> None: + """Do action""" + assert not action_board.flowerGone + assert action_board.field[self.source_field_id] == board.SpecialCard.Hua + action_board.field[self.source_field_id].pop() + action_board.flowerGone = True -Action = Union[MoveAction, DragonKillAction, HuaKillAction] + def undo(self, action_board: board.Board) -> None: + """Undo action""" + assert action_board.flowerGone + action_board.field[self.source_field_id].append(board.SpecialCard.Hua) + action_board.flowerGone = False + + +Action = Union[MoveAction, DragonKillAction, + HuaKillAction, StoreAction, RestoreAction, GoalAction] diff --git a/board_possibilities.py b/board_possibilities.py index a549e13..ceb3bdb 100644 --- a/board_possibilities.py +++ b/board_possibilities.py @@ -16,7 +16,7 @@ def possible_dragonkill_actions( """Enumerate all possible dragon kills""" possible_dragons = [board.SpecialCard.Zhong, board.SpecialCard.Fa, board.SpecialCard.Bai] - if not any(x == board.BunkerStatus.Empty for x in search_board.bunker): + if not any(x is None for x in search_board.bunker): new_possible_dragons = [] for dragon in possible_dragons: if any(x == dragon for x in search_board.bunker): @@ -35,7 +35,7 @@ def possible_dragonkill_actions( destination_bunker_id = bunker_dragons[0] else: destination_bunker_id = [ - i for i, x in enumerate(search_board.bunker) if x == board.BunkerStatus.Empty][0] + i for i, x in enumerate(search_board.bunker) if x is None][0] source_stacks = [(board.Position.Bunker, i) for i in bunker_dragons] source_stacks.extend([(board.Position.Field, i) @@ -45,10 +45,10 @@ def possible_dragonkill_actions( destination_bunker_id=destination_bunker_id) -def possible_bunkerize_actions(search_board: board.Board) -> Iterator[board_actions.MoveAction]: +def possible_bunkerize_actions(search_board: board.Board) -> Iterator[board_actions.StoreAction]: """Enumerates all possible card moves from the field to the bunker""" open_bunker_list = [i for i, x in enumerate( - search_board.bunker) if x == board.BunkerStatus.Empty] + search_board.bunker) if x is None] if not open_bunker_list: return @@ -57,14 +57,13 @@ def possible_bunkerize_actions(search_board: board.Board) -> Iterator[board_acti for index, stack in enumerate(search_board.field): if not stack: continue - yield board_actions.MoveAction(card=stack[-1], - source_position=board.Position.Field, - source_id=index, - destination_position=board.Position.Bunker, - destination_id=open_bunker) + yield board_actions.StoreAction(card=stack[-1], + source_id=index, + destination_id=open_bunker) -def possible_debunkerize_actions(search_board: board.Board) -> Iterator[board_actions.MoveAction]: +def possible_debunkerize_actions( + search_board: board.Board) -> Iterator[board_actions.RestoreAction]: """Enumerates all possible card moves from the bunker to the field""" bunker_number_cards = [(i, x) for i, x in enumerate( search_board.bunker) if isinstance(x, board.NumberCard)] @@ -78,16 +77,26 @@ def possible_debunkerize_actions(search_board: board.Board) -> Iterator[board_ac continue if other_stack[-1].number != card.number + 1: continue - yield board_actions.MoveAction(card=card, - source_position=board.Position.Bunker, - source_id=index, - destination_position=board.Position.Field, - destination_id=other_index) + yield board_actions.RestoreAction(card=card, + source_id=index, + destination_id=other_index) -def possible_goal_move_actions(search_board: board.Board) -> Iterator[board_actions.MoveAction]: +def possible_goal_move_actions(search_board: board.Board) -> Iterator[board_actions.GoalAction]: """Enumerates all possible moves from anywhere to the goal""" - #TODO + field_cards = [(board.Position.Field, index, stack[-1]) for index, stack in enumerate( + search_board.field) if stack if isinstance(stack[-1], board.NumberCard)] + bunker_cards = [(board.Position.Bunker, index, stack) + for index, stack in enumerate(search_board.bunker) + if isinstance(stack, board.NumberCard)] + top_cards = field_cards + bunker_cards + + for suit, number in search_board.goal.items(): + for source, index, stack in top_cards: + if not (stack.suit == suit and stack.number == number + 1): + continue + yield board_actions.GoalAction(card=stack, source_id=index, source_position=source) + break def possible_field_move_actions(search_board: board.Board) -> Iterator[board_actions.MoveAction]: @@ -107,9 +116,7 @@ def possible_field_move_actions(search_board: board.Board) -> Iterator[board_act if other_stack[-1].number != stack[-1].number + 1: continue yield board_actions.MoveAction(card=stack[-1], - source_position=board.Position.Field, source_id=index, - destination_position=board.Position.Field, destination_id=other_index) @@ -117,6 +124,7 @@ def possible_actions(search_board: board.Board) -> Iterator[board_actions.Action """Enumerate all possible actions on the current search_board""" yield from possible_huakill_action(search_board) yield from possible_dragonkill_actions(search_board) + yield from possible_goal_move_actions(search_board) yield from possible_debunkerize_actions(search_board) yield from possible_field_move_actions(search_board) yield from possible_bunkerize_actions(search_board)