# 小鸿 AI 经典俄罗斯方块（Tetris）掌机游戏

**URL:** <https://forum.hinearlink.com/t/ai-tetris/32>\
**Category:** 造物工坊\
**Created:** [2026 年10 月 26 日 15:27 UTC](https://forum.hinearlink.com/t/ai-tetris/32 "2026-10-26T15:27:16Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![huqi](https://forum.hinearlink.com/letter_avatar_proxy/v4/letter/h/ba8739/32.png) [@huqi](https://forum.hinearlink.com/u/huqi)\
发布日期： [2026 年10 月 26 日 15:27 UTC](https://forum.hinearlink.com/t/ai-tetris/32/1 "2026-10-26T15:27:16Z")

</div>

**基于 WS63 + ST7789 240×240 彩屏的经典俄罗斯方块：支持7种方块、踢墙旋转、按键连发、等级加速与消行音效。**

# 俄罗斯方块（Tetris）

## 1. 项目简介

本项目在小鸿 AI 的 240×240 彩屏上实现了经典俄罗斯方块游戏。棋盘 10×20 格，支持七种方块（I/J/L/O/S/T/Z）的移动、旋转（带水平踢墙）、消行计分与等级加速，右侧信息栏实时显示得分、消行数、等级和下一块预览。音效基于小鸿的 CI1302 声音积木以非阻塞短音呈现，按键接管音量键与 M 键实现全键盘操作。整个游戏只有一个源文件，适合作为小鸿 AI 上屏应用的入门参考案例。

### 项目信息

| 项 | 值 |
| --- | --- |
| 工程路径 | `vendor/atomgit/xiaohong/samples/tetris/` |
| 核心源码 | `screen/bayes_user_screen.c` |
| 屏幕/外设 | ST7789 240×240 SPI 彩屏（无额外外设） |
| 音频 | CI1302 语音芯片 + 板载扬声器，`bayes_user_audio` 积木（16kHz/16bit 非阻塞短音） |
| 输入 | 三个实体按键：音量+（GPIO13）/ 音量-（GPIO12）/ M 键（GPIO11），低电平有效 |
| 固件产物 | `firmware/ws63-liteos-app_all.fwpkg` |

### 功能与规则

- 经典 10×20 棋盘俄罗斯方块，七种方块各有固定配色，运行时预生成 4 个旋转朝向的 4×4 位图；
- 旋转支持水平踢墙（±1、±2 格偏移尝试），贴墙、贴堆也能顺利转正；
- 消行计分：1/2/3/4 行分别得 100/300/500/800 分并乘以当前等级；
- 每消 10 行升 1 级，下落周期从 520ms 起每级减 45ms，下限 120ms；
- 右侧信息栏显示 SCORE / LINES / LV / NEXT 预览；
- 菜单、暂停、结束三种全屏覆盖层，状态机切换；
- 全程非阻塞：逻辑跑在两个 `lv_timer` 上，音效走声音积木短音。

| 按键 | 功能 | 效果 |
| --- | --- | --- |
| 音量+（GPIO13） | 左移 | 点按移动一格，按住每约 160ms 连发 |
| 音量-（GPIO12） | 右移 | 点按移动一格，按住每约 160ms 连发 |
| M 键（GPIO11） | 开始 / 旋转 / 暂停 | 菜单与结束界面按下开始；游戏中点按顺时针旋转一格；长按约 0.8s 暂停/继续 |

## 2. 图片展示

![游戏菜单界面](https://atomgit.com/huqi/xiaohong_samples/raw/main/25_tetris/images/tetris_menu.png)

![游戏进行中，右侧为得分与 NEXT 预览](https://atomgit.com/huqi/xiaohong_samples/raw/main/25_tetris/images/tetris_gameplay.png)

## 3. 核心代码分析

### 3.1 源码位置

- **样例目录** ：`vendor/atomgit/xiaohong/samples/tetris/`
- **入口文件** ：`screen/bayes_user_screen.c`

### 3.2 代码结构

| 段落/模块 | 职责 |
| --- | --- |
| 基础工具（`rng_next` / `rot_cw` / `init_shapes` / `fits`） | xorshift32 随机数、4×4 位图顺时针旋转、7 种方块 4 朝向预生成、碰撞检测 |
| LVGL 小工具（`make_rect` / `make_label` / `overlay_show` / `update_hud` / `update_preview` / `render_board`） | 矩形/文本控件创建、覆盖层显隐、HUD 与预览刷新、棋盘差异渲染 |
| 游戏逻辑（`try_move` / `try_rotate` / `lock_piece` / `spawn_piece` / `step_down` / `start_game` / `game_over`） | 移动旋转、锁定入格、消行计分升级、出新块与状态切换 |
| 定时器回调（`gravity_cb` / `input_cb`） | 按等级周期的重力下落；40ms 一帧的按键扫描（边沿检测 + 连发 + 长按暂停） |
| 界面搭建（`build_ui`） | 根容器、标题、棋盘底框、右侧信息栏、菜单/暂停/结束三个覆盖层 |
| 入口（`bayes_user_screen_init`） | 初始化形状表与 UI、按键接管、创建重力与输入两个 `lv_timer` |

### 3.3 关键代码

棋盘渲染采用「差异重绘」：把堆叠格与当前方块叠加成目标视图，只对与上次渲染状态不同的格子做创建、删除或改色，避免每帧重建控件树：

```c
/* 只重画变化的格子:堆叠 + 当前方块叠加后的视图与已渲染状态做差 */
static void render_board(void)
{
    uint8_t view[ROWS][COLS];
    memcpy(view, s_grid, sizeof(view));
    uint16_t m = s_shapes[s_cur][s_rot];
    uint8_t col = (uint8_t)(s_cur + 1);
    for (int r = 0; r < 4; r++)
        for (int c = 0; c < 4; c++)
            if (m & (1 << (15 - (r * 4 + c)))) {
                int bx = s_px + c;
                int by = s_py + r;
                if (bx >= 0 && bx < COLS && by >= 0 && by < ROWS)
                    view[by][bx] = col;
            }
    for (int r = 0; r < ROWS; r++)
        for (int c = 0; c < COLS; c++) {
            uint8_t v = view[r][c];
            if (v == s_shown[r][c])
                continue;
            if (v == 0) {
                lv_obj_del(s_cells[r][c]);
                s_cells[r][c] = NULL;
            } else if (s_cells[r][c] == NULL) {
                s_cells[r][c] = make_rect(s_root, BOARD_X + c * CELL, BOARD_Y + r * CELL,
                                          CELL - 1, CELL - 1, s_shape_rgb[v - 1]);
            } else {
                lv_obj_set_style_bg_color(s_cells[r][c], lv_color_hex(s_shape_rgb[v - 1]), 0);
            }
            s_shown[r][c] = v;
        }
}

```

输入帧用按键积木做边沿检测：点按走一格，按住累计 4 帧（约 160ms）连发一次，手感与手感党都照顾到：

```c
/* 40ms 输入帧:按键扫描 + 边沿/连发 + 长按暂停 */
static void input_cb(lv_timer_t *timer)
{
    (void)timer;
    switch (s_state) {
    case STATE_PLAY: {
        bool r_down = bayes_key_down(BAYES_KEY_VOLDW);
        bool l_down = bayes_key_down(BAYES_KEY_VOLUP);
        bool moved = false;

        /* 音量-: 右移(点按一格,按住每 4 帧≈160ms 连发) */
        if (bayes_key_pressed(BAYES_KEY_VOLDW)) {
            s_rep_r = 0;
            moved = try_move(1, 0);
        } else if (r_down) {
            if (++s_rep_r >= 4) {
                s_rep_r = 0;
                moved = try_move(1, 0);
            }
        } else {
            s_rep_r = 0;
        }
        /* 音量+: 左移 */
        if (bayes_key_pressed(BAYES_KEY_VOLUP)) {
            s_rep_l = 0;
            moved = try_move(-1, 0) ? true : moved;
        } else if (l_down) {
            if (++s_rep_l >= 4) {
                s_rep_l = 0;
                moved = try_move(-1, 0) ? true : moved;
            }
        } else {
            s_rep_l = 0;
        }
        if (moved)
            bayes_play_tone(600, 120);
        /* ... M 键: 点按旋转,长按约 0.8s 暂停 ... */
    }
    /* ... */
    }
}

```

### 3.4 运行原理

启动后，LVGL 与屏幕驱动初始化完毕时框架会调用本文件的唯一入口 `bayes_user_screen_init()`。它先做四件事：用 `rot_cw` 把 7 个基础方块位图各预旋转出 4 个朝向；用开机 tick 作种子初始化随机数（`lv_tick_get() | 1`，保证非零）；搭建整屏 UI（深色根容器、TETRIS 标题、带边框的棋盘底框、右侧 SCORE/LINES/LV/NEXT 信息栏，以及菜单、暂停、结束三个隐藏的覆盖层）；调用 `bayes_keys_takeover(true)` 接管三个物理键，屏蔽原厂音量/语音行为。最后创建两个 `lv_timer`：500ms 的重力定时器（随等级动态调速）和 40ms 的输入定时器。

数据流非常直接：`s_grid[20][10]` 是唯一的世界状态（0 为空、1~7 为固定方块的形状色），当前方块由形状号 + 旋转号 + 坐标表示。输入定时器每 40ms 查询一次按键，移动/旋转前先经 `fits()` 碰撞检测；重力定时器到点尝试下移，落不下去就 `lock_piece()` 把方块写进 `s_grid`，逐行扫描消行（整行上移并计分升级），再 `spawn_piece()` 取下一块——若出生点即碰撞则进入 GAME OVER。每次状态变化后调用 `render_board()`，它对比目标视图与已渲染状态，只增删改有变化的格子对象，240×240 的小屏上也毫无压力。所有音效都是 `bayes_play_tone()` 非阻塞短音（移动 600Hz、旋转 1000Hz、锁定 320Hz、消行 2200Hz、开始 1400Hz、结束 200Hz 等），不阻塞 UI 线程。

### 3.5 编译与烧录

**编译：**

```bash
rm -fr out/xiaohong/xiaohong/ .ccache/* # 清理
hb set # 选择 mini + xiaohong
hb build -f # 全量编译

```

**需要修改的文件：**

| 文件 | 修改内容 |
| --- | --- |
| 无 | 本案例复用小鸿 AI 产品工程预留的屏幕钩子，将 `screen/bayes_user_screen.c` 整体替换为样例中的同名文件即可，无需改 `samples/BUILD.gn`、`config.py` 或 `ohos.cmake` |

编译成功后固件产物输出在 `firmware/ws63-liteos-app_all.fwpkg`。

**烧录：**

通过 ws63flash（或 Bayes 平台的烧录入口）将 `firmware/ws63-liteos-app_all.fwpkg` 烧录到设备，串口为 CH340 USB 转串口（如 `/dev/cu.wchusbserial*`），波特率 115200；工具提示后短按一次 RST 键开始写盘。详细步骤参见《小鸿AI 固件烧录与启动指导手册》。

**运行验证：**

烧录完成后设备自动重启，屏幕点亮并显示 “TETRIS” 菜单（含 “PRESS M TO START”）即为启动成功。按 M 开始游戏，验证：音量+ 左移、音量- 右移、点按 M 旋转、长按 M 约 0.8s 暂停/继续；消行时应有 2200Hz 提示音，右侧 NEXT 预览随出块刷新。串口以 115200 波特率连接可观察系统启动日志；编译阶段的成功标志如下（运行期日志因 SDK 版本略有差异，以屏幕菜单为准）：

```auto
[OHOS INFO] xiaohong build success
FWPKG=/work/out/ws63-liteos-app_all.fwpkg

```

## 4. 作者信息

- 作者：不羁的木木
- 邮箱：qiaomu8559968@126.com

* * *

- 分类：显示
- 许可证：Apache-2.0
- 代码仓库：[xiaohong\_samples/25\_tetris · huqi/xiaohong\_samples - AtomGit](https://atomgit.com/huqi/xiaohong_samples/tree/main/25_tetris)

站内项目：[小鸿 AI 经典俄罗斯方块（Tetris）掌机游戏 · 海星社区](https://hinearlink.com/workshop/xiaohong-tetris)

> 这条主题由主站发布项目时自动创建；主站与论坛的评论会互相同步。
