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

推箱子 用一张字符地图记下整个关卡

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

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

读完之后,你应该能:

  • 说出"用一张字符地图描述一个关卡"是什么意思,# . * $ @ + 各代表什么;
  • 明白"人能不能走 / 能不能推箱子"是先问前面是什么再决定的;
  • 说清"怎么判断过关"——数一数有几个箱子已经站在目标点上。

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

一、这次不是消除,是搬运

第 31 课俄罗斯方块是"新方块掉下来,我们要消除"。这一课推箱子玩法完全相反:箱子、墙、目标点一开始就摆好了,你要做的,是把箱子搬到橙色的目标点。

规则只有四条:人只能上下左右走;撞墙不能走;推箱子时,箱子背后必须是空地/目标,否则推不动;所有箱子都推到目标点,就算过关。

可板子没有眼睛,它怎么知道哪格是墙、哪格是目标、哪格有箱子、哪格是我?答案:用一张字符地图,把整个关卡"写"进板子。

二、我观察到的现象

✏️ 可以直接在表格里填写
现象勾选
屏幕出现一个四周是墙、中间是地板的房间
有几个橙色的"目标点"散在地板上
手指滑动,绿色小人走一格(上/下/左/右)
小人朝墙走,走不动;箱子被推向墙,推不动
把箱子推到橙色格子上,箱子变绿
顶部显示"已到位箱子数 / 箱子总数"

三、关卡,就是一张字符地图

demo 里这关不是一格格画的,而是用字符写成的地图:

✏️ 可以直接在表格里填写
字符意思
#墙
.地板(空地)
目标点
$箱子
@人
+人站在目标点

这关就写在这一串字符里,一共 12 行,每行 12 个字符,四周都围一圈 #:

############
#*.........#
#..........#
#..$...$...#
#....@.....#
#..........#
#....$...*.#
#..........#
#..........#
#..........#
#..........#
############

但这里有个聪明的安排:墙 / 地板 / 目标点是"地基",永远不变;箱子是会动的"家具";人还会动。 所以程序分成两层:

名字记什么变不变
base[r][c]墙 / 地板 / 目标点不变
grid[r][c]这一格有没有箱子会变
pr,pc人在第几行第几列会变

一个关卡 = 一张字符地图:#墙/.地板/*目标/$箱子/@人/+人站目标;分两层,base 地基不变、grid 箱子会变

四、两个代码名,够了

1. tryMove(dc, dr)——朝这个方向挪一步,顺便决定能不能推箱

tryMove(dc, dr):人想朝"列偏移 dc、行偏移 dr"方向走一步。
  第1步:算出人走过去会站到哪一格 (nr, nc)。
  第2步:看那一格是什么——
    是墙 / 越界        → 不动,返回。
    是箱子            → 再看箱子"再往前一格":
                          背后是墙 / 越界 / 另一个箱子 → 推不动;
                          背后是空地 / 目标点         → 箱子挪过去,人跟一步。
    是空地            → 人直接走过去。

dc 是"往左还是往右"(列),dr 是"往上还是往下"(行)。"能不能走、能不能推",全在这一句里被问清楚。

2. recomputeDone()——数一数,几个箱子到位了

recomputeDone():
  把"已到位箱子数" boxDone 清零;
  一格格扫:只要这格是箱子(grid==BOX) 且 地基是目标点(base==TARGET),
        → boxDone 加 1。
  扫完比一比:boxDone == 箱子总数 boxCount → 过关(WIN);
             不然 → 继续玩。

过关不是靠"你刚好推最后一下"的巧合,而是每次都重新数一遍:现在到底几个箱子稳稳站在目标点上?

最该记住的:能不能"走"和"推",全靠 tryMove 先问一句"前面是什么";能不能"赢",全靠 recomputeDone 数"箱子站没站在目标点上"。

tryMove 先问一句「前面是什么」:墙/越界不动、空地走、箱子看背后才决定能不能推

五、为什么箱子要单独一张表?

做法判断推箱不对问题
一张表全记想判断"箱子背后是不是空地/目标"时,得再想这格原先是墙还是箱子墙和箱子搅在一起,容易算重、把墙当箱子
分两张表base 永远回答"是不是墙/目标",grid 永远回答"有没有箱子"地基不动,箱子单独管,清爽

数据怎么存,决定规则好不好写。 分开记,tryMove 就能写得特别干净。

六、改一个条件:箱子比目标点多一个

数一数 demo 这关:$(箱子)有 3 个,*(目标点)有 2 个。

✏️ 可以直接在表格里填写
我改了什么我预测实际
箱子 3 个、目标点 2 个目标点只有 2 个 → boxDone 最多 2;boxDone==boxCount(2==3)永远不成立 → 这一关永远到不了过关
再补一个目标点(把某个 $ 改 *),箱=目标boxDone 最多能到 3,boxCount 也是 3,就能触发 WIN

过关这条规则本身没错,是关卡数据"箱子≠目标点"让它失效了。 写游戏时,规则和关卡是两回事:规则对了,还得保证关卡数据能让规则成立。

怎么判断过关:recomputeDone 数「箱子站没站到目标点上」,boxDone==箱子总数→WIN;箱子≠目标则永远不过关

七、一条完整的"推一次箱子"链路

手指滑动 → 算出方向(上/下/左/右)→ tryMove(方向)
人走过去会到哪一格:
    墙 / 越界     → 不动
    空地          → 人走过去
    箱子          → 看箱子背后:
                      墙 / 越界 / 另一个箱子 → 推不动
                      空地 / 目标点         → 箱子挪一步,人跟上
每推/走一步 → recomputeDone() 数"箱子站目标点上"的个数
    已到位数 == 箱子总数 → 过关(WIN)
    未到位             → 继续玩
想重开 → 轻触屏幕:IDLE 开始这关,WIN 进(或循环回)下一关

真正要记住的只有一句:推箱子 = 一张字符地图记下墙/地/目标/箱/人 + tryMove 先问前面是什么 + recomputeDone 数到位箱子判过关。

八、推不动 / 没反应,这样排查

现象是观察还是推测?下一步查什么
手指滑动,人不动观察推测没识别成滑动 → 查 SWIPE_THRESH 和触摸坐标
冲着空地划却没走观察推测方向算错 → 查主导轴判定 abs(dx)>abs(dy)
推箱子"穿墙"或乱跑观察推测箱子背后没认真查 → 查 tryMove 有没有漏掉越界/墙/另一个箱子
箱子推到目标点却推不动观察推测把箱子背后当成墙了 → 查"允许推"的条件
顶部"已到位/总数"不变观察推测 boxDone 数得不对 → 查 recomputeDone 的判断条件
过关后"下一关"又回开头观察推测只内置了一关 → totalLevels() 返回 1

老规矩:"滑了人不动"是观察,"没滑动超过门槛"是推测。 先看是不是滑动没触发,再查是方向错还是规则漏判。

九、过关检查

  1. 一个关卡怎么装进板子?# . * $ @ + 各代表什么?
  2. "人往前挪一格"要先检查什么?"撞墙不能走"怎么判断?
  3. 为什么推箱子要先看箱子背后那格?背后是什么就推不动?
  4. 怎么判断过关?recomputeDone() 数的是什么?
  5. 为什么箱子要单独一张表,和墙/地/目标分开?
  6. 手指"滑动"怎么变成"上/下/左/右"?

留下三样东西:你说说每个字符代表什么、你亲眼把箱子推到橙色目标点看到它变绿的那一步、"推箱子在板子里是什么"的一句话。

十、还能玩什么

作品方向描述给 AI 的效果再想想
多关卡推箱子过一关进下一关,难度递进totalLevels() 只返回 1,怎么改多关?关卡数据放哪?
步数挑战顶部记录并显示你走了多少步每走一步在哪加 1?怎么清零重开?
限时通关倒计时内推完才算赢时钟怎么和画面共用 loop()?会不会拖慢滑块?
换主题皮肤把墙/箱子/目标点画成不同图案改颜色动哪几行?画成"礼物盒"改 drawTile 哪里?
推箱子 AI让板子自己找到解法自动通关它怎么试一条路?走错要"回头"(回溯)该怎么记?

下节课预告:学会"关卡=一张字符地图 + 规则先问前面是什么 + 数到位箱子判过关"后,第 33 课让飞机在天上飞、躲子弹、开炮打敌机——碰撞、计分、状态切换正是飞行大战的核心。

第 32 课 · 推箱子

第 32 课 · 推箱子

手指滑动控制小人走动,把场上的箱子一个个推到目标格上,全部推对就过关。

32游戏关卡字符地图