This commit is contained in:
Lukas Wölfer
2019-04-09 01:47:15 +02:00
parent 208c2c83a4
commit 8d5eeda2dc
4 changed files with 267 additions and 75 deletions

View File

@@ -1,11 +1,14 @@
"""Contains board class"""
import enum
from typing import Union, List, Dict, Optional, NewType
from typing import Union, List, Dict, Optional, Set, Tuple
import dataclasses
from dataclasses import dataclass
import itertools
class SpecialCard(enum.Enum):
"""Different types of special cards"""
Zhong = enum.auto()
Bai = enum.auto()
Fa = enum.auto()
@@ -15,11 +18,14 @@ class SpecialCard(enum.Enum):
@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
@@ -29,19 +35,65 @@ Card = Union[NumberCard, SpecialCard]
class Position(enum.Enum):
"""Possible Board positions"""
Field = enum.auto()
Bunker = enum.auto()
Goal = enum.auto()
KilledDragon = NewType('KilledDragon', SpecialCard)
@dataclass
class Board:
"""Solitaire board"""
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}
def __init__(self) -> None:
self.field: List[List[Card]] = [[]] * 8
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,
}
flowerGone: bool = False
def check_correct(self) -> bool:
"""Returns true, if the board is in a valid state"""
number_cards: Dict[NumberCard.Suit, Set[int]] = {
NumberCard.Suit.Red: set(),
NumberCard.Suit.Green: set(),
NumberCard.Suit.Black: set(),
}
special_cards: Dict[SpecialCard, int] = {
SpecialCard.Zhong: 0,
SpecialCard.Bai: 0,
SpecialCard.Fa: 0,
SpecialCard.Hua: 0,
}
if self.flowerGone:
special_cards[SpecialCard.Hua] += 1
for card in itertools.chain(
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):
special_cards[card] += 1
elif isinstance(card, NumberCard):
if card.number in number_cards[card.suit]:
return False
number_cards[card.suit].add(card.number)
for _, numbers in number_cards.items():
if set(range(1, 10)) != numbers:
return False
for cardtype, count in special_cards.items():
if cardtype == SpecialCard.Hua:
if count != 1:
return False
else:
if count != 4:
return False
return True

View File

@@ -7,6 +7,7 @@ import board
@dataclass
class GoalAction:
"""Move card from field to goal"""
card: board.NumberCard
source_id: int
source_position: board.Position
@@ -40,49 +41,89 @@ class GoalAction:
@dataclass
class RestoreAction:
class BunkerizeAction:
"""Move card from bunker to field"""
card: board.Card
source_id: int
destination_id: int
to_bunker: bool
def _move_from_bunker(self, action_board: board.Board) -> None:
assert action_board.bunker[self.source_id] == self.card
action_board.bunker[self.source_id] = None
action_board.field[self.destination_id].append(self.card)
def _move_to_bunker(self, action_board: board.Board) -> None:
assert action_board.field[self.source_id][-1] == self.card
assert action_board.bunker[self.destination_id] is None
action_board.bunker[self.destination_id] = self.card
action_board.field[self.source_id].pop()
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
if self.to_bunker:
self._move_to_bunker(action_board)
else:
self._move_from_bunker(action_board)
@dataclass
class StoreAction:
"""Move card from field to bunker"""
card: board.Card
source_id: int
destination_id: int
def undo(self, action_board: board.Board) -> None:
"""Undo action"""
if self.to_bunker:
self._move_from_bunker(action_board)
else:
self._move_to_bunker(action_board)
@dataclass
class MoveAction:
"""Moving a card from one field stack to another"""
card: board.Card
cards: List[board.Card]
source_id: int
destination_id: int
def _shift(self, action_board: board.Board, source: int, dest: int) -> None:
"""Shift a card from the field id 'source' to field id 'dest'"""
for stack_offset, card in enumerate(self.cards, start=-len(self.cards)):
assert action_board.field[source][stack_offset] == card
if action_board.field[dest]:
dest_card = action_board.field[dest]
if not isinstance(dest_card, board.NumberCard):
raise AssertionError()
if dest_card.suit != self.cards[0].suit:
raise AssertionError()
if dest_card.number + 1 == self.cards[0].number:
raise AssertionError()
action_board.field[source] = action_board.field[source][: -len(self.cards)]
action_board.field[dest].extend(self.cards)
def apply(self, action_board: board.Board) -> None:
"""Do action"""
self._shift(action_board, self.source_id, self.destination_id)
def undo(self, action_board: board.Board) -> None:
"""Undo action"""
self._shift(action_board, self.destination_id, self.source_id)
@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
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 (
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:
@@ -94,13 +135,11 @@ class DragonKillAction:
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)
action_board.bunker[self.destination_bunker_id] = (self.dragon, 4)
def undo(self, action_board: board.Board) -> None:
"""Undo action"""
assert action_board.bunker[self.destination_bunker_id] == board.KilledDragon(
self.dragon)
assert action_board.bunker[self.destination_bunker_id] == (self.dragon, 4)
assert len(self.source_stacks) == 4
for position, index in self.source_stacks:
if position == board.Position.Field:
@@ -115,6 +154,7 @@ class DragonKillAction:
@dataclass
class HuaKillAction:
"""Remove the flower card"""
source_field_id: int
def apply(self, action_board: board.Board) -> None:
@@ -131,5 +171,4 @@ class HuaKillAction:
action_board.flowerGone = False
Action = Union[MoveAction, DragonKillAction,
HuaKillAction, StoreAction, RestoreAction, GoalAction]
Action = Union[MoveAction, DragonKillAction, HuaKillAction, BunkerizeAction, GoalAction]

View File

@@ -4,7 +4,9 @@ import board
import board_actions
def possible_huakill_action(search_board: board.Board) -> Iterator[board_actions.HuaKillAction]:
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:
@@ -12,10 +14,14 @@ def possible_huakill_action(search_board: board.Board) -> Iterator[board_actions
def possible_dragonkill_actions(
search_board: board.Board) -> Iterator[board_actions.DragonKillAction]:
search_board: board.Board
) -> Iterator[board_actions.DragonKillAction]:
"""Enumerate all possible dragon kills"""
possible_dragons = [board.SpecialCard.Zhong,
board.SpecialCard.Fa, board.SpecialCard.Bai]
possible_dragons = [
board.SpecialCard.Zhong,
board.SpecialCard.Fa,
board.SpecialCard.Bai,
]
if not any(x is None for x in search_board.bunker):
new_possible_dragons = []
for dragon in possible_dragons:
@@ -24,10 +30,10 @@ def possible_dragonkill_actions(
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]
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
@@ -35,20 +41,24 @@ 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 is None][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)
for i in field_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)
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.StoreAction]:
def possible_bunkerize_actions(
search_board: board.Board
) -> Iterator[board_actions.BunkerizeAction]:
"""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 is None]
open_bunker_list = [i for i, x in enumerate(search_board.bunker) if x is None]
if not open_bunker_list:
return
@@ -57,16 +67,20 @@ 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.StoreAction(card=stack[-1],
source_id=index,
destination_id=open_bunker)
yield board_actions.BunkerizeAction(
card=stack[-1], source_id=index, destination_id=open_bunker, to_bunker=True
)
def possible_debunkerize_actions(
search_board: board.Board) -> Iterator[board_actions.RestoreAction]:
search_board: board.Board
) -> Iterator[board_actions.BunkerizeAction]:
"""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)]
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:
@@ -77,29 +91,41 @@ def possible_debunkerize_actions(
continue
if other_stack[-1].number != card.number + 1:
continue
yield board_actions.RestoreAction(card=card,
source_id=index,
destination_id=other_index)
yield board_actions.BunkerizeAction(
card=card, source_id=index, destination_id=other_index, to_bunker=False
)
def possible_goal_move_actions(search_board: board.Board) -> Iterator[board_actions.GoalAction]:
def possible_goal_move_actions(
search_board: board.Board
) -> Iterator[board_actions.GoalAction]:
"""Enumerates all possible moves from anywhere to the goal"""
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)]
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)
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]:
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:
@@ -107,17 +133,16 @@ def possible_field_move_actions(search_board: board.Board) -> Iterator[board_act
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_id=index,
destination_id=other_index)
if other_stack:
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(
cards=[stack[-1]], source_id=index, destination_id=other_index
)
def possible_actions(search_board: board.Board) -> Iterator[board_actions.Action]:

80
main.py
View File

@@ -1,10 +1,86 @@
"""Main module"""
from typing import List, Tuple
import board
from board import Board, NumberCard, SpecialCard
import board_possibilities
import board_actions
class SolitaireSolver:
"""Solver for Shenzhen Solitaire"""
search_board: board.Board
search_board: Board
stack: List[Tuple[board_actions.Action, int]]
def main() -> None:
t: Board = Board()
t.field[0] = [
SpecialCard.Fa,
NumberCard(NumberCard.Suit.Black, 8),
SpecialCard.Bai,
NumberCard(NumberCard.Suit.Black, 7),
SpecialCard.Zhong,
]
t.field[1] = [
NumberCard(NumberCard.Suit.Red, 9),
SpecialCard.Zhong,
SpecialCard.Zhong,
NumberCard(NumberCard.Suit.Black, 4),
NumberCard(NumberCard.Suit.Black, 3),
]
t.field[2] = [
SpecialCard.Hua,
NumberCard(NumberCard.Suit.Red, 1),
NumberCard(NumberCard.Suit.Red, 4),
NumberCard(NumberCard.Suit.Green, 1),
NumberCard(NumberCard.Suit.Red, 6),
]
t.field[3] = [
SpecialCard.Zhong,
SpecialCard.Bai,
NumberCard(NumberCard.Suit.Red, 3),
NumberCard(NumberCard.Suit.Red, 7),
NumberCard(NumberCard.Suit.Green, 6),
]
t.field[4] = [
NumberCard(NumberCard.Suit.Green, 7),
NumberCard(NumberCard.Suit.Green, 4),
NumberCard(NumberCard.Suit.Red, 5),
NumberCard(NumberCard.Suit.Green, 5),
NumberCard(NumberCard.Suit.Black, 6),
]
t.field[5] = [
NumberCard(NumberCard.Suit.Green, 3),
SpecialCard.Bai,
SpecialCard.Fa,
NumberCard(NumberCard.Suit.Black, 2),
NumberCard(NumberCard.Suit.Black, 5),
]
t.field[6] = [
SpecialCard.Fa,
NumberCard(NumberCard.Suit.Green, 9),
NumberCard(NumberCard.Suit.Green, 2),
NumberCard(NumberCard.Suit.Black, 9),
NumberCard(NumberCard.Suit.Red, 8),
]
t.field[7] = [
SpecialCard.Bai,
NumberCard(NumberCard.Suit.Red, 2),
SpecialCard.Fa,
NumberCard(NumberCard.Suit.Black, 1),
NumberCard(NumberCard.Suit.Green, 8),
]
print(t.check_correct())
print(*list(board_possibilities.possible_actions(t)), sep='\n')
if __name__ == "__main__":
main()