152 lines
7.6 KiB
Markdown
152 lines
7.6 KiB
Markdown
# 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` 兜底。
|