在第二单元里,你学会了单分支的 if 语句——"如果有敌人,就攻击它"。它很强大,但有一个明显的"留白":当条件不成立时,程序什么都不做。
这在很多场景下是不够的。想象英雄在村口巡逻:有敌人时当然要攻击,但没有敌人时英雄该干什么? 用单分支,英雄只会原地发呆。可我们真正想要的是——没有敌人时,英雄应该回到防守位置保护村民。这就需要"否则该做什么"的能力。
双分支选择结构在单分支的基础上,增加了一条 else(否则)分支。它的逻辑是:"如果条件成立,做 A;否则,做 B。" 两条分支必定会走其中一条,绝不会两条都走,也绝不会一条都不走。
双分支在生活中比单分支更常见,因为我们的决定往往是"二选一"的:
注意这些句子都有完整的"如果……就……否则……"三段式。翻译成 Python,就是本单元的主角:if-else 语句。
if-else 语句在第二单元 if 的下方,多了一个 else 代码块。它的基本语法是:
if 条件: # 条件成立时执行这里 动作 A else: # 条件不成立时执行这里 动作 B
注意 else 后面没有条件,直接跟一个冒号 :。因为它代表的就是"除了 if 条件成立之外的所有情况",不需要再写条件。
正如"Else 之战"关卡所说:"else 在 if 语句条件不成立时执行动作。可以这样认为,else 的情况也就是 if 语句以外的所有情况。" 这是理解 else 最精准的一句话。
"背靠背"是你第一次接触 else 的关卡。它的逻辑非常清晰:"如果(if) 有敌人,那么(then) 攻击它,否则(else) 移动到 X 标记处。" 攻击和撤防是两个互斥的动作——英雄要么在前线杀敌,要么回阵地防守,二者必居其一。这正是双分支的精髓。
书写 if-else 时有两个缩进规则必须牢记:第一,else 关键字要和它配对的 if 处在同一缩进层级(左边对齐);第二,if 和 else 各自的代码块都要向右缩进。
if enemy: hero.attack(enemy) # 属于 if 的代码块(缩进) else: # else 与 if 对齐(顶格) hero.moveXY(40, 34) # 属于 else 的代码块(缩进)
"Else 之战"关卡明确指出:"else 只能与相应的 if 语句配对使用。" 这意味着你不能写一个孤零零的 else——它必须紧跟在某个 if(及其代码块)之后。else 就像 if 的"影子",没有 if 就没有 else。
if-else 有两个重要特性,理解它们能帮你避免很多 bug:
| 特性 | 含义 |
|---|---|
| 互斥(Mutually Exclusive) | if 块和 else 块不可能同时执行,每次只走一条 |
| 完备(Complete) | 两条分支覆盖了所有情况,必定会执行其中一条 |
| 对比项 | 单分支 if | 双分支 if-else |
|---|---|---|
| 结构 | 只有 if | if + else |
| 条件为假时 | 什么都不做,跳过 | 执行 else 块 |
| 形象比喻 | "有就做,没有就算了" | "有就做这个,没有就做那个" |
| 典型场景 | 发现敌人才攻击 | 有敌人攻击,否则撤防 |
本单元的条件不再只是"有没有敌人",而是引入了更丰富的判断依据:距离和技能冷却。这让英雄的决策更加精细。
新的眼镜赋予了英雄 distanceTo() 技能:它能测量英雄与某个目标(或坐标)之间的距离,单位是米。通常我们会把结果存进一个变量,方便后面判断:
enemy = hero.findNearestEnemy() distance = hero.distanceTo(enemy) # 测出离这个敌人多远
在"测距仪"关卡里,英雄甚至不用亲自战斗——只需用 distanceTo() 测出每个敌人的距离,再用 say() 喊给大炮听,让大炮去轰击。这是 distanceTo 的纯粹应用。
光测出距离还不够,我们需要拿它和一个数字比较,得到一个布尔值,才能放进 if 里。这就要用到小于运算符 <:
if distance < 10: # 如果距离小于 10 米…… hero.attack(enemy)
这行代码可以读作:"如果距离小于 10 米,那么就攻击敌人。" 当 distance 确实小于 10 时,distance < 10 的结果是 True,攻击执行;否则结果是 False,走 else 分支。
| 表达式 | distance 的值 | 结果 |
|---|---|---|
distance < 10 | 7 | True(执行 if) |
distance < 10 | 15 | False(执行 else) |
distance < 5 | 3 | True(执行 if) |
英雄的长剑有一个强力技能 cleave()(劈斩)——它能一次攻击周围十米内的所有敌人!但如此强大的技能不能连续使用,它有冷却时间(每 10 秒一次)。所以使用前必须先用日晷腕表的 isReady() 方法检查它是否准备好了:
if hero.isReady("cleave"): # 如果劈斩准备好了…… hero.cleave(enemy) # 就劈斩! else: # 否则(还在冷却中)…… hero.attack(enemy) # 用普通攻击撑过冷却
else 在冷却期间发动普通攻击,能让英雄"不浪费每一秒"——劈斩好了就劈斩,没好就普攻,火力永不停歇。
本单元的关卡虽多,但 if-else 的用法可归纳为四种经典"决策模式"。认清模式,就能以不变应万变。
这是最基础的 if-else 模式:条件是"是否有敌人",两条分支分别是"攻击"和"回到防守位置"。代表关卡有"背靠背""林间圣地"。逻辑骨架是:
if enemy: hero.attack(enemy) else: hero.moveXY(防守点 x, 防守点 y)
这个模式把 else 分支换成了"做正事"——比如攻击宝箱开锁。代表关卡是"食人魔营地":有食人魔来就反击,没有了就持续攻击 "Chest" 把宝箱打开。它体现了一种"战斗优先,间隙做事"的智能。
这个模式的条件不是"有没有敌人",而是"技能是否就绪"。代表关卡有"森林劈斩者""Else 之战""边地僵局"系列。它的价值在于用 else 填满技能的冷却空窗,让输出最大化。
这个模式用 distanceTo + < 作为条件,只有当敌人足够近时才采取行动。代表关卡有"贴身护卫""矮人蜂拥而至""勇敢的食人魔"。它让英雄学会"沉住气、不贪攻"——不被远处的敌人引诱离开关键位置。
这一节我们聚焦每一关的难点、思路和策略,只用关键片段辅助讲解,不展示完整解法。读懂"为什么这样设计",你就能自己补全代码。
这是你第一次使用 else 的关卡,也是理解"否则"的最佳起点。英雄要同时保护背靠背的两个农民,难点在于:不能为了追一个敌人而跑太远,否则另一边的农民就危险了。
策略是把"中间防守点"设为 else 分支的归宿:发现敌人就上前清剿,一旦清完(没有敌人了),立刻 moveXY 回到正中央的防守坐标。这样英雄就像一个钟摆,始终守在能照顾两侧的位置。
这一关的目标是夺回被锁在宝箱里的宝物。挑战在于:营地里的食人魔会不断骚扰你,你不能光顾着开箱被打死,也不能光打怪不开箱。
if-else 完美解决了这个两难:把"有敌人"作为条件,if 分支反击敌人,else 分支攻击宝箱。这样英雄会自动"战斗优先"——只要还有食人魔就先打它们,一旦清场就抓紧时间凿宝箱。注意这里 else 攻击的目标是用字符串 "Chest" 指定的:
if enemy: hero.attack(enemy) else: hero.attack("Chest") # 用名字攻击宝箱
"森林劈斩者""Else 之战""边地僵局 A/B"这一系列,核心都是围绕技能冷却来做决策。矮人成群结队地涌来,cleave() 能一次清掉一片,但它有 10 秒冷却。如果只会等劈斩,那冷却的这 10 秒英雄就只能挨打。
聪明的做法是用 if-else 把这 10 秒"榨干":if 判断 isReady("cleave")——好了就劈斩;else——没好就普通攻击。这样英雄永远在输出,要么群秒,要么单点,绝不空闲。
这一关首次把距离引入 if 条件。要护卫脆弱的农民 Victor,英雄不能见敌人就追——追远了 Victor 就暴露了。所以判断标准变成了"敌人离得够不够近"。
策略是:测出与最近敌人的距离,如果 distance < 10 就攻击(说明敌人已逼近,威胁到农民);否则就 moveXY 回到 Victor 身边的防守点。这是"模式一"的升级版——把条件从"有没有敌人"换成了"敌人近不近"。
{x: 40, y: 37}。else 分支一定要回到这个正确的位置,否则走远了食人魔就会伤到 Victor。
这是一道"综合大考",需要同时用上 if/else、distanceTo()、<、cleave()、while True 循环和 attack("Chest")。任务是:在被矮人群包围的情况下,打开一个大宝箱并存活。
关键策略是用距离来决定何时放劈斩:矮人因为怕你,会扎堆靠近后才前进。所以当最近的矮人距离 < 10 时用 cleave 群秒,把它们一锅端;else 则去攻击 "Chest",趁敌人不近的间隙开箱。
distance < 10 逻辑出了问题。
这道概念挑战引入了一个更高级的结构:嵌套(Nesting)。大多数食人魔害怕英雄不敢上前,但有的会勇敢进攻。规则是:离得近(< 5 米)的就打它,否则去攻击 "Door"。
这里巧妙的地方是双层判断:外层先用 if enemy: 确认敌人存在(避免对 None 测距出错),内层再用 if distance < 5 / else 按距离决策。来看这个嵌套骨架:
if enemy: # 外层:先确认有敌人 distance = hero.distanceTo(enemy) if distance < 5: # 内层 if:近 hero.attack(enemy) else: # 内层 else:远 hero.attack("Door")
findNearestEnemy() 返回 None,对 None 调用 distanceTo 会出错。外层的 if enemy 就是一道"安全锁"——这也呼应了第二单元学过的存在性检查。
else 后面误写条件——else 不带条件,直接跟冒号。else 没和 if 对齐——缩进层级不一致会报错。else 也写了条件却忘了——孤立的 else 没有配对的 if。if enemy——对 None 调用 distanceTo 会崩溃。< 写反成 >——逻辑就完全相反了。isReady 检查——技能在冷却时调用会无效。到目前为止,你已经接触了选择结构的几种形态。把它们放在一起对比,脉络就清晰了:
| 形态 | 结构 | 含义 |
|---|---|---|
| 单分支 | if | 满足才做,否则跳过 |
| 双分支 | if … else | 二选一,必走其一 |
| 嵌套 | if 里再放 if-else | 先大类判断,再细分决策 |
恭喜你完成边际森林第三单元!你的英雄从"会判断"进化到了"会权衡"——不仅知道该不该做,还知道该做这个还是做那个。来回顾一下核心概念:
| 核心概念 | 关键点 | 对应关卡 |
|---|---|---|
| 双分支结构 | 条件成立做 A,否则做 B | 全部 |
| if-else 语法 | else: 不带条件 | 背靠背 |
| else 规则 | 必须配对 if、互斥完备 | Else 之战 |
| 缩进对齐 | else 与 if 同层级 | 全部 |
| distanceTo | 测量到目标的距离 | 测距仪 |
| 比较运算符 < | 距离与数字比较得真假 | 贴身护卫 |
| isReady | 检查技能是否冷却完毕 | 森林劈斩者 |
| cleave 劈斩 | 群体攻击,需配合 isReady | 边地僵局 |
| else 干正事 | 有敌人打,没敌人开箱 | 食人魔营地 |
| 嵌套判断 | if 里再套 if-else | 勇敢的食人魔 |
会权衡、懂取舍的英雄,才能在险境中游刃有余。
继续征战边际森林吧,勇敢的程序员!🌲⚔️
CodeCombat 编程知识 · 边际森林:第三单元
CodeCombat教研团队