diff --git a/board.py b/board.py new file mode 100644 index 0000000..28d78b1 --- /dev/null +++ b/board.py @@ -0,0 +1,52 @@ +"""Contains board class""" +import enum +from typing import Union, Tuple, List +from dataclasses import dataclass + + +class SpecialCard(enum.Enum): + """Different types of special cards""" + Zhong = enum.auto() + Bai = enum.auto() + Fa = enum.auto() + Hua = enum.auto() + + +@dataclass(frozen=True) +class NumberCard: + """Different number cards""" + class Suit(enum.Enum): + """Different colors number cards can have""" + Red = enum.auto() + Green = enum.auto() + Black = enum.auto() + suit: Suit + number: int + + +Card = Union[NumberCard, SpecialCard] + + +class Position(enum.Enum): + """Possible Board positions""" + Field = enum.auto() + Bunker = enum.auto() + 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() + + +@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]] = [] + flowerGone: bool = False diff --git a/board_actions.py b/board_actions.py new file mode 100644 index 0000000..fcb83fd --- /dev/null +++ b/board_actions.py @@ -0,0 +1,31 @@ +"""Contains actions that can be used on the board""" +from typing import List, Tuple, Union +from dataclasses import dataclass +import board + + +@dataclass +class MoveAction: + """Moving a card from one stack to another""" + card: board.Card + source_position: board.Position + source_id: int + destination_position: board.Position + destination_id: int + + +@dataclass +class DragonKillAction: + """Removing four dragons from the top of the stacks to a bunker""" + dragon: board.SpecialCard + source_stacks: List[Tuple[board.Position, int]] + destination_bunker_id: int + + +@dataclass +class HuaKillAction: + """Remove the flower card""" + source_field_id: int + + +Action = Union[MoveAction, DragonKillAction, HuaKillAction] diff --git a/board_possibilities.py b/board_possibilities.py new file mode 100644 index 0000000..a549e13 --- /dev/null +++ b/board_possibilities.py @@ -0,0 +1,122 @@ +"""Contains function to iterate different kinds of possible actions""" +from typing import Iterator +import board +import board_actions + + +def possible_huakill_action(search_board: board.Board) -> Iterator[board_actions.HuaKillAction]: + """Check if the flowercard can be eliminated""" + for index, stack in enumerate(search_board.field): + if stack[-1] == board.SpecialCard.Hua: + yield board_actions.HuaKillAction(source_field_id=index) + + +def possible_dragonkill_actions( + search_board: board.Board) -> Iterator[board_actions.DragonKillAction]: + """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): + new_possible_dragons = [] + for dragon in possible_dragons: + if any(x == dragon for x in search_board.bunker): + new_possible_dragons.append(dragon) + possible_dragons = new_possible_dragons + + for dragon in possible_dragons: + bunker_dragons = [i for i, d in enumerate( + search_board.bunker) if d == dragon] + field_dragons = [i for i, f in enumerate( + search_board.field) if f if f[-1] == dragon] + if len(bunker_dragons) + len(field_dragons) != 4: + continue + destination_bunker_id = 0 + if bunker_dragons: + 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] + + source_stacks = [(board.Position.Bunker, i) for i in bunker_dragons] + source_stacks.extend([(board.Position.Field, i) + for i in field_dragons]) + + yield board_actions.DragonKillAction(dragon=dragon, source_stacks=source_stacks, + destination_bunker_id=destination_bunker_id) + + +def possible_bunkerize_actions(search_board: board.Board) -> Iterator[board_actions.MoveAction]: + """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] + + if not open_bunker_list: + return + + open_bunker = open_bunker_list[0] + 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) + + +def possible_debunkerize_actions(search_board: board.Board) -> Iterator[board_actions.MoveAction]: + """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)] + for index, card in bunker_number_cards: + for other_index, other_stack in enumerate(search_board.field): + if not other_stack: + continue + if not isinstance(other_stack[-1], board.NumberCard): + continue + if other_stack[-1].suit == card.suit: + 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) + + +def possible_goal_move_actions(search_board: board.Board) -> Iterator[board_actions.MoveAction]: + """Enumerates all possible moves from anywhere to the goal""" + #TODO + + +def possible_field_move_actions(search_board: board.Board) -> Iterator[board_actions.MoveAction]: + """Enumerate all possible move actions from one field stack to another field stack""" + for index, stack in enumerate(search_board.field): + if not stack: + continue + if not isinstance(stack[-1], board.NumberCard): + continue + for other_index, other_stack in enumerate(search_board.field): + if not other_stack: + continue + if not isinstance(other_stack[-1], board.NumberCard): + continue + if other_stack[-1].suit == stack[-1].suit: + continue + 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) + + +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_debunkerize_actions(search_board) + yield from possible_field_move_actions(search_board) + yield from possible_bunkerize_actions(search_board) diff --git a/main.py b/main.py index 9b22dd8..92d56cf 100644 --- a/main.py +++ b/main.py @@ -1,195 +1,10 @@ """Main module""" -from typing import List, Union, Tuple, Iterator -import enum -from dataclasses import dataclass - - -class SpecialCard(enum.Enum): - """Different types of special cards""" - Zhong = enum.auto() - Bai = enum.auto() - Fa = enum.auto() - Hua = enum.auto() - - -@dataclass(frozen=True) -class NumberCard: - """Different number cards""" - class Suit(enum.Enum): - """Different colors number cards can have""" - Red = enum.auto() - Green = enum.auto() - Black = enum.auto() - suit: Suit - number: int - - -Card = Union[NumberCard, SpecialCard] - - -class Position(enum.Enum): - """Possible Board positions""" - Field = enum.auto() - Bunker = enum.auto() - Goal = enum.auto() - - -@dataclass -class MoveAction: - """Moving a card from one stack to another""" - card: Card - source_position: Position - source_id: int - destination_position: Position - destination_id: int - - -@dataclass -class DragonKillAction: - """Removing four dragons from the top of the stacks to a bunker""" - dragon: SpecialCard - source_stacks: List[Tuple[Position, int]] - destination_bunker_id: int - - -@dataclass -class HuaKillAction: - """Remove the flower card""" - source_field_id: int - - -Action = Union[MoveAction, DragonKillAction, HuaKillAction] - - -class BunkerStatus(enum.Enum): - """States a bunker can be in, if it not holding a card""" - Empty = enum.auto() - Dragon = enum.auto() - - -@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]] = [] - flowerGone: bool = False - - def possible_huakill_action(self) -> Iterator[HuaKillAction]: - """Check if the flowercard can be eliminated""" - for index, stack in enumerate(self.field): - if stack[-1] == SpecialCard.Hua: - yield HuaKillAction(source_field_id=index) - - def possible_dragonkill_actions(self) -> Iterator[DragonKillAction]: - """Enumerate all possible dragon kills""" - possible_dragons = [SpecialCard.Zhong, SpecialCard.Fa, SpecialCard.Bai] - if not any(x == BunkerStatus.Empty for x in self.bunker): - new_possible_dragons = [] - for dragon in possible_dragons: - if any(x == dragon for x in self.bunker): - new_possible_dragons.append(dragon) - possible_dragons = new_possible_dragons - - for dragon in possible_dragons: - bunker_dragons = [i for i, d in enumerate( - self.bunker) if d == dragon] - field_dragons = [i for i, f in enumerate( - self.field) if f if f[-1] == dragon] - if len(bunker_dragons) + len(field_dragons) != 4: - continue - destination_bunker_id = 0 - if bunker_dragons: - destination_bunker_id = bunker_dragons[0] - else: - destination_bunker_id = [ - i for i, x in enumerate(self.bunker) if x == BunkerStatus.Empty][0] - - source_stacks = [(Position.Bunker, i) for i in bunker_dragons] - source_stacks.extend([(Position.Field, i) for i in field_dragons]) - - yield DragonKillAction(dragon=dragon, source_stacks=source_stacks, - destination_bunker_id=destination_bunker_id) - - def possible_bunkerize_actions(self) -> Iterator[MoveAction]: - """Enumerates all possible card moves from the field to the bunker""" - open_bunker_list = [i for i, x in enumerate( - self.bunker) if x == BunkerStatus.Empty] - - if not open_bunker_list: - return - - open_bunker = open_bunker_list[0] - for index, stack in enumerate(self.field): - if not stack: - continue - yield MoveAction(card=stack[-1], - source_position=Position.Field, - source_id=index, - destination_position=Position.Bunker, - destination_id=open_bunker) - - def possible_debunkerize_actions(self) -> Iterator[MoveAction]: - """Enumerates all possible card moves from the bunker to the field""" - bunker_number_cards = [(i, x) for i, x in enumerate( - self.bunker) if isinstance(x, NumberCard)] - for index, card in bunker_number_cards: - for other_index, other_stack in enumerate(self.field): - if not other_stack: - continue - if not isinstance(other_stack[-1], NumberCard): - continue - if other_stack[-1].suit == card.suit: - continue - if other_stack[-1].number != card.number + 1: - continue - yield MoveAction(card=card, - source_position=Position.Bunker, - source_id=index, - destination_position=Position.Field, - destination_id=other_index) - - def possible_goal_move_actions(self) -> Iterator[MoveAction]: - """Enumerates all possible moves from anywhere to the goal""" - #TODO - - - - def possible_field_move_actions(self) -> Iterator[MoveAction]: - """Enumerate all possible move actions from one field stack to another field stack""" - for index, stack in enumerate(self.field): - if not stack: - continue - if not isinstance(stack[-1], NumberCard): - continue - for other_index, other_stack in enumerate(self.field): - if not other_stack: - continue - if not isinstance(other_stack[-1], NumberCard): - continue - if other_stack[-1].suit == stack[-1].suit: - continue - if other_stack[-1].number != stack[-1].number + 1: - continue - yield MoveAction(card=stack[-1], - source_position=Position.Field, - source_id=index, - destination_position=Position.Field, - destination_id=other_index) - - def possible_actions(self) -> Iterator[Action]: - """Enumerate all possible actions on the current board""" - yield from self.possible_huakill_action() - yield from self.possible_dragonkill_actions() - yield from self.possible_debunkerize_actions() - yield from self.possible_field_move_actions() - yield from self.possible_bunkerize_actions() +from typing import List, Tuple +import board +import board_actions class SolitaireSolver: """Solver for Shenzhen Solitaire""" - board: Board - stack: List[Tuple[Action, int]] + search_board: board.Board + stack: List[Tuple[board_actions.Action, int]]