From b5d74d1ac0fb188d0d1809536365dbd9e2e3b984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20W=C3=B6lfer?= Date: Fri, 12 Jun 2020 22:40:58 +0200 Subject: [PATCH] Worked on assistant --- .../card_detection/configuration.py | 1 - shenzhen_solitaire/clicker/__init__.py | 128 +++++----- shenzhen_solitaire/solver/__init__.py | 0 shenzhen_solitaire/solver/board_actions.py | 217 ---------------- .../solver/board_possibilities.py | 234 ------------------ shenzhen_solitaire/solver/solver.py | 129 ---------- tools/assistant.py | 54 ++-- tools/generate/border.py | 41 --- tools/generate/bunker.py | 43 ---- tools/generate/catalogue.py | 12 +- tools/generate/field.py | 43 ---- tools/generate/goal.py | 38 --- tools/generate/hua.py | 34 --- tools/to_json.py | 21 +- 14 files changed, 112 insertions(+), 883 deletions(-) delete mode 100644 shenzhen_solitaire/solver/__init__.py delete mode 100644 shenzhen_solitaire/solver/board_actions.py delete mode 100644 shenzhen_solitaire/solver/board_possibilities.py delete mode 100644 shenzhen_solitaire/solver/solver.py delete mode 100644 tools/generate/border.py delete mode 100644 tools/generate/bunker.py delete mode 100644 tools/generate/field.py delete mode 100644 tools/generate/goal.py delete mode 100644 tools/generate/hua.py diff --git a/shenzhen_solitaire/card_detection/configuration.py b/shenzhen_solitaire/card_detection/configuration.py index 0f89083..a9a9870 100644 --- a/shenzhen_solitaire/card_detection/configuration.py +++ b/shenzhen_solitaire/card_detection/configuration.py @@ -105,7 +105,6 @@ def _save_adjustments(zip_file: zipfile.ZipFile, conf: Configuration) -> None: adjustments[SPECIAL_BUTTON_ADJUSTMENT_KEY] = dataclasses.asdict( conf.special_button_adjustment ) - print(adjustments) zip_file.writestr( ADJUSTMENT_FILE_NAME, json.dumps(adjustments), ) diff --git a/shenzhen_solitaire/clicker/__init__.py b/shenzhen_solitaire/clicker/__init__.py index 687b838..2a73aa8 100644 --- a/shenzhen_solitaire/clicker/__init__.py +++ b/shenzhen_solitaire/clicker/__init__.py @@ -1,13 +1,14 @@ import time -from typing import List, Tuple +from typing import List, Tuple, Dict, Any import pyautogui import shenzhen_solitaire.board as board import shenzhen_solitaire.card_detection.adjustment as adjustment import shenzhen_solitaire.card_detection.configuration as configuration -import shenzhen_solitaire.solver.board_actions as board_actions import warnings +from dataclasses import dataclass + def drag( src: Tuple[int, int], dst: Tuple[int, int], offset: Tuple[int, int] = (0, 0) @@ -50,80 +51,73 @@ def clickSquare( ) -def handle_action( - action: board_actions.Action, - offset: Tuple[int, int], - conf: configuration.Configuration, -) -> None: - if isinstance(action, board_actions.MoveAction): - src = adjustment.get_square( - conf.field_adjustment, - index_x=action.source_id, - index_y=action.source_row_index, +@dataclass +class DragAction: + source: Tuple[int, int] + destination: Tuple[int, int] + + +@dataclass +class ClickAction: + destination: Tuple[int, int] + + +class WaitAction: + pass + + +def _parse_field( + field: Dict[str, Any], conf: configuration.Configuration +) -> Tuple[int, int]: + return ( + int(field["column"]) * conf.field_adjustment.dx + conf.field_adjustment.x, + int(field["row"]) * conf.field_adjustment.dy + conf.field_adjustment.y, + ) + + +def parse_action(action: Dict[str, Any], conf: configuration.Configuration): + assert len(action) == 1 + action_name, info = next(iter(action.items())) + action_name = action_name.lower() + if action_name == "bunkerize": + field = _parse_field(info["field_position"], conf) + bunker = ( + int(info["bunker_slot_index"]) * conf.bunker_adjustment.dx + + conf.bunker_adjustment.x, + conf.bunker_adjustment.y, ) - dst = adjustment.get_square( - conf.field_adjustment, - index_x=action.destination_id, - index_y=action.destination_row_index, - ) - dragSquare(src, dst, offset) - return - if isinstance(action, board_actions.HuaKillAction): - warnings.warn("Hua kill should be handled before handle_action") - return - if isinstance(action, board_actions.BunkerizeAction): - field = adjustment.get_square( - conf.field_adjustment, - index_x=action.field_id, - index_y=action.field_row_index, - ) - bunker = adjustment.get_square( - conf.bunker_adjustment, index_x=action.bunker_id, index_y=0, - ) - if action.to_bunker: - dragSquare(field, bunker, offset) + if str(info["to_bunker"]).lower() == "true": + return DragAction(source=field, destination=bunker) else: - dragSquare(bunker, field, offset) - return - if isinstance(action, board_actions.DragonKillAction): - dragon_sequence = [ - board.SpecialCard.Zhong, - board.SpecialCard.Fa, - board.SpecialCard.Bai, - ] - field = adjustment.get_square( - conf.special_button_adjustment, - index_x=0, - index_y=dragon_sequence.index(action.dragon), + return DragAction(source=bunker, destination=field) + elif action_name == "move": + return DragAction( + source=_parse_field(info["source"], conf), + destination=_parse_field(info["source"], conf), ) - clickSquare( - field, offset, + elif action_name == "dragonkill": + return ClickAction() + elif action_name == "goal": + goal = ( + int(info["goal_slot_index"]) * conf.goal_adjustment.dx + + conf.goal_adjustment.x, + conf.goal_adjustment.y, ) - time.sleep(1) - return - if isinstance(action, board_actions.GoalAction): - dst = adjustment.get_square( - conf.goal_adjustment, index_x=action.goal_id, index_y=0, - ) - if action.source_position == board.Position.Field: - assert action.source_row_index is not None - src = adjustment.get_square( - conf.field_adjustment, - index_x=action.source_id, - index_y=action.source_row_index, - ) + if "Field" in info["source"]: + source = _parse_field(info["source"]["Field"], conf) else: - assert action.source_position == board.Position.Bunker - src = adjustment.get_square( - conf.bunker_adjustment, index_x=action.source_id, index_y=0, + source = ( + int(info["source"]["Bunker"]["slot_index"]) * conf.bunker_adjustment.dx + + conf.bunker_adjustment.x, + conf.bunker_adjustment.y, ) - dragSquare(src, dst, offset) - return - raise AssertionError("You forgot an Action type") + return DragAction(source=source, destination=goal) + elif action_name == "huakill": + return WaitAction() def handle_actions( - actions: List[board_actions.Action], + actions: List[Dict[str, Dict[str, Any]]], offset: Tuple[int, int], conf: configuration.Configuration, ) -> None: diff --git a/shenzhen_solitaire/solver/__init__.py b/shenzhen_solitaire/solver/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/shenzhen_solitaire/solver/board_actions.py b/shenzhen_solitaire/solver/board_actions.py deleted file mode 100644 index d6e0e5b..0000000 --- a/shenzhen_solitaire/solver/board_actions.py +++ /dev/null @@ -1,217 +0,0 @@ -"""Contains actions that can be used on the board""" -from dataclasses import dataclass -from typing import List, Optional, Tuple - -from .. import board - - -class Action: - """Base class for a card move action on a solitaire board""" - - _before_state: int = 0 - _after_state: int = 0 - - def _apply(self, action_board: board.Board) -> None: - pass - - def _undo(self, action_board: board.Board) -> None: - pass - - def apply(self, action_board: board.Board) -> None: - """Apply action to board""" - if __debug__: - self._before_state = action_board.state_identifier - self._apply(action_board) - if __debug__: - self._after_state = action_board.state_identifier - - def undo(self, action_board: board.Board) -> None: - """Undo action to board""" - assert action_board.state_identifier == self._after_state - self._undo(action_board) - 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 -class GoalAction(Action): - """Move card from field to goal""" - - card: board.NumberCard - source_id: int - source_row_index: Optional[int] - source_position: board.Position - goal_id: int - obvious: bool - - def _apply(self, action_board: board.Board) -> None: - """Do action""" - assert action_board.getGoalId(self.card.suit) == self.goal_id - assert action_board.getGoal(self.card.suit) + 1 == self.card.number - if self.source_position == board.Position.Field: - assert action_board.field[self.source_id][-1] == self.card - action_board.field[self.source_id].pop() - action_board.incGoal(self.card.suit) - elif self.source_position == board.Position.Bunker: - assert action_board.bunker[self.source_id] == self.card - action_board.bunker[self.source_id] = None - action_board.incGoal(self.card.suit) - else: - raise RuntimeError("Unknown position") - - def _undo(self, action_board: board.Board) -> None: - """Undo action""" - assert action_board.getGoalId(self.card.suit) == self.goal_id - assert action_board.getGoal(self.card.suit) == self.card.number - if self.source_position == board.Position.Field: - action_board.field[self.source_id].append(self.card) - elif self.source_position == board.Position.Bunker: - assert action_board.bunker[self.source_id] is None - action_board.bunker[self.source_id] = self.card - else: - raise RuntimeError("Unknown position") - action_board.setGoal(self.card.suit, action_board.getGoal(self.card.suit) - 1) - - -@dataclass -class BunkerizeAction(Action): - """Move card from bunker to field""" - - card: board.Card - bunker_id: int - field_id: int - field_row_index: int - to_bunker: bool - - def _move_from_bunker(self, action_board: board.Board) -> None: - assert action_board.bunker[self.bunker_id] == self.card - action_board.bunker[self.bunker_id] = None - action_board.field[self.field_id].append(self.card) - - def _move_to_bunker(self, action_board: board.Board) -> None: - assert action_board.field[self.field_id][-1] == self.card - assert action_board.bunker[self.bunker_id] is None - action_board.bunker[self.bunker_id] = self.card - action_board.field[self.field_id].pop() - - def _apply(self, action_board: board.Board) -> None: - """Do action""" - if self.to_bunker: - self._move_to_bunker(action_board) - else: - self._move_from_bunker(action_board) - - 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(Action): - """Moving a card from one field stack to another""" - - cards: List[board.Card] - source_id: int - source_row_index: int - destination_id: int - destination_row_index: 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 - - 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""" - if action_board.field[self.destination_id]: - dest_card = action_board.field[self.destination_id][-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 isinstance(self.cards[0], board.NumberCard): - raise AssertionError() - if dest_card.suit == self.cards[0].suit: - raise AssertionError() - if dest_card.number != self.cards[0].number + 1: - raise AssertionError() - 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(Action): - """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 len(self.source_stacks) == 4 - for position, index in self.source_stacks: - if position == board.Position.Field: - assert action_board.field[index] - assert action_board.field[index][-1] == self.dragon - action_board.field[index].pop() - elif position == board.Position.Bunker: - assert action_board.bunker[index] == self.dragon - action_board.bunker[index] = None - else: - raise RuntimeError("Can only kill dragons in field and bunker") - 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] == (self.dragon, 4) - assert len(self.source_stacks) == 4 - action_board.bunker[self.destination_bunker_id] = None - for position, index in self.source_stacks: - if position == board.Position.Field: - action_board.field[index].append(self.dragon) - elif position == board.Position.Bunker: - action_board.bunker[index] = self.dragon - else: - raise RuntimeError("Can only kill dragons in field and bunker") - - -@dataclass -class HuaKillAction(Action): - """Remove the flower card""" - - source_field_id: int - source_field_row_index: int - - def _apply(self, action_board: board.Board) -> None: - """Do action""" - 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.flower_gone = True - - def _undo(self, action_board: board.Board) -> None: - """Undo action""" - assert action_board.flower_gone - action_board.field[self.source_field_id].append(board.SpecialCard.Hua) - action_board.flower_gone = False diff --git a/shenzhen_solitaire/solver/board_possibilities.py b/shenzhen_solitaire/solver/board_possibilities.py deleted file mode 100644 index 0e838ea..0000000 --- a/shenzhen_solitaire/solver/board_possibilities.py +++ /dev/null @@ -1,234 +0,0 @@ -"""Contains function to iterate different kinds of possible actions""" -from typing import Iterator, List, Tuple - -from .. import board -from . import board_actions - - -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 and stack[-1] == board.SpecialCard.Hua: - yield board_actions.HuaKillAction( - source_field_id=index, source_field_row_index=len(stack) - 1 - ) - - -def possible_dragonkill_actions( - search_board: board.Board, -) -> Iterator[board_actions.DragonKillAction]: - """Enumerate all possible dragon kills""" - 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: - if any(x == dragon for x in search_board.bunker): - new_possible_dragons.append(dragon) - 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 - ] - if len(bunker_dragons) + len(field_dragons) != 4: - continue - destination_bunker_id = 0 - if bunker_dragons: - destination_bunker_id = bunker_dragons[0] - else: - destination_bunker_id = [ - 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]) - - 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.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] - - if not open_bunker_list: - return - - open_bunker = open_bunker_list[0] - for index, stack in enumerate(search_board.field): - if not stack: - continue - yield board_actions.BunkerizeAction( - card=stack[-1], - field_id=index, - field_row_index=len(stack) - 1, - bunker_id=open_bunker, - to_bunker=True, - ) - - -def possible_debunkerize_actions( - 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) - ] - for index, card in bunker_number_cards: - 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 == card.suit: - continue - if other_stack[-1].number != card.number + 1: - continue - yield board_actions.BunkerizeAction( - card=card, - bunker_id=index, - field_id=other_index, - field_row_index=len(other_stack), - to_bunker=False, - ) - - -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 - ] - bunker_cards = [ - (board.Position.Bunker, index, card) - for index, card in enumerate(search_board.bunker) - ] - top_cards = [ - x for x in field_cards + bunker_cards if isinstance(x[2], board.NumberCard) - ] - top_cards = [ - x for x in top_cards if x[2].number == search_board.getGoal(x[2].suit) + 1 - ] - - result = [] - for source, index, card in top_cards: - obvious = all( - search_board.getGoal(other_suit) >= card.number - 2 - for other_suit in set(board.NumberCard.Suit) - {card.suit} - ) - result.append( - board_actions.GoalAction( - card=card, - source_id=index, - source_row_index=len(search_board.field[index]) - 1 - if source == board.Position.Field - else None, - source_position=source, - goal_id=search_board.getGoalId(card.suit), - obvious=obvious, - ) - ) - break - yield from sorted(result, key=lambda x: x.card.number) - - -def _can_stack(bottom: board.Card, top: board.Card) -> bool: - if not isinstance(bottom, board.NumberCard): - return False - if not isinstance(top, board.NumberCard): - return False - if bottom.suit == top.suit: - return False - if bottom.number != top.number + 1: - return False - return True - - -def _get_cardstacks(search_board: board.Board) -> List[List[board.Card]]: - """Returns all cards on one stack that can be moved at once""" - result: List[List[board.Card]] = [] - for stack in search_board.field: - result.append([]) - if not stack: - continue - result[-1].append(stack[-1]) - for card in stack[-2::-1]: - if not _can_stack(card, result[-1][0]): - break - if not isinstance(card, board.NumberCard): - break - result[-1].insert(0, card) - return result - - -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""" - first_empty_field_id = -1 - cardstacks = [x for x in enumerate(_get_cardstacks(search_board)) if x[1]] - cardstacks = sorted(cardstacks, key=lambda x: len(x[1])) - substacks: List[Tuple[int, List[board.Card]]] = [] - - for index, stack in cardstacks: - substacks.extend( - (index, substack) for substack in (stack[i:] for i in range(len(stack))) - ) - - for source_index, source_substack in substacks: - for destination_index, destination_stack in enumerate(search_board.field): - if source_index == destination_index: - continue - if destination_stack: - if not _can_stack(destination_stack[-1], source_substack[0]): - continue - elif len(source_substack) == len(search_board.field[source_index]): - continue - elif first_empty_field_id == -1: - first_empty_field_id = destination_index - elif destination_index != first_empty_field_id: - continue - yield board_actions.MoveAction( - cards=source_substack, - source_id=source_index, - source_row_index=len(search_board.field[source_index]) - - len(source_substack), - destination_id=destination_index, - destination_row_index=len(destination_stack), - ) - - -def possible_actions(search_board: board.Board) -> List[board_actions.Action]: - """Enumerate all possible actions on the current search_board""" - result: List[board_actions.Action] = [ - *list(possible_huakill_action(search_board)), - *list(possible_dragonkill_actions(search_board)), - *list(possible_goal_move_actions(search_board)), - *list(possible_debunkerize_actions(search_board)), - *list(possible_field_move_actions(search_board)), - *list(possible_bunkerize_actions(search_board)), - ] - - for action in result: - if action.automatic(): - return [action] - - return result diff --git a/shenzhen_solitaire/solver/solver.py b/shenzhen_solitaire/solver/solver.py deleted file mode 100644 index 3f09f03..0000000 --- a/shenzhen_solitaire/solver/solver.py +++ /dev/null @@ -1,129 +0,0 @@ -"""Contains solver for solitaire""" -import typing -from typing import Iterator, List, Optional -import time -from dataclasses import dataclass -from ..board import Board -from . import board_actions -from .board_actions import DragonKillAction, GoalAction, HuaKillAction, MoveAction -from .board_possibilities import possible_actions - - -@dataclass -class ActionStackFrame: - iterator: Iterator[board_actions.Action] - last_action: Optional[board_actions.Action] - state: int - - def next(self) -> Optional[board_actions.Action]: - """Get next iteration of top action iterator""" - try: - self.last_action = next(self.iterator) - except StopIteration: - return None - return self.last_action - - -class ActionStack: - """Stack of chosen actions on the board""" - - def __init__(self) -> None: - self.frames: List[ActionStackFrame] = [] - - def push(self, board: Board) -> None: - """Append another board state to stack""" - self.frames.append( - ActionStackFrame( - iterator=iter(possible_actions(board)), - last_action=None, - state=board.state_identifier, - ) - ) - - @property - def top(self) -> ActionStackFrame: - """Get next iteration of top action iterator""" - return self.frames[-1] - - def pop(self) -> None: - """Pop one action from stack""" - self.frames.pop() - - def __len__(self) -> int: - return len(self.frames) - - -def solve( - board: Board, *, timeout: Optional[float] = None, verbose: bool = False -) -> Iterator[List[board_actions.Action]]: - """Solve a solitaire puzzle""" - state_set = {board.state_identifier} - stack = ActionStack() - stack.push(board) - - def _limit_stack_size(stack_size: int) -> None: - if len(stack) == stack_size: - stack.pop() - assert stack.top.last_action is not None - stack.top.last_action.undo(board) - assert board.state_identifier in state_set - - def _backtrack_action() -> None: - stack.pop() - assert stack.top.last_action is not None - stack.top.last_action.undo(board) - assert board.state_identifier in state_set - - def _skip_loop_move(action: board_actions.Action) -> bool: - if not isinstance(action, MoveAction): - return False - for frame in stack.frames[-2::-1]: - if not isinstance(frame.last_action, MoveAction): - continue - if frame.last_action.cards == action.cards: - return True - return False - - iter_start = time.time() - count = 0 - while len(stack) > 0: - - count += 1 - if count > 5000: - count = 0 - if verbose: - print(f"{time.time() - iter_start} {len(stack)} {board.goal}") - if timeout is not None and time.time() - iter_start > timeout: - return - - # _limit_stack_size(80) - - assert board.state_identifier == stack.top.state - action = stack.top.next() - - if action is None: - _backtrack_action() - continue - - if _skip_loop_move(action): - continue - - action.apply(board) - - if board.solved(): - assert all(x.last_action is not None for x in stack.frames) - yield [ - typing.cast(board_actions.Action, x.last_action) for x in stack.frames - ] - iter_start = time.time() - action.undo(board) - assert board.state_identifier in state_set - continue - - if board.state_identifier in state_set: - action.undo(board) - assert board.state_identifier in state_set - continue - - state_set.add(board.state_identifier) - stack.push(board) diff --git a/tools/assistant.py b/tools/assistant.py index 800054f..07d6c62 100644 --- a/tools/assistant.py +++ b/tools/assistant.py @@ -1,19 +1,19 @@ +import argparse import os +import subprocess import tempfile import time from pathlib import Path -from typing import List - +from typing import List, Dict, Any +import json import cv2 import numpy as np import pyautogui import shenzhen_solitaire.card_detection.configuration as configuration import shenzhen_solitaire.clicker as clicker -import shenzhen_solitaire.solver.solver as solver from shenzhen_solitaire.board import Board from shenzhen_solitaire.card_detection.board_parser import parse_start_board -from shenzhen_solitaire.solver.board_actions import Action OFFSET = (0, 0) # SIZE = (2560, 1440) @@ -22,39 +22,29 @@ NEW_BUTTON = (1900, 1100) SAVE_UNSOLVED = False UNSOLVED_DIR = "E:/shenzhen-solitaire/unsolved" +SOLVER_PATH = '/home/lukas/documents/coding/rust/shenzhen-solitaire/target/release/solver' +def extern_solve(board: Board) -> List[Dict[str, Any]]: + result = subprocess.run([SOLVER_PATH], input=board.to_json(), capture_output=True, text=True) + return json.loads(result.stdout) -def extern_solve(board: Board) -> List[Action]: - pass - - -def solve(conf: configuration.Configuration) -> None: +def take_screenshot() : with tempfile.TemporaryDirectory(prefix="shenzhen_solitaire") as screenshot_dir: print("Taking screenshot") screenshot_file = Path(screenshot_dir) / "screenshot.png" screenshot = pyautogui.screenshot(region=(*OFFSET, *SIZE)) screenshot.save(screenshot_file) image = cv2.imread(str(screenshot_file)) - input() + return image - print("Solving") +def solve(conf: configuration.Configuration) -> None: + image = take_screenshot() board = parse_start_board(image, conf) - print(board.to_json()) assert board.check_correct() - input() - solution_iterator = next(solver.solve(board, timeout=10, verbose=True), None) - if solution_iterator is None: - clicker.click(NEW_BUTTON, OFFSET) - time.sleep(10) - if SAVE_UNSOLVED: - fd, outfile = tempfile.mkstemp(dir=UNSOLVED_DIR, suffix=".png") - sock = os.fdopen(fd, "w") - sock.close() - cv2.imwrite(outfile, image) - return - solution = list(solution_iterator) - print(f"Solved in {len(solution)} steps") - clicker.handle_actions(solution, OFFSET, conf) + actions = extern_solve(board) + assert 0 + print(f"Solved in {len(actions)} steps") + clicker.handle_actions(actions, OFFSET, conf) print("Solved") time.sleep(2) clicker.click(NEW_BUTTON, OFFSET) @@ -62,8 +52,18 @@ def solve(conf: configuration.Configuration) -> None: def main() -> None: + parser = argparse.ArgumentParser( + description="Solve board" + ) + parser.add_argument( + "config_path", + type=str, + help="Config path", + ) + + args = parser.parse_args() time.sleep(3) - conf = configuration.load("test_config.zip") + conf = configuration.load(args.config_path) while True: solve(conf) diff --git a/tools/generate/border.py b/tools/generate/border.py deleted file mode 100644 index 1fc8795..0000000 --- a/tools/generate/border.py +++ /dev/null @@ -1,41 +0,0 @@ -import copy -import dataclasses -import json - -import cv2 -import numpy as np - -import shenzhen_solitaire.card_detection.adjustment as adjustment -import shenzhen_solitaire.card_detection.card_finder as card_finder -from shenzhen_solitaire.card_detection.configuration import Configuration -import argparse - - -def main() -> None: - """Generate a configuration""" - - parser = argparse.ArgumentParser(description='Process some integers.') - parser.add_argument('screenshot_path', metavar='screenshot_path', type=str, - help='Path to the screenshot') - - args = parser.parse_args() - print(args.screenshot_path) - image = cv2.imread(args.screenshot_path) - - border_adjustment = adjustment.adjust_squares(image, count_x=8, count_y=13) - border_square_pos = adjustment.adjust_squares( - image, count_x=1, count_y=1, adjustment=copy.deepcopy(border_adjustment) - ) - border_square = card_finder.get_field_squares(image, border_square_pos, 1, 1) - empty_square_pos = adjustment.adjust_squares( - image, count_x=1, count_y=1, adjustment=copy.deepcopy(border_adjustment) - ) - empty_square = card_finder.get_field_squares(image, empty_square_pos, 1, 1) - - cv2.imwrite("/tmp/border_square.png", border_square[0]) - cv2.imwrite("/tmp/empty_square.png", empty_square[0]) - print(json.dumps(dataclasses.asdict(border_adjustment))) - - -if __name__ == "__main__": - main() diff --git a/tools/generate/bunker.py b/tools/generate/bunker.py deleted file mode 100644 index 9a76314..0000000 --- a/tools/generate/bunker.py +++ /dev/null @@ -1,43 +0,0 @@ -import copy -import dataclasses -import json - -import cv2 -import numpy as np - -import shenzhen_solitaire.card_detection.adjustment as adjustment -import shenzhen_solitaire.card_detection.card_finder as card_finder -from shenzhen_solitaire.card_detection.configuration import Configuration - - -def main() -> None: - """Generate a configuration""" - image = cv2.imread("pictures/specific/BunkerCards.jpg") - - bunker_adjustment = adjustment.adjust_squares( - image, - count_x=3, - count_y=1, - adjustment=adjustment.Adjustment( - **{"x": 730, "y": 310, "w": 19, "h": 21, "dx": 152, "dy": 0} - ), - ) - print(json.dumps(dataclasses.asdict(bunker_adjustment))) - - back_image = cv2.imread("pictures/specific/BaiShiny.jpg") - back_squares = card_finder.get_field_squares( - back_image, count_x=1, count_y=3, adjustment=copy.deepcopy(bunker_adjustment) - ) - - green_image = cv2.imread("pictures/20190809172213_1.jpg") - green_squares = card_finder.get_field_squares( - green_image, count_x=1, count_y=3, adjustment=copy.deepcopy(bunker_adjustment) - ) - - cv2.imwrite("/tmp/bunker_green_1.png", green_squares[0]) - cv2.imwrite("/tmp/bunker_green_2.png", green_squares[1]) - cv2.imwrite("/tmp/bunker_green_3.png", green_squares[2]) - - -if __name__ == "__main__": - main() diff --git a/tools/generate/catalogue.py b/tools/generate/catalogue.py index 792dff9..b93079e 100644 --- a/tools/generate/catalogue.py +++ b/tools/generate/catalogue.py @@ -2,7 +2,7 @@ import argparse import cv2 import numpy as np - +import copy from shenzhen_solitaire.card_detection import configuration, adjustment, card_finder from shenzhen_solitaire.card_detection.configuration import Configuration @@ -31,11 +31,19 @@ def main() -> None: args = parser.parse_args() image = cv2.imread(args.screenshot_path) conf = configuration.load(args.config_path) + squares = card_finder.get_field_squares(image, conf.field_adjustment, 5, 8) catalogue = card_finder.catalogue_cards(squares) + conf.catalogue.extend(catalogue) + conf.card_border.extend( card_finder.get_field_squares(image, conf.border_adjustment, 1, 1) ) + + empty_adjust = copy.deepcopy(conf.border_adjustment) + empty_adjust.y = empty_adjust.y + 4 * empty_adjust.dy + conf.empty_card.extend(card_finder.get_field_squares(image, empty_adjust, 1, 1)) + conf.green_card.extend( card_finder.get_field_squares(image, conf.bunker_adjustment, 1, 3) ) @@ -45,7 +53,7 @@ def main() -> None: conf.green_card.extend( card_finder.get_field_squares(image, conf.hua_adjustment, 1, 1) ) - conf.catalogue.extend(catalogue) + configuration.save(conf, args.config_path) diff --git a/tools/generate/field.py b/tools/generate/field.py deleted file mode 100644 index bd2a14c..0000000 --- a/tools/generate/field.py +++ /dev/null @@ -1,43 +0,0 @@ -import argparse - -import cv2 -import numpy as np - -from shenzhen_solitaire.card_detection import configuration, adjustment, card_finder -from shenzhen_solitaire.card_detection.configuration import Configuration - - -def main() -> None: - """Generate a configuration""" - parser = argparse.ArgumentParser( - description="Generate pictures for symbols, " - "requires screenshot of field with no moved cards, " - "so 8 columns of 5 cards each" - ) - parser.add_argument( - "screenshot_path", - metavar="screenshot_path", - type=str, - help="Path to the screenshot", - ) - parser.add_argument( - "--conf", - dest="config_path", - type=str, - default="config.zip", - help="Path to existing config to be merged, or new config", - ) - - args = parser.parse_args() - print(args.screenshot_path) - image = cv2.imread(args.screenshot_path) - - adj = adjustment.adjust_field(image) - squares = card_finder.get_field_squares(image, adj, 5, 8) - catalogue = card_finder.catalogue_cards(squares) - generated_config = Configuration(field_adjustment=adj, catalogue=catalogue, meta={}) - configuration.save(generated_config, args.config_path) - - -if __name__ == "__main__": - main() diff --git a/tools/generate/goal.py b/tools/generate/goal.py deleted file mode 100644 index 9cdd6cd..0000000 --- a/tools/generate/goal.py +++ /dev/null @@ -1,38 +0,0 @@ -import copy -import dataclasses -import json - -import cv2 -import numpy as np - -import shenzhen_solitaire.card_detection.adjustment as adjustment -import shenzhen_solitaire.card_detection.card_finder as card_finder -from shenzhen_solitaire.card_detection.configuration import Configuration - - -def main() -> None: - """Generate a configuration""" - image = cv2.imread("pictures/specific/BaiShiny.jpg") - - goal_adjustment = adjustment.adjust_squares( - image, - count_x=3, - count_y=1, - adjustment=adjustment.Adjustment( - **{"x": 1490, "y": 310, "w": 19, "h": 21, "dx": 152, "dy": 0} - ), - ) - print(json.dumps(dataclasses.asdict(goal_adjustment))) - - green_image = cv2.imread("pictures/20190809172213_1.jpg") - green_squares = card_finder.get_field_squares( - green_image, count_x=1, count_y=3, adjustment=copy.deepcopy(goal_adjustment) - ) - - cv2.imwrite("/tmp/goal_green_1.png", green_squares[0]) - cv2.imwrite("/tmp/goal_green_2.png", green_squares[1]) - cv2.imwrite("/tmp/goal_green_3.png", green_squares[2]) - - -if __name__ == "__main__": - main() diff --git a/tools/generate/hua.py b/tools/generate/hua.py deleted file mode 100644 index 861bf9e..0000000 --- a/tools/generate/hua.py +++ /dev/null @@ -1,34 +0,0 @@ -import copy -import dataclasses -import json - -import cv2 -import numpy as np - -import shenzhen_solitaire.card_detection.adjustment as adjustment -import shenzhen_solitaire.card_detection.card_finder as card_finder -from shenzhen_solitaire.card_detection.configuration import Configuration - - -def main() -> None: - """Generate a configuration""" - image = cv2.imread("pictures/specific/BunkerCards.jpg") - - hua_adjustment = adjustment.adjust_squares( - image, - count_x=1, - count_y=1, - adjustment=adjustment.Adjustment( - **{"x": 1299, "y": 314, "w": 19, "h": 21, "dx": 0, "dy": 0} - ), - ) - print(json.dumps(dataclasses.asdict(hua_adjustment))) - green_image = cv2.imread("pictures/specific/ZhongShiny.jpg") - hua_green = card_finder.get_field_squares( - green_image, hua_adjustment, count_x=1, count_y=1 - ) - cv2.imwrite("/tmp/hua_green.png", hua_green[0]) - - -if __name__ == "__main__": - main() diff --git a/tools/to_json.py b/tools/to_json.py index b777cbd..97c786d 100644 --- a/tools/to_json.py +++ b/tools/to_json.py @@ -1,14 +1,21 @@ -from shenzhen_solitaire.card_detection.board_parser import parse_to_json -import shenzhen_solitaire.card_detection.configuration as configuration -import cv2 +import argparse import sys +import cv2 + +import shenzhen_solitaire.card_detection.configuration as configuration +from shenzhen_solitaire.card_detection.board_parser import parse_to_json + def main() -> None: - if len(sys.argv) < 2: - print("Give filename pls") - return - image = cv2.imread(str(sys.argv[1])) + parser = argparse.ArgumentParser(description="Parse board to json") + parser.add_argument("board_path", type=str, help="Path to image of board") + parser.add_argument( + "--config", dest="config_path", type=str, help="Config path", + ) + + args = parser.parse_args() + image = cv2.imread(args.board_path) conf = configuration.load("test_config.zip")