到目前为止,你已经掌握了两种判断武器。第一种是单分支 if——"如果条件成立,就做某件事,否则跳过"。第二种是双分支 if/else——"如果条件成立做这个,否则(条件不成立)做那个"。它们都只在一个层面上做决定。
| 结构 | 逻辑 | 分支数 | 掌握进度 |
|---|---|---|---|
| 单分支 if | 成立则做,否则跳过 | 1 条路 | ✅ 已学 |
| 双分支 if/else | 成立做 A,否则做 B | 2 条路 | ✅ 已学 |
| 嵌套 if | 判断里再套判断 | 多层多路 | ⭐ 本单元 |
真实的森林越来越凶险,光问"一个问题"已经不够了。比如在"跃火林中"关卡里,你不仅要问"有没有恶魔石?",还得接着问"它在哪一边?"——这是两层的问题。又比如打怪时,你不仅要问"有没有敌人?",还要再问"它离我有多远?"才能决定是攻击还是格挡。
一个判断的答案,往往会引出下一个需要判断的问题。要表达这种"问题里还套着问题"的逻辑,就需要本单元的主角:嵌套选择结构(Nested Selection)。
嵌套判断在生活里随处可见,只是你没注意。比如出门前:
看到了吗?"看天气"这个判断,是嵌在"要不要出门"这个判断里面的——只有先决定出门,才轮到考虑带什么。这就是嵌套的精髓。
嵌套(Nest)这个词,本意是"鸟巢"——一层包一层。在编程里,嵌套 if(Nested if)指的是把一个完整的 if 结构,放进另一个 if 结构的代码块里面。这样,程序就能做出"先判断,再判断"的多层、更复杂的选择。
正如关卡里说的:"你可以在一个 if 语句内嵌套(nest)另一个 if 语句,从而做出更复杂的选择。"
"以静制动"关卡给了一个绝妙的比喻:"将每个 if/else 考虑成一个容器,其中又可以容纳另一个 if/else 结构!" 外层的 if 是一个大盒子,里面又可以塞一个小盒子,小盒子里还能再塞更小的盒子。下面用"盒子套盒子"的方式,直观感受三层嵌套(来自"最后的守护者"关卡):
嵌套的层级,完全靠缩进来表达。关卡里特别强调:"嵌套内层的 if 结构需要更多缩进,说明它在外层结构的里面。" 每深入一层,就向右多缩进一次(通常 4 个空格)。看代码时,只要数一数前面有几格缩进,就知道它属于第几层。
最常见的嵌套,是在外层 if/else 的某个分支里,再写一个内层 if/else。它的骨架长这样:
if 外层条件: # 第一层判断 if 内层条件: # 第二层判断(缩进一次) 做事 A # 两个条件都成立(缩进两次) else: 做事 B # 外层成立、内层不成立 else: 做事 C # 外层就不成立
注意观察缩进的"台阶":外层 if/else 顶格,内层 if/else 缩进一次,内层要执行的代码缩进两次。这种层层递进的台阶感,正是嵌套结构的标志。
| 缩进层级 | 归属 | 什么时候执行 |
|---|---|---|
| 0 格(顶格) | 外层 if / else | 循环每圈都会检查 |
| 4 格 | 内层 if / else | 仅当外层条件成立时检查 |
| 8 格 | 内层的动作 | 外层、内层条件都满足时 |
嵌套 if 一旦层数多了就容易乱。"以静制动"关卡传授了一个非常实用的技巧——"如果你的 if 语句很复杂,你可以一步步来,先用注释写下想法,然后再修改成代码。" 也就是先用人类语言(注释)把逻辑搭好骨架,再把每条注释逐一翻译成代码。
第一步:先用注释列出全部想法,给内层结构"占位"。
while True: enemy = hero.findNearestEnemy() # 如果有敌人出现,那么就…… # 如果敌人很近,那么就攻击 # 否则,那么就举盾防御 # 否则(没有敌人)…… # 那么,回到中心 X 位置
第二步:把外层注释先变成真正的 if/else 代码。第三步:再把内层注释翻译成代码,并确保缩进正确。逐层落实,复杂逻辑就被驯服了。
Tab 整体增加缩进,按 Shift + Tab 整体减少缩进。处理多层嵌套时这招特别省事。
嵌套判断之所以强大,是因为每一层都可以问不同种类的问题。本单元给你的工具箱添了几件新装备,让内层条件能问出"在哪边""有多远""技能好了没"这些更精细的问题。
关卡告诉我们:"游戏中每个物品(或者单位)都有一个 pos 属性,代表了其坐标位置。而每个 pos 本身也是个对象,它又包含了 x 和 y 属性。" 也就是说,找到一个物体后,用 物体.pos.x 就能拿到它的横坐标,物体.pos.y 拿到纵坐标。
evilstone = hero.findNearestItem() pos = evilstone.pos # 取出这块恶魔石的位置对象 # pos.x 是它的横坐标,pos.y 是它的纵坐标
把 pos 想成一张写着地址的卡片,上面有两行:第一行 x(横向第几格),第二行 y(纵向第几格)。有了它,你就能精确判断一个物体到底站在地图的哪个位置——这正是"跃火林中"判断"恶魔石在左边还是右边"的关键。
在内层条件里,我们经常要问"这个值是不是等于某个数?"。这时用双等号 ==。关卡注释里特意提醒:"== 的意思是 is equal to(等于)。" 它会返回 True 或 False,正好可以当作 if 的条件。
if pos.x == 34: # 横坐标是不是正好等于 34? hero.moveXY(46, 22) # 恶魔石在左,就跑到右边
=(单等号)是赋值,把右边装进左边的盒子;==(双等号)是比较,问"两边是否相等"。在 if 条件里要用的是 ==,写成 = 会出错!
hero.distanceTo(目标) 会返回英雄到某个目标之间的距离(单位:米)。把它存进一个变量,就能在内层 if 里用大于、小于运算符来比较远近,从而决定不同的战术。
distance = hero.distanceTo(enemy) # 算出到敌人的距离 if distance < 5: # 近到 5 米以内? hero.attack(enemy) # 近了才动手 else: hero.shield() # 还远,先举盾
像 cleave(顺劈斩)这样的强力技能有冷却时间(Cooldown),用完得等一会儿才能再用。hero.isReady("cleave") 会返回 True 或 False,告诉你这个技能现在能不能用。这又是一个天然适合放进内层 if 的条件。
if hero.isReady("cleave"): # 顺劈斩冷却好了吗? hero.cleave(enemy) # 好了,放大招 else: hero.attack(enemy) # 没好,普通攻击别闲着
isReady 配 else 的意义所在。
这一节我们聚焦每一关的难点、思路和嵌套结构的设计。看懂"为什么这样嵌套",你就能举一反三,自己写出完整解法。
这一关有点像之前的"焰中舞动",但这次要同时躲两个火球,而且火球随机出现,不能简单地左右循环走。安全策略是看恶魔石的位置:恶魔石在哪一侧,就远离它跑到另一侧;如果根本没有恶魔石,就回到中间。
这正好是一个两层判断:外层问"有没有恶魔石",内层问"它在哪一边"。关键逻辑片段如下:
evilstone = hero.findNearestItem() if evilstone: # 外层:有恶魔石吗? pos = evilstone.pos if pos.x == 34: # 内层:它在左边(x=34)吗? hero.moveXY(46, 22) # 在左 → 跑到右边 else: hero.moveXY(34, 22) # 在右 → 跑到左边 else: hero.moveXY(40, 22) # 没有 → 待在中间
这一关要求你守在中间区域不被猎头者发现。难点是三种情况要分清楚:有敌人且近、有敌人但远、没敌人。这构成一个经典的嵌套 if/else:外层判断"有没有敌人",外层成立后内层再判断"距离够不够近"。
if enemy: # 外层:有敌人吗? distance = hero.distanceTo(enemy) if distance < 5: # 内层:近到 5 米以内? hero.attack(enemy) # 近 → 攻击 else: hero.shield() # 远 → 举盾 else: hero.moveXY(40, 34) # 没敌人 → 回到中心
这一关的精妙之处在于不能一看到敌人就 cleave。如果太早放大招,只能打到最前面几个,剩下的会围上来。策略是用 distanceTo 等敌人靠近(约 5 米)再出手,而且要先确认大招冷却好了。这就需要三层嵌套:
if enemy: # 第一层:有敌人吗? distance = hero.distanceTo(enemy) if distance < 5: # 第二层:靠得够近了吗? if hero.isReady("cleave"): # 第三层:大招好了吗? hero.cleave(enemy) # 都满足 → 顺劈斩! else: hero.attack(enemy) # 大招没好 → 普通攻击
这是一个概念挑战。借助森林迷彩,食人魔只有靠近时才看得见你。逻辑很清晰:如果看到敌人,就检查距离;若不到 15 米就攻击;如果根本没看到敌人,就回到中心点。它是一个"外层 if/else + 内层单分支 if"的组合——注意内层这次只有 if、没有 else(够远就什么都不做,继续观察)。
if enemy: # 外层:看到敌人了吗? distance = hero.distanceTo(enemy) if distance < 15: # 内层:单分支,没有 else hero.attack(enemy) # 够近才打,太远就略过 else: hero.moveXY(40, 34) # 没看到敌人 → 回中心
这是本单元的组合挑战,要求你把学过的本领同时用上:if/else 语句、嵌套 if、以及大于/小于比较。一个巧妙的设定是:如果食人魔很远,只要用 say 说出到它的距离,你的大炮就会自动开火。于是战术变成——近的自己用 cleave/attack 解决,远的交给大炮。这就形成了一个三层嵌套 + 外层 else 的完整结构:
if enemy: # 第一层:有敌人吗? distance = hero.distanceTo(enemy) if distance < 10: # 第二层:近(<10 米)吗? if hero.isReady("cleave"): # 第三层:大招好了吗? hero.cleave(enemy) # 近 + 大招好 → 顺劈斩 else: hero.attack(enemy) # 近 + 大招没好 → 普攻 else: hero.say(distance) # 远 → 报距离,召唤大炮
嵌套结构里最致命的坑就是缩进。同一段代码,缩进多一层就"钻进"了内层 if,少一层就"跳出"到了外层,含义天差地别。务必做到:同一层级的代码左对齐,每深入一层就统一多缩进 4 个空格,绝不能空格和 Tab 混用。
有了多层 if,就有多个可能的 else。else 属于和它缩进对齐的那个 if。 写"跃火林中"时,"去中间"的 else 要和外层 if 对齐(属于外层);写"以静制动"时,"举盾"的 else 要和内层 if 对齐(属于内层)。下表帮你对号入座:
| else 缩进位置 | 它配对的 if | 例子 |
|---|---|---|
| 与外层 if 对齐 | 外层 if | 跃火林中"去中间" |
| 与内层 if 对齐 | 内层 if | 以静制动"举盾" |
== 写成 =——条件判断里要用双等号。distance 变量——直接比较前要先 distanceTo。恭喜你完成边际森林第五单元!你的英雄现在不仅会"看情况",还会"看了情况之后再看情况"——能做出层层递进的复杂决策了。来回顾一下核心概念:
| 核心概念 | 关键点 | 对应关卡 |
|---|---|---|
| 嵌套 if | 判断里面再放判断 | 全部 |
| 盒子比喻 | 每个 if/else 是个容器 | 以静制动 |
| 缩进定层级 | 越深表示嵌得越里 | 全部 |
| 注释占位法 | 先写想法,再翻译成码 | 以静制动 |
| pos 属性 | 物体.pos.x / .y | 跃火林中 |
| == 等于 | 比较是否相等 | 跃火林中 |
| distanceTo | 测量到目标的距离 | 以静制动、危险的树 |
| isReady | 技能冷却好了没 | Agrippa守卫战 |
| 三层嵌套 | 有→近→技能好 | Agrippa、最后的守护者 |
| else 配对 | 跟谁对齐就属于谁 | 全部 |
会层层思考、步步为营的英雄,才是真正深谋远虑的英雄。
继续守护边际森林吧,勇敢的程序员!🌲🛡️🔥
CodeCombat 编程知识 · 边际森林:第五单元
CodeCombat教研团队