第二单元:程序、编程、注释、字符串与调试

Unit 2: Programs, Programming, Comments, Strings & Debugging
对应关卡 Level 7–9 | 暗影地牢 Kithgard Dungeon

2.1 深入理解程序

Understanding Programs in Depth

2.1.1 程序的结构(Structure of a Program)

2.1.1.1 程序由语句组成 A program consists of statements

CodeCombat 程序由语句组成 - codecombat.cn
图1:程序就像乐高积木——由一块块"语句"搭建而成

在第一单元中,你已经体验过写程序的感觉了。现在让我们更深入地理解它的结构。

一个程序就像一栋建筑,而语句(Statement)就是建筑中的一块块砖。每一行 hero.moveRight() 或 hero.attack("Brak") 都是一条语句。所有语句组合在一起,就构成了一个完整的程序。

来看第 7 关"真名实姓"的完整程序——它由 6 条语句组成:

hero.moveRight()       # 语句1
hero.attack("Brak")    # 语句2
hero.attack("Brak")    # 语句3
hero.moveRight()       # 语句4
hero.attack("Treg")    # 语句5
hero.attack("Treg")    # 语句6

6 条指令,按顺序执行,塔林就能消灭两个食人魔并收集宝石。简洁而有力!

2.1.1.2 语句(Statement)的定义:一条完整的指令 A complete instruction

语句是程序中的最小执行单元——一条完整的指令,告诉计算机做一件事。在 Python 中,通常一行就是一条语句。

在 CodeCombat 的第 7–9 关中,你会遇到这些类型的语句:

每条语句都是一个完整的动作——它能独立执行,产生明确的效果。

2.1.1.3 表达式(Expression)的定义:产生值的代码片段 Code that produces a value

表达式(Expression)是一段能"算出结果"的代码。比如:

简单说:表达式关注的是"得到什么值",语句关注的是"做什么动作"。

2.1.1.4 语句与表达式的区别 Difference between statement and expression

概念核心作用例子
语句 Statement执行一个动作hero.attack("Brak")
表达式 Expression产生一个值3 + 2、"Hello"
小提示:在前 9 关中,你写的基本都是语句。表达式经常"藏在"语句里面——比如 hero.attack("Brak") 中的 "Brak" 就是一个表达式(它产生了一个字符串值),整行代码则是一条语句。

2.1.2 程序的执行(Program Execution)

2.1.2.1 解释器(Interpreter)的概念 Concept of interpreter

CodeCombat 解释器概念 - codecombat.cn
图2:解释器就像一个实时翻译——你说一句,它翻译一句给计算机

你写的 Python 代码,计算机并不能直接"读懂"。它需要一个解释器(Interpreter)来当翻译。

想象你在和一个只懂机器语言的外星人对话。解释器就是那个站在你们中间的翻译:你说一句 Python,它立刻翻译成机器语言告诉外星人。

2.1.2.2 逐行解释执行 Line-by-line interpretation

Python 解释器的工作方式是:读一行 → 翻译一行 → 执行一行 → 读下一行……

在 CodeCombat 中,你能清楚地观察到这个过程——代码左侧会有一个高亮标记,显示当前执行到了哪一行。塔林执行完 hero.moveRight() 之后,解释器才会去读 hero.attack("Brak")。

2.1.2.3 编译 vs. 解释 Compilation vs. Interpretation,简要对比

编程语言有两种翻译策略:

方式类比特点代表语言
编译 Compile先把整本书翻译好,再交给读者执行速度快,但修改后需重新编译C, C++, Java
解释 Interpret同声传译,说一句翻一句开发灵活,写完就能运行Python, JavaScript

2.1.2.4 Python 是解释型语言 Python is an interpreted language

Python 是解释型语言——写完代码点击"运行"就能立即看到效果,不需要等待编译过程。这就是为什么 CodeCombat 中你修改一行代码后,立刻就能重新运行看到不同的结果。

2.1.2.5 运行时(Runtime)的概念 Concept of runtime

运行时(Runtime)就是"程序正在运行的那段时间"。在 CodeCombat 中,从你点击"运行"按钮到英雄完成任务(或失败),这整个过程就是运行时。运行时出现的错误叫"运行时错误"(后面会学到)。

2.1.3 程序的输入与输出(Program Input and Output)

2.1.3.1 输入(Input):程序接收的数据 Data that a program receives

输入(Input)是程序从外部接收的信息。在 CodeCombat 中,输入包括:地图布局(墙壁、尖刺、敌人的位置)、敌人的名字(如 "Brak"、"Treg")、关卡目标(收集宝石、消灭食人魔)。

你在写代码之前观察地图,获取这些"输入信息",然后用代码做出相应的决策。

2.1.3.2 输出(Output):程序产生的结果 Results that a program produces

输出(Output)是程序执行后产生的效果。在 CodeCombat 中,输出就是英雄的行为:安雅移动了、塔林攻击了食人魔、敌人被消灭了——这些都是你的程序的"输出"。

2.1.3.3 hero.say() 作为一种输出方式 hero.say() as output

CodeCombat hero.say()输出 - codecombat.cn
图3:hero.say() 让英雄说出文字——这是一种可视化的输出方式

hero.say("message") 是一种特殊的输出方式——它让英雄在游戏画面中说出一段文字(就像漫画对话气泡)。这既是一种信息展示,有时候也能触发游戏事件(比如对着门卫说出密码来开门)。

2.1.3.4 print() 函数简介 Introduction to print() function

在传统 Python 编程中,最常用的输出方式是 print() 函数——它把信息显示在控制台(终端)上:

print("Hello, World!")   # 在屏幕上显示文字
print(3 + 2)              # 显示计算结果:5

在 CodeCombat 中你主要用 hero.say(),但 print() 在以后的纯 Python 编程中会变得非常重要。

2.2 字符串

Strings

2.2.1 字符串的定义(Definition of Strings)

2.2.1.1 字符串是字符的序列 A sequence of characters

CodeCombat 字符串定义 - codecombat.cn
图4:字符串就像糖葫芦——一个一个字符串在竹签上

字符串(String)就是一串字符排在一起组成的数据。想象一串糖葫芦——每颗山楂就是一个字符,整串就是一个字符串。

比如 "Brak" 这个字符串由 4 个字符组成:B、r、a、k。

在第 7 关"真名实姓"中,你第一次正式使用字符串作为参数——"Brak" 和 "Treg" 就是告诉英雄"攻击谁"的字符串。

2.2.1.2 字符串是一种数据类型 String as a data type

在第一单元中你学了数字类型(整数和浮点数),现在来认识一个新的数据类型:str(字符串类型)。

三种你已经了解的数据类型:

2.2.1.3 字符串用引号包围 Strings are enclosed in quotes

字符串的标志就是那对引号。没有引号的 Brak 是一个变量名,加了引号的 "Brak" 才是字符串。这个区别非常重要(2.2.5 节会详细讲)!

"Brak"      # ✓ 这是字符串(有引号)
"Treg"      # ✓ 这也是字符串
"Hello!"    # ✓ 包含字母和标点的字符串
Brak        # ✗ 这不是字符串!这是一个变量名

2.2.1.4 字符串的内容可以是任何字符 Content can be any character

字符串里可以放任何字符:英文字母("Hello")、中文("你好")、数字字符("123")、标点符号("!@#")、空格("hi there")、甚至完全为空("")。

注意:"3" 是字符串(文本),3 是整数(数字)——它们不是同一种东西!

2.2.2 字符串的语法(String Syntax)

2.2.2.1 双引号 "Hello" Double quotes

Python 中最常用的字符串写法是双引号:

hero.attack("Brak")    # 双引号包围的字符串
hero.say("Hello!")    # 双引号包围的字符串

在 CodeCombat 中,你看到的字符串基本都用双引号。

2.2.2.2 单引号 'Hello' Single quotes

单引号也能创建字符串,效果完全相同:

hero.attack('Brak')    # 单引号也可以!
hero.say('Hello!')    # 效果和双引号一样

2.2.2.3 双引号与单引号的等价性 Equivalence of single and double quotes

"Brak" 和 'Brak' 是完全一样的。选哪种都行,只要保持一致就好。CodeCombat 中默认使用双引号,建议你也用双引号。

2.2.2.4 引号的配对规则 Matching quote rules

CodeCombat 引号配对规则 - codecombat.cn
图5:引号必须配对——开头用什么引号,结尾就用什么引号

引号必须成对出现,而且类型要一致:

"Brak"     # ✓ 双引号开头,双引号结尾
'Brak'     # ✓ 单引号开头,单引号结尾
"Brak'     # ✗ 错误!双引号开头,单引号结尾——不匹配!
'Brak"     # ✗ 错误!单引号开头,双引号结尾——不匹配!
配对口诀:双引号开,双引号收;单引号开,单引号收。绝不能"混搭"!

2.2.2.5 空字符串 "" Empty string

两个引号之间什么都不写,就是空字符串:"" 或 ''。它是一个合法的字符串,只不过里面没有任何字符(长度为 0)。虽然在前 9 关中不会用到,但知道它的存在就好。

2.2.3 字符串的大小写敏感性(Case Sensitivity of Strings)

2.2.3.1 "Brak" 与 "brak" 是不同的字符串 "Brak" ≠ "brak"

CodeCombat 大小写敏感 - codecombat.cn
图6:在计算机看来,"Brak" 和 "brak" 是两个完全不同的名字

这是第 7 关中最容易翻车的地方!大小写不同 = 不同的字符串。

hero.attack("Brak")    # ✓ 正确!找到了食人魔 Brak
hero.attack("brak")    # ✗ 找不到叫 "brak" 的敌人!
hero.attack("BRAK")    # ✗ 也找不到 "BRAK"!

食人魔的名字是 "Brak"(首字母大写),你写 "brak" 或 "BRAK" 计算机都不认识。就像在通讯录里搜"张三"能找到人,搜"张三三"就找不到了。

2.2.3.2 大小写敏感在编程中的普遍性 Case sensitivity is common in programming

不只是字符串,Python 中几乎所有东西都区分大小写:hero ≠ Hero、moveRight ≠ moveright、True ≠ true。养成注意大小写的习惯,能帮你避免大量的错误。

2.2.3.3 正确拼写的重要性 Importance of correct spelling

在第 7a 关中,敌人叫 "Krug" 和 "Grump"。如果你写成 "krug"、"Krugh" 或 "Grumpy"——哪怕只差一个字母——攻击指令也不会生效。计算机要求精确匹配(Exact Match)。

2.2.3.4 字符串比较是精确匹配 String comparison is exact match

计算机比较两个字符串时,会逐个字符检查。每个字符的位置、大小写、拼写都必须完全一致才算相同。没有"差不多"这个概念——要么完全匹配,要么完全不匹配。

通关秘诀:观察游戏画面中敌人头顶的名字,一个字母一个字母地抄到代码里。首字母通常大写!

2.2.4 字符串作为参数(Strings as Arguments)

2.2.4.1 将字符串传递给方法:hero.attack("Brak") Passing strings to methods

CodeCombat 字符串作为参数 - codecombat.cn
图7:字符串参数告诉方法"对谁做"——attack 谁?say 什么?

在第一单元中,你给方法传递的参数是数字(步数)。现在在第 7 关中,你开始给方法传递字符串参数——用来指定目标的名字:

hero.attack("Brak")     # "Brak" 是字符串参数——指定攻击目标
hero.attack("Treg")     # "Treg" 是另一个字符串参数
hero.moveRight(2)       # 2 是数字参数——指定步数

数字参数回答"多少"的问题,字符串参数回答"谁"或"什么"的问题。

2.2.4.2 字符串参数必须加引号 String arguments must be quoted

传递字符串参数时,引号不能少!引号是字符串的"身份证"——没了它,Python 就不知道这是一段文字。

hero.attack("Brak")    # ✓ 正确:有引号,"Brak" 是字符串
hero.attack(Brak)      # ✗ 错误:没引号,Python 以为 Brak 是变量名!

2.2.4.3 不加引号会被视为变量名 Without quotes, it's treated as a variable name

如果你写 hero.attack(Brak)(没有引号),Python 会认为 Brak 是一个变量的名字,然后去查找叫做 Brak 的变量——结果找不到,就会报错:NameError: name 'Brak' is not defined(名字错误:Brak 未定义)。

这就是为什么引号如此重要——它告诉 Python:"这是一段文字,不是变量名!"

2.2.4.4 字符串参数的用途:指定目标名称 Use: specifying target names

在第 7–9 关中,字符串参数的主要用途是指定攻击目标的名称。来看第 8 关"危险脚步"中四个不同的敌人名字:

hero.attack("Sog")     # 攻击食人魔 Sog
hero.attack("Gos")     # 攻击食人魔 Gos
hero.attack("Kro")     # 攻击食人魔 Kro
hero.attack("Ergo")    # 攻击食人魔 Ergo

不同的字符串 → 不同的目标 → 不同的效果。字符串让你的代码能精确地指定"对谁做什么"。

2.2.5 字符串与标识符的区别(Difference Between Strings and Identifiers)

2.2.5.1 带引号 = 字符串 With quotes = string literal

字符串就是代码中直接写出来的字符串值——用引号括起来的那些文字。它们是"字面意思"的数据,不需要进一步查找。

2.2.5.2 不带引号 = 变量 或 标识符 Without quotes = variable/identifier

不带引号的名字会被 Python 当作标识符(Identifier)——也就是变量名、对象名或方法名。Python 会尝试去找这个名字对应的"东西"。

2.2.5.3 这一区别是初学者常见错误来源 Common source of beginner errors

CodeCombat 字符串vs标识符 - codecombat.cn
图8:一个引号的差别,就是"字符串"和"变量名"的天壤之别

这可能是编程初学者最常犯的错误之一!让我们用一个表格彻底搞清楚:

代码Python 的理解结果
"Brak"一个字符串值,内容是 Brak✓ 正常工作
Brak一个叫做 Brak 的变量✗ NameError(变量未定义)
"hero"一个字符串值,内容是 hero只是文字,不是英雄对象!
hero英雄对象✓ 指向游戏中的英雄

2.2.5.4 示例对比:"enemy1" vs. enemy1 Example comparison

# "enemy1" 是字符串——就是文字 "enemy1"
hero.attack("enemy1")   # 攻击名字叫 enemy1 的敌人

# enemy1 是变量名——Python 会去找这个变量的值
# 如果 enemy1 变量存储了 "Brak",那就攻击 Brak
# 如果 enemy1 变量不存在,就报错!
终极口诀:有引号 = 文字本身;没引号 = 去找对应的东西。

2.2.6 字符串的基本操作(Basic String Operations,初步了解)

2.2.6.1 字符串长度 String length,概念预告

每个字符串都有长度——就是它包含的字符数。"Brak" 的长度是 4,"Treg" 的长度也是 4,"Ergo" 的长度也是 4。在未来你会学到用 len() 函数来获取长度。

2.2.6.2 字符串拼接 String concatenation,概念预告

拼接(Concatenation)就是把两个字符串连在一起。在 Python 中用 + 号:"Hello" + " World" = "Hello World"。前 9 关中不需要用到,先知道就好。

2.2.6.3 字符串在 Python 中是不可变的 Strings are immutable in Python

字符串一旦创建就不可修改——你不能改变 "Brak" 中的某个字母。如果需要不同的字符串,只能创建一个新的。这个概念现在了解即可,后面用到时会更深入学习。

2.3 注释

Comments

2.3.1 注释的概念(The Concept of Comments)

2.3.1.1 注释的定义:给人看的解释,不被执行 Explanations for humans, not executed

CodeCombat 注释的定义 - codecombat.cn
图9:注释就像便利贴——贴在代码旁边给人看,计算机完全无视它

注释(Comment)是代码中给人类程序员看的文字说明。计算机在执行程序时会完全跳过注释——就像它们根本不存在一样。

在第 7 关的代码中,你会看到这样的注释:

# 抵御 "Brak" 和 "Treg"!
# 小食人魔攻击两次才会被击败。

hero.moveRight()
hero.attack("Brak")
hero.attack("Brak")

前两行以 # 开头的灰色文字就是注释。它们告诉你这段代码要做什么,但塔林完全不会理会它们——他只执行真正的代码。

2.3.1.2 注释的目的:解释代码意图 Purpose: explain code intent

注释的核心目的是回答一个问题:"这段代码为什么这样写?"它帮助未来的你(或其他读代码的人)快速理解代码的思路。

2.3.1.3 注释不影响程序运行 Comments don't affect program execution

你可以写100行注释,程序的行为也不会有任何变化。你也可以删除所有注释,程序照样正常运行。注释纯粹是给人看的"笔记"。

2.3.1.4 注释是程序员之间的沟通工具 Comments as communication between programmers

在团队合作中,注释就像队友之间的留言条。在 CodeCombat 中,关卡的默认代码里经常有注释给你提示——这就是游戏设计师在通过注释和你"沟通"。

2.3.2 Python 注释语法(Python Comment Syntax)

2.3.2.1 单行注释:# 符号 Single-line comment: # symbol

CodeCombat 井号注释 - codecombat.cn
图10:# 号是 Python 注释的标志——它后面的内容全被忽略

在 Python 中,# 号(井号 / hash)标记注释的开始。从 # 开始到这一行结束的所有内容都会被忽略。

# 这整行都是注释
hero.moveRight()    # 这里开始也是注释

2.3.2.2 # 后的内容全部被忽略 Everything after # is ignored

从 # 号开始,这一行后面的所有文字都不会被执行。哪怕你写了一行代码在 # 后面,它也不会运行:

# hero.moveRight()    ← 这行不会执行!因为被注释掉了
hero.moveRight()      # ← 这行才会执行

2.3.2.3 注释可以在行首 Comment at the beginning of a line

# 写在行的最开头,整行都是注释:

# 击败第一对食人魔。
hero.attack("Sog")
hero.attack("Sog")

2.3.2.4 注释可以在代码行末尾 Inline comment at end of code line

# 也可以写在一行代码的后面,用来解释这行代码:

hero.attack("Brak")   # 第一次攻击 Brak
hero.attack("Brak")   # 第二次——这下它倒了!

2.3.2.5 多行注释的写法 Multi-line comments:多个 # 或三引号

Python 没有专门的"多行注释"语法,但有两种常用方法:

# 方法1:每行都加 # 号
# 这是第一行注释
# 这是第二行注释
# 这是第三行注释

# 方法2:三引号(技术上是多行字符串,不是真正的注释)
"""
这里可以写很多行
不会被执行
但通常用于文档说明
"""

在 CodeCombat 中,我们通常只用 # 号。

2.3.3 注释的最佳实践(Best Practices for Comments)

2.3.3.1 解释"为什么"而非"是什么" Explain "why" not "what"

# 不好的注释:说了等于没说
hero.moveRight()   # 向右移动  ← 这不是废话吗?

# 好的注释:解释了为什么
hero.moveRight()   # 走到 Brak 的攻击范围内

2.3.3.2 保持注释与代码同步更新 Keep comments updated with code

如果你修改了代码但忘了改注释,旧注释就会变成"误导性谎言"。每次改代码时,顺手检查一下旁边的注释是否还正确。

2.3.3.3 不要注释显而易见的代码 Don't comment the obvious

像 hero.moveRight() # 向右移动 这种注释没有价值——代码本身已经够清楚了。只在代码不容易理解的地方添加注释。

2.3.3.4 使用注释规划代码结构 Use comments to plan code structure

在第 8 关"危险脚步"中,代码用注释分成了逻辑组:

hero.moveRight()
# 击败第一对食人魔。
hero.attack("Sog")
hero.attack("Sog")
hero.attack("Gos")
hero.attack("Gos")

hero.moveRight(2)
# 击败第二对食人魔。
hero.attack("Kro")
hero.attack("Kro")
hero.attack("Ergo")
hero.attack("Ergo")

注释把代码分成了"打第一组"和"打第二组"两个阶段,逻辑清晰明了!

2.3.3.5 先写注释再写代码的习惯 Habit: write comments before code

面对复杂关卡时,可以先用注释写出"计划",再填入实际代码:

# 步骤1:走到第一个敌人面前
# 步骤2:消灭 Ursa(攻击两次)
# 步骤3:移动到第二个敌人
# 步骤4:消灭 Rexxar(攻击两次)
# 步骤5:到达出口

有了这个"骨架",再一步步填入代码就轻松多了!

2.3.4 注释作为学习工具(Comments as a Learning Tool)

2.3.4.1 阅读注释获取线索和指导 Reading comments for clues and guidance

在 CodeCombat 中,关卡的默认代码里经常有注释提示。比如第 9 关"睡眠时刻"的开头:

# 这是一个组合挑战关卡。
# 打败食人魔,收集宝石,并逃到红色X处
# 使用字符串和移动参数!

这些注释告诉你关卡目标和需要用到的技能——一定要认真阅读!

2.3.4.2 注释中包含密码或指令 Comments containing passwords or instructions

有些关卡的注释里藏着通关必需的信息——比如密码、敌人名字的正确拼写、或特定的操作提示。养成阅读注释的习惯,可能会在关键时刻帮你一把。

2.3.4.3 通过注释理解代码意图 Understanding code intent through comments

当你看别人写的代码时,注释能帮你快速理解"这段代码想做什么"。即使你暂时看不懂具体语法,注释也能让你把握代码的整体思路。

2.4 hero.attack() 方法

The hero.attack() Method

2.4.1 attack 方法的使用(Using the attack Method)

2.4.1.1 语法:hero.attack("目标名称") Syntax: hero.attack("targetName")

CodeCombat attack方法语法 - codecombat.cn
图11:hero.attack("Brak") —— 对象.方法("字符串参数")

hero.attack() 是第 7 关引入的新方法。它让英雄攻击一个指定的敌人:

hero.attack("Brak")
# ^      ^       ^
# 对象  方法   字符串参数(目标名称)

和 moveRight(3) 的结构一样:对象.方法(参数)。只不过这次参数是字符串而不是数字。

2.4.1.2 参数为字符串类型的目标名称 Argument is a string target name

attack 方法的参数必须是一个字符串,代表你要攻击的敌人的名字。这个名字必须和游戏中敌人头顶显示的名字完全一致(包括大小写)。

2.4.1.3 多次攻击同一目标 Attacking the same target multiple times

在地牢的关卡中,由于我们使用的剑是系统赠送的普通剑,攻击力太低,每个食人魔矮人都需要被攻击两次才能被消灭。所以你必须连续写两行相同的攻击指令:

hero.attack("Brak")   # 第一下!Brak 还没倒
hero.attack("Brak")   # 第二下!Brak 被消灭了!

只写一次 attack 是不够的——食人魔会剩一点血,然后反击你!

2.4.1.4 不同目标可能需要不同攻击次数 Different targets may need different attacks

在当前关卡中所有矮人都需要两次攻击。但在未来的关卡中,更强大的敌人可能需要更多次攻击,或者更好的武器能让你一击必杀。如果你装备了更强的剑(比如第 7a 关提到的),有可能一次攻击就能解决!

2.4.2 attack 方法的策略(Strategy for attack Method)

2.4.2.1 确定攻击顺序 Determining attack order

面对多个敌人时,攻击顺序很重要!在第 7b 关"高举之剑"中,三个敌人 Rig、Gurt、Ack 从不同方向靠近。按照他们到达的先后顺序攻击,可以减少挨打次数:

# 按到达顺序攻击,减少受伤
hero.attack("Rig")     # 先打最近的
hero.attack("Rig")
hero.attack("Gurt")    # 再打第二个
hero.attack("Gurt")
hero.attack("Ack")     # 最后打第三个
hero.attack("Ack")

2.4.2.2 移动到攻击范围内 Moving within attack range

英雄不能隔空攻击——必须先走到敌人旁边!在第 7 关中,塔林需要先 moveRight() 走到 Brak 面前,才能 attack("Brak")。

2.4.2.3 先移动后攻击的模式 Pattern: move then attack

这是第 7–9 关最常用的代码模式:移动 → 攻击 → 攻击 → 移动 → 攻击 → 攻击……

第 9 关"睡眠时刻"是这个模式的综合运用:

hero.moveRight(2)        # 移动到 Ursa 面前
hero.attack("Ursa")     # 攻击!
hero.attack("Ursa")     # 再攻击!
hero.moveDown()          # 移动到下一个区域
hero.moveLeft(2)         # 继续移动
hero.moveUp(2)           # 到 Rexxar 面前
hero.attack("Rexxar")   # 攻击!
hero.attack("Rexxar")   # 再攻击!

2.4.2.4 优先级判断 Priority judgment:先打哪个敌人

当路线上有多个敌人时,通常的策略是:先打挡路的敌人(不打掉就过不去),然后打近的敌人(减少来回走的路程)。

2.5 hero.say() 方法

The hero.say() Method

2.5.1 say 方法的使用(Using the say Method)

2.5.1.1 语法:hero.say("内容") Syntax: hero.say("content")

CodeCombat say方法语法 - codecombat.cn
图13:hero.say() 让英雄在游戏中说出对话气泡

hero.say() 让英雄在游戏画面中"说话"——就像漫画里的对话气泡:

hero.say("Hello!")          # 安雅说:"Hello!"
hero.say("开门芝麻!")      # 塔林说出密码

2.5.1.2 参数为字符串 Argument is a string

和 attack 一样,say 的参数也是字符串。不同的是,attack 的字符串是敌人名字,say 的字符串是英雄要说出的话——可以是任何内容。

2.5.1.3 say 方法作为输出/交互方式 say as output/interaction

say 是游戏中英雄与世界互动的方式之一。它不仅能显示文字,还能用来调试——当你不确定代码执行到了哪里时,加一句 hero.say("到这里了") 就能验证。

2.5.1.4 say 方法触发游戏事件 say triggering game events,如开门、招募

在某些关卡中,hero.say() 不只是"说话"——它可以触发特殊事件。比如对着门卫说出正确的密码,门就会打开;或者对着友军说"跟我来",他们就会跟随你。这让 say 方法不仅仅是输出工具,更是一种与游戏世界互动的手段。

2.6 错误与调试

Bugs and Debugging

2.6.1 Bug 的概念(The Concept of Bugs)

2.6.1.1 Bug 的定义:程序中的错误 An error in a program

CodeCombat Bug的定义 - codecombat.cn
图14:Bug 就是让程序出问题的"小虫子"

Bug 就是程序中的错误——任何导致程序不能正常工作的问题都叫 Bug。在 CodeCombat 中,Bug 可能导致安雅走错路、攻击失败、或者代码根本无法运行。

2.6.1.2 Bug 一词的历史由来 Historical origin of the term "bug"

1947 年,计算机科学家 Grace Hopper 的团队在调试一台大型计算机时,发现了一只飞蛾卡在了继电器中导致故障。他们把这只蛾子粘在日志本上并写道:"First actual case of bug being found."(找到了第一个真正的 bug。)从此,"Bug"就成了程序错误的代名词。

有趣的事实:Debug(调试)的字面意思就是"去除虫子"——把程序里的"小虫子"找出来消灭掉!

2.6.1.3 Bug 的类型概述 Overview of bug types

Bug 主要分为三大类:语法错误(代码写法不对)、运行时错误(运行中崩溃)、逻辑错误(不崩溃但结果不对)。下面一一介绍。

2.6.2 错误类型(Types of Errors)

2.6.2.1 语法错误(Syntax errors):代码不符合语言规则 Code that violates language rules

语法错误是最常见的错误类型——代码不符合 Python 的语法规则,程序根本无法启动。就像你写了一句话但标点全乱了,别人读不懂。

hero.attack("Brak)     # ✗ 语法错误:缺少结尾引号
hero.attack("Brak"     # ✗ 语法错误:缺少右括号
hero.atack("Brak")    # ✗ 拼写错误:atack → attack

2.6.2.2 运行时错误(Runtime errors):执行时崩溃 Crashes during execution

运行时错误是代码语法没问题,但执行到某一行时出了问题。比如攻击一个不存在的目标:

hero.attack("Ghost")   # 运行时错误:没有叫 Ghost 的敌人!

2.6.2.3 逻辑错误(Logic errors):结果不符合预期 Results don't match expectations

逻辑错误是最棘手的——代码能运行,但结果不是你想要的。比如只攻击了 Brak 一次就走开了:

hero.moveRight()
hero.attack("Brak")    # 只打了一次——Brak 还活着!
hero.moveRight()       # 就走了……然后被 Brak 追上打死了

代码没有报错,但塔林挂了——这就是逻辑错误。

2.6.2.4 拼写错误 Spelling/typo errors

把 attack 写成 atack、把 "Brak" 写成 "Brek"——少一个字母或拼错都会导致 Bug。

2.6.2.5 大小写错误 Case errors

把 "Brak" 写成 "brak"——只是大小写不同就找不到目标了。

2.6.2.6 缺少引号错误 Missing quotes errors

写了 hero.attack(Brak) 忘了加引号——Python 以为 Brak 是变量名,找不到就报错。

2.6.3 调试的概念(The Concept of Debugging)

2.6.3.1 调试的定义:找到并修复错误的过程 Finding and fixing errors

调试(Debugging)就是发现、定位并修复 Bug 的过程。它是编程中最重要的技能之一——没有人能写出完全没有 Bug 的代码(至少第一遍不行)!

2.6.3.2 调试是编程的核心技能之一 Debugging is a core programming skill

专业程序员花在调试上的时间可能比写新代码还多!学会高效地调试,就是学会做一个优秀的"代码侦探"。

2.6.3.3 调试的基本步骤 Discover → Locate → Analyze → Fix → Verify

CodeCombat 调试步骤 - codecombat.cn
图16:调试五步法——发现、定位、分析、修复、验证

调试就像当侦探破案:

2.6.4 调试技巧(Debugging Techniques)

2.6.4.1 仔细阅读错误信息 Read error messages carefully

当 Python 报错时,它会告诉你:错误类型(SyntaxError? NameError?)和错误位置(第几行)。别怕看英文——错误信息是你的"破案线索"!

2.6.4.2 检查拼写和大小写 Check spelling and case

90% 的初学者错误是拼写和大小写问题。检查清单:hero(全小写?)、方法名(camelCase?)、敌人名字(首字母大写?每个字母都对?)

2.6.4.3 检查引号是否配对 Check for matching quotes

每个字符串的开头引号和结尾引号是否匹配?有没有忘记其中一个?

2.6.4.4 检查括号是否配对 Check for matching parentheses

每个 ( 都必须有对应的 )。试试数一下:左括号和右括号的数量应该相等。

2.6.4.5 逐行检查代码 Check code line by line

用手指或笔指着代码,一行一行地读——在脑中"模拟"英雄的行为。这个方法简单但非常有效。

2.6.4.6 使用回放/运行观察结果 Use playback/run to observe results

在 CodeCombat 中,拖动回放进度条可以逐步观察英雄的行为,看看他在哪一步开始出错的。

2.6.4.7 删除或注释掉可疑代码 Comment out suspicious code

不确定哪一行有问题?在可疑的代码前面加 # 把它"临时禁用",看看去掉它后程序是否正常。这是一种排除法。

2.6.4.8 与预期结果对比 Compare with expected results

问自己:"我期望英雄做什么?"和"英雄实际做了什么?"找到差异,就找到了 Bug。

2.6.4.9 小黄鸭调试法 Rubber duck debugging

把你的代码逐行"解释"给一只小黄鸭(或任何物品)听。在解释的过程中,你往往会突然发现"等等,这行不对!"——这就是小黄鸭调试法。听起来很傻,但真的很有效!

2.6.5 常见初学者错误(Common Beginner Mistakes)

2.6.5.1 忘记调用方法的括号 Forgetting parentheses when calling methods

hero.attack("Brak")   # ✓ 正确
hero.attack "Brak"    # ✗ 错误:忘了括号!

2.6.5.2 字符串忘记加引号 Forgetting quotes around strings

hero.attack("Brak")   # ✓ 正确
hero.attack(Brak)      # ✗ 错误:Brak 没引号!

2.6.5.3 变量名拼写不一致 Inconsistent variable name spelling

把 hero 写成 horo、把 attack 写成 atack——差一个字母就出错。

2.6.5.4 缩进错误 Indentation errors

在前 9 关中所有代码都是顶格写的(不需要缩进)。如果你不小心在某行前面加了空格,Python 可能会报 IndentationError。

2.6.5.5 语句顺序错误 Wrong statement order

先攻击后移动(但还没走到攻击范围)、忘记攻击第二次就走开了——这些都是逻辑上的顺序错误。

调试心态:犯错是正常的!每个程序员都在不断地写 Bug、修 Bug。重要的是学会高效地找到和修复它们。不要灰心,多试几次一定能过关!

2.7 顺序结构的正式定义

Formal Definition of Sequential Structure

2.7.1 三种基本控制结构(Three Basic Control Structures)

2.7.1.1 顺序结构(Sequence):按顺序执行 Execute in order

CodeCombat 三种控制结构 - codecombat.cn
图17:所有程序都由顺序、选择、循环三种结构组合而成

顺序结构就是代码从上到下、一行一行执行——这是第 1–9 关中你一直在使用的方式。没有跳转、没有重复、没有分支。

2.7.1.2 选择/分支结构(Selection/branching):预告 Preview of selection structure

选择结构能让程序根据条件做不同的事情。在未来关卡中你会学到 if/else:"如果有敌人就攻击,否则继续走"。

2.7.1.3 循环/迭代结构(Iteration/looping):预告 Preview of iteration structure

循环结构能让程序重复执行某段代码。下一单元就会学到 while True:——让英雄不断巡逻、持续攻击,不用手动写几百行重复代码!

2.7.1.4 AP CSP:所有算法由这三种结构构建 All algorithms built from these three

AP 计算机科学原理课程明确指出:世界上所有的计算机程序——从简单的游戏到复杂的 AI——都是由这三种结构组合嵌套而成的。掌握了它们,你就掌握了编程的核心骨架。

2.7.1.5 AQA A Level:三种组合原则 Three combining principles

英国 A Level 计算机科学课程也把这三种结构作为编程基础的核心教学内容。无论哪个国家的 CS 教育体系,这三种结构都是必修的核心知识。

2.7.2 顺序结构的应用(Applications of Sequential Structure)

2.7.2.1 步骤明确的任务 Tasks with clear steps

顺序结构最适合处理步骤固定、不需要判断的任务。第 7–9 关的每一关都是这种类型:你已经知道所有敌人的位置和名字,只需要按照正确顺序写出指令。

2.7.2.2 食谱类比 Recipe analogy

做菜就是典型的顺序结构:先洗菜 → 切菜 → 热油 → 下锅 → 翻炒 → 调味 → 出锅。每步都必须按顺序来,跳过任何一步都可能"翻车"。

2.7.2.3 导航指令类比 Navigation directions analogy

GPS 给你的路线指示也是顺序结构:"直行200米 → 右转 → 直行500米 → 到达目的地"。CodeCombat 中的代码就像英雄的 GPS!

2.7.2.4 顺序结构的局限性 Limitations of pure sequential structure

纯顺序结构有一个大问题:如果敌人数量不固定怎么办?如果你不确定该走哪条路怎么办?这些情况需要循环和条件判断来解决——这就是下一单元的内容!

2.8 本单元相关函数/方法总结

Functions/Methods Summary for This Unit

2.8.1 新增方法列表(New Methods Introduced)

2.8.1.1 hero.attack(target) —— 攻击指定目标 Attack a specified target

项目说明
方法名attack
所属对象hero
参数字符串类型,敌人的名称(如 "Brak")
效果英雄对指定目标发起一次攻击
注意事项需在攻击范围内;矮人需攻击2次

2.8.1.2 hero.say(message) —— 说出信息 Say a message

项目说明
方法名say
所属对象hero
参数字符串类型,要说的内容(如 "Hello")
效果英雄在游戏中显示对话气泡;可能触发事件
注意事项内容为字符串,需要引号包围

2.8.2 Python 内置函数预告(以后会学到,现在先了解)

2.8.2.1 print() —— 输出到控制台 Output to console

print() 在传统 Python 开发中是最常用的输出方式。它把信息显示在终端/控制台中。虽然 CodeCombat 中主要用 hero.say(),但 print() 在后续课程中会频繁出现。

2.8.2.2 type() —— 查看数据类型 Check data type

type("Brak") 返回 <class 'str'>,告诉你 "Brak" 是字符串类型。这是个很好的"身份鉴定"工具。

2.8.2.3 len() —— 获取长度 Get length

len("Brak") 返回 4——表示这个字符串有 4 个字符。在后续关卡中处理数组和列表时,len() 会非常有用。

本单元小结 ✨

CodeCombat 单元总结 - codecombat.cn
图18:第二单元核心概念全景图

恭喜你完成了第二单元!来看第 9 关"睡眠时刻"的完整解法,回顾所有核心概念:

# 这是一个组合挑战关卡。
# 打败食人魔,收集宝石,并逃到红色X处
# 使用字符串和移动参数!
hero.moveRight(2)          # 移动参数(整数)
hero.attack("Ursa")       # 字符串参数(目标名称)
hero.attack("Ursa")       # 攻击两次消灭矮人
hero.moveDown()            # 顺序执行——一步步向前
hero.moveLeft(2)           # 路径规划
hero.moveUp(2)             # 绕过障碍
hero.attack("Rexxar")     # 注意大小写!R 大写
hero.attack("Rexxar")     # 攻击两次
hero.moveRight()          # 继续前进
hero.attack("Brack")      # 第三个敌人
hero.attack("Brack")      # 消灭!
hero.moveRight(2)         # 到达红色X——通关!🎉
核心概念关键知识对应关卡
程序结构程序由语句组成,逐行执行第7–9关
字符串用引号包围的文字数据:"Brak"第7关
大小写敏感"Brak" ≠ "brak"第7关
字符串作为参数hero.attack("目标名")第7关
字符串 vs 标识符有引号=文字,无引号=变量名第7关
注释# 给人看的说明第7–9关
hero.attack()攻击指定名称的敌人第7关
hero.say()说出指定内容概念引入
Bug 与调试发现→定位→分析→修复→验证全部关卡
顺序结构三种控制结构之一(最基本)全部关卡

安雅已经学会了用名字精准打击敌人,塔林也掌握了阅读注释获取情报的技能。
下一站:第三单元——循环结构 while True:,让英雄拥有无限战斗力!⚔️

CodeCombat 编程知识 · 计算机科学入门(暗影地牢):第二单元

CodeCombat教研团队