From 147bb584beba4df5d2c79f4a421c064dee651ae1 Mon Sep 17 00:00:00 2001 From: Marvin <52848568+mleem97@users.noreply.github.com> Date: Sun, 10 Aug 2025 03:37:41 +0200 Subject: [PATCH 1/3] Add OCR chapter fallback and latency tracking --- Src/bot_core.py | 32 ++++++++++++++++++++++++++------ Src/gui.py | 23 +++++++++++++---------- Src/latency.py | 24 +++++++++++++++++------- Src/ocr_utils.py | 24 ++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 23 deletions(-) diff --git a/Src/bot_core.py b/Src/bot_core.py index 27a785f..97de3e8 100644 --- a/Src/bot_core.py +++ b/Src/bot_core.py @@ -72,7 +72,8 @@ class TouchConstants: import port_scan import ocr_utils -SLEEP_DELAY = 0.1 +# default delay between sequential actions (seconds) +SLEEP_DELAY = 0.05 class Bot: @@ -384,7 +385,14 @@ def get_current_icons(self, new=True, available=False): img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY) self.logger.debug(f'Screenshot shape: {img_gray.shape}') - + + # OCR fallback for chapter headers + try: + ocr_chapters = ocr_utils.find_chapter_headers(img_rgb) + except Exception as e: + ocr_chapters = {} + self.logger.debug(f'Chapter OCR failed: {e}') + # Check every target in dir icon_count = 0 for target in os.listdir("icons"): @@ -403,14 +411,26 @@ def get_current_icons(self, new=True, available=False): max_val = res.max() loc = np.where(res >= threshold) icon_found = len(loc[0]) > 0 - + + if icon_found: + y = loc[0][0] + x = loc[1][0] + elif target.startswith('chapter_'): + # Fallback to OCR if chapter icon not found + try: + num = int(target.split('_')[1].split('.')[0]) + if num in ocr_chapters: + x, y = ocr_chapters[num] + icon_found = True + self.logger.debug(f'OCR detected {target} at position {(x, y)}') + except Exception: + pass + # Debug for key icons if target in ['home_screen.png', 'battle_icon.png'] or 'chapter_' in target: self.logger.debug(f'Icon {target}: max_val={max_val:.3f}, found={icon_found}') - + if icon_found: - y = loc[0][0] - x = loc[1][0] icon_count += 1 current_icons.append([target, icon_found, (x, y)]) diff --git a/Src/gui.py b/Src/gui.py index a2381de..947e0f3 100644 --- a/Src/gui.py +++ b/Src/gui.py @@ -55,9 +55,9 @@ def __init__(self): self.stop_flag = False self.running = False self.info_ready = threading.Event() - self.thread_run: threading.Thread | None = None - self.thread_init: threading.Thread | None = None - self.bot_instance: Any | None = None + self.thread_run: threading.Thread | None = None + self.thread_init: threading.Thread | None = None + self.bot_instance: Any | None = None # Read config file self.config = configparser.ConfigParser() self.config.read('config.ini') @@ -127,9 +127,9 @@ def start_command(self): # Update config file def update_config(self): # Update config file - floor_var = int(self.floor.get()) - card_level = np.array([var.get() for var in self.mana_vars]) * np.arange(1, 6) - card_level = card_level[card_level != 0] + floor_var = int(self.floor.get()) + card_level = np.array([var.get() for var in self.mana_vars]) * np.arange(1, 6) + card_level = card_level[card_level != 0] self.config.read('config.ini') self.config['bot']['floor'] = str(floor_var) self.config['bot']['mana_level'] = np.array2string(card_level, separator=',')[1:-1] @@ -156,8 +156,8 @@ def start_bot(self): infos_ready = threading.Event() # Pass gui info to bot self.bot_instance.bot_stop = False - setattr(self.bot_instance, 'logger', self.logger) - setattr(self.bot_instance, 'config', self.config) + setattr(self.bot_instance, 'logger', self.logger) + setattr(self.bot_instance, 'config', self.config) bot = self.bot_instance # Start bot thread thread_bot = threading.Thread(target=bot_handler.bot_loop, args=([bot, infos_ready])) @@ -198,10 +198,13 @@ def leave_game(self): def _schedule_latency_update(self): try: stats = LT.rolling_stats() + last = LT.last_thinking_ms() if stats.get('count', 0) and stats['count'] > 0: - text = f"RT avg {stats['avg_ms']:.0f} ms p50 {stats['p50_ms']:.0f} p90 {stats['p90_ms']:.0f}" + text = ( + f"RT avg {stats['avg_ms']:.0f} ms p50 {stats['p50_ms']:.0f} p90 {stats['p90_ms']:.0f} last {last:.0f}" + ) else: - text = 'RT: -- ms' + text = f'RT: -- ms last {last:.0f}' self.latency_label.config(text=text) except Exception: # Keep label as-is on errors diff --git a/Src/latency.py b/Src/latency.py index ad6a4a6..d34fa72 100644 --- a/Src/latency.py +++ b/Src/latency.py @@ -55,13 +55,15 @@ def __init__(self, csv_path: str = 'latency_metrics.csv') -> None: self._csv_path = csv_path self._local = threading.local() self._ensure_header() - # enable/disable via env - self.enabled = os.getenv('LATENCY_ENABLED', '1') not in ('0', 'false', 'False') - # rolling stats - self._roll = deque(maxlen=int(os.getenv('LATENCY_ROLLING_N', '200'))) - self._flush_count = 0 - self._summary_every = int(os.getenv('LATENCY_SUMMARY_EVERY', '50')) - self._summary_path = os.getenv('LATENCY_SUMMARY_PATH', 'latency_summary.txt') + # enable/disable via env + self.enabled = os.getenv('LATENCY_ENABLED', '1') not in ('0', 'false', 'False') + # rolling stats + self._roll = deque(maxlen=int(os.getenv('LATENCY_ROLLING_N', '200'))) + self._flush_count = 0 + self._summary_every = int(os.getenv('LATENCY_SUMMARY_EVERY', '50')) + self._summary_path = os.getenv('LATENCY_SUMMARY_PATH', 'latency_summary.txt') + # store last screenshot->action latency for quick access + self._last_s2a_ms: float = 0.0 def _ensure_header(self) -> None: if not os.path.exists(self._csv_path): @@ -124,6 +126,9 @@ def flush(self, extra: Optional[Dict[str, str]] = None) -> None: # update rolling stats with primary KPI (screenshot_to_action_ms) if screenshot_to_action_ms is not None: self._roll.append(float(screenshot_to_action_ms)) + # keep last thinking time regardless of rolling window + if screenshot_to_action_ms is not None: + self._last_s2a_ms = float(screenshot_to_action_ms) self._flush_count += 1 if self._summary_every > 0 and self._flush_count % self._summary_every == 0: self._write_summary() @@ -145,6 +150,11 @@ def _fmt(v: Optional[float]) -> str: return '' return f'{v:.3f}' + # --- Helpers for UI/monitoring --- + def last_thinking_ms(self) -> float: + """Return milliseconds between screenshot capture end and action issue for last flush.""" + return float(self._last_s2a_ms) + # --- Extras --- def set_enabled(self, enabled: bool) -> None: self.enabled = enabled diff --git a/Src/ocr_utils.py b/Src/ocr_utils.py index 562bda7..4f39983 100644 --- a/Src/ocr_utils.py +++ b/Src/ocr_utils.py @@ -115,3 +115,27 @@ def read_floor_from_chapter(screen_bgr: np.ndarray, chapter_header_xy: Tuple[int val, conf = val2, conf2 results[slot] = (val, conf) return results + + +def find_chapter_headers(screen_bgr: np.ndarray) -> Dict[int, Tuple[int, int]]: + """Locate positions of 'Chapter N' headers on the given screen using OCR. + Returns mapping {chapter_number: (x, y)} for the top-left of the word 'Chapter'. + """ + if not _TESS: + return {} + try: + data = pytesseract.image_to_data(screen_bgr, output_type=pytesseract.Output.DICT) + except Exception: + return {} + results: Dict[int, Tuple[int, int]] = {} + words = [w.strip().lower() for w in data.get('text', [])] + for i, w in enumerate(words): + if w == 'chapter' and i + 1 < len(words): + try: + num = int(words[i + 1]) + x = int(data['left'][i]) + y = int(data['top'][i]) + results[num] = (x, y) + except Exception: + continue + return results From 9a44a97a78b4bf0f4439dc6ffbb684f714f3742f Mon Sep 17 00:00:00 2001 From: Marvin <52848568+mleem97@users.noreply.github.com> Date: Sun, 10 Aug 2025 03:46:19 +0200 Subject: [PATCH 2/3] Update Src/ocr_utils.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Src/ocr_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/ocr_utils.py b/Src/ocr_utils.py index 4f39983..2b51eca 100644 --- a/Src/ocr_utils.py +++ b/Src/ocr_utils.py @@ -136,6 +136,6 @@ def find_chapter_headers(screen_bgr: np.ndarray) -> Dict[int, Tuple[int, int]]: x = int(data['left'][i]) y = int(data['top'][i]) results[num] = (x, y) - except Exception: + except (ValueError, TypeError): continue return results From 7bbb22279cc73c0336fd06a04f8d447ebf9809ca Mon Sep 17 00:00:00 2001 From: Marvin <52848568+mleem97@users.noreply.github.com> Date: Sun, 10 Aug 2025 03:46:24 +0200 Subject: [PATCH 3/3] Update Src/ocr_utils.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Src/ocr_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Src/ocr_utils.py b/Src/ocr_utils.py index 2b51eca..6330864 100644 --- a/Src/ocr_utils.py +++ b/Src/ocr_utils.py @@ -125,7 +125,9 @@ def find_chapter_headers(screen_bgr: np.ndarray) -> Dict[int, Tuple[int, int]]: return {} try: data = pytesseract.image_to_data(screen_bgr, output_type=pytesseract.Output.DICT) - except Exception: + except Exception as e: + if isinstance(e, (KeyboardInterrupt, SystemExit)): + raise return {} results: Dict[int, Tuple[int, int]] = {} words = [w.strip().lower() for w in data.get('text', [])]