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""" """Contains board class"""
import enum 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 from dataclasses import dataclass
import itertools
class SpecialCard(enum.Enum): class SpecialCard(enum.Enum):
"""Different types of special cards""" """Different types of special cards"""
Zhong = enum.auto() Zhong = enum.auto()
Bai = enum.auto() Bai = enum.auto()
Fa = enum.auto() Fa = enum.auto()
@@ -15,11 +18,14 @@ class SpecialCard(enum.Enum):
@dataclass(frozen=True) @dataclass(frozen=True)
class NumberCard: class NumberCard:
"""Different number cards""" """Different number cards"""
class Suit(enum.Enum): class Suit(enum.Enum):
"""Different colors number cards can have""" """Different colors number cards can have"""
Red = enum.auto() Red = enum.auto()
Green = enum.auto() Green = enum.auto()
Black = enum.auto() Black = enum.auto()
suit: Suit suit: Suit
number: int number: int
@@ -29,19 +35,65 @@ Card = Union[NumberCard, SpecialCard]
class Position(enum.Enum): class Position(enum.Enum):
"""Possible Board positions""" """Possible Board positions"""
Field = enum.auto() Field = enum.auto()
Bunker = enum.auto() Bunker = enum.auto()
Goal = enum.auto() Goal = enum.auto()
KilledDragon = NewType('KilledDragon', SpecialCard)
@dataclass
class Board: class Board:
"""Solitaire board""" """Solitaire board"""
field: List[List[Card]] = [[]] * 8
bunker: List[Union[KilledDragon, Optional[Card]]] = [None] * 3 def __init__(self) -> None:
goal: Dict[NumberCard.Suit, int] = {NumberCard.Suit.Red: 0, self.field: List[List[Card]] = [[]] * 8
NumberCard.Suit.Green: 0, self.bunker: List[Union[Tuple[SpecialCard, int], Optional[Card]]] = [None] * 3
NumberCard.Suit.Black: 0} self.goal: Dict[NumberCard.Suit, int] = {
NumberCard.Suit.Red: 0,
NumberCard.Suit.Green: 0,
NumberCard.Suit.Black: 0,
}
flowerGone: bool = False 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 @dataclass
class GoalAction: class GoalAction:
"""Move card from field to goal""" """Move card from field to goal"""
card: board.NumberCard card: board.NumberCard
source_id: int source_id: int
source_position: board.Position source_position: board.Position
@@ -40,49 +41,89 @@ class GoalAction:
@dataclass @dataclass
class RestoreAction: class BunkerizeAction:
"""Move card from bunker to field""" """Move card from bunker to field"""
card: board.Card card: board.Card
source_id: int source_id: int
destination_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: def apply(self, action_board: board.Board) -> None:
"""Do action""" """Do action"""
assert action_board.bunker[self.source_id] == self.card if self.to_bunker:
action_board.bunker[self.source_id] = None self._move_to_bunker(action_board)
else:
self._move_from_bunker(action_board)
def undo(self, action_board: board.Board) -> None:
"""Undo action"""
@dataclass if self.to_bunker:
class StoreAction: self._move_from_bunker(action_board)
"""Move card from field to bunker""" else:
card: board.Card self._move_to_bunker(action_board)
source_id: int
destination_id: int
@dataclass @dataclass
class MoveAction: class MoveAction:
"""Moving a card from one field stack to another""" """Moving a card from one field stack to another"""
card: board.Card
cards: List[board.Card]
source_id: int source_id: int
destination_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: def apply(self, action_board: board.Board) -> None:
"""Do action""" """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 @dataclass
class DragonKillAction: class DragonKillAction:
"""Removing four dragons from the top of the stacks to a bunker""" """Removing four dragons from the top of the stacks to a bunker"""
dragon: board.SpecialCard dragon: board.SpecialCard
source_stacks: List[Tuple[board.Position, int]] source_stacks: List[Tuple[board.Position, int]]
destination_bunker_id: int destination_bunker_id: int
def apply(self, action_board: board.Board) -> None: def apply(self, action_board: board.Board) -> None:
"""Do action""" """Do action"""
assert (action_board.bunker[self.destination_bunker_id] is None or assert (
action_board.bunker[self.destination_bunker_id] == self.dragon) 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 assert len(self.source_stacks) == 4
for position, index in self.source_stacks: for position, index in self.source_stacks:
if position == board.Position.Field: if position == board.Position.Field:
@@ -94,13 +135,11 @@ class DragonKillAction:
action_board.bunker[index] = None action_board.bunker[index] = None
else: else:
raise RuntimeError("Can only kill dragons in field and bunker") raise RuntimeError("Can only kill dragons in field and bunker")
action_board.bunker[self.destination_bunker_id] = board.KilledDragon( action_board.bunker[self.destination_bunker_id] = (self.dragon, 4)
self.dragon)
def undo(self, action_board: board.Board) -> None: def undo(self, action_board: board.Board) -> None:
"""Undo action""" """Undo action"""
assert action_board.bunker[self.destination_bunker_id] == board.KilledDragon( assert action_board.bunker[self.destination_bunker_id] == (self.dragon, 4)
self.dragon)
assert len(self.source_stacks) == 4 assert len(self.source_stacks) == 4
for position, index in self.source_stacks: for position, index in self.source_stacks:
if position == board.Position.Field: if position == board.Position.Field:
@@ -115,6 +154,7 @@ class DragonKillAction:
@dataclass @dataclass
class HuaKillAction: class HuaKillAction:
"""Remove the flower card""" """Remove the flower card"""
source_field_id: int source_field_id: int
def apply(self, action_board: board.Board) -> None: def apply(self, action_board: board.Board) -> None:
@@ -131,5 +171,4 @@ class HuaKillAction:
action_board.flowerGone = False action_board.flowerGone = False
Action = Union[MoveAction, DragonKillAction, Action = Union[MoveAction, DragonKillAction, HuaKillAction, BunkerizeAction, GoalAction]
HuaKillAction, StoreAction, RestoreAction, GoalAction]

View File

@@ -4,7 +4,9 @@ import board
import board_actions 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""" """Check if the flowercard can be eliminated"""
for index, stack in enumerate(search_board.field): for index, stack in enumerate(search_board.field):
if stack[-1] == board.SpecialCard.Hua: 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( 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""" """Enumerate all possible dragon kills"""
possible_dragons = [board.SpecialCard.Zhong, possible_dragons = [
board.SpecialCard.Fa, board.SpecialCard.Bai] board.SpecialCard.Zhong,
board.SpecialCard.Fa,
board.SpecialCard.Bai,
]
if not any(x is None for x in search_board.bunker): if not any(x is None for x in search_board.bunker):
new_possible_dragons = [] new_possible_dragons = []
for dragon in possible_dragons: for dragon in possible_dragons:
@@ -24,10 +30,10 @@ def possible_dragonkill_actions(
possible_dragons = new_possible_dragons possible_dragons = new_possible_dragons
for dragon in possible_dragons: for dragon in possible_dragons:
bunker_dragons = [i for i, d in enumerate( bunker_dragons = [i for i, d in enumerate(search_board.bunker) if d == dragon]
search_board.bunker) if d == dragon] field_dragons = [
field_dragons = [i for i, f in enumerate( i for i, f in enumerate(search_board.field) if f if f[-1] == dragon
search_board.field) if f if f[-1] == dragon] ]
if len(bunker_dragons) + len(field_dragons) != 4: if len(bunker_dragons) + len(field_dragons) != 4:
continue continue
destination_bunker_id = 0 destination_bunker_id = 0
@@ -35,20 +41,24 @@ def possible_dragonkill_actions(
destination_bunker_id = bunker_dragons[0] destination_bunker_id = bunker_dragons[0]
else: else:
destination_bunker_id = [ 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 = [(board.Position.Bunker, i) for i in bunker_dragons]
source_stacks.extend([(board.Position.Field, i) source_stacks.extend([(board.Position.Field, i) for i in field_dragons])
for i in field_dragons])
yield board_actions.DragonKillAction(dragon=dragon, source_stacks=source_stacks, yield board_actions.DragonKillAction(
destination_bunker_id=destination_bunker_id) 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""" """Enumerates all possible card moves from the field to the bunker"""
open_bunker_list = [i for i, x in enumerate( open_bunker_list = [i for i, x in enumerate(search_board.bunker) if x is None]
search_board.bunker) if x is None]
if not open_bunker_list: if not open_bunker_list:
return return
@@ -57,16 +67,20 @@ def possible_bunkerize_actions(search_board: board.Board) -> Iterator[board_acti
for index, stack in enumerate(search_board.field): for index, stack in enumerate(search_board.field):
if not stack: if not stack:
continue continue
yield board_actions.StoreAction(card=stack[-1], yield board_actions.BunkerizeAction(
source_id=index, card=stack[-1], source_id=index, destination_id=open_bunker, to_bunker=True
destination_id=open_bunker) )
def possible_debunkerize_actions( 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""" """Enumerates all possible card moves from the bunker to the field"""
bunker_number_cards = [(i, x) for i, x in enumerate( bunker_number_cards = [
search_board.bunker) if isinstance(x, board.NumberCard)] (i, x)
for i, x in enumerate(search_board.bunker)
if isinstance(x, board.NumberCard)
]
for index, card in bunker_number_cards: for index, card in bunker_number_cards:
for other_index, other_stack in enumerate(search_board.field): for other_index, other_stack in enumerate(search_board.field):
if not other_stack: if not other_stack:
@@ -77,29 +91,41 @@ def possible_debunkerize_actions(
continue continue
if other_stack[-1].number != card.number + 1: if other_stack[-1].number != card.number + 1:
continue continue
yield board_actions.RestoreAction(card=card, yield board_actions.BunkerizeAction(
source_id=index, card=card, source_id=index, destination_id=other_index, to_bunker=False
destination_id=other_index) )
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""" """Enumerates all possible moves from anywhere to the goal"""
field_cards = [(board.Position.Field, index, stack[-1]) for index, stack in enumerate( field_cards = [
search_board.field) if stack if isinstance(stack[-1], board.NumberCard)] (board.Position.Field, index, stack[-1])
bunker_cards = [(board.Position.Bunker, index, stack) for index, stack in enumerate(search_board.field)
for index, stack in enumerate(search_board.bunker) if stack
if isinstance(stack, board.NumberCard)] 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 top_cards = field_cards + bunker_cards
for suit, number in search_board.goal.items(): for suit, number in search_board.goal.items():
for source, index, stack in top_cards: for source, index, stack in top_cards:
if not (stack.suit == suit and stack.number == number + 1): if not (stack.suit == suit and stack.number == number + 1):
continue 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 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""" """Enumerate all possible move actions from one field stack to another field stack"""
for index, stack in enumerate(search_board.field): for index, stack in enumerate(search_board.field):
if not stack: 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): if not isinstance(stack[-1], board.NumberCard):
continue continue
for other_index, other_stack in enumerate(search_board.field): for other_index, other_stack in enumerate(search_board.field):
if not other_stack: if other_stack:
continue if not isinstance(other_stack[-1], board.NumberCard):
if not isinstance(other_stack[-1], board.NumberCard): continue
continue if other_stack[-1].suit == stack[-1].suit:
if other_stack[-1].suit == stack[-1].suit: continue
continue if other_stack[-1].number != stack[-1].number + 1:
if other_stack[-1].number != stack[-1].number + 1: continue
continue yield board_actions.MoveAction(
yield board_actions.MoveAction(card=stack[-1], cards=[stack[-1]], source_id=index, destination_id=other_index
source_id=index, )
destination_id=other_index)
def possible_actions(search_board: board.Board) -> Iterator[board_actions.Action]: def possible_actions(search_board: board.Board) -> Iterator[board_actions.Action]:

80
main.py
View File

@@ -1,10 +1,86 @@
"""Main module""" """Main module"""
from typing import List, Tuple from typing import List, Tuple
import board from board import Board, NumberCard, SpecialCard
import board_possibilities
import board_actions import board_actions
class SolitaireSolver: class SolitaireSolver:
"""Solver for Shenzhen Solitaire""" """Solver for Shenzhen Solitaire"""
search_board: board.Board
search_board: Board
stack: List[Tuple[board_actions.Action, int]] 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()