第四单元:多分支选择结构

Unit 4: Multi-Branch Selection Structures
边际森林 Border Forest · 发狂的矮人 → 释放树精

4.1 为什么需要多分支

Why We Need Multiple Branches

4.1.1 复杂的战场环境(Complex Battlefield)

4.1.1.1 两个选项已经不够用了 Two options are no longer enough

在前面的单元里,我们学会了两种判断结构:单分支 if(“如果……就……”)和 双分支 if-else(“如果……否则……”)。它们在面对只有“是/否”两种情况时非常有效。

但真实的战场往往更加多变。想象一下英雄在森林中守卫宝箱时,他每一刻都要在脑海里回答这样一连串问题:

4.1.2 互相排斥的情况(Mutually Exclusive Cases)

4.1.2.1 同一时刻只能做一件事 Only one action at a time

这三种情况是互相排斥(Mutually Exclusive)的:同一时刻,英雄只能选择其中一个动作。由于双分支只能处理两种情况的对立(A 或 B),当面对三种甚至更多情况(A 或 B 或 C)时,就需要引入本单元的主角:多分支选择结构(Multi-Branch Selection)

CodeCombat 多分支决策示意 - codecombat.cn
图 1:英雄在战场上需要在多个动作之间做选择
结构逻辑描述适用场景
单分支 if成立就做,不成立就跳过只处理一种特殊情况
双分支 if-else非此即彼,二选一处理对立的两种情况
多分支 if-elif-else按重要性排队,多选一处理三种及以上的互斥情况

4.2 if-elif-else 结构

The if-elif-else Structure

4.2.1 结构与分工(Structure and Roles)

4.2.1.1 认识新关键词 elif Meet the new keyword: elif

Python 使用 elif(是 else if 的缩写,读作“else if”)把多个条件像链条一样串起来。一个完整的多分支结构骨架如下:

if 条件A:
    动作1        # 当条件A成立时执行
elif 条件B:
    动作2        # A不成立、但B成立时执行
elif 条件C:
    动作3        # A、B都不成立、C成立时执行
else:
    动作4        # 上面全都不成立时执行(兜底)

这三个关键词各司其职,且有着严格的数量限制:

关键词作用数量要求
if开头第一个条件,必须由它开启分支有且只有 1 个
elif中间的“否则如果”,负责检查额外的条件可以有 0 个、1 个或多个
else最后的兜底分支,前面全失败时执行它最多 1 个,可省略
致命要点: ifelifelse 这三个关键词必须对齐在同一列(同级缩进),它们各自下属的代码块需要统一向右缩进(通常 4 个空格)。缩进层级一旦混乱,Python 将无法识别这些分支属于同一个整体。

4.2.2 从双分支到多分支(From Dual to Multi-Branch)

4.2.2.1 代码对比 Code comparison

通过对比双分支和多分支代码,你能更直观地看到 elif 的作用:

# 只能区分两件事:顺劈斩 OR 普攻敌人
if hero.isReady("cleave"):
    hero.cleave()
else:
    hero.attack(enemy)

# 可以区分三件事:顺劈斩 OR 普攻敌人 OR 趁机打宝箱
if hero.isReady("cleave"):
    hero.cleave()
elif distance < 5:
    hero.attack(enemy)
else:
    hero.attack("Chest")

4.3 条件的判断顺序

Evaluation Order

4.3.1 命中即停原则(Hit and Stop)

4.3.1.1 顺序决定一切 Order dictates behavior

这是多分支最容易出错、也是最考验战术智慧的地方:Python 是自上而下逐条检查条件的,一旦命中,立刻停止。

程序运行到多分支结构时,会严格执行以下流程:

  1. 先检查 if 的条件。如果成立,就执行它的动作,然后跳过后续所有的 elif 和 else,整个多分支结构结束。
  2. 如果 if 不成立,程序才会往下看第一个 elif。如果成立,执行后直接结束。
  3. 依此类推。如果前面的 if 和所有 elif 都不成立,才会执行 else

4.3.2 流程图解读(Reading the Flowchart)

4.3.2.1 一条链上的分岔路 Forks on a single chain

开始判断 if 劈斩就绪? isReady cleave() elif 距离<5? distance<5 attack(敌人) else (打宝箱) 继续下一行代码 / 循环
图 2:多分支结构如同阶梯,自上而下“命中即停”,剩下的会被全部跳过
战术启示: 因为“命中即停”,你必须把最优先、最重要的动作放在最上面(比如威力巨大的劈斩),把默认的保底动作放在 else 里。如果你把 elif distance < 5 写在了 isReady 之前,当敌人贴脸时,英雄就会优先普攻,即使劈斩好了也不会放!

4.4 比较运算符的陷阱

Pitfalls with Comparison Operators

4.4.1 数字与数字的比较(Comparing Numbers)

4.4.1.1 避免“类型错误” Avoiding Type Errors

多分支的条件中经常用到小于号 <、大于号 > 等比较运算符。但在 Python 中有一个不可动摇的规则:

牢记法则: <> 只能用来比较两个“数字”! 绝对不能拿一个物体(比如敌人对象)直接和数字比大小。

很多新手英雄会写出这样看似合理、实则报错的代码:

# ❌ 错误!findNearestEnemy 返回的是一个"敌人对象",它不是一个数字!
if hero.findNearestEnemy() < 5:
    hero.attack(enemy)

为什么错?这就好比你在问:“这只食人魔 小于 5 吗?”——毫无逻辑可言。正确的方法是先量出距离(数字),再用距离和 5 做比较:

CodeCombat 比较运算符的正确与错误用法 - codecombat.cn
图 3:只有「数字 vs 数字」才能用 < 比较
方法返回的是什么类型?能用 < 比较吗?
findNearestEnemy()一个对象(比如某个食人魔)❌ 不能
isReady("cleave")一个布尔值(True / False)❌ 不能
distanceTo(enemy)一个数字(距离多少米)✅ 必须可以

4.5 分支判断的新工具箱

New Tools for Branch Conditions

为了让条件判断更加聪明,本单元正式为你引入两个极具实战价值的新工具:一个管时间(冷却),一个管空间(距离)。

4.5.1 技能冷却与 isReady(Cooldown and isReady)

4.5.1.1 大招准备好了吗? Is the ultimate ready?

强力技能(如劈斩 cleave)非常厉害,可以一次击中周围一圈敌人,但它有冷却时间(Cooldown)。用过一次后,要等秒表走完一圈才能再次释放。

hero.isReady("cleave") 专门用于检查技能状态,它本身就会回答“是”(True)或“否”(False):

if hero.isReady("cleave"):   # 因为它返回 True/False,可直接作为 if 条件
    hero.cleave(enemy)         # 好了就劈!

4.5.2 距离判断 distanceTo(Measuring Distance)

4.5.2.1 敌人离我有多远? How far is the enemy?

hero.distanceTo(目标) 是解决上文“比较陷阱”的钥匙。它会计算英雄坐标与目标坐标之间的实际直线距离,并返回一个准确的数字(米)

enemy = hero.findNearestEnemy()
if enemy:
    distance = hero.distanceTo(enemy) # 先拿到数字
    if distance < 5:                  # 再比较数字大小
        hero.attack(enemy)

4.7 实战关卡精讲

Level Strategy & Analysis

4.7.1 发狂的矮人(Furious Dwarves)

4.7.1.1 思路:生存、输出与任务的平衡 Balancing survival, damage, and objectives

CodeCombat 发狂的矮人关卡场景 - codecombat.cn
图 4:发狂的矮人——在敌人的周期性围攻中存活,并伺机打开宝箱

本关的精髓在于用代码将战术优先级具象化。我们把三个动作按优先级填入多分支的骨架:

  1. 第一优先(顶层 if): 劈斩(cleave)是性价比最高的群攻,只要冷却就绪就立刻释放,决不浪费。
  2. 第二优先(中间 elif): 如果大招没好,但敌人已逼近到 5 米内,为了活命必须立刻普攻反击。
  3. 第三优先(兜底 else): 没大招、也没有近身威胁(战场暂时安全),这个时候正是去攻击宝箱 "Chest" 的绝佳时机。
while True:
    enemy = hero.findNearestEnemy()
    if enemy:
        distance = hero.distanceTo(enemy)
        
    if hero.isReady('cleave'):
        hero.cleave(enemy)      # 第一优先:群攻输出
    elif distance < 5:
        hero.attack(enemy)      # 第二优先:近战保命
    else:
        hero.attack("Chest")    # 第三优先:安全时开箱(注意引号!)

4.8.1 释放树精(Release the Dryad)

4.8.1.1 举一反三的实战演练 Applying the pattern to a new target

CodeCombat 释放树精关卡场景 - codecombat.cn
图 5:释放树精——核心逻辑完全相同,只是把打宝箱换成了打门(Door)

这一关是一个概念挑战(Concept Challenge)。食人魔困住了一只树精,你需要在击退敌人的同时攻击大门释放它。仔细观察你会发现,这一关的结构和“发狂的矮人”完全一模一样

宝箱和门,本质上都是“静态目标(需要持续攻击完成任务)”。你只需要把上一个关卡 else 分支里的 "Chest" 换成 "Door",这套“三段式”战术模板就成功迁移到了新的场景中。

4.9 决策优先级思维

Priority Decision Thinking

4.9.1 优先级阶梯法(The Priority Ladder)

4.9.1.1 写出好代码的思考路径 The mental model for good code

学完上面两个实战关卡,我们可以提炼出一套通用的“多分支决策法”,它不仅能用在游戏里,更是真正程序员每天都在使用的思维工具:

CodeCombat 决策优先级阶梯 - codecombat.cn
图 6:把动作按优先级排成「阶梯」,正是多分支的本质
📈 优先级阶梯五步法
  1. 罗列: 列出当前情况下的所有可能动作(放技能、近战、跑路、做任务)。
  2. 排序: 问自己:“哪个必须优先做?”“哪个是在没事干时用来兜底的?”
  3. 分配: 把最重要的放在最顶部的 if,次重要的放在 elif
  4. 兜底: 把“剩下的默认选项”装进最后的 else 里。
  5. 验算: 检查用 <> 的地方,两边是不是都是实实在在的数字。

4.9.2 常见错误自查表(Troubleshooting Checklist)

报错或异常现象可能的错误原因
英雄从不使用劈斩isReady 判断写得太靠后,被前面的条件(如距离判断)抢先截胡了。
程序报错说“不能进行比较”使用了 < 比较“敌人对象”或者“True/False”,没有提前算出数字距离。
代码总是报缩进错误 (IndentationError)ifelifelse 这三个词没有左侧对齐,或者内部的动作忘记了缩进。
兜底动作(如开宝箱)永远不执行前面的条件太容易满足了,导致程序根本走不到 else 这个阶梯。
英雄还没开完箱子就被打死由于条件缺失或判定优先级过低,没有及时察觉并反击近身的敌人。

本单元小结 ✨

恭喜你完成边际森林第四单元!你从“二选一”成功升级到了掌控“多选一”,掌握了编程中极其重要的优先级决策工具。一起来回顾一下核心收获:

核心概念关键点实战应用
多分支 if-elif-else处理三个及以上的互斥情况兼顾打怪、放技能与开箱任务
条件命中即停自上而下逐条检查,命中第一个就停止高优先级的判断必须放前面
距离 distanceTo返回距离数字,必须用数字和数字比大小精准把握敌人的远近威胁
冷却 isReady返回 True/False,直接做条件保证大招冷却完毕第一时间释放
三段式战术模板强力技能 → 近敌自保 → 完成目标任务发狂的矮人、释放树精

能把混乱的战场排练得井井有条,才是真正的指挥官。
继续前进,给森林中的敌人点颜色看看吧!🌲🗡️

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

CodeCombat教研团队