From d4f24aa3072686339660c355de9990a8a9d9b90d Mon Sep 17 00:00:00 2001 From: Anas LIMOURI Date: Sun, 16 Apr 2023 13:31:23 +0200 Subject: [PATCH 1/2] [FEATURE] Introduce variable mouse latency and add non-stop online matches --- src/grabbers/chesscom_grabber.py | 47 ++++++++++++++++++++++++-------- src/grabbers/lichess_grabber.py | 34 +++++++++++++++++------ src/gui.py | 19 +++++++++++-- src/stockfish_bot.py | 37 ++++++++++++++++--------- 4 files changed, 102 insertions(+), 35 deletions(-) diff --git a/src/grabbers/chesscom_grabber.py b/src/grabbers/chesscom_grabber.py index a1ad553..691739c 100644 --- a/src/grabbers/chesscom_grabber.py +++ b/src/grabbers/chesscom_grabber.py @@ -1,4 +1,4 @@ -from selenium.common import NoSuchElementException +from selenium.common import NoSuchElementException, StaleElementReferenceException from selenium.webdriver.common.by import By from grabbers.grabber import Grabber @@ -10,13 +10,17 @@ def __init__(self, chrome_url, chrome_session_id): self.moves_list = {} def update_board_elem(self): - try: - self._board_elem = self.chrome.find_element(By.XPATH, "//*[@id='board-vs-personalities']") - except NoSuchElementException: + # Keep looking for board + while True: try: - self._board_elem = self.chrome.find_element(By.XPATH, "//*[@id='board-single']") + self._board_elem = self.chrome.find_element(By.XPATH, "//*[@id='board-vs-personalities']") + return except NoSuchElementException: - self._board_elem = None + try: + self._board_elem = self.chrome.find_element(By.XPATH, "//*[@id='board-single']") + return + except NoSuchElementException: + self._board_elem = None def is_white(self): # Find the square names list @@ -63,7 +67,14 @@ def is_game_over(self): return False except NoSuchElementException: # Return False since the game over window is not found - return False + try: + game_over_window = self.chrome.find_element(By.CLASS_NAME, "board-modal-container-container") + if game_over_window is not None: + return True + else: + return False + except NoSuchElementException: + return False def get_move_list(self): # Find the moves list @@ -71,16 +82,25 @@ def get_move_list(self): move_list_elem = self.chrome.find_element(By.TAG_NAME, "vertical-move-list") except NoSuchElementException: return None + except StaleElementReferenceException: + return None # Select all children with class containing "white node" or "black node" # Moves that are not pawn moves have a different structure # containing children if not self.moves_list: - # If the moves list is empty, find all moves - moves = move_list_elem.find_elements(By.CSS_SELECTOR, "div.move [data-ply]") + try: + # If the moves list is empty, find all moves + moves = move_list_elem.find_elements(By.CSS_SELECTOR, "div.move [data-ply]") + except StaleElementReferenceException: + return + else: - # If the moves list is not empty, find only the new moves - moves = move_list_elem.find_elements(By.CSS_SELECTOR, "div.move [data-ply]:not([data-processed])") + try: + # If the moves list is not empty, find only the new moves + moves = move_list_elem.find_elements(By.CSS_SELECTOR, "div.move [data-ply]:not([data-processed])") + except StaleElementReferenceException: + return for move in moves: move_class = move.get_attribute("class") @@ -93,6 +113,8 @@ def get_move_list(self): figure = child.get_attribute("data-figurine") except NoSuchElementException: figure = None + except StaleElementReferenceException: + figure = None # Check if it was en-passant or figure-move if figure is None: @@ -124,5 +146,8 @@ def is_game_puzzles(self): def click_puzzle_next(self): pass + def click_game_next(self): + pass + def make_mouseless_move(self, move, move_count): pass diff --git a/src/grabbers/lichess_grabber.py b/src/grabbers/lichess_grabber.py index 71c30d5..60ef801 100644 --- a/src/grabbers/lichess_grabber.py +++ b/src/grabbers/lichess_grabber.py @@ -1,6 +1,6 @@ import re -from selenium.common import NoSuchElementException +from selenium.common import NoSuchElementException, StaleElementReferenceException from selenium.webdriver.common.by import By from grabbers.grabber import Grabber @@ -13,16 +13,20 @@ def __init__(self, chrome_url, chrome_session_id): self.moves_list = {} def update_board_elem(self): - try: - # Try finding the normal board - self._board_elem = self.chrome.find_element(By.XPATH, - '//*[@id="main-wrap"]/main/div[1]/div[1]/div/cg-container') - except NoSuchElementException: + # Keep looking for board + while True: try: - # Try finding the board in the puzzles page - self._board_elem = self.chrome.find_element(By.XPATH, '/html/body/div[2]/main/div[1]/div/cg-container') + # Try finding the normal board + self._board_elem = self.chrome.find_element(By.XPATH, + '//*[@id="main-wrap"]/main/div[1]/div[1]/div/cg-container') + return except NoSuchElementException: - self._board_elem = None + try: + # Try finding the board in the puzzles page + self._board_elem = self.chrome.find_element(By.XPATH, '/html/body/div[2]/main/div[1]/div/cg-container') + return + except NoSuchElementException: + self._board_elem = None def is_white(self): # Get "ranks" child @@ -168,6 +172,18 @@ def click_puzzle_next(self): # Click the continue training button self.chrome.execute_script("arguments[0].click();", next_button) + def click_game_next(self): + # Find the next new game button + try: + next_button = self.chrome.find_element(By.XPATH, "//*[contains(text(), 'New opponent')]") + except NoSuchElementException: + return + except StaleElementReferenceException: + return + + # Click the next game button + self.chrome.execute_script("arguments[0].click();", next_button) + def make_mouseless_move(self, move, move_count): message = '{"t":"move","d":{"u":"' + move + '","b":1,"a":' + str(move_count) + '}}' script = 'lichess.socket.ws.send(JSON.stringify(' + message + '))' diff --git a/src/gui.py b/src/gui.py index 3b1fab5..dbac7ca 100644 --- a/src/gui.py +++ b/src/gui.py @@ -1,5 +1,3 @@ -import multiprocessing - import multiprocess import threading import time @@ -110,11 +108,26 @@ def __init__(self, master): variable=self.enable_non_stop_puzzles) self.non_stop_puzzles_check_button.pack(anchor=tk.NW) + # Create the non-stop matches check button + self.enable_non_stop_matches = tk.IntVar(value=0) + self.non_stop_matches_check_button = tk.Checkbutton(left_frame, text="Non-stop online matches", + variable=self.enable_non_stop_matches) + self.non_stop_matches_check_button.pack(anchor=tk.NW) + # Create the bongcloud check button self.enable_bongcloud = tk.IntVar() self.bongcloud_check_button = tk.Checkbutton(left_frame, text='Bongcloud', variable=self.enable_bongcloud) self.bongcloud_check_button.pack(anchor=tk.NW) + # Create the mouse latency scale + mouse_latency_frame = tk.Frame(left_frame) + tk.Label(mouse_latency_frame, text="Mouse Latency (seconds)").pack(side=tk.LEFT, pady=(17, 0)) + self.mouse_latency = tk.DoubleVar(value=0.0) + self.mouse_latency_scale = tk.Scale(mouse_latency_frame, from_=0.0, to=5, resolution=0.2, orient=tk.HORIZONTAL, + variable=self.mouse_latency) + self.mouse_latency_scale.pack() + mouse_latency_frame.pack(anchor=tk.NW) + # Separator separator_frame = tk.Frame(left_frame) separator = ttk.Separator(separator_frame, orient='horizontal') @@ -431,6 +444,8 @@ def on_start_button_listener(self): self.enable_manual_mode.get() == 1, self.enable_mouseless_mode.get() == 1, self.enable_non_stop_puzzles.get() == 1, + self.enable_non_stop_matches.get() == 1, + self.mouse_latency.get(), self.enable_bongcloud.get() == 1, self.slow_mover.get(), self.skill_level.get(), diff --git a/src/stockfish_bot.py b/src/stockfish_bot.py index 415c599..a47dcf0 100644 --- a/src/stockfish_bot.py +++ b/src/stockfish_bot.py @@ -1,5 +1,3 @@ -from random import random - import multiprocess from stockfish import Stockfish import pyautogui @@ -15,7 +13,7 @@ class StockfishBot(multiprocess.Process): - def __init__(self, chrome_url, chrome_session_id, website, pipe, overlay_queue, stockfish_path, enable_manual_mode, enable_mouseless_mode, enable_non_stop_puzzles, bongcloud, slow_mover, skill_level, stockfish_depth, memory, cpu_threads): + def __init__(self, chrome_url, chrome_session_id, website, pipe, overlay_queue, stockfish_path, enable_manual_mode, enable_mouseless_mode, enable_non_stop_puzzles, enable_non_stop_matches, mouse_latency, bongcloud, slow_mover, skill_level, stockfish_depth, memory, cpu_threads): multiprocess.Process.__init__(self) self.chrome_url = chrome_url @@ -27,6 +25,8 @@ def __init__(self, chrome_url, chrome_session_id, website, pipe, overlay_queue, self.enable_manual_mode = enable_manual_mode self.enable_mouseless_mode = enable_mouseless_mode self.enable_non_stop_puzzles = enable_non_stop_puzzles + self.enable_non_stop_matches = enable_non_stop_matches + self.mouse_latency = mouse_latency self.bongcloud = bongcloud self.slow_mover = slow_mover self.skill_level = skill_level @@ -73,6 +73,7 @@ def make_move(self, move): # Drag the piece from the start to the end position pyautogui.moveTo(start_pos[0], start_pos[1]) + time.sleep(self.mouse_latency) pyautogui.dragTo(end_pos[0], end_pos[1]) # Check for promotion. If there is a promotion, @@ -95,6 +96,17 @@ def wait_for_gui_to_delete(self): while self.pipe.recv() != "DELETE": pass + def go_to_next_puzzle(self): + self.grabber.click_puzzle_next() + self.pipe.send("RESTART") + self.wait_for_gui_to_delete() + + def find_new_online_match(self): + time.sleep(2) + self.grabber.click_game_next() + self.pipe.send("RESTART") + self.wait_for_gui_to_delete() + def run(self): if self.website == "chesscom": self.grabber = ChesscomGrabber(self.chrome_url, self.chrome_session_id) @@ -223,9 +235,9 @@ def run(self): if board.is_checkmate(): # Send restart message to GUI if self.enable_non_stop_puzzles and self.grabber.is_game_puzzles(): - self.grabber.click_puzzle_next() - self.pipe.send("RESTART") - self.wait_for_gui_to_delete() + self.go_to_next_puzzle() + elif self.enable_non_stop_matches and not self.enable_non_stop_puzzles: + self.find_new_online_match() return time.sleep(0.1) @@ -238,9 +250,9 @@ def run(self): if self.grabber.is_game_over(): # Send restart message to GUI if self.enable_non_stop_puzzles and self.grabber.is_game_puzzles(): - self.grabber.click_puzzle_next() - self.pipe.send("RESTART") - self.wait_for_gui_to_delete() + self.go_to_next_puzzle() + elif self.enable_non_stop_matches and not self.enable_non_stop_puzzles: + self.find_new_online_match() return move_list = self.grabber.get_move_list() if move_list is None: @@ -256,10 +268,9 @@ def run(self): if board.is_checkmate(): # Send restart message to GUI if self.enable_non_stop_puzzles and self.grabber.is_game_puzzles(): - self.grabber.click_puzzle_next() - self.pipe.send("RESTART") - self.wait_for_gui_to_delete() - + self.go_to_next_puzzle() + elif self.enable_non_stop_matches and not self.enable_non_stop_puzzles: + self.find_new_online_match() return except Exception as e: print(e) From 85945fd796e5e7f9dac13962abf657f8312beb6c Mon Sep 17 00:00:00 2001 From: Anas LIMOURI Date: Sun, 16 Apr 2023 13:36:03 +0200 Subject: [PATCH 2/2] [CHORE] Remove unused exception logic --- src/grabbers/chesscom_grabber.py | 44 ++++++++------------------------ 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/src/grabbers/chesscom_grabber.py b/src/grabbers/chesscom_grabber.py index 691739c..1269e2b 100644 --- a/src/grabbers/chesscom_grabber.py +++ b/src/grabbers/chesscom_grabber.py @@ -1,4 +1,4 @@ -from selenium.common import NoSuchElementException, StaleElementReferenceException +from selenium.common import NoSuchElementException from selenium.webdriver.common.by import By from grabbers.grabber import Grabber @@ -10,17 +10,13 @@ def __init__(self, chrome_url, chrome_session_id): self.moves_list = {} def update_board_elem(self): - # Keep looking for board - while True: + try: + self._board_elem = self.chrome.find_element(By.XPATH, "//*[@id='board-vs-personalities']") + except NoSuchElementException: try: - self._board_elem = self.chrome.find_element(By.XPATH, "//*[@id='board-vs-personalities']") - return + self._board_elem = self.chrome.find_element(By.XPATH, "//*[@id='board-single']") except NoSuchElementException: - try: - self._board_elem = self.chrome.find_element(By.XPATH, "//*[@id='board-single']") - return - except NoSuchElementException: - self._board_elem = None + self._board_elem = None def is_white(self): # Find the square names list @@ -67,14 +63,7 @@ def is_game_over(self): return False except NoSuchElementException: # Return False since the game over window is not found - try: - game_over_window = self.chrome.find_element(By.CLASS_NAME, "board-modal-container-container") - if game_over_window is not None: - return True - else: - return False - except NoSuchElementException: - return False + return False def get_move_list(self): # Find the moves list @@ -82,25 +71,16 @@ def get_move_list(self): move_list_elem = self.chrome.find_element(By.TAG_NAME, "vertical-move-list") except NoSuchElementException: return None - except StaleElementReferenceException: - return None # Select all children with class containing "white node" or "black node" # Moves that are not pawn moves have a different structure # containing children if not self.moves_list: - try: - # If the moves list is empty, find all moves - moves = move_list_elem.find_elements(By.CSS_SELECTOR, "div.move [data-ply]") - except StaleElementReferenceException: - return - + # If the moves list is empty, find all moves + moves = move_list_elem.find_elements(By.CSS_SELECTOR, "div.move [data-ply]") else: - try: - # If the moves list is not empty, find only the new moves - moves = move_list_elem.find_elements(By.CSS_SELECTOR, "div.move [data-ply]:not([data-processed])") - except StaleElementReferenceException: - return + # If the moves list is not empty, find only the new moves + moves = move_list_elem.find_elements(By.CSS_SELECTOR, "div.move [data-ply]:not([data-processed])") for move in moves: move_class = move.get_attribute("class") @@ -113,8 +93,6 @@ def get_move_list(self): figure = child.get_attribute("data-figurine") except NoSuchElementException: figure = None - except StaleElementReferenceException: - figure = None # Check if it was en-passant or figure-move if figure is None: