Worked a bit

This commit is contained in:
Lukas Wölfer
2019-04-19 23:58:07 +02:00
parent f65cdc42e5
commit af2565548e
6 changed files with 151 additions and 83 deletions

View File

@@ -62,6 +62,18 @@ class Board:
} }
self.flower_gone: bool = False self.flower_gone: bool = False
def solved(self) -> bool:
"""Returns true if the board is solved"""
if any(x != 9 for x in self.goal.values()):
return False
if any(not isinstance(x, tuple) for x in self.bunker):
return False
if not self.flower_gone:
return False
assert all(not x for x in self.field)
return True
@property
def state_identifier(self) -> int: def state_identifier(self) -> int:
"""Returns a unique identifier to represent the board state""" """Returns a unique identifier to represent the board state"""
result: int = 0 result: int = 0
@@ -98,7 +110,7 @@ class Board:
result <<= 5 result <<= 5
result |= field_card.identifier() result |= field_card.identifier()
return 0 return result
def check_correct(self) -> bool: def check_correct(self) -> bool:
"""Returns true, if the board is in a valid state""" """Returns true, if the board is in a valid state"""

View File

@@ -1,9 +1,17 @@
"""Contains actions that can be used on the board""" """Contains actions that can be used on the board"""
from typing import List, Tuple, Union from typing import List, Tuple, Union
from dataclasses import dataclass from dataclasses import dataclass
import itertools
from . import board from . import board
class Action:
def apply(self, action_board: board.Board) -> None:
pass
def undo(self, action_board: board.Board) -> None:
pass
@dataclass @dataclass
class GoalAction: class GoalAction:
"""Move card from field to goal""" """Move card from field to goal"""
@@ -45,20 +53,22 @@ class BunkerizeAction:
"""Move card from bunker to field""" """Move card from bunker to field"""
card: board.Card card: board.Card
source_id: int bunker_id: int
destination_id: int field_id: int
to_bunker: bool to_bunker: bool
_before_state: int = 0
_after_state: int = 0
def _move_from_bunker(self, action_board: board.Board) -> None: def _move_from_bunker(self, action_board: board.Board) -> None:
assert action_board.bunker[self.source_id] == self.card assert action_board.bunker[self.bunker_id] == self.card
action_board.bunker[self.source_id] = None action_board.bunker[self.bunker_id] = None
action_board.field[self.destination_id].append(self.card) action_board.field[self.field_id].append(self.card)
def _move_to_bunker(self, action_board: board.Board) -> None: def _move_to_bunker(self, action_board: board.Board) -> None:
assert action_board.field[self.source_id][-1] == self.card assert action_board.field[self.field_id][-1] == self.card
assert action_board.bunker[self.destination_id] is None assert action_board.bunker[self.bunker_id] is None
action_board.bunker[self.destination_id] = self.card action_board.bunker[self.bunker_id] = self.card
action_board.field[self.source_id].pop() action_board.field[self.field_id].pop()
def apply(self, action_board: board.Board) -> None: def apply(self, action_board: board.Board) -> None:
"""Do action""" """Do action"""
@@ -82,6 +92,8 @@ class MoveAction:
cards: List[board.Card] cards: List[board.Card]
source_id: int source_id: int
destination_id: int destination_id: int
_before_state: int = 0
_after_state: int = 0
def _shift( def _shift(
self, self,
@@ -90,6 +102,15 @@ class MoveAction:
dest: int) -> None: dest: int) -> None:
"""Shift a card from the field id 'source' to field id 'dest'""" """Shift a card from the field id 'source' to field id 'dest'"""
print(f"Incoming shift from {source} to {dest}")
print(
*
list(itertools.zip_longest(
action_board.field[source][::-1],
self.cards[::-1]))[::-1],
sep="\n")
print("--- GO ---")
for stack_offset, card in enumerate( for stack_offset, card in enumerate(
self.cards, start=-len(self.cards)): self.cards, start=-len(self.cards)):
assert action_board.field[source][stack_offset] == card assert action_board.field[source][stack_offset] == card
@@ -107,17 +128,37 @@ class MoveAction:
if dest_card.number != self.cards[0].number + 1: if dest_card.number != self.cards[0].number + 1:
raise AssertionError() raise AssertionError()
print(
*
itertools.zip_longest(
action_board.field[source],
action_board.field[dest]),
sep="\n")
action_board.field[source] = action_board.field[ action_board.field[source] = action_board.field[
source][:-len(self.cards)] source][:-len(self.cards)]
action_board.field[dest].extend(self.cards) action_board.field[dest].extend(self.cards)
print()
print(
*
itertools.zip_longest(
action_board.field[source],
action_board.field[dest]),
sep="\n")
print()
print()
def apply(self, action_board: board.Board) -> None: def apply(self, action_board: board.Board) -> None:
"""Do action""" """Do action"""
self._before_state = action_board.state_identifier
self._shift(action_board, self.source_id, self.destination_id) self._shift(action_board, self.source_id, self.destination_id)
self._after_state = action_board.state_identifier
def undo(self, action_board: board.Board) -> None: def undo(self, action_board: board.Board) -> None:
"""Undo action""" """Undo action"""
print("Undo shift")
assert action_board.state_identifier == self._after_state
self._shift(action_board, self.destination_id, self.source_id) self._shift(action_board, self.destination_id, self.source_id)
assert action_board.state_identifier == self._before_state
@dataclass @dataclass

View File

@@ -73,8 +73,8 @@ def possible_bunkerize_actions(
continue continue
yield board_actions.BunkerizeAction( yield board_actions.BunkerizeAction(
card=stack[-1], card=stack[-1],
source_id=index, field_id=index,
destination_id=open_bunker, bunker_id=open_bunker,
to_bunker=True to_bunker=True
) )
@@ -100,8 +100,8 @@ def possible_debunkerize_actions(
continue continue
yield board_actions.BunkerizeAction( yield board_actions.BunkerizeAction(
card=card, card=card,
source_id=index, bunker_id=index,
destination_id=other_index, field_id=other_index,
to_bunker=False to_bunker=False
) )
@@ -142,6 +142,7 @@ def _can_stack(bottom: board.Card, top: board.Card) -> bool:
return False return False
if bottom.number != top.number + 1: if bottom.number != top.number + 1:
return False return False
print(f"{bottom.number} and {top.number} can stack")
return True return True
@@ -159,6 +160,8 @@ def _get_cardstacks(search_board: board.Board) -> List[List[board.Card]]:
if not isinstance(card, board.NumberCard): if not isinstance(card, board.NumberCard):
break break
result[-1].insert(0, card) result[-1].insert(0, card)
print(f"Cardstack {search_board.state_identifier}:")
print(*result, sep='\n')
return result return result
@@ -167,18 +170,25 @@ def possible_field_move_actions(
) -> Iterator[board_actions.MoveAction]: ) -> Iterator[board_actions.MoveAction]:
"""Enumerate all possible move actions """Enumerate all possible move actions
from one field stack to another field stack""" from one field stack to another field stack"""
my_id = search_board.state_identifier
for index, stack in enumerate(_get_cardstacks(search_board)): for index, stack in enumerate(_get_cardstacks(search_board)):
if not stack: if not stack:
continue continue
print(f"{index}: {stack}")
# TODO: sort all substacks by length # TODO: sort all substacks by length
for substack in (stack[i:] for i in range(len(stack))): for substack in (stack[i:] for i in range(len(stack))):
for other_index, other_stack in enumerate(search_board.field): for other_index, other_stack in enumerate(search_board.field):
print(f"{index} - {other_index}: \n\t{substack} \n\t{other_stack}")
if index == other_index:
continue
if other_stack: if other_stack:
if not _can_stack(other_stack[-1], substack[0]): if not _can_stack(other_stack[-1], substack[0]):
continue continue
assert search_board.state_identifier == my_id
yield board_actions.MoveAction( yield board_actions.MoveAction(
cards=substack, source_id=index, destination_id=other_index cards=substack, source_id=index, destination_id=other_index
) )
assert search_board.state_identifier == my_id
def possible_actions( def possible_actions(

View File

@@ -1,11 +1,77 @@
"""Contains solver for solitaire""" """Contains solver for solitaire"""
from typing import List, Tuple from typing import List, Tuple, Iterator, Set, Optional
from .board import Board from .board import Board
from . import board_actions from . import board_actions
from .board_possibilities import possible_actions
class ActionStack:
iterator_stack: List[Iterator[board_actions.Action]]
action_stack: List[Optional[board_actions.Action]]
index_stack: List[int]
def __init__(self) -> None:
self.iterator_stack = []
self.index_stack = []
self.action_stack = []
def push(self, board: Board) -> None:
self.iterator_stack.append(possible_actions(board))
self.action_stack.append(None)
self.index_stack.append(0)
def get(self) -> Optional[board_actions.Action]:
try:
self.action_stack[-1] = next(self.iterator_stack[-1])
except StopIteration:
return None
self.index_stack[-1] += 1
return self.action_stack[-1]
def pop(self) -> None:
self.action_stack.pop()
self.iterator_stack.pop()
self.index_stack.pop()
def __len__(self) -> int:
return len(self.index_stack)
class SolitaireSolver: class SolitaireSolver:
"""Solver for Shenzhen Solitaire""" """Solver for Shenzhen Solitaire"""
search_board: Board search_board: Board
stack: List[Tuple[board_actions.Action, int]] stack: ActionStack
state_set: Set[int]
def __init__(self, board: Board) -> None:
self.search_board = board
self.state_set = {board.state_identifier}
self.stack = ActionStack()
self.stack.push(self.search_board)
def solve(self) -> List[board_actions.Action]:
applied = True
action = None
while self.stack:
last_action = action
action = self.stack.get()
if not action:
if last_action and applied:
last_action.undo(self.search_board)
assert (self.search_board.state_identifier
in self.state_set)
self.stack.pop()
continue
action.apply(self.search_board)
print(f"Applying {str(action)}")
applied = True
if self.search_board.state_identifier in self.state_set:
action.undo(self.search_board)
applied = False
assert self.search_board.state_identifier in self.state_set
continue
self.state_set.add(self.search_board.state_identifier)
self.stack.push(self.search_board)
return self.stack.action_stack

View File

@@ -3,3 +3,5 @@ import os
import sys import sys
sys.path.insert(0, os.path.abspath( sys.path.insert(0, os.path.abspath(
os.path.join(os.path.dirname(__file__), '..'))) os.path.join(os.path.dirname(__file__), '..')))
import shenzhen_solitaire

View File

@@ -1,9 +1,10 @@
"""Contains tests for chain module""" """Contains tests for chain module"""
import unittest import unittest
from . import context # pylint: disable=unused-import from .context import shenzhen_solitaire
from shenzhen_solitaire.board import NumberCard, SpecialCard, Board # pylint: disable=wrong-import-order from shenzhen_solitaire.board import NumberCard, SpecialCard, Board
from shenzhen_solitaire import board_possibilities # pylint: disable=wrong-import-order from shenzhen_solitaire import board_possibilities
from .boards import my_board
class ChainTestClass(unittest.TestCase): class ChainTestClass(unittest.TestCase):
@@ -11,70 +12,6 @@ class ChainTestClass(unittest.TestCase):
def test_sequence(self) -> None: def test_sequence(self) -> None:
"""Tests a given sequence. Might break if I change the iterators""" """Tests a given sequence. Might break if I change the iterators"""
my_board: Board = Board()
my_board.field[0] = [
SpecialCard.Fa,
NumberCard(NumberCard.Suit.Black, 8),
SpecialCard.Bai,
NumberCard(NumberCard.Suit.Black, 7),
SpecialCard.Zhong,
]
my_board.field[1] = [
NumberCard(NumberCard.Suit.Red, 9),
SpecialCard.Zhong,
SpecialCard.Zhong,
NumberCard(NumberCard.Suit.Black, 4),
NumberCard(NumberCard.Suit.Black, 3),
]
my_board.field[2] = [
SpecialCard.Hua,
NumberCard(NumberCard.Suit.Red, 1),
NumberCard(NumberCard.Suit.Red, 4),
NumberCard(NumberCard.Suit.Green, 1),
NumberCard(NumberCard.Suit.Red, 6),
]
my_board.field[3] = [
SpecialCard.Bai,
SpecialCard.Zhong,
NumberCard(NumberCard.Suit.Red, 3),
NumberCard(NumberCard.Suit.Red, 7),
NumberCard(NumberCard.Suit.Green, 6),
]
my_board.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),
]
my_board.field[5] = [
NumberCard(NumberCard.Suit.Green, 3),
SpecialCard.Bai,
SpecialCard.Fa,
NumberCard(NumberCard.Suit.Black, 2),
NumberCard(NumberCard.Suit.Black, 5),
]
my_board.field[6] = [
SpecialCard.Fa,
NumberCard(NumberCard.Suit.Green, 9),
NumberCard(NumberCard.Suit.Green, 2),
NumberCard(NumberCard.Suit.Black, 9),
NumberCard(NumberCard.Suit.Red, 8),
]
my_board.field[7] = [
SpecialCard.Bai,
NumberCard(NumberCard.Suit.Red, 2),
SpecialCard.Fa,
NumberCard(NumberCard.Suit.Black, 1),
NumberCard(NumberCard.Suit.Green, 8),
]
self.assertTrue(my_board.check_correct()) self.assertTrue(my_board.check_correct())
sequence = [ sequence = [