AI脑洞板
完整教案
自学课件
第 30 课 · 共 30/51 课

贪吃蛇 让网格动起来,摸到状态机

课时
待定
器材
ESP32-S3 触摸屏板 + USB 线
前置知识
无
配套代码
见项目 demo 目录

你好,我是吴老师。这份是给你自己读的——不用等我讲,看不懂就整段复制给 AI,加一句"请一次只解释一个概念,讲完问我一个小问题"。

读完之后,你应该能:

  • 说清"蛇身 = 一串格子(数组)"是什么意思;
  • 明白"方向"在板子里就是一个数(上/右/下/左);
  • 理解蛇每 tick 整体挪一格、吃到食物才变长;
  • 分清"撞墙只是反弹",真正结束的是撞自己/撞对方/两蛇头对撞;
  • 说出板子有"标题 / 倒计时 / 进行中 / 结束"四个状态(状态机)。

用时:约 60 分钟。 准备:烧录 demo01_snake 的板子 + 一根 USB 线。

一、让板子玩贪吃蛇

第 29 课我们让板子当棋盘陪你下五子棋。这一课更带劲——让一条蛇在格子里咬来咬去。

规则你玩过:蛇一直往前爬,吃到食物变长;撞墙、撞自己就结束。可板子没有"墙/蛇/食物"这些实体,它只有像素点。真正的问题是:

板子怎么知道蛇现在在哪一格?朝哪个方向爬?这一步该往哪走?吃到东西怎么变长?

答案很省事:蛇身就是数组里的一串格子,方向就是一个数。 这一课把上一课"棋盘=数组"升级成"蛇身=一串格子"。

先看现象:烧录后屏幕上出现两条蛇——一条绿的你控制(滑动转向),一条红的 NPC(板子自己控制),它俩抢一个卡通食物。谁先撞自己/撞对方就输,两蛇头撞同一格算平局。

二、我观察到的现象

✏️ 可以直接在表格里填写
现象勾选
屏幕上有条会自己移动的蛇
我"滑动"屏幕,蛇就转向
蛇头吃到食物,身体变长、分数 +1
有条红色的 NPC 蛇,自己会去追食物
蛇撞到墙会"反弹"回来(没死)
蛇撞到自己/撞到对方,游戏结束
两蛇头撞在同一格,显示"平局"

三、蛇,就是"一串格子"

屏幕上共 20 列 × 24 行 = 480 个格子,每格 12 像素。每条蛇就住在这张网格上,是一串连着的格子。代码把它记成:

struct Snake {
  Cell body[480];    // 一串格子:body[0]=头,body[1]=第二节…最后是尾
  uint16_t len;      // 蛇现在有多长
  uint8_t  dir;      // 当前方向(一个数)
  uint8_t  queuedDir;// 你刚滑动、下一 tick 才生效的方向
  bool     alive;    // 还活着吗
  uint16_t score;    // 吃了多少分
};

你滑一下屏幕,蛇不会"立刻转弯"——程序只是把目标方向存进 queuedDir,下一 tick 才真正朝那边走,这样画面才不鬼畜。

四、方向,就一个数

蛇朝哪爬,不是箭头,就是一个数:

enum Dir { UP=0, RIGHT=1, DOWN=2, LEFT=3 };

DX[] = {  0,  1,  0, -1 };   // 列怎么变
DY[] = { -1,  0,  1,  0 };   // 行怎么变
  • UP=0:往上行减1(-1);RIGHT=1:往右列加1;DOWN=2:往下行加1;LEFT=3:往左列减1。

方向变成"一个数"的好处:想走一步,就拿蛇头的行列 + 一组 DX/DY 就行。 反方向也超好判断——(dir+2)%4(上的反是下,右的反是左)。

蛇身=一串格子(数组)、方向=一个数(上0/右1/下2/左3),DX/DY 控步进

五、两个代码名,够了

1. stepSnake(snake, 新头x, 新头y, 吃了没)——整条蛇往前挪一格

stepSnake 做的事:
  1) 身体从头到尾,每一节都往后腾一格;
  2) 把新蛇头塞到 body[0];
  3) 吃到食物(grow=true) → 长度+1、分数+1、重新生成一颗食物;
  4) 踩到道具(吸铁石/闪电) → 只有玩家会生效。

蛇是"往前挪一格"而不是"整个重画":头前插一格、尾巴去掉一格。只有吃到食物时尾巴才不"去掉",于是变长。想通这一点,"变长"其实只是"尾巴这次没跟上"。

2. doMove()——这一 tick,两条蛇各走一步,并处理所有碰撞

doMove 每 150ms 触发一次,按顺序做三件事:
  1) 解转向:玩家转向真正生效;如果蛇头在墙边,先算出"撞墙反弹"的新方向。
  2) 算两个新头:玩家和 NPC 各自按方向走一格,得到两个"新头"位置。
  3) 解碰撞:新蛇头去比"墙/自己的身体/对方身体/对方头":
       撞自己或撞对方 → alive=false(死了)
       两蛇头撞同一格,或互相踩对方现在的位置 → 两条都死,算平局
       谁都没死 → 才各自 stepSnake 真走这一步。

这个 demo 里撞到墙只是反弹(回到场内继续走),不会死!真正结束的是撞到自己、撞到对方,或两蛇头对撞。和你以前玩过的"撞墙就死"不一样,一定记住。

最该记住的:蛇 = 一串格子(数组)+ 一个方向数;每 tick 整体挪一格,吃到食物尾巴才不掉所以变长;撞自己/撞对方/两蛇头对撞才结束,撞墙只反弹。

stepSnake 整条蛇往前挪一格:头前插、尾去掉(吃到食物才变长);撞自己/撞对方/两蛇头对撞才结束,撞墙只反弹

六、为什么蛇身要用数组,而不是一条会动的线?

做法怎么知道"撞到自己身体"前进一步就算了
记一串像素点判断"新点是否落在旧点上"很麻烦整体平移图形,费劲
用数组记一串格子拿新蛇头去 body[i] 比是不是同一格头前插 + 尾去掉,一步完成

数据怎么存,决定规则好不好写。 把世界"对齐成格子",所有"撞没撞"都变成"两个格子是不是同一个",简单一眼看懂。

七、改一个条件:撞墙就死?

现在的蛇撞墙会反弹。如果改成"经典贪吃蛇"——撞墙直接死,要改什么?

✏️ 可以直接在表格里填写
我改了什么我预测实际
撞墙反弹 → 撞墙即死把撞墙那一步交给碰撞判定判死、去掉 bounceDir,其余不用动

方向是数、格子是行列,所以改一条规则常常只是改一小段判断。"反弹"和"撞死"只是对"新头出界没"的两种处理。

八、状态机:板子怎么知道"该干嘛"?

板子不是一直在动,它在几种状态间切换:

状态板子在干什么
标题显示 SNAKE、PLAYER/NPC、swipe to steer / tap to start,等你点屏
倒计时点屏后先 3-2-1 数秒
进行中两条蛇动起来,你滑动转向、吃食物、抢道具
结束有蛇死了,显示 YOU WIN / NPC WIN / DRAW,等你点屏重开

代码是个 switch(state),每个状态只干一两件事,在特定时机跳到下一个状态。

状态机就是"按当前状态决定该干嘛"。以后做任何有开始、有过程、有结束的程序(切屏、菜单、多关卡)都会用到。

状态机四状态:标题 / 倒计时 / 进行中 / 结束,用 switch(state) 每状态只干一两件事

九、过关检查

  1. 板子怎么记住一条蛇?"蛇身=一串格子"是什么意思?
  2. "方向"在板子里是一个什么?DX/DY 那组数字和方向什么关系?
  3. stepSnake 具体做了哪几件事?为什么吃到食物才变长?
  4. 这个 demo 里撞到墙会怎样?真正结束的"死法"有哪几种?
  5. 状态机有哪四个状态?靠什么从"倒计时"切到"进行中"?
  6. 为什么滑动转向用 queuedDir(下一 tick 才生效)而不是立刻转弯?

留下三样东西:你一局的结果(你几分、NPC 几分、谁赢)、你第一次转向的方向(上/右/下/左)、"贪吃蛇在板子里是什么"的一句话。

十、还能玩什么

作品方向描述给 AI 的效果再想想
经典死法撞墙也直接死把 bounceDir 换成"撞墙判死",改哪里?
我是一条蛇去掉 NPC,只有一条蛇自己玩chooseNpcDir 还留着吗?
串串香每 15 秒多刷新一颗食物食物生成用定时器,怎么跟移动 tick 分开?
贪吃满分增加"最高分"记录,打完显示历史最佳最佳分存 TF 卡还是掉电就丢?
双人齐心两条蛇都人控制触摸只报一个触点,怎么做双指控制两蛇?

下节课预告:学会"网格 + 方向 + 状态机"后,第 31 课俄罗斯方块也是这张网格、也有方向和状态机,只是改成方块往下掉——这个三角摸熟,俄罗斯方块就是小菜一碟。

第 30 课 · 贪吃蛇

第 30 课 · 贪吃蛇

绿蛇是我、红蛇是板子,两条蛇在同一场地抢食物,谁先撞墙、撞自己或撞对方就输。

30游戏NPC滑动操作