diff --git a/laptop_conf.zip b/laptop_conf.zip new file mode 100644 index 0000000..99a9e6e Binary files /dev/null and b/laptop_conf.zip differ diff --git a/shenzhen_solitaire/clicker/__init__.py b/shenzhen_solitaire/clicker/__init__.py index 9c6923f..2cd698e 100644 --- a/shenzhen_solitaire/clicker/__init__.py +++ b/shenzhen_solitaire/clicker/__init__.py @@ -10,28 +10,36 @@ import warnings from dataclasses import dataclass from shenzhen_solitaire.board import SpecialCard -DRAG_DURATION = 0.4 -CLICK_DURATION = 0.5 +DRAG_DURATION = 0.2 +CLICK_DURATION = 1 +DRAGON_WAIT = 1 +HUA_WAIT = 1 +GOAL_WAIT = 0.4 def drag( src: Tuple[int, int], dst: Tuple[int, int], offset: Tuple[int, int] = (0, 0) ) -> None: + time.sleep(DRAG_DURATION / 3) pyautogui.moveTo(x=src[0] + offset[0], y=src[1] + offset[1]) - pyautogui.dragTo( - x=dst[0] + offset[0], - y=dst[1] + offset[1], - duration=DRAG_DURATION, - tween=lambda x: 0 if x < 0.5 else 1, + pyautogui.mouseDown() + time.sleep(DRAG_DURATION / 3) + pyautogui.moveTo( + x=dst[0] + offset[0], y=dst[1] + offset[1], ) + pyautogui.mouseUp() + time.sleep(DRAG_DURATION / 3) def click(point: Tuple[int, int], offset: Tuple[int, int] = (0, 0)) -> None: + time.sleep(CLICK_DURATION / 3) pyautogui.moveTo(x=point[0] + offset[0], y=point[1] + offset[1]) pyautogui.mouseDown() - time.sleep(CLICK_DURATION) + time.sleep(CLICK_DURATION / 3) pyautogui.mouseUp() + time.sleep(CLICK_DURATION / 3) + time.sleep(DRAGON_WAIT) @dataclass @@ -44,9 +52,9 @@ class DragAction: class ClickAction: destination: Tuple[int, int] - +@dataclass class WaitAction: - pass + duration: float def _parse_field( @@ -106,15 +114,24 @@ def parse_action( ) ) elif action_name == "goal": - obvious = ( - goal_values[info["card"]["suit"].lower()] <= min(goal_values.values()) + 1 - ) - assert (goal_values[info["card"]["suit"].lower()] == 0) or ( - goal_values[info["card"]["suit"].lower()] + 1 == info["card"]["value"] - ) - goal_values[info["card"]["suit"].lower()] = info["card"]["value"] + + current_value = goal_values[info["card"]["suit"].lower()] + proposed_value = info["card"]["value"] + + assert (current_value == 0) or (current_value + 1 == proposed_value) + + if proposed_value == min(goal_values.values()) + 1: + obvious = True + elif proposed_value == 2: + obvious = True + else: + obvious = False + + goal_values[info["card"]["suit"].lower()] = proposed_value + if obvious: - return WaitAction() + return WaitAction(duration=GOAL_WAIT) + goal = ( int(info["goal_slot_index"]) * conf.goal_adjustment.dx + conf.goal_adjustment.x @@ -132,7 +149,9 @@ def parse_action( ) return DragAction(source=source, destination=goal) elif action_name == "huakill": - return WaitAction() + return WaitAction(duration=HUA_WAIT) + else: + assert 0 def handle_actions( @@ -141,9 +160,9 @@ def handle_actions( conf: configuration.Configuration, ) -> None: goal_values = {"red": 0, "black": 0, "green": 0} - action_tuples = [ + action_tuples = ( (action, parse_action(action, conf, goal_values)) for action in actions - ] + ) for name, action in action_tuples: print(name) if isinstance(action, DragAction): @@ -151,4 +170,4 @@ def handle_actions( elif isinstance(action, ClickAction): click(action.destination, offset) elif isinstance(action, WaitAction): - time.sleep(2) + time.sleep(action.duration) diff --git a/tools/assistant.py b/tools/assistant.py index 46a0da4..c1a810d 100644 --- a/tools/assistant.py +++ b/tools/assistant.py @@ -18,17 +18,24 @@ from shenzhen_solitaire.card_detection.board_parser import parse_start_board OFFSET = (0, 0) # SIZE = (2560, 1440) SIZE = (1366, 768) -NEW_BUTTON = (1900, 1100) +# NEW_BUTTON = (1900, 1100) +NEW_BUTTON = (1340, 750) SAVE_UNSOLVED = False UNSOLVED_DIR = "E:/shenzhen-solitaire/unsolved" -SOLVER_PATH = '/home/lukas/documents/coding/rust/shenzhen-solitaire/target/release/solver' +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) + result = subprocess.run( + [SOLVER_PATH], input=board.to_json(), capture_output=True, text=True + ) return json.loads(result.stdout) -def take_screenshot() : + +def take_screenshot(): with tempfile.TemporaryDirectory(prefix="shenzhen_solitaire") as screenshot_dir: print("Taking screenshot") screenshot_file = Path(screenshot_dir) / "screenshot.png" @@ -37,6 +44,7 @@ def take_screenshot() : image = cv2.imread(str(screenshot_file)) return image + def solve(conf: configuration.Configuration) -> None: image = take_screenshot() board = parse_start_board(image, conf) @@ -52,16 +60,20 @@ def solve(conf: configuration.Configuration) -> None: def main() -> None: - parser = argparse.ArgumentParser( - description="Solve board" + parser = argparse.ArgumentParser(description="Solve board") + parser.add_argument( + "config_path", type=str, help="Config path", ) parser.add_argument( - "config_path", - type=str, - help="Config path", + "--no-failsafe", + dest="no_failsafe", + action="store_false", + help="Enable 0.1 second delay between all actions to allow user to interrupt", ) - args = parser.parse_args() + + if not args.no_failsafe: + pyautogui.PAUSE = 0 time.sleep(3) conf = configuration.load(args.config_path) while True: