Made calibration more ergonomic. Crashes because loading empty 'empty_card' directory
This commit is contained in:
@@ -3,7 +3,7 @@ import enum
|
|||||||
import itertools
|
import itertools
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict, List, Optional, Set, Tuple, Union
|
from typing import Dict, List, Optional, Set, Tuple, Union
|
||||||
|
import json
|
||||||
|
|
||||||
class SpecialCard(enum.Enum):
|
class SpecialCard(enum.Enum):
|
||||||
"""Different types of special cards"""
|
"""Different types of special cards"""
|
||||||
@@ -50,6 +50,27 @@ class Position(enum.Enum):
|
|||||||
Bunker = enum.auto()
|
Bunker = enum.auto()
|
||||||
Goal = 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:
|
class Board:
|
||||||
"""Solitaire board"""
|
"""Solitaire board"""
|
||||||
@@ -187,3 +208,12 @@ class Board:
|
|||||||
if count != 4:
|
if count != 4:
|
||||||
return False
|
return False
|
||||||
return True
|
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)
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ def get_field_square_iterator(
|
|||||||
"""Return iterator for both the square, as well as the matching card border"""
|
"""Return iterator for both the square, as well as the matching card border"""
|
||||||
my_adj = fake_adjustment(conf.field_adjustment)
|
my_adj = fake_adjustment(conf.field_adjustment)
|
||||||
my_border_adj = fake_adjustment(conf.border_adjustment)
|
my_border_adj = fake_adjustment(conf.border_adjustment)
|
||||||
|
|
||||||
squares = card_finder.get_field_squares(
|
squares = card_finder.get_field_squares(
|
||||||
image, my_adj, count_x=row_count, count_y=column_count
|
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:
|
def match_template(template: np.ndarray, search_image: np.ndarray) -> float:
|
||||||
"""Return matchiness for the template on the search image"""
|
"""Return matchiness for the template on the search image"""
|
||||||
|
|
||||||
res = cv2.matchTemplate(search_image, template, cv2.TM_CCOEFF_NORMED)
|
res = cv2.matchTemplate(search_image, template, cv2.TM_CCOEFF_NORMED)
|
||||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
|
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
|
||||||
assert isinstance(max_val, (int, float))
|
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
|
(match_template(template, square), name) for template, name in conf.catalogue
|
||||||
]
|
]
|
||||||
best_val, best_name = max(square_fits, key=lambda x: x[0])
|
best_val, best_name = max(square_fits, key=lambda x: x[0])
|
||||||
|
|
||||||
best_border = max(
|
best_border = max(
|
||||||
match_template(template=template, search_image=border)
|
match_template(template=template, search_image=border)
|
||||||
for template in conf.card_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)
|
result.goal = parse_goal(image, conf)
|
||||||
return result
|
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):
|
def field_card_to_str(card: Card):
|
||||||
if card == SpecialCard.Hua:
|
if card == SpecialCard.Hua:
|
||||||
|
|||||||
@@ -1,43 +1,53 @@
|
|||||||
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import os
|
|
||||||
|
|
||||||
import pyautogui
|
import pyautogui
|
||||||
|
|
||||||
import shenzhen_solitaire.card_detection.configuration as configuration
|
import shenzhen_solitaire.card_detection.configuration as configuration
|
||||||
import shenzhen_solitaire.clicker as clicker
|
import shenzhen_solitaire.clicker as clicker
|
||||||
import shenzhen_solitaire.solver.solver as solver
|
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)
|
OFFSET = (0, 0)
|
||||||
SIZE = (2560, 1440)
|
# SIZE = (2560, 1440)
|
||||||
|
SIZE = (1366, 768)
|
||||||
NEW_BUTTON = (1900, 1100)
|
NEW_BUTTON = (1900, 1100)
|
||||||
|
|
||||||
SAVE_UNSOLVED = False
|
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")
|
print("Taking screenshot")
|
||||||
screenshot_file = Path(screenshot_dir) / "screenshot.png"
|
screenshot_file = Path(screenshot_dir) / "screenshot.png"
|
||||||
screenshot = pyautogui.screenshot(region=(*OFFSET, *SIZE))
|
screenshot = pyautogui.screenshot(region=(*OFFSET, *SIZE))
|
||||||
screenshot.save(screenshot_file)
|
screenshot.save(screenshot_file)
|
||||||
image = cv2.imread(str(screenshot_file))
|
image = cv2.imread(str(screenshot_file))
|
||||||
|
input()
|
||||||
|
|
||||||
print("Solving")
|
print("Solving")
|
||||||
conf = configuration.load("test_config.zip")
|
board = parse_start_board(image, conf)
|
||||||
board = parse_board(image, conf)
|
print(board.to_json())
|
||||||
assert board.check_correct()
|
assert board.check_correct()
|
||||||
|
input()
|
||||||
solution_iterator = next(solver.solve(board, timeout=10, verbose=True), None)
|
solution_iterator = next(solver.solve(board, timeout=10, verbose=True), None)
|
||||||
if solution_iterator is None:
|
if solution_iterator is None:
|
||||||
clicker.click(NEW_BUTTON, OFFSET)
|
clicker.click(NEW_BUTTON, OFFSET)
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
if SAVE_UNSOLVED:
|
if SAVE_UNSOLVED:
|
||||||
fd, outfile = tempfile.mkstemp(
|
fd, outfile = tempfile.mkstemp(dir=UNSOLVED_DIR, suffix=".png")
|
||||||
dir="E:/shenzhen-solitaire/unsolved", suffix=".png"
|
|
||||||
)
|
|
||||||
sock = os.fdopen(fd, "w")
|
sock = os.fdopen(fd, "w")
|
||||||
sock.close()
|
sock.close()
|
||||||
cv2.imwrite(outfile, image)
|
cv2.imwrite(outfile, image)
|
||||||
@@ -53,8 +63,9 @@ def solve() -> None:
|
|||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
conf = configuration.load("test_config.zip")
|
||||||
while True:
|
while True:
|
||||||
solve()
|
solve(conf)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -49,25 +49,30 @@ def main() -> None:
|
|||||||
image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment)
|
image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment)
|
||||||
)
|
)
|
||||||
print("Field borders")
|
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)
|
image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment)
|
||||||
)
|
)
|
||||||
conf.bunker_adjustment.w = conf.field_adjustment.w
|
for adj in (conf.bunker_adjustment, conf.goal_adjustment,conf.hua_adjustment):
|
||||||
conf.bunker_adjustment.h = conf.field_adjustment.h
|
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")
|
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)
|
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")
|
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)
|
image, count_x=3, count_y=1, adjustment=copy.deepcopy(conf.goal_adjustment)
|
||||||
)
|
)
|
||||||
conf.hua_adjustment.w = conf.field_adjustment.w
|
conf.hua_adjustment.y = conf.bunker_adjustment.y
|
||||||
conf.hua_adjustment.h = conf.field_adjustment.h
|
|
||||||
print("Hua card")
|
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)
|
image, count_x=1, count_y=1, adjustment=copy.deepcopy(conf.hua_adjustment)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ def main() -> None:
|
|||||||
help="Path to the screenshot",
|
help="Path to the screenshot",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--conf",
|
"--config",
|
||||||
dest="config_path",
|
dest="config_path",
|
||||||
type=str,
|
type=str,
|
||||||
default="config.zip",
|
default="config.zip",
|
||||||
@@ -29,9 +29,8 @@ def main() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
print(args.screenshot_path)
|
|
||||||
image = cv2.imread(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)
|
squares = card_finder.get_field_squares(image, conf.field_adjustment, 5, 8)
|
||||||
catalogue = card_finder.catalogue_cards(squares)
|
catalogue = card_finder.catalogue_cards(squares)
|
||||||
conf.card_border.extend(
|
conf.card_border.extend(
|
||||||
|
|||||||
Reference in New Issue
Block a user