---三消自动挂机脚本(框选区域 + 暴力搜索 + 极速点击)-QZQ-2026-9-20)
# -*- coding: utf-8 -*- 三消自动挂机脚本框选区域 暴力搜索 极速点击 依赖: pip install numpy opencv-python dxcam keyboard pyautogui pillow import os import json import time import random import threading import tkinter as tk from tkinter import ttk import ctypes import numpy as np import cv2 import dxcam import keyboard import pyautogui # 去掉 pyautogui 内置延迟 pyautogui.PAUSE 0 # # 配置 # REGION_FILE region.json class Config: # 默认值首次运行会被框选覆盖 GAME_X 0 GAME_Y 0 GAME_W 800 GAME_H 800 ROWS 8 COLS 8 classmethod def cell_w(cls): return cls.GAME_W // cls.COLS classmethod def cell_h(cls): return cls.GAME_H // cls.ROWS # 颜色映射BGR - 字符 COLOR_MAP { (0, 0, 255): A, # 红 (0, 255, 0 ): B, # 绿 (255, 0, 0 ): C, # 蓝 (0, 255, 255): D, # 黄 (255, 0, 255): E, # 紫 (255, 255, 0 ): F, # 青 (0, 128, 255): G, # 橙 (128, 0, 128): H, # 深紫 } COLOR_TOL 40 # 延迟全部 0 CLICK_DELAY 0 STEP_DELAY 0 # 暴力搜索模式 # l1 - 所有相邻交换试一次消除选消除数最多 # l2 - 所有相邻交换模拟完整连锁选总收益最高 BRUTE_MODE l1 # L2 模拟连锁时是否补新元素补元素有随机性建议 False BRUTE_REFILL False # # 区域读写 # def load_region(): if os.path.exists(REGION_FILE): try: with open(REGION_FILE, r, encodingutf-8) as f: data json.load(f) Config.GAME_X int(data[x]) Config.GAME_Y int(data[y]) Config.GAME_W int(data[w]) Config.GAME_H int(data[h]) print(f[区域] 已加载: x{Config.GAME_X}, y{Config.GAME_Y}, fw{Config.GAME_W}, h{Config.GAME_H}) return True except Exception as e: print(f[区域] 读取失败: {e}) return False def save_region(x, y, w, h): data {x: int(x), y: int(y), w: int(w), h: int(h)} with open(REGION_FILE, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) Config.GAME_X int(x) Config.GAME_Y int(y) Config.GAME_W int(w) Config.GAME_H int(h) print(f[区域] 已保存: {data}) # # 框选工具 # class RegionSelector: def __init__(self, on_doneNone): self.on_done on_done self.start_x 0 self.start_y 0 self.rect_id None self.root tk.Tk() self.root.attributes(-fullscreen, True) self.root.attributes(-alpha, 0.25) self.root.attributes(-topmost, True) self.root.configure(bgblack) self.root.config(cursorcrosshair) self.canvas tk.Canvas(self.root, bgblack, highlightthickness0) self.canvas.pack(filltk.BOTH, expandTrue) self.canvas.bind(ButtonPress-1, self.on_press) self.canvas.bind(B1-Motion, self.on_drag) self.canvas.bind(ButtonRelease-1, self.on_release) self.root.bind(Escape, lambda e: self.root.destroy()) self.canvas.create_text( 200, 30, text拖拽框选游戏棋盘区域Esc 取消, fillwhite, font(Microsoft YaHei, 16), anchorw ) def on_press(self, event): self.start_x event.x self.start_y event.y if self.rect_id: self.canvas.delete(self.rect_id) self.rect_id self.canvas.create_rectangle( self.start_x, self.start_y, self.start_x, self.start_y, outlinered, width2 ) def on_drag(self, event): if self.rect_id: self.canvas.coords( self.rect_id, self.start_x, self.start_y, event.x, event.y ) def on_release(self, event): x1, y1 self.start_x, self.start_y x2, y2 event.x, event.y x min(x1, x2) y min(y1, y2) w abs(x2 - x1) h abs(y2 - y1) if w 20 or h 20: print([框选] 区域太小忽略) self.root.destroy() return save_region(x, y, w, h) self.root.destroy() if self.on_done: self.on_done(x, y, w, h) def run(self): self.root.mainloop() def select_region_interactive(on_doneNone): RegionSelector(on_done).run() # # 截屏 # class ScreenCapture: def __init__(self): self.camera dxcam.create() def grab(self): region ( Config.GAME_X, Config.GAME_Y, Config.GAME_X Config.GAME_W, Config.GAME_Y Config.GAME_H, ) frame self.camera.grab(regionregion) if frame is None: return None return cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) # # 视觉识别 # class Vision: def __init__(self): self.capture ScreenCapture() staticmethod def _nearest_color(bgr): best_char ? best_dist 1e9 for (r, g, b), ch in Config.COLOR_MAP.items(): d (int(bgr[0]) - b) ** 2 \ (int(bgr[1]) - g) ** 2 \ (int(bgr[2]) - r) ** 2 if d best_dist: best_dist d best_char ch if best_dist Config.COLOR_TOL ** 2 * 3: return ? return best_char def read_map(self): frame self.capture.grab() if frame is None: return None rows, cols Config.ROWS, Config.COLS cw, ch Config.cell_w(), Config.cell_h() map_data [] for i in range(rows): row_chars [] for j in range(cols): cx j * cw cw // 2 cy i * ch ch // 2 patch frame[cy - 3:cy 4, cx - 3:cx 4] if patch.size 0: row_chars.append(?) continue avg patch.reshape(-1, 3).mean(axis0) row_chars.append(self._nearest_color(avg)) map_data.append(row_chars) return map_data # # 消除核心 # def find_matches(map_data, row, col): 找出所有需要消除的坐标集合 to_remove set() # 横向 for i in range(row): j 0 while j col: if map_data[i][j] .: j 1 continue k j 1 while k col and map_data[i][k] map_data[i][j]: k 1 if k - j 3: for t in range(j, k): to_remove.add((i, t)) j k # 纵向 for j in range(col): i 0 while i row: if map_data[i][j] .: i 1 continue k i 1 while k row and map_data[k][j] map_data[i][j]: k 1 if k - i 3: for t in range(i, k): to_remove.add((t, j)) i k return to_remove def drop_elements(map_data, row, col): 下落每列非 . 压到底部 for j in range(col): write row - 1 for i in range(row - 1, -1, -1): if map_data[i][j] ! .: map_data[write][j] map_data[i][j] write - 1 for i in range(write, -1, -1): map_data[i][j] . def create_element(map_data, row, col, rng): 补新元素 for i in range(row): for j in range(col): if map_data[i][j] .: map_data[i][j] chr(ord(A) rng.randint(0, 25)) # # 暴力搜索 # def _try_swap_and_count(map_data, row, col, x1, y1, x2, y2): L1: 只做一次交换 一次消除检测。 返回 (是否可消, 单次消除数)。 map_data[x1][y1], map_data[x2][y2] map_data[x2][y2], map_data[x1][y1] matches find_matches(map_data, row, col) map_data[x1][y1], map_data[x2][y2] map_data[x2][y2], map_data[x1][y1] return (len(matches) 0, len(matches)) def _simulate_chain(map_data, row, col, refillFalse, rngNone): L2: 在副本上模拟完整连锁消除 - 下落 - (可选补元素) - 再检测。 返回总消除数。 test [r[:] for r in map_data] total 0 while True: matches find_matches(test, row, col) if not matches: break total len(matches) for (i, j) in matches: test[i][j] . drop_elements(test, row, col) if refill and rng is not None: create_element(test, row, col, rng) return total def brute_force_moves(map_data, row, col, model1, refillFalse, rngNone): 穷举所有相邻交换只扫右、下避免重复。 返回 [((x1,y1), (x2,y2), score), ...] moves [] dirs [(0, 1), (1, 0)] for i in range(row): for j in range(col): for di, dj in dirs: ni, nj i di, j dj if ni row or nj col: continue if mode l2: # 临时交换后模拟完整连锁 map_data[i][j], map_data[ni][nj] map_data[ni][nj], map_data[i][j] score _simulate_chain(map_data, row, col, refill, rng) map_data[i][j], map_data[ni][nj] map_data[ni][nj], map_data[i][j] else: # L1 ok, score _try_swap_and_count( map_data, row, col, i, j, ni, nj ) if not ok: continue if score 0: moves.append(((i, j), (ni, nj), score)) return moves def choose_best_move_brute(map_data, row, col, model1, refillFalse, rngNone): 暴力搜索并返回最优交换。 返回 ((x1,y1), (x2,y2)) 或 None。 moves brute_force_moves(map_data, row, col, modemode, refillrefill, rngrng) if not moves: return None moves.sort(keylambda m: m[2], reverseTrue) return moves[0][0], moves[0][1] # # 普通搜索保留作为对照 # def find_all_valid_moves(map_data, row, col): moves [] for i in range(row): for j in range(col): for di, dj in [(0, 1), (1, 0)]: ni, nj i di, j dj if ni row or nj col: continue test [r[:] for r in map_data] test[i][j], test[ni][nj] test[ni][nj], test[i][j] matches find_matches(test, row, col) if matches: moves.append(((i, j), (ni, nj), len(matches))) return moves def choose_best_move(map_data, row, col, strategybrute, rngNone): strategy: max - 单次消除最多普通 first - 第一个可消普通 random - 随机普通 brute - 穷举暴力用 Config.BRUTE_MODE 决定 L1 / L2 if strategy brute: return choose_best_move_brute( map_data, row, col, modeConfig.BRUTE_MODE, refillConfig.BRUTE_REFILL, rngrng ) moves find_all_valid_moves(map_data, row, col) if not moves: return None if strategy max: moves.sort(keylambda m: m[2], reverseTrue) return moves[0][0], moves[0][1] elif strategy random: rng rng or random.Random() m rng.choice(moves) return m[0], m[1] else: return moves[0][0], moves[0][1] # # 控制器ctypes 极速点击 # class Controller: staticmethod def cell_to_screen(x, y): cw, ch Config.cell_w(), Config.cell_h() sx Config.GAME_X x * cw cw // 2 sy Config.GAME_Y y * ch ch // 2 return sx, sy staticmethod def click_cell(x, y): sx, sy Controller.cell_to_screen(x, y) ctypes.windll.user32.SetCursorPos(sx, sy) ctypes.windll.user32.mouse_event(2, 0, 0, 0, 0) # 左键按下 ctypes.windll.user32.mouse_event(4, 0, 0, 0, 0) # 左键松开 staticmethod def swap(x1, y1, x2, y2): Controller.click_cell(x1, y1) Controller.click_cell(x2, y2) # # 自动挂机线程 # class AutoPlayer(threading.Thread): def __init__(self, vision, controller, strategybrute): super().__init__(daemonTrue) self.vision vision self.controller controller self.strategy strategy self.running False self.paused False self.steps 0 self.rng random.Random() def run(self): self.running True while self.running: if self.paused: continue map_data self.vision.read_map() if map_data is None: continue move choose_best_move( map_data, Config.ROWS, Config.COLS, strategyself.strategy, rngself.rng ) if move is None: print([挂机] 无可消操作等待...) time.sleep(0.001) continue (x1, y1), (x2, y2) move print(f[挂机] 第 {self.steps1} 步: f({x1},{y1}) - ({x2},{y2})) self.controller.swap(x1, y1, x2, y2) self.steps 1 def stop(self): self.running False def toggle_pause(self): self.paused not self.paused return self.paused # # GUI # class App: def __init__(self): self.root tk.Tk() self.root.title(三消自动挂机暴力搜索版) self.root.geometry(360x380) self.root.attributes(-topmost, True) self.vision Vision() self.controller Controller() self.player None self._build_ui() self._bind_hotkeys() def _build_ui(self): frm ttk.Frame(self.root, padding10) frm.pack(filltk.BOTH, expandTrue) ttk.Label(frm, text策略:).pack(anchorw) self.strategy_var tk.StringVar(valuerandom) ttk.Combobox( frm, textvariableself.strategy_var, values[brute, max, first, random], statereadonly ).pack(filltk.X, pady4) ttk.Label(frm, text暴力模式:).pack(anchorw) self.brute_var tk.StringVar(valuel1) ttk.Combobox( frm, textvariableself.brute_var, values[l1, l2], statereadonly ).pack(filltk.X, pady4) ttk.Button(frm, text重新框选 (F7), commandself.reselect)\ .pack(filltk.X, pady4) ttk.Button(frm, text开始 (F8), commandself.start)\ .pack(filltk.X, pady4) ttk.Button(frm, text暂停/继续 (F9), commandself.toggle_pause)\ .pack(filltk.X, pady4) ttk.Button(frm, text停止 (F10), commandself.stop)\ .pack(filltk.X, pady4) self.status_var tk.StringVar(value状态: 未运行) ttk.Label(frm, textvariableself.status_var).pack(anchorw, pady6) self.region_var tk.StringVar() self._update_region_label() ttk.Label(frm, textvariableself.region_var).pack(anchorw) def _update_region_label(self): self.region_var.set( f区域: x{Config.GAME_X}, y{Config.GAME_Y}, fw{Config.GAME_W}, h{Config.GAME_H} ) def _bind_hotkeys(self): keyboard.add_hotkey(f7, self.reselect) keyboard.add_hotkey(f8, self.start) keyboard.add_hotkey(f9, self.toggle_pause) keyboard.add_hotkey(f10, self.stop) keyboard.add_hotkey(f12, self.root.destroy) def reselect(self): self.stop() self.root.withdraw() def done(x, y, w, h): self.root.deiconify() self._update_region_label() self.status_var.set(状态: 区域已更新) select_region_interactive(on_donedone) def start(self): if self.player and self.player.running: return # 把 GUI 选择写回 Config Config.BRUTE_MODE self.brute_var.get() self.player AutoPlayer( self.vision, self.controller, strategyself.strategy_var.get() ) self.player.start() self.status_var.set(状态: 运行中) def toggle_pause(self): if not self.player: return paused self.player.toggle_pause() self.status_var.set(状态: 暂停 if paused else 状态: 运行中) def stop(self): if self.player: self.player.stop() self.status_var.set(状态: 已停止) def run(self): self.root.mainloop() # # 入口 # if __name__ __main__: try: ctypes.windll.shcore.SetProcessDpiAwareness(1) except Exception: pass if not load_region(): print([区域] 未找到 region.json启动框选工具...) select_region_interactive() App().run()python-宝石迷阵3CHS 中文版-Bejeweled 3 经典模式游戏自动化脚本源代码.ziphttps://download.csdn.net/download/qq_32257509/93805601