去做题

第一单元:函数综合应用 · 封装你的行为

Unit 1: Working with Functions — Encapsulating Behavior
计算机科学 5 · 安息云峰 Cloudrip Mountain · 关键力量 → 双生花

1.1 什么是函数

What Is a Function

欢迎来到计算机科学 5!在前面的旅程里,你已经掌握了变量、条件、循环、数组、字典,还学会了召唤和指挥军队。但随着程序越写越长,你会发现同一段逻辑要反复出现,主循环挤得密密麻麻、越来越难读。本单元你将拿到一件真正把编程能力"提高到下一个等级"的工具——函数(function)。关卡"关键力量"的开场白说得很直白:"用新的概念:函数,把你的编程能力提高到下一个等级!"

本单元主线:函数让你把一段"做某件事"的代码打包起来、起个名字,之后想做这件事,只要喊一声名字就行。你将学会用 def 定义函数、用缩进写函数体、理解函数内代码"先保存、后执行"的特性,并在 while 循环里调用自己写的函数,把"捡金币、召唤兵、指挥兵"这些重复行为各自封装成一个函数。

1.1.1 从"重复"到"封装"(From Repetition to Encapsulation)

1.1.1.1 把行为打包成一个名字 Pack a behavior behind a name

关卡概览一语中的:"函数非常适合将高级行为分解为更多小的部分。"想象你的主循环里要做三件事——捡最近的金币、召唤士兵、指挥士兵攻击。如果全堆在一起,循环体又长又乱。函数的思路是:把"捡金币"那几行打包成 pickUpNearestCoin,把"召唤兵"打包成 summonSoldier,把"指挥兵"打包成 commandSoldiers。主循环就变成清清爽爽的三行调用。

把一大段行为,拆进三个命名的"盒子" 乱糟糟的主循环 找物品…移动… 比金币…召唤… 遍历盟友…攻击… 全挤在一起 😵 pickUpNearestCoin() summonSoldier() commandSoldiers()

1.1.2 函数的两个阶段(Define, then Call)

1.1.2.1 先"定义",后"调用" Definition and invocation are separate

使用函数永远分两步:先定义(告诉程序"这个名字对应哪些代码"),再调用(真正让这些代码跑起来)。这两步是分开的——只定义不调用,代码一行都不会执行。关卡特别强调:"请记住,在定义函数之后,你必须调用它!"这是初学者最常忘的一点。

📦 函数就像"食谱"
定义(def):写下一张菜谱,标题是菜名,下面是做法步骤——但写菜谱本身并不会做出菜来。
调用(名字 + 括号):喊出菜名"照这张菜谱做一份!"——这时厨房才真正动起来。
只写菜谱不点菜,永远吃不上饭。

1.2 定义函数

Defining a Function

1.2.1 def 关键字与命名(The def Keyword)

1.2.1.1 def 名字 括号 冒号 def name ( ) :

定义函数用关键字 def(definition 的缩写)开头,后面跟函数名、一对括号 ()、再加一个冒号 :。函数名要能说明它"做什么",通常用动词短语,如 pickUpNearestCoin(捡起最近的金币)、summonSoldier(召唤士兵)。这一行叫函数的"头部"。

函数头部的四个零件 def summonSoldier ( ) : 关键字 函数名 括号 冒号

1.2.2 冒号与缩进的函数体(Colon & Indentation)

1.2.2.1 缩进决定"哪些代码属于函数" Indentation marks the function body

头部那行以冒号结尾,紧接着的缩进代码块就是"函数体"——真正要做的事。Python 靠缩进(通常 4 个空格)来判断哪些行属于这个函数:缩进在里面的就属于它,一旦回到不缩进,就说明函数结束了。下面这个 pickUpNearestCoin 是关卡给好的示范。

# 定义:让英雄收集最近的金币
def pickUpNearestCoin():
    items = hero.findItems()           # ← 缩进,属于函数体
    nearestCoin = hero.findNearest(items)
    if nearestCoin:
        hero.move(nearestCoin.pos)    # ← 更深缩进,属于 if
缩进错了,函数就散了:函数体的每一行都必须缩进。如果忘了缩进,Python 会认为那行"不属于函数",报缩进错误或逻辑出错。同一个函数体里的缩进层级也要一致——别一行 4 空格、一行 2 空格。

1.2.3 代码延迟执行(Deferred Execution)

1.2.3.1 定义时"只保存,不运行" Defining saves the code for later

这是函数最关键、也最容易被忽略的特性。关卡默认代码的注释写得非常清楚:"放在函数内的代码并不会立刻执行,而是先保存好,以备后用。"也就是说,当程序读到 def pickUpNearestCoin(): 这一段时,里面的 findItems、move 统统不会马上发生;它们只是被记下来、贴上 pickUpNearestCoin 这个标签,静静等待你日后"点名"。

def 阶段:代码被"存进抽屉",不运行 def pickUpNearestCoin(): findItems / move … 💤 保存待用,未执行 被调用时 pickUpNearestCoin() ▶ 抽屉里的代码 这时才真正运行!

1.3 调用函数

Calling a Function

1.3.1 函数名加括号(Name Plus Parentheses)

1.3.1.1 一对括号就是"运行指令" The parentheses trigger the run

要让保存好的函数真正执行,就调用它——写下函数名,末尾加一对括号 ()。关卡说得明明白白:"要调用一个函数,请在函数名称末尾使用 (),如 commandSoldiers()。"注意区别:定义时写 def 名字():,调用时只写 名字()——没有 def、不缩进在别的函数里、末尾也没有冒号。

定义(写菜谱)
def summonSoldier():
    # ...函数体...
    pass
调用(点这道菜)
summonSoldier()

# 无 def、无冒号、不缩进
别忘那对括号:只写函数名 summonSoldier 而漏掉括号,Python 不会执行它,只是"提到"了这个函数。加上 (),才是真正下令"现在就跑一遍"。

1.3.2 在 while 里调用(Calling Inside a Loop)

1.3.2.1 主循环变成一份"行动清单" The main loop becomes a checklist

把函数调用放进 while True,主循环就变成一份清爽的"行动清单":每一帧依次点名三个函数,游戏就会反复执行"捡币→召唤→指挥"。关卡默认代码提示:"在你的循环里,你可以'调用'你在上面定义的函数……下面一行代码会让 pickUpNearestCoin 函数里的代码被执行。"

while True:
    pickUpNearestCoin()   # 捡最近的金币
    summonSoldier()       # 有钱就召唤
    commandSoldiers()     # 指挥全军攻击
读代码像读目录:看到这个主循环,就算不看函数内部,也能立刻明白程序"每帧做三件事:捡币、召唤、指挥"。这就是函数带来的可读性——把"做什么"和"怎么做"分开,主循环负责"做什么",函数内部负责"怎么做"。

1.4 用函数分解行为

Decomposing Behavior with Functions

1.4.1 一个函数做一件事(One Function, One Job)

1.4.1.1 每项行动都有自己的函数 Each action gets its own function

关卡给出了一条重要的设计原则:"每一项行动都要有自己的函数!"也就是一个函数只专注做一件事:pickUpNearestCoin 只管捡币、summonSoldier 只管召唤、commandSoldiers 只管指挥。这样每个函数都短小、清晰、好测试,哪块出了问题也容易定位。这是编程里"关注点分离"的入门体现。

💰 pickUpNearestCoin

找到所有物品里最近的一个金币,移动过去捡起来。只负责"捡钱"。

⭐ summonSoldier

判断 gold 是否够 costOf("soldier"),够就召唤。只负责"扩军"。

⚔️ commandSoldiers

遍历所有士兵,各自找最近敌人并下令攻击。只负责"作战"。

1.5 实战关卡精讲

Level Strategy & Analysis

这一节聚焦每一关的策略、思路与算法设计,只展示用于说明的关键片段,完整通关程序请你自己动手完成。

关卡 · 关键力量

🔑 关键力量 —— 补全并调用你的第一个函数

扣哒世界 关键力量关卡 - koudashijie.com
图2:关键力量——一边捡金币、召唤士兵,一边指挥他们迎击食人魔,全部用函数组织

策略:三个函数各司其职

本关是函数的入门关。关卡已经替你写好了 pickUpNearestCoin 和 commandSoldiers 两个函数,留下 summonSoldier 让你补全,并要求你在主循环里把三个函数都调用一遍。策略非常清晰:让程序每帧都"捡币攒钱→有钱扩军→指挥作战",同时英雄注意躲避食人魔的攻击。

思路拆解

算法思路是两件事。其一,补全 summonSoldier 函数体:在函数里写一个 if 判断——if hero.gold >= hero.costOf("soldier"): 就 hero.summon("soldier"),把"钱够才召唤"的逻辑封装进去。其二,在 while 循环里调用三个函数:默认代码已经调用了 pickUpNearestCoin(),你需要在下面补上 summonSoldier() 和 commandSoldiers()——因为"定义之后必须调用,否则函数里的代码永远不会跑"。

# 关键片段一:补全 summonSoldier 的函数体
def summonSoldier():
    if hero.gold >= hero.costOf("soldier"):
        hero.summon("soldier")

# 关键片段二:在主循环里逐一调用三个函数
while True:
    pickUpNearestCoin()
    summonSoldier()
    commandSoldiers()
定义 ≠ 调用:本关最想让你记住的,就是"定义好 summonSoldier 只是写好了菜谱,必须在 while 里写上 summonSoldier() 才会真正召唤士兵"。很多人卡在"函数写对了却不生效",十有八九是忘了调用。
函数调用的顺序有讲究:先捡币再召唤,是因为召唤需要金币;先召唤再指挥,是因为要先有士兵才能指挥。把三个函数按"攒钱→扩军→作战"的逻辑顺序排好,整支队伍的运转才顺畅。
关卡 · 双生花

🌸 双生花 —— 自己动手写两个函数

扣哒世界 双生花关卡 - koudashijie.com
图3:双生花——花匠安危牵动双生花大小,靠士兵保护花匠、捡币扩军来撑过攻击

策略:保护花匠,撑过攻势

本关的设定很有意思:双生花和花匠命运相连——花匠被攻击,双生花就缩小;缩到普通花朵大小后花匠会开始受伤;花匠平安时双生花则会长大。所以策略核心是尽力保护花匠:不停召唤士兵、指挥士兵清剿逼近的敌人、捡金币维持扩军,用一支持续壮大的部队把攻击挡在花匠之外,撑到胜利。

思路拆解

与"关键力量"不同,本关的 summonSoldiers 已给好,需要你亲手定义 commandSoldiers 和 pickUpNearestCoin 两个函数,并在 while 循环里给它们取消注释后调用。算法思路是:commandSoldiers 用 for 遍历所有士兵(可用 hero.findByType("soldier") 精确取士兵),每个士兵 findNearestEnemy() 找到敌人就 command(soldier, "attack", enemy);pickUpNearestCoin 则 findNearest(findItems()) 找到金币就 move 过去。定义好后,在循环里依次调用 summonSoldiers()、commandSoldiers()、pickUpNearestCoin()。

# 关键片段:亲手定义两个函数
def commandSoldiers():
    for soldier in hero.findByType("soldier"):
        enemy = soldier.findNearestEnemy()
        if enemy:
            hero.command(soldier, "attack", enemy)

def pickUpNearestCoin():
    coin = hero.findNearest(hero.findItems())
    if coin:
        hero.move(coin.pos)
别忘取消注释:关卡默认把循环里的 commandSoldiers() 和 pickUpNearestCoin() 用 # 注释掉了。你把函数定义写好后,一定要记得删掉这两行前面的 #,否则函数虽已定义却从没被调用,士兵不会作战、金币也不会去捡。
findByType 精确取某类单位:本关用 hero.findByType("soldier") 只挑出"士兵",而 hero.findByType("peasant")[0] 则取出第一个花匠。相比 findFriends()(所有盟友),findByType 能精确锁定某一类单位,让"指挥士兵"这件事不会误伤或漏掉其他角色。

1.6 常见错误

Common Mistakes

1.6.1 新手最容易踩的坑(Pitfalls for Beginners)

1.6.1.1 定义与调用的典型问题 Typical issues with define and call

扣哒世界 函数常见错误速查 - koudashijie.com
图4:定义了却不调用、漏括号、缩进错乱,是本单元最常见的失误
易错点清单:
  • 定义了却不调用——函数体永远不会执行;定义之后一定要在循环里 名字() 调用它。
  • 调用漏括号——写成 summonSoldier 而非 summonSoldier(),函数不会运行。
  • 忘记取消注释——双生花里循环内的调用被 # 注释掉了,要记得删掉 #。
  • 函数体没缩进——def 下面的代码必须缩进;不缩进会报错或不属于该函数。
  • 缩进层级不一致——同一函数体里空格数要统一,别混用不同缩进。
  • 头部漏冒号——def 名字() 后面必须有冒号 :,否则语法错误。
  • 把调用写进别的函数里——三个函数应"平级"定义,调用则放在 while 循环中,别层层嵌套。
  • 召唤条件写错——summonSoldier 里应是 gold >= costOf(...),够钱才召唤。

本单元小结 ✨

恭喜你把编程能力提升到了"下一个等级"——你学会了函数!这一单元你理解了函数的核心思想:把一段"做某件事"的代码用 def 打包命名、用缩进划定函数体,并牢牢记住了它最关键的两个特性——定义时代码只保存、不执行,必须用"函数名 + 括号"调用后才真正运行。你把"捡币、召唤、指挥"三项行为各自封装成一个函数,让主循环变成一份清爽的行动清单,还在"关键力量"和"双生花"两关里亲手定义并调用了自己的函数。来回顾一下核心概念:

核心概念关键点对应关卡
def 定义函数def 名字(): 头部四零件两关
缩进函数体缩进的代码块才属于函数两关
延迟执行定义只保存,调用才运行关键力量
函数调用函数名 + ()两关
循环内调用while 里点名,反复执行两关
封装重复行为一个函数只做一件事两关
取消注释调用删掉 # 才会被调用双生花
findByType 取单位精确挑出某一类角色双生花

懂得把杂乱的行为封装成一个个命名函数、让主循环清晰如目录的英雄,才是云峰之上真正优雅的程序员。
继续向雪山之巅挺进吧,勇敢的程序员!🏔️🔑🌸

扣哒世界 编程知识 · 计算机科学 5(安息云峰):第一单元

扣哒世界教研团队