python语言-Bejeweled 3 经典模式 —— 穷举暴力试错版 v8(三重识别 + 穷举兜底)-QZQ

发布时间:2026/11/6 1:38:46

python语言-Bejeweled 3 经典模式 —— 穷举暴力试错版 v8(三重识别 + 穷举兜底)-QZQ # -*- coding: utf-8 -*- Bejeweled 3 经典模式 —— 穷举暴力试错版 v8三重识别 穷举兜底 -------------------------------------------------------------------- Bejeweled 3 / 宝石迷阵3CHS 中文版 识别策略三重保险 1) 模板匹配主 2) 颜色直方图聚类模板失败时兜底 3) 全盘配对硬点前两者都算不出合法对时按顺序硬点 改动相对 v7 ✓ 修复 type_id 未初始化导致的 cannot access local variable 崩溃 ✓ mss 使用 MSS() 消除 DeprecationWarning ✓ 兜底指针每圈独立避免反复点同一对 import os import json import time import threading import numpy as np import cv2 import pyautogui from PIL import Image, ImageTk import tkinter as tk from tkinter import ttk, messagebox # ★ 零延迟关键 pyautogui.PAUSE 0 pyautogui.FAILSAFE False # ★ mss 截屏兼容新旧版本 try: import mss _SCT mss.MSS() if hasattr(mss, MSS) else mss.mss() _SCT_LOCK threading.Lock() HAS_MSS True except ImportError: HAS_MSS False _SCT_LOCK None try: import keyboard HAS_KEYBOARD True except ImportError: HAS_KEYBOARD False print([警告] 未安装 keyboard 库F1/F2 热键不可用。请运行: pip install keyboard) # 常量 BOARD_W 8 BOARD_H 8 DEFAULT_GEM_SIZE 101 BASE_DIR os.path.dirname(os.path.abspath(__file__)) CONFIG_PATH os.path.join(BASE_DIR, bej3_bf_config.json) TEMPLATE_DIR os.path.join(BASE_DIR, bej3_templates) DEFAULT_CONFIG { window_left: 0, window_top: 0, capture_width: 900, capture_height: 984, board_w: BOARD_W, board_h: BOARD_H, gem_size: DEFAULT_GEM_SIZE, x_margin: -1, y_margin: -1, type_merge_threshold: 0.35, # 直方图聚类阈值0~1越小越严 template_threshold: 0.55, # 模板匹配阈值 swap_settle: 0.00, # 交换后等待稳定 show_preview: True, force_brute_fallback: True, # 兜底开关 fallback_pairs_per_lap: 20, # 兜底时每圈硬点几对 } # 配置 def load_config(): if os.path.exists(CONFIG_PATH): try: with open(CONFIG_PATH, r, encodingutf-8) as f: c json.load(f) c[board_w] BOARD_W c[board_h] BOARD_H return {**DEFAULT_CONFIG, **c} except Exception: pass return dict(DEFAULT_CONFIG) def save_config(cfg): with open(CONFIG_PATH, w, encodingutf-8) as f: json.dump(cfg, f, indent2, ensure_asciiFalse) def get_margins(cfg): gs cfg[gem_size] xm cfg[x_margin] if cfg[x_margin] 0 else int((cfg[capture_width] - gs * cfg[board_w]) / 2) ym cfg[y_margin] if cfg[y_margin] 0 else int((cfg[capture_height] - gs * cfg[board_h]) / 2) return xm, ym # 截屏 def _mss_grab(region): with _SCT_LOCK: mon {left: int(region[0]), top: int(region[1]), width: int(region[2]), height: int(region[3])} img np.array(_SCT.grab(mon)) return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR) def grab_window(cfg): region (int(cfg[window_left]), int(cfg[window_top]), cfg[capture_width], cfg[capture_height]) if HAS_MSS: return _mss_grab(region) return cv2.cvtColor(np.array(pyautogui.screenshot(regionregion)), cv2.COLOR_RGB2BGR) def grab_cell(cfg, x, y): xm, ym get_margins(cfg) gs cfg[gem_size] px int(cfg[window_left]) xm x * gs py int(cfg[window_top]) ym y * gs if HAS_MSS: return _mss_grab((px, py, gs, gs)) return cv2.cvtColor(np.array(pyautogui.screenshot(region(px, py, gs, gs))), cv2.COLOR_RGB2BGR) def cell_center(cfg, x, y): xm, ym get_margins(cfg) gs cfg[gem_size] return (int(cfg[window_left]) xm x * gs gs // 2, int(cfg[window_top]) ym y * gs gs // 2) # 棋盘逻辑 def all_adjacent_pairs(bw, bh): pairs [] for y in range(bh): for x in range(bw - 1): pairs.append((x, y, x 1, y)) for x in range(bw): for y in range(bh - 1): pairs.append((x, y, x, y 1)) return pairs def has_match_at(g, bw, bh, cx, cy): v g[cy][cx] if v 0: return False cnt 1 i cx - 1 while i 0 and g[cy][i] v: cnt 1; i - 1 i cx 1 while i bw and g[cy][i] v: cnt 1; i 1 if cnt 3: return True cnt 1 j cy - 1 while j 0 and g[j][cx] v: cnt 1; j - 1 j cy 1 while j bh and g[j][cx] v: cnt 1; j 1 return cnt 3 # 颜色直方图签名 def cell_color_signature(crop): 8x8x8 BGR 直方图归一化抗光照 if crop is None or crop.size 0: return None h, w crop.shape[:2] cy0, cy1 int(h * 0.15), int(h * 0.85) cx0, cx1 int(w * 0.15), int(w * 0.85) center crop[cy0:cy1, cx0:cx1] if center.size 0: return None hist cv2.calcHist([center], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256]) cv2.normalize(hist, hist) return hist.flatten().astype(np.float32) def color_diff(h1, h2): if h1 is None or h2 is None: return 999.0 d cv2.compareHist(h1.reshape(-1, 1), h2.reshape(-1, 1), cv2.HISTCMP_BHATTACHARYYA) return float(d) # 模板管理 class TemplateManager: def __init__(self): self.templates [] self.names [] self._load_all() def _load_all(self): self.templates [] self.names [] if not os.path.isdir(TEMPLATE_DIR): return files sorted([f for f in os.listdir(TEMPLATE_DIR) if f.lower().endswith(.png)]) for f in files: path os.path.join(TEMPLATE_DIR, f) img cv2.imread(path, cv2.IMREAD_COLOR) if img is not None and img.size 0: self.templates.append(img) self.names.append(os.path.splitext(f)[0]) if self.templates: print(f[模板] 已加载 {len(self.templates)} 个模板{self.names}) def save_template(self, idx, crop): os.makedirs(TEMPLATE_DIR, exist_okTrue) name f{idx1:02d} path os.path.join(TEMPLATE_DIR, f{name}.png) cv2.imwrite(path, crop) self._load_all() return path def clear_all(self): if os.path.isdir(TEMPLATE_DIR): for f in os.listdir(TEMPLATE_DIR): if f.lower().endswith(.png): os.remove(os.path.join(TEMPLATE_DIR, f)) self.templates [] self.names [] def has_templates(self): return len(self.templates) 0 def match(self, crop, thresh): 返回 (best_idx, best_score) if not self.templates or crop is None or crop.size 0: return -1, 0.0 best_idx, best_score -1, -1.0 for i, tpl in enumerate(self.templates): if tpl.shape[0] ! crop.shape[0] or tpl.shape[1] ! crop.shape[1]: tpl_r cv2.resize(tpl, (crop.shape[1], crop.shape[0])) else: tpl_r tpl res cv2.matchTemplate(crop, tpl_r, cv2.TM_CCOEFF_NORMED) _, max_val, _, _ cv2.minMaxLoc(res) if max_val best_score: best_score max_val best_idx i if best_score thresh: return -1, float(best_score) return best_idx, float(best_score) # tkinter GUI class AutoGUI: def __init__(self, root, cfg): self.root root self.cfg cfg self.running False self.thread None self.photo None self.last_try None self.last_good False self._log_buf [] self._log_lock threading.Lock() self._grid_lock threading.Lock() self._last_grid None self.tpl_mgr TemplateManager() self._collect_mode False self._collect_index 0 self._collect_count 7 self._collect_canvas_bind None root.title(Bejeweled 3 暴力试错版 v8三重识别 穷举兜底 [F1开始 F2停止]) root.resizable(False, False) # ---------- 左侧画面 ---------- left ttk.Frame(root, padding8) left.grid(row0, column0, stickyn) ttk.Label(left, text实时画面 棋盘网格绿合法 红被回 白正在试, font(Microsoft YaHei, 10)).grid(row0, column0, pady(0, 4)) self.canvas tk.Canvas(left, widthcfg[capture_width], heightcfg[capture_height], bgblack, highlightthickness1, highlightbackground#888) self.canvas.grid(row1, column0) # ---------- 右侧控制 ---------- right ttk.Frame(root, padding8) right.grid(row0, column1, stickyn) row 0 ttk.Label(right, text【窗口位置】, font(Microsoft YaHei, 10, bold) ).grid(rowrow, column0, columnspan2, stickyw) row 1 self.var_x tk.IntVar(valuecfg[window_left]) self.var_y tk.IntVar(valuecfg[window_top]) row self._add_slider(right, row, 窗口 X 偏移, self.var_x, -2000, 2000, lambda v: cfg.update(window_leftint(float(v))), fmt%d) row self._add_slider(right, row, 窗口 Y 偏移, self.var_y, -2000, 2000, lambda v: cfg.update(window_topint(float(v))), fmt%d) ttk.Label(right, text【网格 / 边距】, font(Microsoft YaHei, 10, bold) ).grid(rowrow, column0, columnspan2, stickyw, pady(8, 0)) row 1 ttk.Label(right, textf棋盘: {cfg[board_w]} 列 × {cfg[board_h]} 行 (固定 8×8) ).grid(rowrow, column0, columnspan2, stickyw) row 1 self.var_gem tk.IntVar(valuecfg[gem_size]) row self._add_slider(right, row, 每格像素, self.var_gem, 16, 200, lambda v: cfg.update(gem_sizeint(float(v))), fmt%d) self.var_xm tk.IntVar(valuecfg[x_margin]) row self._add_slider(right, row, 水平边距(-1自动), self.var_xm, -1, 500, lambda v: cfg.update(x_marginint(float(v))), fmt%d) self.var_ym tk.IntVar(valuecfg[y_margin]) row self._add_slider(right, row, 垂直边距(-1自动), self.var_ym, -1, 500, lambda v: cfg.update(y_marginint(float(v))), fmt%d) ttk.Label(right, text【识别 / 判定】, font(Microsoft YaHei, 10, bold) ).grid(rowrow, column0, columnspan2, stickyw, pady(8, 0)) row 1 self.var_tth tk.DoubleVar(valuecfg[template_threshold]) row self._add_slider(right, row, 模板匹配阈值, self.var_tth, 0.3, 0.99, lambda v: cfg.update(template_thresholdfloat(v)), fmt%.2f) self.var_tmt tk.DoubleVar(valuecfg[type_merge_threshold]) row self._add_slider(right, row, 直方图聚类阈值, self.var_tmt, 0.05, 1.0, lambda v: cfg.update(type_merge_thresholdfloat(v)), fmt%.2f) self.var_settle tk.DoubleVar(valuecfg[swap_settle]) row self._add_slider(right, row, 交换稳定等待(s), self.var_settle, 0.0, 1.0, lambda v: cfg.update(swap_settlefloat(v)), fmt%.2f) self.var_fb tk.IntVar(valuecfg[fallback_pairs_per_lap]) row self._add_slider(right, row, 兜底硬点对数/圈, self.var_fb, 0, 20, lambda v: cfg.update(fallback_pairs_per_lapint(float(v))), fmt%d) self.var_show tk.BooleanVar(valuecfg.get(show_preview, True)) ttk.Checkbutton(right, text显示实时画面网格, variableself.var_show, commandlambda: cfg.update(show_previewself.var_show.get()) ).grid(rowrow, column0, columnspan2, stickyw) row 1 self.var_fb_on tk.BooleanVar(valuecfg.get(force_brute_fallback, True)) ttk.Checkbutton(right, text启用穷举兜底合法0时硬点, variableself.var_fb_on, commandlambda: cfg.update(force_brute_fallbackself.var_fb_on.get()) ).grid(rowrow, column0, columnspan2, stickyw) row 1 # ---------- 模板管理 ---------- ttk.Label(right, text【模板管理】, font(Microsoft YaHei, 10, bold) ).grid(rowrow, column0, columnspan2, stickyw, pady(8, 0)) row 1 tpl_frame ttk.Frame(right) tpl_frame.grid(rowrow, column0, columnspan2, stickyew) row 1 self.btn_collect ttk.Button(tpl_frame, text采集模板(7种), commandself.start_collect) self.btn_collect.grid(row0, column0, padx2, pady2, stickyew) ttk.Button(tpl_frame, text清空模板, commandself.clear_templates).grid(row0, column1, padx2, pady2, stickyew) tpl_frame.columnconfigure(0, weight1) tpl_frame.columnconfigure(1, weight1) self.tpl_canvas tk.Canvas(right, width280, height60, bg#222, highlightthickness1, highlightbackground#555) self.tpl_canvas.grid(rowrow, column0, columnspan2, stickyew, pady(2, 4)) row 1 self.tpl_photo None self._refresh_template_preview() # ---------- 主按钮 ---------- btns ttk.Frame(right) btns.grid(rowrow, column0, columnspan2, pady(6, 4), stickyew) row 1 ttk.Button(btns, text鼠标校准, commandself.calibrate).grid(row0, column0, padx2, stickyew) ttk.Button(btns, text保存配置, commandself.save).grid(row0, column1, padx2, stickyew) ttk.Button(btns, text重置 0,0, commandself.reset_pos).grid(row1, column0, padx2, stickyew) ttk.Button(btns, text退出, commandself.quit).grid(row1, column1, padx2, stickyew) btns.columnconfigure(0, weight1) btns.columnconfigure(1, weight1) self.btn_start ttk.Button(right, text▶ 开始暴力试错 (F1), commandself.toggle_run) self.btn_start.grid(rowrow, column0, columnspan2, pady(8, 4), stickyew) row 1 self.lbl_status ttk.Label(right, text就绪 | F1开始 F2停止, foreground#080, font(Microsoft YaHei, 10)) self.lbl_status.grid(rowrow, column0, columnspan2, stickyw) row 1 self.txt_log tk.Text(right, width44, height13, font(Consolas, 9)) self.txt_log.grid(rowrow, column0, columnspan2, stickyew) self.txt_log.configure(statedisabled) self._setup_hotkeys() self.refresh_preview() self._flush_log_periodic() # ---------- 热键 ---------- def _setup_hotkeys(self): if not HAS_KEYBOARD: self.log([提示] 未安装 keyboard热键不可用) return try: keyboard.add_hotkey(f1, lambda: self.root.after(0, self._start_run), suppressFalse) keyboard.add_hotkey(f2, lambda: self.root.after(0, self._stop_run), suppressFalse) self.log([热键] F1开始 F2停止) except Exception as e: self.log(f[热键注册失败] {e}) def _start_run(self): if self.running: return if self._collect_mode: self.log([提示] 模板采集中先完成采集) return self.running True self.btn_start.config(text■ 停止 (F2)) self.lbl_status.config(text暴力运行中三重识别, foreground#080) self.log([开始] 逻辑穷举已启动) self._set_sliders_enabled(False) self.thread threading.Thread(targetself.worker, daemonTrue) self.thread.start() def _stop_run(self): if not self.running: return self.running False self.btn_start.config(text▶ 开始暴力试错 (F1)) self.lbl_status.config(text已停止, foreground#c00) self.log([停止] 已停止) self._set_sliders_enabled(True) def _set_sliders_enabled(self, enabled): state normal if enabled else disabled for s in self._sliders: try: s.configure(statestate) except Exception: pass # ---------- 滑块 ---------- def _add_slider(self, parent, row, label, var, from_, to_, cmd, fmt%d): ttk.Label(parent, textlabel).grid(rowrow, column0, columnspan2, stickyw) row 1 s ttk.Scale(parent, from_from_, toto_, orienthorizontal, length280) s.set(var.get()) lbl ttk.Label(parent, textfmt % var.get(), width8) s.grid(rowrow, column0, stickyew) lbl.grid(rowrow, column1, stickyw) s._fmt fmt s._label lbl s._var var s._cmd cmd s.configure(commandlambda v, scales: self._on_slider(v, scale)) if not hasattr(self, _sliders): self._sliders [] self._sliders.append(s) row 1 return row def _on_slider(self, v, scale): fmt scale._fmt var scale._var if fmt %d: var.set(int(float(v))) elif fmt %.2f: var.set(round(float(v), 2)) else: var.set(round(float(v), 1)) scale._label.config(textfmt % var.get()) if scale._cmd: scale._cmd(v) # ---------- 模板采集 ---------- def start_collect(self): if self.running: self.log([提示] 请先停止运行再采集模板) return if self._collect_mode: self.log([提示] 已在采集中请先完成当前采集) return if not self.cfg.get(show_preview, True): self.log([提示] 请先勾选显示实时画面网格再采集) return self._collect_mode True self._collect_index 0 self.btn_collect.config(text采集中... 点格子) self.lbl_status.config(text模板采集请点击第 1 种宝石所在格子, foreground#06c) self.log([采集] 请依次点击 7 种不同宝石各一格) self.canvas.bind_all(Button-1, self._on_collect_click) self.canvas.config(cursorcrosshair) def _on_collect_click(self, event): self.log(f[采集-调试] 收到点击 ({event.x},{event.y})) if not self._collect_mode: return xm, ym get_margins(self.cfg) gs self.cfg[gem_size] cx (event.x - xm) // gs cy (event.y - ym) // gs self.log(f[采集-调试] 换算棋盘坐标 ({cx},{cy}) 边距({xm},{ym}) 格子{gs}) if not (0 cx self.cfg[board_w] and 0 cy self.cfg[board_h]): self.log(f[采集] 点击位置不在棋盘内 ({event.x},{event.y})) return crop grab_cell(self.cfg, cx, cy) if crop is None or crop.size 0: self.log([采集] 截图失败) return idx self._collect_index path self.tpl_mgr.save_template(idx, crop) self.log(f[采集] 第 {idx 1} 种宝石 ← 格子({cx},{cy}) 已存 {os.path.basename(path)}) self._collect_index 1 self._refresh_template_preview() if self._collect_index self._collect_count: self._collect_mode False self.btn_collect.config(text采集模板(7种)) self.lbl_status.config(text模板采集完成, foreground#080) self.log(f[采集] 完成共 {self._collect_count} 个模板可以开始运行) self.canvas.unbind_all(Button-1) self.canvas.config(cursor) else: self.lbl_status.config( textf模板采集请点击第 {self._collect_index 1} 种宝石所在格子, foreground#06c) def clear_templates(self): if self.running: self.log([提示] 请先停止运行) return if messagebox.askyesno(确认, 确定清空所有模板): self.tpl_mgr.clear_all() self._refresh_template_preview() self.log([模板] 已清空) def _refresh_template_preview(self): try: self.tpl_canvas.delete(all) tpls self.tpl_mgr.templates if not tpls: self.tpl_canvas.create_text(140, 30, text无模板将使用颜色聚类降级, fill#aaa, font(Microsoft YaHei, 9)) return thumb 40 gap 6 total_w len(tpls) * (thumb gap) - gap start_x max(4, (280 - total_w) // 2) for i, t in enumerate(tpls): img cv2.resize(t, (thumb, thumb)) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) photo ImageTk.PhotoImage(Image.fromarray(img_rgb)) x start_x i * (thumb gap) self.tpl_canvas.create_image(x, 10, anchornw, imagephoto) self.tpl_canvas.create_text(x thumb // 2, 10 thumb 4, textf{i1}, fill#fff, font(Consolas, 8)) if not hasattr(self, _tpl_photos): self._tpl_photos [] self._tpl_photos.append(photo) if len(self._tpl_photos) 50: self._tpl_photos self._tpl_photos[-20:] except Exception as e: self.log(f[模板预览错误] {e}) # ---------- 预览 ---------- def refresh_preview(self): try: if self.cfg.get(show_preview, True): frame grab_window(self.cfg) vis frame.copy() xm, ym get_margins(self.cfg) gs self.cfg[gem_size] bw, bh self.cfg[board_w], self.cfg[board_h] for x in range(bw 1): px xm x * gs cv2.line(vis, (px, ym), (px, ym bh * gs), (255, 255, 255), 1) for y in range(bh 1): py ym y * gs cv2.line(vis, (xm, py), (xm bw * gs, py), (255, 255, 255), 1) if self.last_try is not None: x1, y1, x2, y2 self.last_try color (0, 230, 0) if self.last_good else (0, 0, 230) for (cx, cy) in ((x1, y1), (x2, y2)): px, py xm cx * gs, ym cy * gs cv2.rectangle(vis, (px 1, py 1), (px gs - 2, py gs - 2), color, 3) self.photo ImageTk.PhotoImage( Image.fromarray(cv2.cvtColor(vis, cv2.COLOR_BGR2RGB))) self.canvas.delete(all) self.canvas.create_image(0, 0, anchornw, imageself.photo) except Exception as e: self.log(f[预览错误] {e}) self.root.after(200, self.refresh_preview) # ---------- 校准 / 保存 ---------- def calibrate(self): self.log(1 秒内把鼠标移到游戏棋盘【左上角第一格的中心】...) self.root.update() time.sleep(1.0) pos pyautogui.position() gs self.cfg[gem_size] xm, ym get_margins(self.cfg) self.cfg[window_left] pos.x - xm - gs // 2 self.cfg[window_top] pos.y - ym - gs // 2 self.var_x.set(self.cfg[window_left]) self.var_y.set(self.cfg[window_top]) self.log(f校准完成窗口左上角 ({self.cfg[window_left]}, {self.cfg[window_top]})) def reset_pos(self): self.var_x.set(0) self.var_y.set(0) self.cfg[window_left] 0 self.cfg[window_top] 0 def save(self): save_config(self.cfg) self.log(配置已保存) def toggle_run(self): if self.running: self._stop_run() else: self._start_run() # 主循环 v8 def worker(self): bw, bh self.cfg[board_w], self.cfg[board_h] pairs all_adjacent_pairs(bw, bh) n len(pairs) lap 0 total_ops 0 total_good_pairs 0 fallback_cursor 0 use_template self.tpl_mgr.has_templates() self.log(f[识别] 使用 {模板匹配 直方图双保险 if use_template else 直方图聚类}) while self.running: try: lap 1 # 重算坐标 centers {} for (x1, y1, x2, y2) in pairs: centers[(x1, y1)] cell_center(self.cfg, x1, y1) centers[(x2, y2)] cell_center(self.cfg, x2, y2) # ---------- 1) 抓整盘快照 ---------- crops [[None] * bw for _ in range(bh)] for y in range(bh): for x in range(bw): crops[y][x] grab_cell(self.cfg, x, y) if not self.running: break if not self.running: break if not self.running: break # ---------- 2) 双识别 ---------- # 2a) 直方图签名 sigs [[None] * bw for _ in range(bh)] for y in range(bh): for x in range(bw): sigs[y][x] cell_color_signature(crops[y][x]) # 2b) 颜色聚类 types [] color_type [[-1] * bw for _ in range(bh)] merge_th float(self.cfg[type_merge_threshold]) for y in range(bh): for x in range(bw): s sigs[y][x] if s is None: continue best, best_d -1, 1e9 for ti, ts in enumerate(types): d color_diff(s, ts) if d best_d: best_d, best d, ti if best 0 and best_d merge_th: color_type[y][x] best else: types.append(s) color_type[y][x] len(types) - 1 # ★★★ 关键type_id 必须先初始化 ★★★ type_id [[-1] * bw for _ in range(bh)] # 2c) 模板匹配主路径 if use_template: th float(self.cfg[template_threshold]) n_fail 0 for y in range(bh): for x in range(bw): idx, score self.tpl_mgr.match(crops[y][x], th) if idx 0: idx color_type[y][x] n_fail 1 type_id[y][x] idx self.log(f[第 {lap} 圈] 模板匹配类型 {len(self.tpl_mgr.templates)} 种 f模板失败 {n_fail}已用颜色兜底合法交换计算中...) else: type_id color_type self.log(f[第 {lap} 圈] 直方图聚类类型 {len(types)} 种合法交换计算中...) with self._grid_lock: self._last_grid [row[:] for row in type_id] # ---------- 3) 逻辑穷举 112 对 ---------- good_pairs [] for (x1, y1, x2, y2) in pairs: a type_id[y1][x1] b type_id[y2][x2] if a 0 or b 0 or a b: continue type_id[y1][x1], type_id[y2][x2] b, a ok has_match_at(type_id, bw, bh, x1, y1) or \ has_match_at(type_id, bw, bh, x2, y2) type_id[y1][x1], type_id[y2][x2] a, b if ok: good_pairs.append((x1, y1, x2, y2)) self.log(f 合法交换 {len(good_pairs)} / {n}) # ---------- 4) 兜底 ---------- if len(good_pairs) 0 and self.cfg.get(force_brute_fallback, True): k int(self.cfg[fallback_pairs_per_lap]) if k 0: fb_pairs [] for i in range(k): fb_pairs.append(pairs[(fallback_cursor i) % n]) fallback_cursor (fallback_cursor k) % n good_pairs fb_pairs self.log(f [兜底] 合法 0硬点 {k} 对指针 {fallback_cursor}) # ---------- 5) 执行 ---------- settle float(self.cfg[swap_settle]) for (x1, y1, x2, y2) in good_pairs: if not self.running: break p1 centers[(x1, y1)] p2 centers[(x2, y2)] pyautogui.moveTo(p1[0], p1[1], duration0) pyautogui.mouseDown() pyautogui.moveTo(p2[0], p2[1], duration0) pyautogui.mouseUp() if settle 0: time.sleep(settle) total_ops 1 self.last_try (x1, y1, x2, y2) self.last_good True self.log(f ✓ ({x1},{y1})-({x2},{y2}) 累计 {total_ops}) total_good_pairs len(good_pairs) self.log(f[统计] 本圈合法 {len(good_pairs)} | f累计操作 {total_ops} | 累计合法对 {total_good_pairs}) except Exception as e: self.log(f[异常] {e}) # ---------- 日志 ---------- def log(self, msg): with self._log_lock: self._log_buf.append(msg) def _flush_log_periodic(self): try: with self._log_lock: if self._log_buf: lines self._log_buf[:] self._log_buf.clear() self.txt_log.configure(statenormal) for m in lines: self.txt_log.insert(end, m \n) self.txt_log.see(end) self.txt_log.configure(statedisabled) except Exception: pass self.root.after(200, self._flush_log_periodic) def quit(self): self.running False self._collect_mode False try: self.canvas.unbind_all(Button-1) except Exception: pass if HAS_KEYBOARD: try: keyboard.remove_all_hotkeys() except Exception: pass self.root.after(200, self.root.destroy) # main def main(): print( * 64) print(Bejeweled 3 经典模式 —— 穷举暴力试错版 v8三重识别 穷举兜底) print( mss 截屏:, 已启用 if HAS_MSS else 未安装) print( 热键F1 开始 F2 停止) print( * 64) cfg load_config() try: root tk.Tk() print([调试] Tk 窗口已创建) app AutoGUI(root, cfg) print([调试] AutoGUI 初始化完成) root.mainloop() print([调试] mainloop 退出) except Exception as e: import traceback print([致命错误], e) traceback.print_exc() input(按回车退出...) if __name__ __main__: main()python-宝石迷阵3CHS 中文版-Bejeweled 3 经典模式游戏自动化脚本源代码.ziphttps://download.csdn.net/download/qq_32257509/93805601
延伸阅读

更多相关文章

2026/11/6 1:38:46

python语言-连连看3.2修改器代码QZQ

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 1:38:46

python语言-连连看v4.92修改器代码QZQ-2026-9-19

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 1:33:46

6步走通 lego 开源贡献:ACME 证书客户端的参与开发指南

6步走通 lego 开源贡献:ACME 证书客户端的参与开发指南 【免费下载链接】lego Lets Encrypt/ACME client and library written in Go 项目地址: https://gitcode.com/gh_mirrors/le/lego lego 是一个用 Go 编写的 ACME 客户端与库,帮你的站点自动…

2026/11/6 1:38:46

Bejeweled 3 经典模式 —— 穷举暴力试错版 v4(真零延迟)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 1:38:46

python语言-连连看3.2修改器代码QZQ

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 1:38:46

python语言-连连看v4.92修改器代码QZQ-2026-9-19

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 1:33:46

6步走通 lego 开源贡献:ACME 证书客户端的参与开发指南

6步走通 lego 开源贡献:ACME 证书客户端的参与开发指南 【免费下载链接】lego Lets Encrypt/ACME client and library written in Go 项目地址: https://gitcode.com/gh_mirrors/le/lego lego 是一个用 Go 编写的 ACME 客户端与库,帮你的站点自动…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 1:03:39

MCU晶振电路设计全解析:从负载电容计算到PCB布局调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑