首页/新闻资讯/正文详情

连连看 4 - 难度Normal - 关卡 无变化-连连看自动消除 —— 可视化标定版(无盲试 + 低置信度不阻断)-中级QZQ-100%

发布时间:2026/11/8 1:10:16 来源:云帆数科 栏目:资讯中心
连连看 4 - 难度Normal - 关卡 无变化-连连看自动消除 —— 可视化标定版(无盲试 + 低置信度不阻断)-中级QZQ-100%
# -*- coding:utf-8 -*- 连连看 4 - 难度:Normal - 关卡: 无变化-连连看自动消除 —— 可视化标定版无盲试 低置信度不阻断 修复 1. 低置信度格子直接当空格(0)不再跳过整帧 2. EMPTY_THRESHOLD 默认 0.75空格更易识别 3. 消除后空格判定更宽容 快捷键F1开始 F2停止 import ctypes, os, threading, time, glob from collections import deque, Counter import tkinter as tk from tkinter import ttk, messagebox import cv2 import numpy as np import win32api, win32gui, win32con from PIL import ImageGrab, Image, ImageTk try: ctypes.windll.shcore.SetProcessDpiAwareness(2) except Exception: try: ctypes.windll.user32.SetProcessDPIAware() except Exception: pass # 配置 class Config: WINDOW_TITLE 连连看 4 - 难度:Normal - 关卡: 无变化 # ---- 棋盘几何标定后自动填入---- GRID_X0 0 GRID_Y0 0 CELL_W 40 CELL_H 50 H_NUM 14 V_NUM 7 # ---- 节奏 ---- TIME_INTERVAL 0.05 ANIM_WAIT 0.25 CONFIRM_WAIT 0.20 # ---- 匹配 ---- TEMPLATE_DIR templates MATCH_METHOD cv2.TM_CCOEFF_NORMED MATCH_THRESHOLD 0.80 # 识别阈值 EMPTY_THRESHOLD 0.75 # 空格判定阈值降低空格更易识别 MERGE_THRESHOLD 0.88 # ---- 防错 ---- MAX_PAIR_RETRIES 1 HARD_BLACKLIST_LIMIT 1 QUIET_WAIT_ROUNDS 5 QUIET_WAIT_INTERVAL 0.8 # ---- 预览 ---- PREVIEW_INTERVAL 0.3 PREVIEW_MAX_W 900 stop_event threading.Event() matcher None # BFS 路径 DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT 0, 1, 2, 3 DIR_DELTA {DIR_UP: (-1, 0), DIR_DOWN: (1, 0), DIR_LEFT: (0, -1), DIR_RIGHT: (0, 1)} def bfs_path(matrix, start, end, max_turns2): if start end: return None rows, cols len(matrix), len(matrix[0]) if matrix[start[0]][start[1]] 0 or matrix[end[0]][end[1]] 0: return None if matrix[start[0]][start[1]] ! matrix[end[0]][end[1]]: return None min_r, max_r, min_c, max_c -1, rows, -1, cols def in_range(r, c): return min_r r max_r and min_c c max_c def is_blocked(r, c): if r 0 or r rows or c 0 or c cols: return False if (r, c) start or (r, c) end: return False return matrix[r][c] ! 0 visited {} q deque() for d in (DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT): dr, dc DIR_DELTA[d] nr, nc start[0] dr, start[1] dc if not in_range(nr, nc) or is_blocked(nr, nc): continue visited[(nr, nc, d)] 0 q.append((nr, nc, d, 0, [(nr, nc)])) while q: r, c, d, turns, path q.popleft() if (r, c) end: return [start] path for nd in (DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT): dr, dc DIR_DELTA[nd] nr, nc r dr, c dc if not in_range(nr, nc): continue new_turns turns if nd d else turns 1 if new_turns max_turns: continue if (nr, nc) ! end and is_blocked(nr, nc): continue state (nr, nc, nd) if state in visited and visited[state] new_turns: continue visited[state] new_turns q.append((nr, nc, nd, new_turns, path [(nr, nc)])) return None # 截图 def get_window_rect(): hwnd win32gui.FindWindow(None, Config.WINDOW_TITLE) if not hwnd: return None try: win32gui.SetForegroundWindow(hwnd) win32gui.ShowWindow(hwnd, win32con.SW_RESTORE) except Exception: pass return win32gui.GetWindowRect(hwnd) def grab_screen(): return cv2.cvtColor(np.array(ImageGrab.grab()), cv2.COLOR_RGB2BGR) def slice_cell(screen, x_idx, y_idx): x1 Config.GRID_X0 x_idx * Config.CELL_W y1 Config.GRID_Y0 y_idx * Config.CELL_H x2, y2 x1 Config.CELL_W, y1 Config.CELL_H h, w screen.shape[:2] x1, y1, x2, y2 max(0, x1), max(0, y1), min(w, x2), min(h, y2) if x2 - x1 5 or y2 - y1 5: return None return screen[y1:y2, x1:x2] def grab_all_cells(screen): return [slice_cell(screen, x, y) for y in range(Config.V_NUM) for x in range(Config.H_NUM)] # 模板匹配器 class TemplateMatcher: def __init__(self): self.templates [] self.empty_template None self._size (64, 64) os.makedirs(Config.TEMPLATE_DIR, exist_okTrue) def _prep(self, img): if img is None or img.size 0: return None g cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if img.ndim 3 else img.copy() g cv2.GaussianBlur(g, (3, 3), 0) return cv2.resize(g, self._size) def _crop(self, g): h, w g.shape m 0.18 return g[int(h * m):int(h * (1 - m)), int(w * m):int(w * (1 - m))] def _score(self, g1, g2): return float(cv2.matchTemplate(self._crop(g1), self._crop(g2), Config.MATCH_METHOD)[0][0]) def reset(self): self.templates.clear() self.empty_template None for f in glob.glob(os.path.join(Config.TEMPLATE_DIR, type_*.png)): try: os.remove(f) except OSError: pass def add(self, cell): tid len(self.templates) 1 self.templates.append((tid, self._prep(cell))) cv2.imwrite(os.path.join(Config.TEMPLATE_DIR, ftype_{tid:03d}.png), cell) return tid def is_empty(self, cell): if cell is None or self.empty_template is None: return False g self._prep(cell) if g is None: return False return self._score(g, self.empty_template) Config.EMPTY_THRESHOLD def recognize(self, cell): 返回 (类型ID, 最高分)。类型ID0 表示无法识别或空格。 if cell is None or cell.size 0: return 0, 0.0 g self._prep(cell) if g is None: return 0, 0.0 # 先判空用较低阈值 if self.empty_template is not None: es self._score(g, self.empty_template) if es Config.EMPTY_THRESHOLD: return 0, es # 再匹配所有模板 best_id, best 0, 0.0 for tid, tpl in self.templates: s self._score(g, tpl) if s best: best, best_id s, tid if best Config.MATCH_THRESHOLD: return 0, best return best_id, best def build(self, cells, allow_newTrue): 根据当前棋盘建库。返回 (matrix, low_conf_list) if allow_new: self.reset() # 找方差最小的格子作为空格模板 if allow_new: best_var, best_g 1e18, None for c in cells: if c is None: continue g self._prep(c) if g is None: continue v float(np.var(g)) if v best_var: best_var, best_g v, g if best_g is not None and best_var 300: self.empty_template best_g print(f[建库] 空格模板方差{best_var:.1f}) else: print(f[建库] 未找到明显空格最小方差{best_var:.1f}) matrix [] low_conf [] for y in range(Config.V_NUM): row [] for x in range(Config.H_NUM): c cells[y * Config.H_NUM x] if c is None: row.append(0) continue g self._prep(c) if g is None: row.append(0) continue # 判空 if self.empty_template is not None: if self._score(g, self.empty_template) Config.EMPTY_THRESHOLD: row.append(0) continue # 匹配已有模板 best_id, best 0, 0.0 for tid, tpl in self.templates: s self._score(g, tpl) if s best: best, best_id s, tid if best Config.MATCH_THRESHOLD: row.append(best_id) else: if allow_new: # 建库时匹配到高相似度的合并否则新建 if best Config.MERGE_THRESHOLD: row.append(best_id) else: row.append(self.add(c)) else: # 运行时不建新模板视为空格 row.append(0) low_conf.append((y, x, best_id, round(best, 3))) matrix.append(row) return matrix, low_conf # 点击 def cell_center(rc): return (Config.GRID_X0 rc[1] * Config.CELL_W Config.CELL_W // 2, Config.GRID_Y0 rc[0] * Config.CELL_H Config.CELL_H // 2) def click_pair(p1, p2): x1, y1 cell_center(p1) x2, y2 cell_center(p2) win32api.SetCursorPos((x1, y1)) time.sleep(0.02) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x1, y1, 0, 0) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x1, y1, 0, 0) if stop_event.wait(Config.TIME_INTERVAL): return win32api.SetCursorPos((x2, y2)) time.sleep(0.02) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x2, y2, 0, 0) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x2, y2, 0, 0) # 热键 from ctypes import wintypes class HotkeyListener(threading.Thread): ID_F1, ID_F2 1, 2 VK_F1, VK_F2 0x70, 0x71 WM_HOTKEY 0x0312 def __init__(self, on_start, on_stop, log): super().__init__(daemonTrue) self.on_start, self.on_stop, self.log on_start, on_stop, log self._stop False def run(self): u ctypes.windll.user32 u.RegisterHotKey(None, self.ID_F1, 0, self.VK_F1) u.RegisterHotKey(None, self.ID_F2, 0, self.VK_F2) msg wintypes.MSG() try: while not self._stop: if u.PeekMessageW(ctypes.byref(msg), None, 0, 0, 1): if msg.message self.WM_HOTKEY: if msg.wParam self.ID_F1: self.log([热键] F1) self.on_start() elif msg.wParam self.ID_F2: self.log([热键] F2) self.on_stop() u.TranslateMessage(ctypes.byref(msg)) u.DispatchMessageW(ctypes.byref(msg)) else: time.sleep(0.03) finally: u.UnregisterHotKey(None, self.ID_F1) u.UnregisterHotKey(None, self.ID_F2) def stop(self): self._stop True # GUI class App: def __init__(self, root): global matcher matcher TemplateMatcher() self.root root root.title(连连看 — 可视化标定版无盲试·低置信度不阻断) root.geometry(1280x860) self.screen None self.matrix None self.running False self._last_preview 0 # 标定 self.calibrating False self.calib_scale 1.0 self.calib_offset (0, 0) self.calib_rect None self.calib_start None self._build_ui() self.hotkey HotkeyListener( on_startlambda: root.after(0, self.start), on_stoplambda: root.after(0, self.stop), logself.log) self.hotkey.start() self.log(① 截图 ② 标定棋盘(鼠标框选) ③ 建库 ④ F1 开始) self.log(f当前阈值MATCH{Config.MATCH_THRESHOLD} EMPTY{Config.EMPTY_THRESHOLD}) root.protocol(WM_DELETE_WINDOW, self.on_close) def _build_ui(self): top ttk.Frame(self.root, padding5) top.pack(filltk.X) ttk.Button(top, text① 截图, commandself.snap).pack(sidetk.LEFT, padx3) ttk.Button(top, text② 标定棋盘(鼠标框选), commandself.start_calib).pack(sidetk.LEFT, padx3) ttk.Button(top, text③ 建库, commandself.build).pack(sidetk.LEFT, padx3) ttk.Button(top, text④ 开始(F1), commandself.start).pack(sidetk.LEFT, padx3) ttk.Button(top, text■ 停止(F2), commandself.stop).pack(sidetk.LEFT, padx3) self.status tk.StringVar(value请先截图) ttk.Label(top, textvariableself.status, foregroundblue).pack(sidetk.LEFT, padx15) mid ttk.Frame(self.root) mid.pack(filltk.BOTH, expandTrue) left ttk.LabelFrame(mid, text棋盘几何标定后自动填入, padding8) left.pack(sidetk.LEFT, filltk.Y, padx5, pady5) self.geo_vars {} for k in [GRID_X0, GRID_Y0, CELL_W, CELL_H, H_NUM, V_NUM]: row ttk.Frame(left) row.pack(filltk.X, pady2) ttk.Label(row, textk, width10).pack(sidetk.LEFT) v tk.StringVar(valuestr(getattr(Config, k))) self.geo_vars[k] v e ttk.Entry(row, textvariablev, width10) e.pack(sidetk.LEFT, padx4) e.bind(FocusOut, lambda ev, kkk: self._apply_geo(kk)) e.bind(Return, lambda ev, kkk: self._apply_geo(kk)) thr ttk.LabelFrame(left, text阈值, padding8) thr.pack(filltk.X, pady8) self.thr_vars {} for k, mn, mx in [(MATCH_THRESHOLD, 40, 100), (EMPTY_THRESHOLD, 40, 100)]: row ttk.Frame(thr) row.pack(filltk.X, pady2) ttk.Label(row, textk, width18).pack(sidetk.LEFT) lab ttk.Label(row, textf{int(getattr(Config, k) * 100)}, width4, foregrounddarkgreen) lab.pack(sidetk.RIGHT) v tk.IntVar(valueint(getattr(Config, k) * 100)) self.thr_vars[k] (v, lab) s ttk.Scale(row, from_mn, tomx, orienttk.HORIZONTAL, variablev, commandlambda val, kkk: self._apply_thr(kk)) s.pack(sidetk.LEFT, filltk.X, expandTrue, padx4) right ttk.LabelFrame(mid, text预览标定时在图上拖拽框选, padding5) right.pack(sidetk.LEFT, filltk.BOTH, expandTrue, padx5, pady5) self.canvas tk.Canvas(right, bggray20, cursorcrosshair) self.canvas.pack(filltk.BOTH, expandTrue) self.canvas.bind(ButtonPress-1, self._calib_press) self.canvas.bind(B1-Motion, self._calib_drag) self.canvas.bind(ButtonRelease-1, self._calib_release) self.canvas.bind(Configure, lambda e: self._redraw(forceTrue)) logf ttk.LabelFrame(self.root, text日志, padding5) logf.pack(sidetk.BOTTOM, filltk.X, padx5, pady5) self.txt tk.Text(logf, height12) self.txt.pack(sidetk.LEFT, filltk.BOTH, expandTrue) sb ttk.Scrollbar(logf, commandself.txt.yview) sb.pack(sidetk.RIGHT, filltk.Y) self.txt.config(yscrollcommandsb.set) def log(self, m): try: self.root.after(0, lambda: (self.txt.insert(tk.END, m \n), self.txt.see(tk.END))) except Exception: pass def _apply_geo(self, k): try: setattr(Config, k, int(self.geo_vars[k].get())) except ValueError: pass def _apply_thr(self, k): v, lab self.thr_vars[k] val v.get() lab.config(textstr(val)) setattr(Config, k, val / 100.0) # ---------- 截图 ---------- def snap(self): rect get_window_rect() if not rect: messagebox.showerror(错误, 找不到连连看窗口) return self.screen grab_screen() self.log(f截图 {self.screen.shape[1]}x{self.screen.shape[0]}窗口 {rect}) self.status.set(请点『标定棋盘』在预览图上框选棋盘) self._redraw(forceTrue) # ---------- 标定 ---------- def start_calib(self): if self.screen is None: messagebox.showwarning(提示, 请先点『截图』) return self.calibrating True self.calib_rect None self.status.set(拖拽框选整个棋盘左上→右下) def _canvas_to_screen(self, cx, cy): return (int((cx - self.calib_offset[0]) / self.calib_scale), int((cy - self.calib_offset[1]) / self.calib_scale)) def _calib_press(self, e): if not self.calibrating: return self.calib_start (e.x, e.y) self.calib_rect (e.x, e.y, e.x, e.y) self._redraw(forceTrue) def _calib_drag(self, e): if not self.calibrating or self.calib_start is None: return self.calib_rect (self.calib_start[0], self.calib_start[1], e.x, e.y) self._redraw(forceTrue) def _calib_release(self, e): if not self.calibrating or self.calib_start is None: return self.calibrating False sx0, sy0 self._canvas_to_screen(*self.calib_start) sx1, sy1 self._canvas_to_screen(e.x, e.y) x0, x1 sorted([sx0, sx1]) y0, y1 sorted([sy0, sy1]) if x1 - x0 50 or y1 - y0 50: self.log(框选太小取消) self._redraw(forceTrue) return Config.GRID_X0 x0 Config.GRID_Y0 y0 Config.CELL_W max(1, (x1 - x0) // Config.H_NUM) Config.CELL_H max(1, (y1 - y0) // Config.V_NUM) for k in [GRID_X0, GRID_Y0, CELL_W, CELL_H, H_NUM, V_NUM]: self.geo_vars[k].set(str(getattr(Config, k))) self.log(f✔ 标定完成GRID({x0},{y0}) CELL{Config.CELL_W}x{Config.CELL_H} f网格{Config.H_NUM}x{Config.V_NUM}) self.status.set(标定完成点『建库』) self.calib_rect None self._redraw(forceTrue) # ---------- 建库 ---------- def build(self): if self.screen is None: messagebox.showwarning(提示, 请先截图) return if Config.GRID_X0 0 and Config.GRID_Y0 0: messagebox.showwarning(提示, 请先标定棋盘) return cells grab_all_cells(self.screen) matrix, low_conf matcher.build(cells, allow_newTrue) n_types len(matcher.templates) self.log(f✅ 建库完成{n_types} 种方块) self.log(识别矩阵) for row in matrix: self.log( .join(f{v:3d} for v in row)) flat [v for r in matrix for v in r if v] odd sum(1 for c in Counter(flat).values() if c % 2) self.log(f非零格 {len(flat)}奇数类 {odd}) if low_conf: self.log(f⚠ 有 {len(low_conf)} 个格子置信度不足前10个) for r, c, bid, s in low_conf[:10]: self.log(f ({r1},{c1}) 最高候选id{bid} 分{s}) self.matrix matrix self._redraw(forceTrue) self.status.set(f库就绪({n_types}类)按 F1 开始) # ---------- 预览 ---------- def _redraw(self, forceFalse): if self.screen is None: return now time.time() if not force and now - self._last_preview Config.PREVIEW_INTERVAL: return self._last_preview now h, w self.screen.shape[:2] cw self.canvas.winfo_width() ch self.canvas.winfo_height() if cw 1: cw, ch 800, 700 max_w min(Config.PREVIEW_MAX_W, cw) scale min(max_w / w, ch / h, 1.0) nw, nh int(w * scale), int(h * scale) ox, oy (cw - nw) // 2, (ch - nh) // 2 self.calib_scale scale self.calib_offset (ox, oy) rgb cv2.cvtColor(self.screen, cv2.COLOR_BGR2RGB) img cv2.resize(rgb, (nw, nh), interpolationcv2.INTER_AREA) # 网格 gx0, gy0 Config.GRID_X0, Config.GRID_Y0 if gx0 or gy0: for j in range(Config.H_NUM 1): x int((gx0 j * Config.CELL_W) * scale) if 0 x nw: cv2.line(img, (x, 0), (x, nh), (0, 255, 0), 1) for i in range(Config.V_NUM 1): y int((gy0 i * Config.CELL_H) * scale) if 0 y nh: cv2.line(img, (0, y), (nw, y), (0, 255, 0), 1) # 识别结果标注 if self.matrix is not None and (gx0 or gy0): for i, row in enumerate(self.matrix): for j, v in enumerate(row): x1 int((gx0 j * Config.CELL_W) * scale) y1 int((gy0 i * Config.CELL_H) * scale) x2 int((gx0 (j 1) * Config.CELL_W) * scale) y2 int((gy0 (i 1) * Config.CELL_H) * scale) if x2 0 or y2 0 or x1 nw or y1 nh: continue color (80, 255, 80) if v ! 0 else (255, 80, 80) cv2.rectangle(img, (x1, y1), (x2, y2), color, 1) if v ! 0: cv2.putText(img, str(v), (x1 2, y1 14), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 0), 1, cv2.LINE_AA) # 标定框 if self.calib_rect: x0, y0, x1, y1 self.calib_rect cv2.rectangle(img, (x0, y0), (x1, y1), (255, 0, 0), 2) im Image.fromarray(img) photo ImageTk.PhotoImage(im) self.canvas.delete(all) self.canvas.create_image(ox, oy, anchortk.NW, imagephoto) self.canvas.image photo # ---------- 开始/停止 ---------- def start(self): if self.running: return if not matcher.templates: self.log(⚠ 请先建库) return if Config.GRID_X0 0 and Config.GRID_Y0 0: self.log(⚠ 请先标定棋盘) return self.running True stop_event.clear() self.log( 开始无盲试 ) threading.Thread(targetself._loop, daemonTrue).start() def stop(self): if not self.running: return stop_event.set() self.running False self.log(已停止) # ---------- 主循环 ---------- def _loop(self): dead set() retry {} last_nz None black set() black_cnt {} while not stop_event.is_set() and self.running: try: screen grab_screen() cells grab_all_cells(screen) # 识别低置信度格子直接当 0不阻断 matrix [] low_cells [] for y in range(Config.V_NUM): row [] for x in range(Config.H_NUM): c cells[y * Config.H_NUM x] if c is None or c.size 0: row.append(0) continue if matcher.is_empty(c): row.append(0) continue tid, sc matcher.recognize(c) if tid 0: row.append(0) low_cells.append((y, x, round(sc, 3))) else: row.append(tid) matrix.append(row) low len(low_cells) flat [v for r in matrix for v in r if v] nz len(flat) self.screen screen self.matrix matrix self._redraw() if nz 0: self.log(棋盘空等待…) time.sleep(1.5) dead.clear() retry.clear() black.clear() black_cnt.clear() last_nz None continue if last_nz is not None and nz last_nz: self.log(f ℹ 非零 {last_nz}-{nz}清空临时黑名单) dead.clear() retry.clear() last_nz nz if low 0: self.log(f ℹ {low} 个格子置信度不足已当空格处理) if low 5: for (r, c, s) in low_cells: self.log(f ({r1},{c1}) 最高分{s}) # 找所有可消除对 pairs [] by_type {} for i, r in enumerate(matrix): for j, v in enumerate(r): if v: by_type.setdefault(v, []).append((i, j)) for v, cs in by_type.items(): for a in range(len(cs)): for b in range(a 1, len(cs)): p bfs_path(matrix, cs[a], cs[b], 2) if p: pairs.append((len(p), cs[a], cs[b], p)) pairs.sort(keylambda t: t[0]) valid [p for p in pairs if self._key(p[1], p[2]) not in dead and self._key(p[1], p[2]) not in black and retry.get(self._key(p[1], p[2]), 0) Config.MAX_PAIR_RETRIES] if not valid: self.log(f无候选对非零 {nz}安静等待…) self._print_matrix(matrix) ok False for _ in range(Config.QUIET_WAIT_ROUNDS): if stop_event.is_set(): break time.sleep(Config.QUIET_WAIT_INTERVAL) s2 grab_screen() c2 grab_all_cells(s2) m2 [] for y in range(Config.V_NUM): row [] for x in range(Config.H_NUM): cc c2[y * Config.H_NUM x] if matcher.is_empty(cc): row.append(0) else: tid, _ matcher.recognize(cc) row.append(tid) m2.append(row) nz2 sum(1 for r in m2 for v in r if v) self.screen s2 self.matrix m2 self._redraw() if nz2 nz: self.log(f ℹ 非零 {nz}-{nz2}恢复) dead.clear() retry.clear() ok True break if not ok: self.log( 仍无解继续等待…) continue # 选一对 chosen valid[0] _, p1, p2, path chosen key self._key(p1, p2) att retry.get(key, 0) 1 self.log(f消 ({p1[0]1},{p1[1]1})-({p2[0]1},{p2[1]1}) f路径{len(path)} 第{att}次) # 点击 click_pair(p1, p2) time.sleep(Config.ANIM_WAIT) # 确认 sc grab_screen() e1 matcher.is_empty(slice_cell(sc, p1[1], p1[0])) e2 matcher.is_empty(slice_cell(sc, p2[1], p2[0])) if not (e1 and e2): time.sleep(Config.CONFIRM_WAIT) sc grab_screen() e1 matcher.is_empty(slice_cell(sc, p1[1], p1[0])) e2 matcher.is_empty(slice_cell(sc, p2[1], p2[0])) if e1 and e2: self.log( ✅ 消除成功) retry.pop(key, None) dead.discard(key) black_cnt.pop(key, None) dead.clear() else: retry[key] att black_cnt[key] black_cnt.get(key, 0) 1 if black_cnt[key] Config.HARD_BLACKLIST_LIMIT: black.add(key) self.log(f ⛔ 连续失败{black_cnt[key]}次永久拉黑) elif att Config.MAX_PAIR_RETRIES: dead.add(key) self.log(f ⚠ 尝试{att}次未消除临时拉黑) except Exception as ex: self.log(f循环异常{ex}) import traceback self.log(traceback.format_exc()) time.sleep(0.5) self.running False self.log( 结束 ) def _key(self, p1, p2): return (p1, p2) if p1 p2 else (p2, p1) def _print_matrix(self, m): self.log(当前矩阵) for row in m: self.log( .join(f{v:3d} for v in row)) def on_close(self): stop_event.set() try: self.hotkey.stop() except Exception: pass self.root.destroy() def main(): root tk.Tk() App(root) root.mainloop() if __name__ __main__: main()python语言-连连看4.10游戏自动化脚本源代码.ziphttps://download.csdn.net/download/qq_32257509/93805125

相关推荐

社团管理系基于springboot + vue社团管理系统(源码+数据库+文档)
社团管理系基于springboot + vue社团管理系统(源码+数据库+文档)

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

连连看3.2-连连看自动消除 —— 可视化标定版(无盲试 + 低置信度不阻断)-初级和中级QZQ-100%
连连看3.2-连连看自动消除 —— 可视化标定版(无盲试 + 低置信度不阻断)-初级和中级QZQ-100%

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

连连看 4 - 难度Easy - 关卡 无变化-连连看自动消除 —— 可视化标定版(无盲试 + 低置信度不阻断)-初级QZQ-100%
连连看 4 - 难度Easy - 关卡 无变化-连连看自动消除 —— 可视化标定版(无盲试 + 低置信度不阻断)-初级QZQ-100%

/* 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 3:30:19

北京特种工求职平台有效期多久 2026河南最新政策如何影响报考资格
北京特种工求职平台有效期多久 2026河南最新政策如何影响报考资格

北京特种工求职平台有效期多久 2026河南最新政策如何影响报考资格 学历不高怕报不上名,你是不是也担心自己因为学历问题无法进入特种作业行业?其实,很多特种作业岗位并不强制要求高学历,关键是要有对应的特种作业操作证,比如电工证、焊工证、高处作… · 2026/11/8 7:37:55

邵阳隆回电工证报名怎么报名不怕考不过白交培训费
邵阳隆回电工证报名怎么报名不怕考不过白交培训费

邵阳隆回电工证报名怎么报名不怕考不过白交培训费 你是不是也担心,辛辛苦苦报了电工证培训班,结果考试挂了,钱白花了?特别是现在特种作业操作证越来越严格,很多人因为流程不清楚,白白交了培训费还拿不到证。今天就来聊聊 邵阳隆回电工证报名… · 2026/11/8 7:37:55

青海高压电工证报名良心建议:实操考试心里没底怕挂科
青海高压电工证报名良心建议:实操考试心里没底怕挂科

青海高压电工证报名良心建议:实操考试心里没底怕挂科 实操考试心里没底怕挂科?青海高压电工证报名门槛高,考试难度大,不少考生在备考过程中常常感到迷茫,尤其是面对实操考试这一关键环节,更是焦虑不已。如果你正在准备报名,或者已经报名却不知如何备考… · 2026/11/8 7:37:55

办理怎样查看电工证级别需要什么材料
办理怎样查看电工证级别需要什么材料

电工证级别怎么看?内部消息:别再花冤枉钱! 怕考不过白交培训费?这年头,很多想转行或者提升自己的人,一上来就冲着电工证去,结果报名完才发现,连电工证级别都搞不清楚,白白浪费了钱。其实, 怎样查看电工证级别… · 2026/11/8 7:37:55

大余特种作业证考试怎么考?证书过期了不知道怎么复审延期?多久拿证全在这
大余特种作业证考试怎么考?证书过期了不知道怎么复审延期?多久拿证全在这

大余特种作业证考试怎么考?证书过期了不知道怎么复审延期?多久拿证全在这 证书过期了不知道怎么复审延期?你不是一个人。大余特种作业证考试是很多一线作业人员必须面对的硬仗,一旦错过了考试或没有及时复审,不仅影响工作,还可能面临停工、罚款甚至违法… · 2026/11/8 7:37:55

怎样查看电工证级别电子证书查询
怎样查看电工证级别电子证书查询

电工证级别怎么查?怎么报名别走弯路 实操考试心里没底怕挂科?别急,先搞清楚自己手里的电工证是几级,再安排考试和报名流程。很多人在报考前就踩坑,不是没看清证书级别,就是选错培训渠道,结果浪费钱又耽误时间。… · 2026/11/8 7:36:55

电工证丢失还能补吗?千万别踩坑,丢了证等于失业
电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/8 0:00:20

电力电缆作业证直播报名入口与办理流程良心建议
电力电缆作业证直播报名入口与办理流程良心建议

电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/8 0:00:20

英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑

英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/8 0:00:20

电工证丢失还能补吗?千万别踩坑,丢了证等于失业
电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/8 0:00:20

电力电缆作业证直播报名入口与办理流程良心建议
电力电缆作业证直播报名入口与办理流程良心建议

电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/8 0:00:20

英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑

英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/8 0:00:20

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

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

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

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

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

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

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码