7.6 KiB
happy-birds-cracker
基于 MaaFramework 的滑动三消(消消乐)自动求解器。
MaaFramework 负责 截屏 + 图像识别 + ADB 滑动控制;本项目负责把画面变成棋盘模型、 计算最优一步、再把交换映射回屏幕滑动。
⚠️ 仅供学习与离线/单机研究。请遵守目标游戏的服务条款,线上对战/带反作弊的游戏请勿使用。
设计要点
- 网格动态可变,恒为矩形:尺寸由
GridConfig(rows, cols)决定,换关卡只需改行列数 (或在识别阶段探测后填入),核心算法与尺寸无关。 - 三种块类型(
CellKind,为后续编程预留meta扩展字段):NORMAL正常块:可交换,带color。BRICK砖块:不可移动的障碍,会打断连线。EMPTY空块/空洞:无方块,会打断连线。
目录结构
src/hbc/
board.py 棋盘数据模型:Cell / CellKind / Board(动态矩形)
solver.py 三消求解:找匹配 + 遍历相邻交换搜索最优解
config.py GridConfig(像素<->格子映射,可存 JSON)/ 模板比对参数
state.py GameState:识别与动作之间共享 Board / 当前关卡
templates.py 模板图像库:子图比对识别,支持多种墙(推荐识别方式)
recognizer.py 截图 -> Board(可插拔分类器:模板/颜色 + MaaFw 识别桩)
level.py 关卡识别:OCR 标题"关卡一" -> 关卡号 -> 载入对应布局
calibrate.py 标定工具:框选棋盘生成布局、预览、导出格子图(窗口自适应屏幕)
actuator.py Swap -> ADB 滑动(纯函数 + MaaFw 自定义动作 SwapAction)
main.py 连接设备、加载资源、注册自定义模块、循环运行(支持 --dry-run)
resource/
pipeline/
pipeline.json MaaFramework 低代码流程:识别棋盘 -> 执行交换 -> 循环
templates/ 模板库(你标定后放入):normal/ wall/ empty/
layouts/ 各关布局:level1.json level2.json ...(你标定后生成)
tests/
test_solver.py 求解器与模型测试
test_recognition.py 模板识别 + 标定 + 多关卡布局测试(合成图,无需真机)
test_level.py 关卡号 OCR 文本解析测试
如何识别棋盘
本游戏网格逐关变大且含空格。布局按关卡分文件存在 layouts/ 文件夹下
(layouts/level1.json、layouts/level2.json ...),每关只需标定一次。
标定窗口会自动缩放适配屏幕(截图比屏幕大也能完整框选)。
# 1) 框出棋盘 + 填该关行列数 -> 写入 layouts/level1.json(弹窗自动缩放,鼠标拖框回车确认)
python -m hbc.calibrate roi --image src-images/level1.jpg --level 1 --rows 5 --cols 5
# 窗口仍太大可调上限:--max-display 700
# 无 GUI / 想直接给坐标:--box x,y,w,h(按原图像素)
# 2) 叠加网格预览,确认每个格子对齐(绿框=棋盘,橙线=网格,红点=格子中心)
python -m hbc.calibrate preview --image src-images/level1.jpg --level 1 --out preview_level1.png
# 3) 把每个格子切成小图,导出到 cells/,再人工分类到模板库
python -m hbc.calibrate export --image src-images/level1.jpg --level 1 --out cells/
# 后续关卡同理,换 --level 2/3/... 和对应行列数即可
python -m hbc.calibrate roi --image src-images/level2.jpg --level 2 --rows 6 --cols 6
运行时用 --level 选择关卡:python -m hbc.main --level 1。
对齐技巧(棋盘四周有 padding)
均匀切分基于你框的矩形,所以框要贴着棋子区域而不是外层面板:
从左上角棋子的左上角拖到右下角棋子的右下角(把面板留白排除在外),
再用 preview 看红点是否落在每个棋子中心,不对就重框。反复"框 → 预览"几次即可对齐。
运行时自动识别当前是第几关(OCR)
游戏顶部写着"关卡一",MaaFramework 自带 OCR(PaddleOCR 的 ONNX 模型),运行时
可直接识别,不用你预先截图。流程:标定一次标题区域 ROI -> LevelRecognition
(level.py)OCR 标题 -> parse_cn_level 解析成关卡号 -> 自动载入 layouts/level{N}.json。
若你的 MaaFw 资源里没带 OCR 模型,去掉该节点、改用 --level 手动指定即可。
把导出的小图按类别放进模板库(文件名即类别):
templates/
normal/red.png green.png blue.png ... 每种正常块一张(文件名->稳定颜色 id)
wall/stone.png ice.png chain.png ... 墙可以有多种,每种一张
empty/hole.png 空块/空洞
为什么用模板比对而不是纯颜色
MaaFramework 的颜色识别对光照/特效/相近色容易误判。本项目识别走子图比对:
把每个格子缩放后与模板逐像素比相似度,取最高分。相似度对颜色和形状都敏感
(不像 TM_CCOEFF_NORMED 会忽略颜色),更适合消消乐。仍保留 ColorClassifier 作为兜底。
冰块 = 低置信度即判为墙
本游戏除正常生物/空格外只有一种特殊方块——冰块(冰下隐约有正常图案)。冰下图案
模糊,模板匹配置信度天然偏低,因此采用一条简单规则:凡是匹配不够自信的格子,
一律判为冰墙(cell.meta['wall']='ice',不可交换、打断连线)。
冰墙不会被直接交换,但当它旁边发生消除时,游戏里冰会化掉、露出正常生物,下一帧重新
识别就变回可用的普通块,于是自然推进。这是有意的取舍:牺牲对"未登记新方块"的健壮性,
换取对冰块的零配置支持。阈值在 TemplateMatchConfig.score_threshold /
empty_min_score,需要时可调。
新增正常生物
后续关卡出现新动物时,把它的一张干净小图放进 templates/normal/<名字>.png 即可
(每个不同文件名 = 一个独立可消除类型;如灰色魟鱼 stingray.png 与青色水母
jellyfish.png 是两种)。用 hbc.calibrate export 可批量导出格子图来挑样本。
多种墙(可选)
若以后遇到别的固定障碍,可在 templates/wall/ 放多张墙图(石墙、锁链……),
它们都识别为 CellKind.BRICK,种类记在 cell.meta['wall'],便于针对性写逻辑。
数据流
MaaFramework 截屏
-> BoardRecognition(recognizer): 按网格切格、判颜色/砖块/空块 -> Board
-> SwapAction(actuator): Solver.find_best_swap(board) -> 最优 Swap
-> 通过控制器 post_swipe 滑动
-> 循环
求解与控制是解耦的:board.py + solver.py 完全不依赖 MaaFramework / 真机,可独立测试。
快速开始
# 安装为可编辑包:之后在项目根目录任何位置都能用 hbc,无需设置 PYTHONPATH 或 cd src
pip install -e .
# 离线验证求解器(不需要设备)
python -m pytest tests/ -q
# 连接设备后运行(需先标定 config 中的 ROI 与颜色,见下)
python -m hbc.main # 自动探测第一个 adb 设备
python -m hbc.main 127.0.0.1:16384 # 或指定模拟器地址
python -m hbc.main --level 2 # 求解第二关
预留的扩展点
Cell.meta:放特殊方块(条状/炸弹/彩球)、墙的种类wall、砖块血量、冰冻层数等。Solver的scorer:自定义打分策略(默认按消除格子数,4/5 连额外加权)。Solver._cells_cleared_by:补充"相邻砖块被消除破坏"等连锁规则。GridConfig.rows/cols:动态调整网格尺寸(每关不同时重新标定/探测)。recognizer的分类器可插拔:默认模板库,必要时换ColorClassifier兜底。