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

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 个字符组成:Brak

在第 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 中几乎所有东西都区分大小写heroHeromoveRightmoverightTruetrue。养成注意大小写的习惯,能帮你避免大量的错误。

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教研团队