news 2026/9/13 11:20:41

Flipper Zero 扫雷游戏全解析:玩法、源码架构与 fbt 编译部署指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flipper Zero 扫雷游戏全解析:玩法、源码架构与 fbt 编译部署指南

Flipper Zero 扫雷游戏全解析:玩法、源码架构与 fbt 编译部署指南

【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper

本指南以仓库中收录的社区作品 Minesweeper(扫雷)应用为主体,完整讲解它在 Flipper Zero 上的玩法操作、棋盘与游戏状态的数据结构、渲染与输入的事件循环,以及基于 fbt 的编译部署方法。读完本文,你将能上手这款游戏、理解其单文件 C 实现的底层原理,并掌握如何按需调整棋盘参数后自行构建 FAP 应用。

应用背景与仓库定位

Minesweeper 是一款为 Flipper Zero 设备实现的经典扫雷游戏,原作者为 panki27,本仓库将其收录在 Applications/Official/source-OLDER/grnch/minesweeper 目录下,属于"官方/更早来源(source-OLDER)"收藏区中的社区移植作品。游戏针对 Flipper Zero 的 128×64 单色屏幕、方向键与 OK 键交互方式做了专门适配,采用全屏 ViewPort 直接渲染,不依赖任何复杂 UI 框架,非常适合作为学习 Flipper Zero 外置应用(FAP)开发的小型样例。

该目录内除 README.md 外,核心文件仅有 5 个:

  • minesweeper.c:约 520 行的单文件完整实现,包含渲染、输入、游戏逻辑与对话框流程;
  • assets.h:由图像资产生成的 XBM 位图数据,定义所有 8×8 瓦片;
  • application.fam:fbt 构建系统的应用清单,声明应用元信息;
  • assets/img/目录:分别存放瓦片 PNG/XBM 源资产与运行截图。

游戏规则与按键操作

根据原文档,游戏操作完全围绕 Flipper Zero 的 5 键(方向键 + OK 键)展开,共 4 条核心规则:

操作按键行为效果
移动光标方向键(短按)在棋盘上移动光标,光标到达边缘时会循环回绕到对侧
翻开格子短按中心 OK 键打开光标所在格子,若踩中地雷则游戏失败
切换旗帜长按中心 OK 键在"未翻开"与"插旗"状态之间切换,用于标记疑似地雷
快速展开(chord)在已翻开且有数字的格子上短按 OK当该格子周围旗帜数量与数字相等时,自动翻开周围剩余未翻开格子(感谢 @gelin 的贡献)

这四条规则在源码中都有对应实现:

  • 光标移动在 minesweeper.c 的短按分支中处理,四个方向均带边界回绕(cursor_x越界时折返到对侧);
  • 翻开格子由InputKeyOk短按触发,见 minesweeper.c;
  • 长按 OK 切换旗帜调用place_flag(),见 minesweeper.c;
  • 数字格上自动展开周围未翻开格子的逻辑位于play_move()的"flags == mines"分支,见 minesweeper.c。

游戏胜出条件为:翻开的格子数fields_cleared等于棋盘总格子数 − 地雷数(见 minesweeper.c)。失败时弹出"Game Over"对话框并触发设备振动提示;胜利时弹出"Game won!"对话框并显示通关用时。

核心数据结构:从宏到状态机

棋盘尺寸与难度常量

在 minesweeper.c 顶部定义了整套游戏的参数:

#define PLAYFIELD_WIDTH 16 // 棋盘宽度(列数) #define PLAYFIELD_HEIGHT 7 // 棋盘高度(行数) #define TILE_WIDTH 8 // 瓦片像素宽度 #define TILE_HEIGHT 8 // 瓦片像素高度 #define MINECOUNT 20 // 地雷总数

16×7 的棋盘加上顶部 8 像素信息栏(8 + y * TILE_HEIGHT的渲染偏移),恰好填满 Flipper Zero 的 128×64 屏幕。20 颗地雷在 112 个格子中约占 17.9% 的密度,属于中等偏易的难度。

双层棋盘:逻辑层与表现层

游戏采用"逻辑棋盘 + 表现棋盘"双数组设计,体现在Minesweeper状态结构体(minesweeper.c)中:

typedef struct { Field minefield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT]; // 逻辑层:哪些格子是雷 TileType playfield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT]; // 表现层:格子当前显示状态 FuriTimer* timer; // 失败振动的单次定时器 int cursor_x, cursor_y; // 光标位置 int mines_left; // 剩余地雷数 int fields_cleared; // 已翻开格子数 int flags_set; // 已插旗数 bool game_started; // 是否已开始(首次翻开前不布置雷) uint32_t game_started_tick; // 计时起点 } Minesweeper;
  • Field枚举只有FieldEmpty/FieldMine两态,回答"这里到底有没有雷";
  • TileType枚举有 12 个成员(minesweeper.c):TileType0~TileType8对应 0~8 的数字提示(源码注释特别强调枚举顺序必须固定,因为提示数字直接复用枚举值作为"周围雷数"),外加TileTypeUncleared(未翻开)、TileTypeFlag(旗帜)、TileTypeMine(地雷)。

这种分离带来的好处是:数字提示可以在翻开瞬间一次性计算并固化到playfield中,无需每次渲染时重新扫描雷区;而minefield只负责布雷与判定。

开局布置与"首击必安全"机制

setup_playfield()(minesweeper.c)负责每局初始化:

  1. minefield全部清为FieldEmptyplayfield全部置为TileTypeUncleared
  2. 循环MINECOUNT次随机布雷,使用rand() % PLAYFIELD_WIDTH/rand() % PLAYFIELD_HEIGHT生成随机坐标;
  3. 关键设计:布雷时要求随机坐标必须满足FieldEmpty且不与当前光标位置同行或同列cursor_x != rand_x && cursor_y != rand_y),从源码注释"make sure first guess isn't a mine"可知,这是为了保证玩家的第一次点击绝不会踩雷;
  4. 重置mines_leftfields_clearedflags_set等计数并记录计时起点。

值得注意的实现细节:首次按下 OK 时(game_started == false)会先调用setup_playfield()再执行play_move()(minesweeper.c),因此"首击安全"实际上是在布雷阶段通过坐标排除实现的,而不是翻开时二次判定。

翻开逻辑:数字提示、空区展开与连锁判定

play_move()(minesweeper.c)是整款游戏的核心函数,返回false表示踩雷失败。其执行顺序:

  1. 旗帜保护:若目标格已插旗,直接返回true(不产生任何操作),防止误翻;
  2. 踩雷判定:若minefield对应位置为FieldMine,将该格设为TileTypeMine并返回false
  3. chord 快速展开:若目标格是已翻开的数字格(1~8),遍历其 3×3 邻域统计旗帜数,若旗帜数等于该格数字,则对周围所有TileTypeUncleared格子递归调用play_move()——若递归过程中踩到雷,说明旗帜标错了,直接判定失败;
  4. 正常翻开:统计 3×3 邻域内的实际地雷数得到hint,写入playfieldfields_cleared++
  5. 空区自动展开:若hint == 0(空区),递归翻开周围所有未翻开格子,形成经典的"一大片空白自动打开"效果,见 minesweeper.c。

边界安全贯穿始终:所有 3×3 邻域遍历都先做x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT的越界检查(例如 minesweeper.c),并跳过自身格子。

旗帜管理

place_flag()(minesweeper.c)是双向切换逻辑:

  • 目标格为TileTypeUncleared时置为TileTypeFlagflags_set++
  • 目标格已是TileTypeFlag时还原为TileTypeUnclearedflags_set--
  • 其余状态(已翻开/数字/雷)不做任何处理。

顶部信息栏实时显示Mines: 20 - flags_set(见 minesweeper.c),即"剩余疑似雷数"。注意游戏胜负判定并不校验旗帜数量,flags_set仅用于展示与 chord 判定,插错旗也不会直接失败,只会在 chord 展开时引爆。

渲染管线:XBM 瓦片、光标反色与顶部 HUD

瓦片资源

assets.h 定义了全部 13 种 8×8 瓦片的位图数据(tile_0_bits~tile_8_bitstile_flag_bitstile_mine_bitstile_uncleared_bits),每个瓦片以 8 字节 XBM 行数据描述,例如数字"1"瓦片:

#define tile_1_width 8 #define tile_1_height 8 static uint8_t tile_1_bits[] = { 0x00, 0x10, 0x18, 0x10, 0x10, 0x10, 0x10, 0x00, };

assets/目录同时保留了对应的 PNG 源文件与.xbm文件,便于开发者用图像工具重新绘制瓦片后重新生成头文件。

渲染函数

render_callback()(minesweeper.c)负责每帧绘制,要点:

  • 先通过acquire_mutex加锁获取共享状态(25 tick 超时,超时则跳过本帧),保证与输入线程间的数据一致性;
  • 顶部 HUD:左上方绘制Mines: N,右上方绘制分:秒计时(从game_started_tick起算,换算为分钟与秒);
  • 棋盘主体:双重循环遍历 16×7 棋盘,根据playfield[x][y]的枚举值 switch 到对应瓦片位图,用canvas_draw_xbmx*8, 8+y*8为坐标绘制;
  • 光标表示:在绘制当前光标格前后各调用一次canvas_invert_color(反色),实现"光标所在格颜色反转"的高亮效果(minesweeper.c 与 minesweeper.c);
  • 数字枚举值直接作为瓦片索引的 switch 分支,与TileType的排序约束呼应。

事件循环与输入处理

minesweeper_app()(minesweeper.c)是应用入口,完整呈现了经典 Flipper Zero FAP 应用的骨架:

  1. 启动对话框:先弹出提示页,显示游戏名与操作提示"Hold OK pressed to toggle flags."(含原作者署名),按 OK 进入游戏;
  2. 事件队列furi_message_queue_alloc(8, sizeof(PluginEvent))创建容量 8 的消息队列,input_callback(minesweeper.c)将每个InputEvent封装为PluginEvent后入队;
  3. 状态互斥ValueMutex保护Minesweeper状态,渲染回调与主循环通过acquire_mutex/release_mutex协作;
  4. ViewPort 注册:创建ViewPort,设置绘制回调与输入回调,通过furi_record_open("gui")获取 GUI 服务并注册到GuiLayerFullscreen全屏层;
  5. 主循环furi_message_queue_get(..., 100)以 100 tick 超时轮询事件;短按InputKeyShort处理方向与 OK,长按InputKeyLong处理插旗与退出;每次循环末尾view_port_update触发重绘;
  6. 退出路径:短按 Back 或长按 Back 均置processing = false退出循环,随后依次注销 ViewPort、释放队列、删除互斥量与定时器、释放内存,返回 0。

游戏失败时,game_lost()(minesweeper.c)通过RECORD_NOTIFICATION触发sequence_set_vibro_on振动,并启动FuriTimerTypeOnce定时器(0.2 秒)在timer_callback中发送sequence_reset_vibro停止振动,实现短暂震感提醒。弹出 "Play again" 对话框后,玩家可按 OK 重开或按其他键退出。

编译、构建与部署

应用清单 application.fam

application.fam 是 fbt 的声明式清单,关键字段如下:

App( appid="Minesweeper", name="Minesweeper", apptype=FlipperAppType.EXTERNAL, # 外部应用(FAP),非固件内置 entry_point="minesweeper_app", # 入口函数 cdefines=["APP_MINESWEEPER"], requires=["gui"], # 依赖 GUI 服务 stack_size=8 * 1024, # 8KB 任务栈 fap_category="Games", # 归类为游戏 fap_icon="minesweeper_icon.png", order=35, )

apptype=FlipperAppType.EXTERNALfap_category="Games"可以看出,它被构建为独立的.fap外部应用,通过 qFlipper 或 Flipper Mobile App 拷入 SD 卡后即可在"应用 → Games"分类下运行。

编译命令

原文档给出的编译命令为:

./fbt firmware_minesweeper

在仓库对应的旧版 fbt 工具链中,该目标会编译整个固件并包含 Minesweeper 应用。需要说明的是:由于application.fam将应用声明为FlipperAppType.EXTERNAL,可以推断在现代 fbt(fbt 2.x / 3.x)下,更通用的做法是使用 FAP 相关目标单独构建该应用(例如./fbt fap_minesweeper或通过./fbt launch_app APPSRC=Applications/...一键编译并部署到已连接的 Flipper Zero),具体目标名以你本地固件仓库的 fbt 版本为准。编译前提是已按官方流程配置好 Flipper Zero 固件开发环境(包含 ARM 交叉编译工具链、fbt脚本及其依赖)。

按需调整参数

想改难度或棋盘布局,只需修改 minesweeper.c 顶部的四个宏后重新构建即可:

  • 增大MINECOUNT提高地雷密度(20/112 ≈ 17.9%,可试 24~28);
  • 修改PLAYFIELD_WIDTH/PLAYFIELD_HEIGHT改变棋盘形状,但需注意与 128×64 屏幕的适配:16 列 × 8 像素瓦片刚好占满横向 128 像素,顶部信息栏占用 8 像素,纵向剩余 56 像素最多容纳 7 行,因此加大棋盘需同步缩小瓦片尺寸或滚动方案;
  • 修改TILE_WIDTH/TILE_HEIGHT可改变瓦片像素尺寸,但必须与 assets.h 中瓦片的实际宽度/高度宏保持一致,否则渲染会错位。

小结

Minesweeper 是观察 Flipper Zero FAP 应用最小实现范式的绝佳样例:一个单文件 C 程序同时完成"事件队列 + 互斥锁 + ViewPort 渲染"的标准主循环、双层棋盘数据模型、首次必安全布雷、递归空区展开与 chord 快速翻开等完整游戏机制,并通过application.fam./fbt一键构建部署。无论你是想在 Flipper Zero 上直接游玩这款扫雷,还是把它当作学习 FAP 开发的入门源码,本文覆盖的玩法、数据结构和构建链路都已足以支撑你完成从"看懂"到"改造成自己的游戏"的跨越。

【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 11:20:08

Triton深度学习编译器:高效GPU编程新范式

1. Triton项目概述Triton是一个用于编写高效自定义深度学习原语的语言和编译器项目&#xff0c;由triton-lang组织在GitHub上维护。这个开源项目旨在提供比CUDA更高生产力、同时比其他领域特定语言(DSL)更灵活的编程环境。经过十年发展&#xff0c;Triton已经从最初的学术研究项…

作者头像 李华
网站建设 2026/9/13 11:13:51

OLAP技术架构与多维数据建模实战指南

1. OLAP技术基础与层次结构解析在大数据时代&#xff0c;OLAP&#xff08;联机分析处理&#xff09;技术已经成为企业数据分析的核心引擎。作为从业15年的数据架构师&#xff0c;我见证了这个领域从传统数据仓库到现代湖仓一体的演进过程。OLAP的本质是通过多维数据模型&#x…

作者头像 李华
网站建设 2026/9/13 11:13:49

SAP HCM自定义薪资函数开发与优化实践

1. SAP HCM自定义薪资函数开发全流程解析在SAP HCM模块实施过程中&#xff0c;标准薪资计算功能往往无法完全满足企业的个性化需求。以某跨国制造企业为例&#xff0c;其复杂的跨地区补贴计算规则&#xff08;涉及工龄、职称、绩效等多维度条件&#xff09;就要求开发人员必须掌…

作者头像 李华
网站建设 2026/9/13 11:13:17

FunASR HTTP 离线转写运行时:依赖安装与编译环境配置实战指南

FunASR HTTP 离线转写运行时&#xff1a;依赖安装与编译环境配置实战指南 【免费下载链接】FunASR Open-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving. …

作者头像 李华