# happy-birds-cracker 基于 [MaaFramework](https://github.com/MaaXYZ/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` ...),每关只需标定一次。 标定窗口会**自动缩放适配屏幕**(截图比屏幕大也能完整框选)。 ```bash # 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 / 真机,可独立测试。 ## 快速开始 ```bash # 安装为可编辑包:之后在项目根目录任何位置都能用 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` 兜底。