本单元最大的惊喜:你有了一个宠物!"边地好伙伴"关卡介绍道:"使用 pet 来引用你的宠物,就像使用 hero 引用英雄一样。" 宠物有自己的一套动作,比如 pet.say() 说话、pet.moveXY() 移动、pet.fetch() 取物、pet.findNearestEnemy() 找敌人。它是你在战场上的得力助手。
但宠物有个特别之处:它不是靠你写一行行命令逐句指挥,而是靠"对事件做出反应"来行动。这就引出了本单元的核心——事件与事件处理机制。
事件(Event)是程序运行过程中某个特定时刻发生的一件事。比如宠物"听到了声音"是一个事件("hear"),宠物"被召唤出场"也是一个事件("spawn")。事件不是你主动调用的,而是在条件满足时自动发起的。
| 事件名 | 英文 | 何时触发 | 触发次数 |
|---|---|---|---|
| 听到声音 | "hear" | 附近有单位 say 说话时 | 每次说话都触发 |
| 被召唤 | "spawn" | 关卡开始、宠物出场时 | 仅一次 |
关卡给出了精准定义:"事件处理器是在事件发生时执行的函数。" 换句话说,你预先写好一个函数,告诉系统"当某事件发生,就运行这个函数"。之后你不必盯着,系统会在事件触发时自动调用你的处理器。
把事件想象成门铃响,事件处理器就是"听到门铃就去开门"这条预设规则。你不用一直守在门口,只要事先约定好规则,铃一响,开门动作就自动发生。"hear" 就是铃响,处理器函数就是开门动作。
要让宠物对某事件做出反应,需要用 on() 方法把处理器函数"挂"到事件上。关卡讲得很清楚:"使用 pet.on(eventType, eventHandler) 为某种事件类型(比如 'hear')指派事件处理器。" 语法结构如下:
这行代码的含义,用关卡原话说就是:"宠物在(on)听到('hear')声音时运行 speak"。第一个参数是事件类型字符串,第二个参数是处理器函数的名字。
这是本单元最关键也最容易错的一点。关卡反复警告:"不要为 pet.on('hear', someFunction) 里的 someFunction 加上 ()。() 代表要马上调用函数。我们要的是将一个函数当作参数传递到 .on(),以便之后事件发生时调用,而不是立刻调用。"
pet.on("hear", speak) # ✓ 正确:传函数的引用 pet.on("hear", speak()) # ✗ 错误:这会立刻执行 speak
speak 是"这个函数本身"(一张菜谱),speak() 是"立刻做这道菜"。注册事件时,你要交给系统的是菜谱,让它日后需要时照做,而不是现在就做一遍。
关卡提到:"该函数需要接受一个参数——事件的相关数据。" 所以事件处理器在定义时,括号里通常写一个形参(习惯命名为 event 或 e),用来接住事件附带的信息。即使暂时用不到这份数据,也要把这个形参留着。
def speak(event): # 处理器接收事件数据 event pet.say("喵~") pet.on("hear", speak)
关卡详细描述了触发条件:"'hear' 事件会在单位听到声音时发生!只要有人在离单位足够近的位置使用 .say('string') 方法,事件就会发起,处理器函数就会被调用,函数内的代码就会被执行!"
注意三个要点:要有人说话(调用 say)、距离要足够近、然后处理器才会被调用。在一些关卡里,是农民和奶牛在说话,每当它们开口,宠物就会听到并回应。
最基本的用法,就是在 "hear" 处理器里让宠物说话。比如训练宠物报出名字、回答问题、礼貌问好。处理器里可以连续 say 多句,宠物会按顺序一句句说出来。
def sayName(event): pet.say("我名叫狂兽。") pet.say("不过我的朋友们叫我毛球。") pet.on("hear", sayName)
"去拿过来"关卡引入了第二种事件:"'spawn'(生成)事件只会在关卡开始后,宠物被召唤出来时发生。" 和反复触发的 "hear" 不同,"spawn" 整局只触发一次——就在宠物登场那一刻。
既然 "spawn" 只触发一次,怎么让宠物持续工作呢?关卡给出了巧妙答案:"虽然 'spawn' 事件只发生一次,但你还是可以在事件处理器中使用 while 循环重复执行代码。" 在处理器里放一个 while True 循环,宠物登场后就会不知疲倦地循环执行任务。
def goFetch(event): while True: # 在处理器里循环 potion = hero.findNearestItem() if potion: pet.fetch(potion) # 让宠物去取药水 pet.on("spawn", goFetch)
"锋利的诱饵"关卡引入了事件线程(Event Thread)这一进阶概念。它的精髓是:事件处理器里的代码,和主循环里的代码是"并行"运行的。也就是说,宠物可以在自己的 spawn 处理器里循环取药水,与此同时,英雄在主循环里循环战斗——两条线同时进行,互不阻塞。
"森林慢跑"也点明了这一点:"宠物可以独立于英雄来执行动作。例如,它可以在英雄说话的时候四处移动。" 这种"宠物一条线、英雄一条线"的并行结构,正是事件机制最强大的地方。
这一节聚焦每一关的策略、思路与算法设计,看懂"事件该怎么用",你就能自己写出完整解法。
这几关是事件机制的入门。算法思路高度一致:定义一个处理器函数(里面写 pet.say),再用 pet.on("hear", 函数名) 注册。区别只在于宠物说什么、由谁来说话触发。在"好伙伴的名字 A"里,是农民和奶牛说话触发,所以英雄自己不必说话;而在"边地好伙伴"里,需要英雄 hero.say 来触发。
hero.say 必须写在 pet.on 后面。因为要先注册好处理器,宠物才能在随后的说话声中正确响应。先注册、后触发,顺序不能颠倒。
这两关让事件回应显得"聪明"。"博士猫咪"的算法妙处在于:所有问题的答案恰好都是 "2",所以处理器 sayTwo 只需固定说 "2"。英雄连问几道难题,每次提问触发一次 hear,宠物每次都答 "2",看起来像在解数学题。"宠物小测试"则要你从头编写 onHear 函数,并多加几句 hero.say 来多次触发,制造问答的连续效果。
这是 "spawn" 事件的首秀。英雄陷入陷阱动弹不得,只能靠宠物。算法思路是:写 goFetch 处理器,内含 while True 循环——不断 findNearestItem 找药水,找到就 pet.fetch(potion) 取回。再用 pet.on("spawn", goFetch) 让宠物一登场就开始干活。
hero.findNearestItem() 负责定位,pet.fetch() 负责跑腿。两个单位配合完成一件事。
这一关展示了宠物的侦查能力。英雄过不了河,但宠物可以。算法思路是:在 spawn 处理器的循环里,用 pet.findNearestEnemy()(宠物也能找敌人!)持续侦查,一旦发现敌人就 pet.say("Woof!") 示警。宠物充当了英雄到不了之处的"眼睛"和"警报器"。
findNearestEnemy()。这说明 pet 是一个功能相当完整的单位,可以独立完成侦查、巡逻等任务。
这几关("漫漫长路""坚韧的诱饵""锋利的诱饵")集中体现事件线程的并行威力。共同模式是:用 spawn 处理器让宠物在后台循环取药水,而主循环里英雄同时做别的事——"漫漫长路"里英雄一边移动撤离一边等解药;"坚韧的诱饵"里英雄保持 hero.shield() 当肉盾;"锋利的诱饵"里英雄一边 cleave 战斗。
hero.shield() 要放在最后一行。因为 shield 会让英雄停在举盾状态,如果写在 pet.on 前面,后面的注册代码就来不及执行了。先注册好事件,再让英雄进入持续状态。
这两关用 spawn 事件控制宠物的移动路线。算法思路是:在 onSpawn 的 while True 循环里,用 pet.moveXY() 依次走过各个标记点,形成循环巡走。与此同时,英雄在自己的主循环里不停 hero.say 加油——这又是一个事件线程并行的典型:宠物跑步线、英雄喊话线同时进行。
while True:一个在宠物的 spawn 处理器里(控制跑动),一个在主程序里(控制英雄喊话)。它们互不阻塞、同时运转,这正是事件机制让程序"多线并行"的直观体现。
这是组合挑战,要求把本单元全部本领融会贯通:同时使用 "spawn" 和 "hear" 两个事件来保护村庄。算法思路是注册两个处理器——onSpawn 让宠物循环取物支援,onHear 让宠物在听到说话时回应(关卡提示:回应内容不重要,任何回应都会召来弓箭手帮忙)。主循环里英雄则负责战斗。三条逻辑(宠物取物、宠物回应、英雄战斗)协同运转,共同守住村庄。
pet.on("spawn", onSpawn) # 宠物登场 → 循环取物 pet.on("hear", onHear) # 听到说话 → 回应召援 # 主循环:英雄持续战斗(劈斩 / 普攻)
pet.on("hear", speak()),会立刻调用而非注册。while True——宠物只动一次就停下了。hero.say 写在了 pet.on 前面,宠物还没"上岗"就错过了声音。event 参数——处理器需要接收一个事件数据形参。"hear"、"spawn" 必须用引号且拼写准确。hero.shield() 等"持续状态"代码放太靠前——挡住了后面 on() 的注册。恭喜你完成边际森林第九单元!你不仅收获了一只得力宠物,还掌握了一种全新的编程范式——事件驱动。从此你的程序不再只是从上到下死板执行,而能"监听变化、自动响应",甚至让多条逻辑并行运转。来回顾一下核心概念:
| 核心概念 | 关键点 | 对应关卡 |
|---|---|---|
| 宠物 pet | 像 hero 一样的独立单位 | 边地好伙伴 |
| 事件 Event | 特定时刻自动发生的事 | 全部 |
| 事件处理器 | 事件发生时自动执行的函数 | 全部 |
| on() 方法 | 把处理器注册到事件上 | 边地好伙伴 |
| 别加括号 | 传函数引用,而非调用结果 | 好伙伴的名字 |
| hear 事件 | 有人说话时反复触发 | 博士猫咪、宠物小测试 |
| spawn 事件 | 仅登场时触发一次 | 去拿过来、看门狗 |
| 处理器里用循环 | 一次触发,持续工作 | 森林慢跑、漫漫长路 |
| 事件线程并行 | 宠物与英雄各干各的 | 锋利的诱饵、充满事件的争论 |
懂事件、会并行的英雄,才能让程序"活"起来、自动响应世界。
带上你的好伙伴,继续守护边际森林吧,勇敢的程序员!🐾🌲🛡️
CodeCombat 编程知识 · 边际森林:第九单元
CodeCombat教研团队