From f4ac445f61f39610cc34ef637cde99ed94e2159c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20W=C3=B6lfer?= Date: Fri, 12 Jun 2020 17:31:12 +0200 Subject: [PATCH] Made calibration more ergonomic. Crashes because loading empty 'empty_card' directory --- shenzhen_solitaire/board.py | 32 ++++++++++++++++- .../card_detection/board_parser.py | 11 ++++-- tools/assistant.py | 35 ++++++++++++------- tools/generate/all_borders.py | 25 +++++++------ tools/generate/catalogue.py | 5 ++- 5 files changed, 79 insertions(+), 29 deletions(-) diff --git a/shenzhen_solitaire/board.py b/shenzhen_solitaire/board.py index 9d7209a..851d39a 100644 --- a/shenzhen_solitaire/board.py +++ b/shenzhen_solitaire/board.py @@ -3,7 +3,7 @@ import enum import itertools from dataclasses import dataclass from typing import Dict, List, Optional, Set, Tuple, Union - +import json class SpecialCard(enum.Enum): """Different types of special cards""" @@ -50,6 +50,27 @@ class Position(enum.Enum): Bunker = enum.auto() Goal = enum.auto() +def _field_card_to_str(card: Card): + if card == SpecialCard.Hua: + return "Hua" + if isinstance(card, SpecialCard): + return {"Special": card.name} + elif isinstance(card, NumberCard): + return {"Number": {"value": card.number, "suit": card.suit.name}} + + +def _bunker_card_to_str(card: Union[Tuple[SpecialCard, int], Optional[Card]]): + if card is None: + return "Empty" + if isinstance(card, tuple): + return {"Blocked": card[0].name} + return {"Stashed": _field_card_to_str(card)} + + +def _goal_card_to_str(card: Optional[NumberCard]): + if card is None: + return None + return {"value": card.number, "suit": card.suit.name} class Board: """Solitaire board""" @@ -187,3 +208,12 @@ class Board: if count != 4: return False return True + + def to_json(self) -> str: + mystruct = { + "field": [[_field_card_to_str(card) for card in row] for row in self.field], + "hua_set": self.flower_gone, + "bunker": [_bunker_card_to_str(card) for card in self.bunker], + "goal": [_goal_card_to_str(card) for card in self.goal], + } + return json.dumps(mystruct) diff --git a/shenzhen_solitaire/card_detection/board_parser.py b/shenzhen_solitaire/card_detection/board_parser.py index 641ad0a..6efe58f 100644 --- a/shenzhen_solitaire/card_detection/board_parser.py +++ b/shenzhen_solitaire/card_detection/board_parser.py @@ -36,7 +36,6 @@ def get_field_square_iterator( """Return iterator for both the square, as well as the matching card border""" my_adj = fake_adjustment(conf.field_adjustment) my_border_adj = fake_adjustment(conf.border_adjustment) - squares = card_finder.get_field_squares( image, my_adj, count_x=row_count, count_y=column_count ) @@ -50,7 +49,6 @@ def get_field_square_iterator( def match_template(template: np.ndarray, search_image: np.ndarray) -> float: """Return matchiness for the template on the search image""" - res = cv2.matchTemplate(search_image, template, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) assert isinstance(max_val, (int, float)) @@ -64,7 +62,6 @@ def parse_field_square( (match_template(template, square), name) for template, name in conf.catalogue ] best_val, best_name = max(square_fits, key=lambda x: x[0]) - best_border = max( match_template(template=template, search_image=border) for template in conf.card_border @@ -232,6 +229,14 @@ def parse_board(image: np.ndarray, conf: Configuration) -> Board: result.goal = parse_goal(image, conf) return result +def parse_start_board(image: np.ndarray, conf: Configuration) -> Board: + result = Board() + result.field = parse_field(image, conf) + result.flower_gone = parse_hua(image, conf) + result.bunker = [None] * 3 + result.goal = parse_goal(image, conf) + return result + def field_card_to_str(card: Card): if card == SpecialCard.Hua: diff --git a/tools/assistant.py b/tools/assistant.py index a16ee58..800054f 100644 --- a/tools/assistant.py +++ b/tools/assistant.py @@ -1,43 +1,53 @@ +import os import tempfile import time from pathlib import Path +from typing import List import cv2 import numpy as np -import os - 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.card_detection.board_parser import parse_board +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) +# SIZE = (2560, 1440) +SIZE = (1366, 768) NEW_BUTTON = (1900, 1100) SAVE_UNSOLVED = False +UNSOLVED_DIR = "E:/shenzhen-solitaire/unsolved" -def solve() -> None: - with tempfile.TemporaryDirectory() as screenshot_dir: + +def extern_solve(board: Board) -> List[Action]: + pass + + +def solve(conf: configuration.Configuration) -> None: + 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() print("Solving") - conf = configuration.load("test_config.zip") - board = parse_board(image, conf) + 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="E:/shenzhen-solitaire/unsolved", suffix=".png" - ) + fd, outfile = tempfile.mkstemp(dir=UNSOLVED_DIR, suffix=".png") sock = os.fdopen(fd, "w") sock.close() cv2.imwrite(outfile, image) @@ -53,8 +63,9 @@ def solve() -> None: def main() -> None: time.sleep(3) + conf = configuration.load("test_config.zip") while True: - solve() + solve(conf) if __name__ == "__main__": diff --git a/tools/generate/all_borders.py b/tools/generate/all_borders.py index 0770de6..5831281 100644 --- a/tools/generate/all_borders.py +++ b/tools/generate/all_borders.py @@ -49,25 +49,30 @@ def main() -> None: image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment) ) print("Field borders") - border_adjustment = adjustment.adjust_squares( + conf.border_adjustment = adjustment.adjust_squares( image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment) ) - conf.bunker_adjustment.w = conf.field_adjustment.w - conf.bunker_adjustment.h = conf.field_adjustment.h + for adj in (conf.bunker_adjustment, conf.goal_adjustment,conf.hua_adjustment): + adj.w = conf.field_adjustment.w + adj.h = conf.field_adjustment.h + adj.dx = conf.field_adjustment.dx + adj.dy = conf.field_adjustment.dy + + conf.bunker_adjustment.x = conf.field_adjustment.x print("Bunker cards") - bunker_adjustment = adjustment.adjust_squares( + conf.bunker_adjustment = adjustment.adjust_squares( image, count_x=3, count_y=1, adjustment=copy.deepcopy(conf.bunker_adjustment) ) - conf.goal_adjustment.w = conf.field_adjustment.w - conf.goal_adjustment.h = conf.field_adjustment.h + + conf.goal_adjustment.x = conf.field_adjustment.x + 5 * conf.field_adjustment.dx + conf.goal_adjustment.y = conf.bunker_adjustment.y print("Goal cards") - goal_adjustment = adjustment.adjust_squares( + conf.goal_adjustment = adjustment.adjust_squares( image, count_x=3, count_y=1, adjustment=copy.deepcopy(conf.goal_adjustment) ) - conf.hua_adjustment.w = conf.field_adjustment.w - conf.hua_adjustment.h = conf.field_adjustment.h + conf.hua_adjustment.y = conf.bunker_adjustment.y print("Hua card") - hua_adjustment = adjustment.adjust_squares( + conf.hua_adjustment = adjustment.adjust_squares( image, count_x=1, count_y=1, adjustment=copy.deepcopy(conf.hua_adjustment) ) diff --git a/tools/generate/catalogue.py b/tools/generate/catalogue.py index 2583aba..792dff9 100644 --- a/tools/generate/catalogue.py +++ b/tools/generate/catalogue.py @@ -21,7 +21,7 @@ def main() -> None: help="Path to the screenshot", ) parser.add_argument( - "--conf", + "--config", dest="config_path", type=str, default="config.zip", @@ -29,9 +29,8 @@ def main() -> None: ) args = parser.parse_args() - print(args.screenshot_path) image = cv2.imread(args.screenshot_path) - conf = configuration.load(args.config) + 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.card_border.extend(