Improved solver performance

This commit is contained in:
Lukas Wölfer
2020-02-12 02:51:26 +01:00
parent c05aa194ce
commit c789e48b06
4 changed files with 73 additions and 64 deletions

View File

@@ -78,7 +78,7 @@ def handle_action(
(field_x + (size_x - field_x) // 2, field_y + (size_y - field_y) // 2), (field_x + (size_x - field_x) // 2, field_y + (size_y - field_y) // 2),
offset, offset,
) )
time.sleep(0.5) time.sleep(1)
return return
if isinstance(action, board_actions.GoalAction): if isinstance(action, board_actions.GoalAction):
dst_x, dst_y, _, _ = adjustment.get_square( dst_x, dst_y, _, _ = adjustment.get_square(
@@ -100,12 +100,6 @@ def handle_action(
return return
raise AssertionError("You forgot an Action type") raise AssertionError("You forgot an Action type")
def automatic(action: board_actions.Action) -> bool:
if isinstance(action, board_actions.HuaKillAction):
return True
if isinstance(action, board_actions.GoalAction) and action.obvious:
return True
return False
def handle_actions( def handle_actions(
actions: List[board_actions.Action], actions: List[board_actions.Action],
@@ -115,9 +109,10 @@ def handle_actions(
automatic_count = 0 automatic_count = 0
for action in actions: for action in actions:
print(action) print(action)
if automatic(action): if action.automatic():
automatic_count += 1 automatic_count += 1
else: else:
time.sleep(0.5 * automatic_count) time.sleep(0.5 * automatic_count)
automatic_count = 0 automatic_count = 0
handle_action(action, offset, conf) handle_action(action, offset, conf)
time.sleep(0.5 * automatic_count)

View File

@@ -31,6 +31,13 @@ class Action:
self._undo(action_board) self._undo(action_board)
assert action_board.state_identifier == self._before_state assert action_board.state_identifier == self._before_state
def automatic(self) -> bool:
if isinstance(self, HuaKillAction):
return True
if isinstance(self, GoalAction) and self.obvious:
return True
return False
@dataclass @dataclass
class GoalAction(Action): class GoalAction(Action):

View File

@@ -125,7 +125,6 @@ def possible_goal_move_actions(
] ]
top_cards = field_cards + bunker_cards top_cards = field_cards + bunker_cards
result: List[board_actions.GoalAction] = []
for source, index, card in top_cards: for source, index, card in top_cards:
if not (card.number == search_board.getGoal(card.suit) + 1): if not (card.number == search_board.getGoal(card.suit) + 1):
continue continue
@@ -145,9 +144,6 @@ def possible_goal_move_actions(
) )
break break
result = sorted(result, key=lambda x: not x.obvious)
yield from iter(result)
def _can_stack(bottom: board.Card, top: board.Card) -> bool: def _can_stack(bottom: board.Card, top: board.Card) -> bool:
if not isinstance(bottom, board.NumberCard): if not isinstance(bottom, board.NumberCard):
@@ -216,11 +212,19 @@ def possible_field_move_actions(
) )
def possible_actions(search_board: board.Board) -> Iterator[board_actions.Action]: def possible_actions(search_board: board.Board) -> List[board_actions.Action]:
"""Enumerate all possible actions on the current search_board""" """Enumerate all possible actions on the current search_board"""
yield from possible_huakill_action(search_board) result: List[board_actions.Action] = [
yield from possible_dragonkill_actions(search_board) *list(possible_huakill_action(search_board)),
yield from possible_goal_move_actions(search_board) *list(possible_dragonkill_actions(search_board)),
yield from possible_debunkerize_actions(search_board) *list(possible_goal_move_actions(search_board)),
yield from possible_field_move_actions(search_board) *list(possible_debunkerize_actions(search_board)),
yield from possible_bunkerize_actions(search_board) *list(possible_field_move_actions(search_board)),
*list(possible_bunkerize_actions(search_board)),
]
for x in result:
if x.automatic():
return [x]
return result

View File

@@ -2,52 +2,55 @@
import typing import typing
from typing import Iterator, List, Optional from typing import Iterator, List, Optional
import time import time
from dataclasses import dataclass
from ..board import Board from ..board import Board
from . import board_actions from . import board_actions
from .board_actions import DragonKillAction, GoalAction, HuaKillAction, MoveAction from .board_actions import DragonKillAction, GoalAction, HuaKillAction, MoveAction
from .board_possibilities import possible_actions from .board_possibilities import possible_actions
@dataclass
class ActionStackFrame:
iterator: Iterator[board_actions.Action]
action: Optional[board_actions.Action]
state: int
def next(self) -> Optional[board_actions.Action]:
"""Get next iteration of top action iterator"""
try:
self.action = next(self.iterator)
except StopIteration:
return None
return self.action
class ActionStack: class ActionStack:
"""Stack of chosen actions on the board""" """Stack of chosen actions on the board"""
iterator_stack: List[Iterator[board_actions.Action]]
action_stack: List[Optional[board_actions.Action]]
index_stack: List[int]
state_stack: List[int]
def __init__(self) -> None: def __init__(self) -> None:
self.iterator_stack = [] self.frames: List[ActionStackFrame] = []
self.index_stack = []
self.action_stack = []
self.state_stack = []
def push(self, board: Board) -> None: def push(self, board: Board) -> None:
"""Append another board state to stack""" """Append another board state to stack"""
self.iterator_stack.append(possible_actions(board)) self.frames.append(
self.action_stack.append(None) ActionStackFrame(
self.index_stack.append(0) iterator=iter(possible_actions(board)),
self.state_stack.append(board.state_identifier) action=None,
state=board.state_identifier,
)
)
def get(self) -> Optional[board_actions.Action]: @property
def top(self) -> ActionStackFrame:
"""Get next iteration of top action iterator""" """Get next iteration of top action iterator"""
try: return self.frames[-1]
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: def pop(self) -> None:
"""Pop one action from stack""" """Pop one action from stack"""
self.action_stack.pop() self.frames.pop()
self.iterator_stack.pop()
self.index_stack.pop()
self.state_stack.pop()
def __len__(self) -> int: def __len__(self) -> int:
return len(self.index_stack) return len(self.frames)
def solve( def solve(
@@ -61,27 +64,30 @@ def solve(
def _limit_stack_size(stack_size: int) -> None: def _limit_stack_size(stack_size: int) -> None:
if len(stack) == stack_size: if len(stack) == stack_size:
stack.pop() stack.pop()
assert stack.action_stack[-1] is not None assert stack.top.action is not None
stack.action_stack[-1].undo(board) stack.top.action.undo(board)
assert board.state_identifier in state_set assert board.state_identifier in state_set
def _backtrack_action() -> None: def _backtrack_action() -> None:
stack.pop() stack.pop()
assert stack.action_stack[-1] is not None assert stack.top.action is not None
stack.action_stack[-1].undo(board) stack.top.action.undo(board)
assert board.state_identifier in state_set assert board.state_identifier in state_set
def _skip_loop_move(action: board_actions.Action) -> bool: def _skip_loop_move(action: board_actions.Action) -> bool:
if isinstance(action, MoveAction): if not isinstance(action, MoveAction):
for prev_action in stack.action_stack[-2::-1]: return False
if isinstance(prev_action, MoveAction): for frame in stack.frames[-2::-1]:
if prev_action.cards == action.cards: if not isinstance(frame.action, MoveAction):
continue
if frame.action.cards == action.cards:
return True return True
return False return False
iter_start = time.time() iter_start = time.time()
count = 0 count = 0
while stack: while stack:
count += 1 count += 1
if count > 5000: if count > 5000:
count = 0 count = 0
@@ -92,10 +98,10 @@ def solve(
# _limit_stack_size(80) # _limit_stack_size(80)
assert board.state_identifier == stack.state_stack[-1] assert board.state_identifier == stack.top.state
action = stack.get() action = stack.top.next()
if not action: if action is None:
_backtrack_action() _backtrack_action()
continue continue
@@ -105,14 +111,11 @@ def solve(
action.apply(board) action.apply(board)
if board.solved(): if board.solved():
yield stack.action_stack assert all(x.action is not None for x in stack.frames)
yield [typing.cast(board_actions.Action, x.action) for x in stack.frames]
iter_start = time.time() iter_start = time.time()
stack.action_stack[-1].undo(board) action.undo(board)
while isinstance( assert board.state_identifier in state_set
stack.action_stack[-1], (GoalAction, HuaKillAction, DragonKillAction)
):
stack.pop()
stack.action_stack[-1].undo(board)
continue continue
if board.state_identifier in state_set: if board.state_identifier in state_set: