This commit is contained in:
Lukas Wölfer
2020-02-12 23:45:51 +01:00
parent 7247f465de
commit ca44b8977d
9 changed files with 167 additions and 74 deletions

View File

@@ -1,51 +0,0 @@
import tempfile
import time
from pathlib import Path
import cv2
import numpy as np
import shenzhen_solitaire.card_detection.configuration as configuration
import shenzhen_solitaire.solver.solver as solver
from shenzhen_solitaire.card_detection.board_parser import parse_board
benchmark_files = [
"pictures/20190809172206_1.jpg",
"pictures/20190809172213_1.jpg",
"pictures/20190809172219_1.jpg",
"pictures/20190809172225_1.jpg",
"pictures/20190809172232_1.jpg",
"pictures/20190809172238_1.jpg",
]
def main() -> None:
for benchmark in benchmark_files:
print(f"{benchmark}:")
read_file_time = time.time()
image = cv2.imread(benchmark)
load_config_time = time.time()
print(f"Load image: {load_config_time - read_file_time:5.2f}")
conf = configuration.load("test_config.zip")
parse_board_time = time.time()
print(f"Load config: {parse_board_time - load_config_time:5.2f}")
board = parse_board(image, conf)
solve_time = time.time()
print(f"Parse image: {solve_time - parse_board_time:5.2f}")
solution_iterator = next(solver.solve(board, timeout=10), None)
finished_time = time.time()
print(f"Solve board: {finished_time - solve_time:5.2f}")
assert board.check_correct()
if solution_iterator is None:
print("Solution timed out")
else:
print(f"Solved in {len(list(solution_iterator))} steps")
if __name__ == "__main__":
main()

35
benchmark/timing.py Normal file
View File

@@ -0,0 +1,35 @@
import multiprocessing
import tempfile
import time
from pathlib import Path
import cv2
import numpy as np
import shenzhen_solitaire.card_detection.configuration as configuration
import shenzhen_solitaire.solver.solver as solver
from shenzhen_solitaire.card_detection.board_parser import parse_board
from .util import run_benchmark
benchmark_files = [
"pictures/20190809172206_1.jpg",
"pictures/20190809172213_1.jpg",
"pictures/20190809172219_1.jpg",
"pictures/20190809172225_1.jpg",
"pictures/20190809172232_1.jpg",
"pictures/20190809172238_1.jpg",
]
def main() -> None:
with multiprocessing.Pool() as pool:
result = pool.imap_unordered(
run_benchmark, [Path(benchmark) for benchmark in benchmark_files]
)
for current_result in result:
print(current_result)
if __name__ == "__main__":
main()

41
benchmark/unsolved.py Normal file
View File

@@ -0,0 +1,41 @@
import multiprocessing
import tempfile
import time
from pathlib import Path
import cv2
import numpy as np
import shenzhen_solitaire.card_detection.configuration as configuration
import shenzhen_solitaire.solver.solver as solver
from shenzhen_solitaire.card_detection.board_parser import parse_board
from .util import run_benchmark
benchmark_files = [
"pictures/unsolved/tmp1ern14si.png",
"pictures/unsolved/tmp2_0vn4tl.png",
"pictures/unsolved/tmp32jmcnfp.png",
"pictures/unsolved/tmpcml2ldfl.png",
"pictures/unsolved/tmpd7rbwwdb.png",
"pictures/unsolved/tmpdudxuw0s.png",
"pictures/unsolved/tmpeplvz9bk.png",
"pictures/unsolved/tmph_esy__3.png",
"pictures/unsolved/tmpn95ueb7_.png",
"pictures/unsolved/tmpqzay4q08.png",
"pictures/unsolved/tmputbych59.png",
"pictures/unsolved/tmpx4uo6pg3.png",
]
def main() -> None:
with multiprocessing.Pool() as pool:
result = pool.imap_unordered(
run_benchmark, [Path(benchmark) for benchmark in benchmark_files]
)
for current_result in result:
print(current_result)
if __name__ == "__main__":
main()

36
benchmark/util.py Normal file
View File

@@ -0,0 +1,36 @@
import time
from pathlib import Path
import cv2
import shenzhen_solitaire.card_detection.configuration as configuration
import shenzhen_solitaire.solver.solver as solver
from shenzhen_solitaire.card_detection.board_parser import parse_board
def run_benchmark(benchmark: Path) -> str:
result = ""
result += f"{benchmark}:\n"
read_file_time = time.time()
image = cv2.imread(str(benchmark))
load_config_time = time.time()
result += f"\tLoad image: {load_config_time - read_file_time:5.2f}\n"
conf = configuration.load("test_config.zip")
parse_board_time = time.time()
result += f"\tLoad config: {parse_board_time - load_config_time:5.2f}\n"
board = parse_board(image, conf)
solve_time = time.time()
result += f"\tParse image: {solve_time - parse_board_time:5.2f}\n"
solution_iterator = next(solver.solve(board, timeout=10), None)
finished_time = time.time()
result += f"\tSolve board: {finished_time - solve_time:5.2f}\n"
assert board.check_correct()
if solution_iterator is None:
result += "\tSolution timed out\n"
else:
result += f"\tSolved in {len(list(solution_iterator))} steps\n"
return result

View File

@@ -8,6 +8,7 @@ import shenzhen_solitaire.card_detection.configuration as configuration
import shenzhen_solitaire.solver.board_actions as board_actions import shenzhen_solitaire.solver.board_actions as board_actions
import warnings import warnings
def drag( def drag(
src: Tuple[int, int], dst: Tuple[int, int], offset: Tuple[int, int] = (0, 0) src: Tuple[int, int], dst: Tuple[int, int], offset: Tuple[int, int] = (0, 0)
) -> None: ) -> None:
@@ -21,6 +22,18 @@ def drag(
) )
def dragSquare(
src: Tuple[int, int, int, int],
dst: Tuple[int, int, int, int],
offset: Tuple[int, int] = (0, 0),
) -> None:
drag(
(src[0] + (src[2] - src[0]) // 2, src[1] + (src[3] - src[1]) // 2),
(dst[0] + (dst[2] - dst[0]) // 2, dst[1] + (dst[3] - dst[1]) // 2),
offset,
)
def click(point: Tuple[int, int], offset: Tuple[int, int] = (0, 0)) -> None: def click(point: Tuple[int, int], offset: Tuple[int, int] = (0, 0)) -> None:
pyautogui.moveTo(x=point[0] + offset[0], y=point[1] + offset[1]) pyautogui.moveTo(x=point[0] + offset[0], y=point[1] + offset[1])
pyautogui.mouseDown() pyautogui.mouseDown()
@@ -28,40 +41,49 @@ def click(point: Tuple[int, int], offset: Tuple[int, int] = (0, 0)) -> None:
pyautogui.mouseUp() pyautogui.mouseUp()
def clickSquare(
field: Tuple[int, int, int, int], offset: Tuple[int, int] = (0, 0)
) -> None:
click(
(field[0] + (field[2] - field[0]) // 2, field[1] + (field[3] - field[1]) // 2),
offset,
)
def handle_action( def handle_action(
action: board_actions.Action, action: board_actions.Action,
offset: Tuple[int, int], offset: Tuple[int, int],
conf: configuration.Configuration, conf: configuration.Configuration,
) -> None: ) -> None:
if isinstance(action, board_actions.MoveAction): if isinstance(action, board_actions.MoveAction):
src_x, src_y, _, _ = adjustment.get_square( src = adjustment.get_square(
conf.field_adjustment, conf.field_adjustment,
index_x=action.source_id, index_x=action.source_id,
index_y=action.source_row_index, index_y=action.source_row_index,
) )
dst_x, dst_y, _, _ = adjustment.get_square( dst = adjustment.get_square(
conf.field_adjustment, conf.field_adjustment,
index_x=action.destination_id, index_x=action.destination_id,
index_y=action.destination_row_index, index_y=action.destination_row_index,
) )
drag((src_x, src_y), (dst_x, dst_y), offset) dragSquare(src, dst, offset)
return return
if isinstance(action, board_actions.HuaKillAction): if isinstance(action, board_actions.HuaKillAction):
warnings.warn("Hua kill should be handled before handle_action") warnings.warn("Hua kill should be handled before handle_action")
return return
if isinstance(action, board_actions.BunkerizeAction): if isinstance(action, board_actions.BunkerizeAction):
field_x, field_y, _, _ = adjustment.get_square( field = adjustment.get_square(
conf.field_adjustment, conf.field_adjustment,
index_x=action.field_id, index_x=action.field_id,
index_y=action.field_row_index, index_y=action.field_row_index,
) )
bunker_x, bunker_y, _, _ = adjustment.get_square( bunker = adjustment.get_square(
conf.bunker_adjustment, index_x=action.bunker_id, index_y=0, conf.bunker_adjustment, index_x=action.bunker_id, index_y=0,
) )
if action.to_bunker: if action.to_bunker:
drag((field_x, field_y), (bunker_x, bunker_y), offset) dragSquare(field, bunker, offset)
else: else:
drag((bunker_x, bunker_y), (field_x, field_y), offset) dragSquare(bunker, field, offset)
return return
if isinstance(action, board_actions.DragonKillAction): if isinstance(action, board_actions.DragonKillAction):
dragon_sequence = [ dragon_sequence = [
@@ -69,34 +91,33 @@ def handle_action(
board.SpecialCard.Fa, board.SpecialCard.Fa,
board.SpecialCard.Bai, board.SpecialCard.Bai,
] ]
field_x, field_y, size_x, size_y = adjustment.get_square( field = adjustment.get_square(
conf.special_button_adjustment, conf.special_button_adjustment,
index_x=0, index_x=0,
index_y=dragon_sequence.index(action.dragon), index_y=dragon_sequence.index(action.dragon),
) )
click( clickSquare(
(field_x + (size_x - field_x) // 2, field_y + (size_y - field_y) // 2), field, offset,
offset,
) )
time.sleep(1) 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 = adjustment.get_square(
conf.goal_adjustment, index_x=action.goal_id, index_y=0, conf.goal_adjustment, index_x=action.goal_id, index_y=0,
) )
if action.source_position == board.Position.Field: if action.source_position == board.Position.Field:
assert action.source_row_index is not None assert action.source_row_index is not None
src_x, src_y, _, _ = adjustment.get_square( src = adjustment.get_square(
conf.field_adjustment, conf.field_adjustment,
index_x=action.source_id, index_x=action.source_id,
index_y=action.source_row_index, index_y=action.source_row_index,
) )
else: else:
assert action.source_position == board.Position.Bunker assert action.source_position == board.Position.Bunker
src_x, src_y, _, _ = adjustment.get_square( src = adjustment.get_square(
conf.bunker_adjustment, index_x=action.source_id, index_y=0, conf.bunker_adjustment, index_x=action.source_id, index_y=0,
) )
drag((src_x, src_y), (dst_x, dst_y), offset) dragSquare(src, dst, offset)
return return
raise AssertionError("You forgot an Action type") raise AssertionError("You forgot an Action type")
@@ -109,7 +130,7 @@ def handle_actions(
automatic_count = 0 automatic_count = 0
for action in actions: for action in actions:
print(action) print(action)
if action.automatic(): if isinstance(action, board_actions.HuaKillAction):
automatic_count += 1 automatic_count += 1
else: else:
time.sleep(0.5 * automatic_count) time.sleep(0.5 * automatic_count)

View File

@@ -129,7 +129,7 @@ def possible_goal_move_actions(
if not (card.number == search_board.getGoal(card.suit) + 1): if not (card.number == search_board.getGoal(card.suit) + 1):
continue continue
obvious = all( obvious = all(
search_board.getGoal(other_suit) >= card.number - 1 search_board.getGoal(other_suit) >= card.number - 2
for other_suit in set(board.NumberCard.Suit) - {card.suit} for other_suit in set(board.NumberCard.Suit) - {card.suit}
) )
yield board_actions.GoalAction( yield board_actions.GoalAction(

View File

@@ -86,7 +86,7 @@ def solve(
iter_start = time.time() iter_start = time.time()
count = 0 count = 0
while stack: while len(stack) > 0:
count += 1 count += 1
if count > 5000: if count > 5000:

View File

@@ -3,9 +3,12 @@ import unittest
from shenzhen_solitaire.board import NumberCard, Position from shenzhen_solitaire.board import NumberCard, Position
from shenzhen_solitaire.solver import board_possibilities from shenzhen_solitaire.solver import board_possibilities
from shenzhen_solitaire.solver.board_actions import (BunkerizeAction, from shenzhen_solitaire.solver.board_actions import (
GoalAction, HuaKillAction, BunkerizeAction,
MoveAction) GoalAction,
HuaKillAction,
MoveAction,
)
from .boards import TEST_BOARD from .boards import TEST_BOARD

View File

@@ -4,6 +4,7 @@ from pathlib import Path
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
@@ -28,14 +29,21 @@ def solve() -> None:
conf = configuration.load("test_config.zip") conf = configuration.load("test_config.zip")
board = parse_board(image, conf) board = parse_board(image, conf)
assert board.check_correct() assert board.check_correct()
solution_iterator = next(solver.solve(board, timeout=10), 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)
fd, outfile = tempfile.mkstemp(
dir="E:/shenzhen-solitaire/unsolved", suffix=".png"
)
sock = os.fdopen(fd, "w")
sock.close()
cv2.imwrite(outfile, image)
return return
solution=list(solution_iterator) solution = list(solution_iterator)
print(f"Solved in {len(solution)} steps") print(f"Solved in {len(solution)} steps")
clicker.handle_actions(solution, OFFSET, conf) clicker.handle_actions(solution, OFFSET, conf)
print("Solved")
time.sleep(2) time.sleep(2)
clicker.click(NEW_BUTTON, OFFSET) clicker.click(NEW_BUTTON, OFFSET)
time.sleep(7) time.sleep(7)