第五单元:嵌套选择结构

Unit 5: Nested Selection Structures
边际森林 Border Forest · 跃火林中 → 最后的守护者

5.1 从单层到嵌套

From Single Layer to Nesting

5.1.1 复习:if 与 if/else(Review: if and if/else)

5.1.1.1 你已经会的判断本领 The decisions you can already make

到目前为止,你已经掌握了两种判断武器。第一种是单分支 if——"如果条件成立,就做某件事,否则跳过"。第二种是双分支 if/else——"如果条件成立做这个,否则(条件不成立)做那个"。它们都只在一个层面上做决定。

结构逻辑分支数掌握进度
单分支 if成立则做,否则跳过1 条路✅ 已学
双分支 if/else成立做 A,否则做 B2 条路✅ 已学
嵌套 if判断里再套判断多层多路⭐ 本单元

5.1.2 单层判断的局限(The Limits of One Layer)

5.1.2.1 当一个问题不够用时 When one question isn't enough

真实的森林越来越凶险,光问"一个问题"已经不够了。比如在"跃火林中"关卡里,你不仅要问"有没有恶魔石?",还得接着问"它在哪一边?"——这是两层的问题。又比如打怪时,你不仅要问"有没有敌人?",还要再问"它离我有多远?"才能决定是攻击还是格挡。

一个判断的答案,往往会引出下一个需要判断的问题。要表达这种"问题里还套着问题"的逻辑,就需要本单元的主角:嵌套选择结构(Nested Selection)

5.1.3 生活中的多层判断(Layered Decisions in Daily Life)

5.1.3.1 你也在做"套娃式"决定 You make nested decisions too

嵌套判断在生活里随处可见,只是你没注意。比如出门前:

看到了吗?"看天气"这个判断,是嵌在"要不要出门"这个判断里面的——只有先决定出门,才轮到考虑带什么。这就是嵌套的精髓。

5.2 什么是嵌套

What is Nesting

5.2.1 嵌套的定义(Definition of Nesting)

5.2.1.1 判断里面再放判断 A decision inside a decision

CodeCombat 嵌套 if 示意:判断里面还有判断 - codecombat.cn
图1:嵌套就是在一个 if 的代码块里,再写一个完整的 if

嵌套(Nest)这个词,本意是"鸟巢"——一层包一层。在编程里,嵌套 if(Nested if)指的是把一个完整的 if 结构,放进另一个 if 结构的代码块里面。这样,程序就能做出"先判断,再判断"的多层、更复杂的选择。

正如关卡里说的:"你可以在一个 if 语句内嵌套(nest)另一个 if 语句,从而做出更复杂的选择。"

5.2.2 盒子套盒子(Boxes Within Boxes)

5.2.2.1 把每个 if/else 想成一个容器 Think of each if/else as a container

"以静制动"关卡给了一个绝妙的比喻:"将每个 if/else 考虑成一个容器,其中又可以容纳另一个 if/else 结构!" 外层的 if 是一个大盒子,里面又可以塞一个小盒子,小盒子里还能再塞更小的盒子。下面用"盒子套盒子"的方式,直观感受三层嵌套(来自"最后的守护者"关卡):

外层 if enemy (有没有敌人?)
先算出距离 distance……
中层 if distance < 10 (近不近?)
内层 if isReady("cleave") (技能好了没?)
好了 → 用 cleave;否则 → 普通 attack
(外层的 else:太远 → say 距离让大炮开火)

5.2.3 缩进与层级(Indentation and Levels)

5.2.3.1 缩进越深,嵌套越里 Deeper indentation, deeper nesting

嵌套的层级,完全靠缩进来表达。关卡里特别强调:"嵌套内层的 if 结构需要更多缩进,说明它在外层结构的里面。" 每深入一层,就向右多缩进一次(通常 4 个空格)。看代码时,只要数一数前面有几格缩进,就知道它属于第几层。

致命要点:嵌套结构里,缩进就是逻辑。缩进多一格少一格,代码的归属和含义就完全变了。"缩进一定要小心。"

5.3 嵌套语法详解

Nested Syntax in Detail

5.3.1 基本格式(Basic Format)

5.3.1.1 两层嵌套的标准写法 The standard form of two-layer nesting

最常见的嵌套,是在外层 if/else 的某个分支里,再写一个内层 if/else。它的骨架长这样:

if 外层条件:                 # 第一层判断
    if 内层条件:             # 第二层判断(缩进一次)
        做事 A                # 两个条件都成立(缩进两次)
    else:
        做事 B                # 外层成立、内层不成立
else:
    做事 C                    # 外层就不成立

注意观察缩进的"台阶":外层 if/else 顶格,内层 if/else 缩进一次,内层要执行的代码缩进两次。这种层层递进的台阶感,正是嵌套结构的标志。

缩进层级归属什么时候执行
0 格(顶格)外层 if / else循环每圈都会检查
4 格内层 if / else仅当外层条件成立时检查
8 格内层的动作外层、内层条件都满足时

5.3.2 流程图解读(Reading the Flowchart)

5.3.2.1 嵌套就是"岔路里还有岔路" Nesting is a fork within a fork

开始 外层条件? if enemy 外层 else 内层条件? if distance<5 attack 攻击 shield 格挡 下一圈循环
图2:嵌套流程图——外层先分一次岔路,"是"的那条路里又分出第二个岔路

5.3.3 注释占位法(The Comment-First Method)

5.3.3.1 先想清楚,再写代码 Think first, code later

嵌套 if 一旦层数多了就容易乱。"以静制动"关卡传授了一个非常实用的技巧——"如果你的 if 语句很复杂,你可以一步步来,先用注释写下想法,然后再修改成代码。" 也就是先用人类语言(注释)把逻辑搭好骨架,再把每条注释逐一翻译成代码。

第一步:先用注释列出全部想法,给内层结构"占位"。

while True:
    enemy = hero.findNearestEnemy()
    # 如果有敌人出现,那么就……
        # 如果敌人很近,那么就攻击
        # 否则,那么就举盾防御
    # 否则(没有敌人)……
        # 那么,回到中心 X 位置

第二步:把外层注释先变成真正的 if/else 代码。第三步:再把内层注释翻译成代码,并确保缩进正确。逐层落实,复杂逻辑就被驯服了。

编辑器小技巧:你可以选中一块代码,按 Tab 整体增加缩进,按 Shift + Tab 整体减少缩进。处理多层嵌套时这招特别省事。

5.4 嵌套必备的新工具箱

New Tools for Nesting

嵌套判断之所以强大,是因为每一层都可以问不同种类的问题。本单元给你的工具箱添了几件新装备,让内层条件能问出"在哪边""有多远""技能好了没"这些更精细的问题。

5.4.1 pos 位置属性(The pos Property)

5.4.1.1 每个单位都知道自己在哪 Every unit knows where it is

关卡告诉我们:"游戏中每个物品(或者单位)都有一个 pos 属性,代表了其坐标位置。而每个 pos 本身也是个对象,它又包含了 x 和 y 属性。" 也就是说,找到一个物体后,用 物体.pos.x 就能拿到它的横坐标,物体.pos.y 拿到纵坐标。

evilstone = hero.findNearestItem()
pos = evilstone.pos        # 取出这块恶魔石的位置对象
# pos.x 是它的横坐标,pos.y 是它的纵坐标
💡 pos 就像物体的"身份证地址"

pos 想成一张写着地址的卡片,上面有两行:第一行 x(横向第几格),第二行 y(纵向第几格)。有了它,你就能精确判断一个物体到底站在地图的哪个位置——这正是"跃火林中"判断"恶魔石在左边还是右边"的关键。

5.4.2 == 等于运算符(The == Operator)

5.4.2.1 判断"是不是相等" Checking whether two values are equal

在内层条件里,我们经常要问"这个值是不是等于某个数?"。这时用双等号 ==。关卡注释里特意提醒:"== 的意思是 is equal to(等于)。" 它会返回 TrueFalse,正好可以当作 if 的条件。

if pos.x == 34:      # 横坐标是不是正好等于 34?
    hero.moveXY(46, 22)   # 恶魔石在左,就跑到右边
千万别搞混:=(单等号)是赋值,把右边装进左边的盒子;==(双等号)是比较,问"两边是否相等"。在 if 条件里要用的是 ==,写成 = 会出错!

5.4.3 distanceTo 距离(The distanceTo Method)

5.4.3.1 量一量"有多远" Measuring how far away

hero.distanceTo(目标) 会返回英雄到某个目标之间的距离(单位:米)。把它存进一个变量,就能在内层 if 里用大于、小于运算符来比较远近,从而决定不同的战术。

distance = hero.distanceTo(enemy)  # 算出到敌人的距离
if distance < 5:            # 近到 5 米以内?
    hero.attack(enemy)        # 近了才动手
else:
    hero.shield()            # 还远,先举盾
为什么要看距离?因为"敌人存在"和"敌人能打到"是两回事。先用外层判断"有没有敌人",再用内层判断"够不够近",这正是嵌套最自然的应用场景。

5.4.4 isReady 技能就绪(The isReady Method)

5.4.4.1 大招好了没? Is the special skill ready?

cleave(顺劈斩)这样的强力技能有冷却时间(Cooldown),用完得等一会儿才能再用。hero.isReady("cleave") 会返回 TrueFalse,告诉你这个技能现在能不能用。这又是一个天然适合放进内层 if 的条件。

if hero.isReady("cleave"):  # 顺劈斩冷却好了吗?
    hero.cleave(enemy)         # 好了,放大招
else:
    hero.attack(enemy)         # 没好,普通攻击别闲着
实战提醒:"如果你的 cleave 没准备好,别呆呆站在那里!冷却时间内记得用普通的 attack() 继续攻击。" 这就是 isReadyelse 的意义所在。

5.5 实战关卡精讲

Level Strategy & Analysis

这一节我们聚焦每一关的难点、思路和嵌套结构的设计。看懂"为什么这样嵌套",你就能举一反三,自己写出完整解法。

5.5.1 跃火林中(Leap of Faith)

5.5.1.1 思路:用嵌套躲避双火球 Strategy: dodge two fireballs with nesting

CodeCombat 跃火林中关卡 - codecombat.cn
图3:跃火林中——根据恶魔石(宝石)出现的位置,躲到安全的另一侧

这一关有点像之前的"焰中舞动",但这次要同时躲两个火球,而且火球随机出现,不能简单地左右循环走。安全策略是看恶魔石的位置:恶魔石在哪一侧,就远离它跑到另一侧;如果根本没有恶魔石,就回到中间。

这正好是一个两层判断:外层问"有没有恶魔石",内层问"它在哪一边"。关键逻辑片段如下:

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 的范围内,而在中间的条件分支则属于外层 if 的范围。" 注意看缩进——"去中间"那行和内层 if 对齐,所以它属于外层的 else。

5.5.2 以静制动(Patience)

5.5.2.1 思路:近了就打,远了举盾 Strategy: attack when close, shield when far

CodeCombat 以静制动关卡 - codecombat.cn
图4:以静制动——只在敌人靠近时攻击,平时举盾并守在中央

这一关要求你守在中间区域不被猎头者发现。难点是三种情况要分清楚:有敌人且近、有敌人但远、没敌人。这构成一个经典的嵌套 if/else:外层判断"有没有敌人",外层成立后内层再判断"距离够不够近"。

if enemy:                       # 外层:有敌人吗?
    distance = hero.distanceTo(enemy)
    if distance < 5:            # 内层:近到 5 米以内?
        hero.attack(enemy)        # 近 → 攻击
    else:
        hero.shield()            # 远 → 举盾
else:
    hero.moveXY(40, 34)         # 没敌人 → 回到中心
配套用注释占位法:这一关正是 5.3.3 节"先注释、后代码"技巧的最佳练习对象——先写四条注释占好位,再逐条翻译,缩进自然就理顺了。

5.5.3 Agrippa守卫战(Agrippa Defense)

5.5.3.1 思路:三层嵌套,把大招用在刀刃上 Strategy: three layers, save the special move

CodeCombat Agrippa守卫战关卡 - codecombat.cn
图5:Agrippa守卫战——等敌人聚拢、且 cleave 就绪时,才放大招

这一关的精妙之处在于不能一看到敌人就 cleave。如果太早放大招,只能打到最前面几个,剩下的会围上来。策略是用 distanceTo 等敌人靠近(约 5 米)再出手,而且要先确认大招冷却好了。这就需要三层嵌套

if enemy:                           # 第一层:有敌人吗?
    distance = hero.distanceTo(enemy)
    if distance < 5:                # 第二层:靠得够近了吗?
        if hero.isReady("cleave"):  # 第三层:大招好了吗?
            hero.cleave(enemy)         # 都满足 → 顺劈斩!
        else:
            hero.attack(enemy)         # 大招没好 → 普通攻击
嵌套的威力:三个问题层层递进——"有没有"→"近不近"→"技能好没好"。只有一路全部满足才会放大招,任何一层不满足就走对应的备选方案。这就是嵌套表达复杂战术的本领。

5.5.4 危险的树(概念挑战 · Dangerous Trees)

5.5.4.1 思路:看见且够近才出手 Strategy: act only if seen and close enough

这是一个概念挑战。借助森林迷彩,食人魔只有靠近时才看得见你。逻辑很清晰:如果看到敌人,就检查距离;若不到 15 米就攻击;如果根本没看到敌人,就回到中心点。它是一个"外层 if/else + 内层单分支 if"的组合——注意内层这次只有 if、没有 else(够远就什么都不做,继续观察)。

if enemy:                       # 外层:看到敌人了吗?
    distance = hero.distanceTo(enemy)
    if distance < 15:           # 内层:单分支,没有 else
        hero.attack(enemy)        # 够近才打,太远就略过
else:
    hero.moveXY(40, 34)         # 没看到敌人 → 回中心
灵活之处:内层不一定非要配 else。"够远就什么都不做"本身就是合理的设计——这说明嵌套里的每一层,都可以是单分支或双分支,按需选用。

5.5.5 最后的守护者(组合挑战 · The Last Guardian)

5.5.5.1 思路:综合运用全部判断技巧 Strategy: combine every decision skill

CodeCombat 最后的守护者关卡 - codecombat.cn
图6:最后的守护者——综合 if/else、嵌套 if、大小比较,全力守护

这是本单元的组合挑战,要求你把学过的本领同时用上: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/else、距离比较全部串了起来。能独立写出它,说明你已经真正掌握了嵌套选择结构的精髓。

5.6 嵌套的常见错误

Common Mistakes in Nesting

5.6.1 缩进陷阱(Indentation Pitfalls)

5.6.1.1 缩进错一格,逻辑全乱 One wrong indent ruins everything

CodeCombat 缩进错误导致逻辑改变示意 - codecombat.cn
图7:内层代码缩进不对,归属就变了,程序行为完全不同

嵌套结构里最致命的坑就是缩进。同一段代码,缩进多一层就"钻进"了内层 if,少一层就"跳出"到了外层,含义天差地别。务必做到:同一层级的代码左对齐,每深入一层就统一多缩进 4 个空格,绝不能空格和 Tab 混用。

5.6.2 else 配对问题(Matching else)

5.6.2.1 这个 else 到底属于哪个 if? Which if does this else belong to?

有了多层 if,就有多个可能的 else。else 属于和它缩进对齐的那个 if。 写"跃火林中"时,"去中间"的 else 要和外层 if 对齐(属于外层);写"以静制动"时,"举盾"的 else 要和内层 if 对齐(属于内层)。下表帮你对号入座:

else 缩进位置它配对的 if例子
与外层 if 对齐外层 if跃火林中"去中间"
与内层 if 对齐内层 if以静制动"举盾"
易错点清单:
  • 内层 if 忘记多缩进——它没"钻进"外层,逻辑就错了。
  • else 缩进错位——本该配内层的 else 却跑去配了外层。
  • == 写成 =——条件判断里要用双等号。
  • 忘记先存 distance 变量——直接比较前要先 distanceTo
  • 空格与 Tab 混用——Python 会直接报缩进错误。

本单元小结 ✨

恭喜你完成边际森林第五单元!你的英雄现在不仅会"看情况",还会"看了情况之后再看情况"——能做出层层递进的复杂决策了。来回顾一下核心概念:

核心概念关键点对应关卡
嵌套 if判断里面再放判断全部
盒子比喻每个 if/else 是个容器以静制动
缩进定层级越深表示嵌得越里全部
注释占位法先写想法,再翻译成码以静制动
pos 属性物体.pos.x / .y跃火林中
== 等于比较是否相等跃火林中
distanceTo测量到目标的距离以静制动、危险的树
isReady技能冷却好了没Agrippa守卫战
三层嵌套有→近→技能好Agrippa、最后的守护者
else 配对跟谁对齐就属于谁全部

会层层思考、步步为营的英雄,才是真正深谋远虑的英雄。
继续守护边际森林吧,勇敢的程序员!🌲🛡️🔥

CodeCombat 编程知识 · 边际森林:第五单元

CodeCombat教研团队