diff --git a/shenzhen_solitaire/board.py b/shenzhen_solitaire/board.py index a8565c8..710410c 100644 --- a/shenzhen_solitaire/board.py +++ b/shenzhen_solitaire/board.py @@ -8,10 +8,14 @@ import itertools class SpecialCard(enum.Enum): """Different types of special cards""" - Zhong = enum.auto() - Bai = enum.auto() - Fa = enum.auto() - Hua = enum.auto() + Zhong = 0 + Bai = 1 + Fa = 2 + Hua = 3 + + def identifier(self) -> int: + """Returns unique identifier representing this card""" + return int(self.value) @dataclass(frozen=True) @@ -21,12 +25,16 @@ class NumberCard: class Suit(enum.Enum): """Different colors number cards can have""" - Red = enum.auto() - Green = enum.auto() - Black = enum.auto() + Red = 0 + Green = 1 + Black = 2 suit: Suit - number: int + number: int # [1 - 9] + + def identifier(self) -> int: + """Returns unique identifier representing this card""" + return int(self.number - 1 + 9 ** int(self.suit.value)) Card = Union[NumberCard, SpecialCard] @@ -45,14 +53,52 @@ class Board: def __init__(self) -> None: self.field: List[List[Card]] = [[]] * 8 - self.bunker: List[Union[Tuple[SpecialCard, int], Optional[Card]]] = [None] * 3 + self.bunker: List[Union[Tuple[SpecialCard, int], + Optional[Card]]] = [None] * 3 self.goal: Dict[NumberCard.Suit, int] = { NumberCard.Suit.Red: 0, NumberCard.Suit.Green: 0, NumberCard.Suit.Black: 0, } + self.flower_gone: bool = False - flowerGone: bool = False + def state_identifier(self) -> int: + """Returns a unique identifier to represent the board state""" + result: int = 0 + for card in self.bunker: + result <<= 2 + if isinstance(card, tuple): + result |= 0 + result <<= 2 + result |= card[0].identifier() # pylint: disable=E1136 + elif card is None: + result |= 1 + else: + result |= 2 + result <<= 5 + result |= card.identifier() + + + result <<= 1 + if self.flower_gone: + result |= 1 + + for _, goal_count in self.goal.items(): + result <<= 4 + result |= goal_count + + # Max stack size is 13 (4 random cards from the start, plus a stack from 9 to 1] + # So 4 bits are sufficient + for stack in self.field: + assert len(stack) == len(stack) & 0b1111 + result <<= 4 + result |= len(stack) + + for field_card in itertools.chain.from_iterable(self.field): + result <<= 5 + result |= field_card.identifier() + + return 0 def check_correct(self) -> bool: """Returns true, if the board is in a valid state""" @@ -68,13 +114,12 @@ class Board: SpecialCard.Hua: 0, } - if self.flowerGone: + if self.flower_gone: special_cards[SpecialCard.Hua] += 1 for card in itertools.chain( - self.bunker, - itertools.chain.from_iterable(stack for stack in self.field if stack), - ): + self.bunker, itertools.chain.from_iterable( + stack for stack in self.field if stack), ): if isinstance(card, tuple): special_cards[card[0]] += 4 # pylint: disable=E1136 elif isinstance(card, SpecialCard): diff --git a/shenzhen_solitaire/board_actions.py b/shenzhen_solitaire/board_actions.py index 1620e24..40e9949 100644 --- a/shenzhen_solitaire/board_actions.py +++ b/shenzhen_solitaire/board_actions.py @@ -91,9 +91,11 @@ class MoveAction: if action_board.field[dest]: dest_card = action_board.field[dest][-1] + if not all(isinstance(x, board.NumberCard) for x in self.cards): + raise AssertionError() if not isinstance(dest_card, board.NumberCard): raise AssertionError() - if not all(isinstance(x, board.NumberCard) for x in self.cards): + if not isinstance(self.cards[0], board.NumberCard): raise AssertionError() if dest_card.suit == self.cards[0].suit: raise AssertionError() @@ -161,16 +163,16 @@ class HuaKillAction: def apply(self, action_board: board.Board) -> None: """Do action""" - assert not action_board.flowerGone + assert not action_board.flower_gone assert action_board.field[self.source_field_id][-1] == board.SpecialCard.Hua action_board.field[self.source_field_id].pop() - action_board.flowerGone = True + action_board.flower_gone = True def undo(self, action_board: board.Board) -> None: """Undo action""" - assert action_board.flowerGone + assert action_board.flower_gone action_board.field[self.source_field_id].append(board.SpecialCard.Hua) - action_board.flowerGone = False + action_board.flower_gone = False Action = Union[MoveAction, DragonKillAction, HuaKillAction, BunkerizeAction, GoalAction] diff --git a/test/context.py b/test/context.py index c0199b4..90f0c95 100644 --- a/test/context.py +++ b/test/context.py @@ -3,5 +3,3 @@ import os import sys sys.path.insert(0, os.path.abspath( os.path.join(os.path.dirname(__file__), '..'))) - -import shenzhen_solitaire diff --git a/test/test_chain.py b/test/test_chain.py index 0a8b23b..f072e31 100644 --- a/test/test_chain.py +++ b/test/test_chain.py @@ -1,7 +1,7 @@ """Contains tests for chain module""" import unittest -from .context import shenzhen_solitaire # pylint: disable=unused-import +from . import context # pylint: disable=unused-import from shenzhen_solitaire.board import NumberCard, SpecialCard, Board # pylint: disable=wrong-import-order from shenzhen_solitaire import board_possibilities # pylint: disable=wrong-import-order