在你踏入边际森林(Border Forest)之前,先听一个有趣的故事。三百多年前,法国数学家兼哲学家勒内·笛卡尔(René Descartes,1596–1650)有一天躺在床上,盯着天花板发呆。一只苍蝇在天花板上爬来爬去,他突然想到:我该怎样精确地描述这只苍蝇的位置呢?
他发现,只要说出苍蝇离左边墙有多远、离前面墙有多远,用两个数字就能唯一地确定它在天花板上的位置。这个看似简单的想法,最终发展成了改变数学历史的笛卡尔坐标系(Cartesian Coordinate System)。
笛卡尔坐标系最伟大的贡献,是把几何(图形)和代数(数字)连接了起来。在此之前,"形状"和"数字"是两个几乎不相干的世界。有了坐标系之后,一个点可以用一对数字表示,一条直线、一个圆都可以用方程描述。这门学问叫做解析几何(Analytic Geometry),它也是后来微积分诞生的基石。
而对你这位即将出发的英雄来说,坐标系还有一个更实在的用处:它能告诉你的英雄到底要走到哪里、在哪里建造围栏。
在之前的暗影地牢里,你学过 hero.moveRight()、hero.moveUp() 这样的相对移动方法——它们的意思是"相对当前位置,朝某个方向走"。这种方法在简单的关卡里很好用,但有两个明显的麻烦:
第一,当目标位置离你很远、路线很曲折时,你需要写一大堆移动指令,还得在脑子里一步步推算英雄当前面朝哪、走到了哪。第二,如果你想直接"瞬间锁定"地图上某个固定的位置(比如那棵特定的大树旁),相对移动就显得很笨拙了。
坐标的思路完全不同:它给地图上的每一个位置都分配一个固定的"门牌号",无论英雄当前站在哪里,这个门牌号都不会变。这种"基于固定参照系的位置"叫做绝对坐标(Absolute Coordinates)。
有了门牌号,你就可以直接说:"英雄,去 (36, 59) 这个位置!"——而不用关心英雄现在站在哪。这正是本单元两个核心方法 moveXY 和 buildXY 的工作方式。
坐标系不只是数学课本里的东西,它是几乎所有计算机图形(Computer Graphics)的基础。你手机屏幕上的每一个像素、电子游戏里每个角色的位置、地图软件里的每个地点,背后都是一对对坐标在工作。学会用坐标思考,是你迈向更复杂编程的重要一步。
在理解二维地图之前,我们先从最简单的一维数轴(Number Line)说起。数轴就是一根带方向、带刻度的直线。它上面的每一个点,都对应着一个数;反过来,每一个数,也都能在数轴上找到唯一的一个点。
一条完整的数轴必须有三样东西,缺一不可:
| 要素 | 含义 | 在图中的体现 |
|---|---|---|
| 原点(Origin) | 数轴的起点,代表 0 | 标着 0 的那个点 |
| 正方向(Direction) | 规定哪边是"变大"的方向 | 箭头指向的方向(通常向右) |
| 单位长度(Unit) | 每一格代表多少 | 相邻刻度之间的距离 |
原点右边是正数(1, 2, 3……),左边是负数(-1, -2, -3……)。这和你在暗影地牢里学过的"负数=反方向"是完全一致的道理。
在一维数轴上,只需要一个数字就能确定一个点的位置。这就是"一维"的含义——只有一个方向、一个自由度。比如数字 2 就唯一对应数轴上原点右侧第二格的那个点。
想象边际森林里有一条笔直的小路,英雄只能在这条路上前后移动。这时一条数轴就够用了:你告诉英雄"去 5 号位置",它就知道该走到哪。但真实的森林是一大片开阔地,英雄既能左右走、也能上下走——这就需要把一维数轴升级成二维坐标系了。
把两条互相垂直的数轴放在一起,让它们在各自的原点处相交,就得到了二维坐标系(也叫平面直角坐标系)。其中一条水平放置,另一条竖直放置。这样一来,平面上的任何一个点都需要两个数字来描述:一个表示水平位置,一个表示竖直位置。
这就是边际森林地图的本质——它是一个铺满坐标的平面,地图上每一棵树、每一颗宝石、每一个食人魔的位置,都能用一对数字精确表示。
两条轴各有名字:
| 动作 | 对坐标的影响 |
|---|---|
| 向右走(moveRight) | x 值增大,y 不变 |
| 向左走(moveLeft) | x 值减小,y 不变 |
| 向上走(moveUp) | y 值增大,x 不变 |
| 向下走(moveDown) | y 值减小,x 不变 |
两条轴相交的那个点叫做原点(Origin),它的坐标是 (0, 0)。原点是整个坐标系的"基准点",所有其他位置都是相对它来度量的。
两条轴把平面分成了四块区域,每一块叫做一个象限(Quadrant)。按照数学惯例,从右上角开始,逆时针依次编号为第一、二、三、四象限。第一象限里 x 和 y 都是正数——这也是CodeCombat地图最常用的区域。
边际森林的每一关地图,本质上就是一张铺好的坐标纸。地图左下角附近接近原点,向右 x 增大、向上 y 增大。地图上常见的坐标范围大约从 0 到 80 不等。当你看到一个 X 标记或一颗宝石时,它都对应着一对确定的 (x, y) 坐标——你的任务,就是把这对坐标"喂"给 moveXY 或 buildXY 方法。
平面上一个点的位置,用一对写在圆括号里、用逗号隔开的数字表示,形如 (x, y)。数学上把它叫做有序数对(Ordered Pair)。"有序"两个字非常关键——它的意思是前后顺序不能调换,第一个数永远代表 x(水平),第二个数永远代表 y(竖直)。
读坐标时永远是"先 x 后 y"。一个经典的英文助记法是 "Walk along the hall, then up the stairs"——先沿着走廊走(横向 x),再爬楼梯上去(纵向 y)。坐标 (36, 59) 的意思就是:先到 x=36 的水平位置,再到 y=59 的竖直位置。
坐标的标准写法是:圆括号 + 两个数字 + 中间一个逗号。例如 (40, 53)、(8, 12)、(66, 17)。在数学里它们带圆括号;而在 Python 代码里调用方法时,这两个数字会作为参数填进方法的括号中:hero.moveXY(40, 53)。
| 数学写法 | 含义 | Python 代码写法 |
|---|---|---|
| (40, 53) | x=40,y=53 | hero.moveXY(40, 53) |
| (8, 12) | x=8,y=12 | hero.moveXY(8, 12) |
| (0, 0) | 原点 | hero.moveXY(0, 0) |
这是初学者最容易踩的坑:坐标里两个数字的顺序绝不能颠倒! (40, 20) 和 (20, 40) 是地图上两个完全不同的位置。前者在偏右偏下,后者在偏左偏上。计算机严格按照"第一个数是 x、第二个数是 y"来理解,它不会替你纠错。
hero.moveXY(40, 20) # ✓ 去 x=40, y=20 的位置 hero.moveXY(20, 40) # ✗ 完全不同的位置!x=20, y=40
你怎么知道一个目标的坐标是多少?非常简单:把鼠标悬停在地图上的任意位置,CodeCombat就会显示出那个位置的 x 和 y 坐标。 这是边际森林里你最常用的小技巧——想让英雄去哪、想在哪建围栏,先把鼠标移过去看一眼坐标,再把数字填进代码里。
moveXY(x, y) 或 buildXY("类型", x, y) → ④ 运行检查。
buildXY 是边际森林里你学到的第一个使用坐标的方法。它的作用是:在地图上你指定的 (x, y) 坐标处,建造一个物体(比如围栏)。它的基本语法是:
hero.buildXY("建造物类型", x, y)
buildXY 需要三个参数,按顺序分别是:
| 位置 | 参数 | 类型 | 说明 |
|---|---|---|---|
| 第 1 个 | "fence" | 字符串(带引号) | 要建造的东西,如围栏"fence" |
| 第 2 个 | 40 | 数字 | 建造位置的 x 坐标 |
| 第 3 个 | 53 | 数字 | 建造位置的 y 坐标 |
注意第一个参数是字符串——它表示"建什么",必须用引号括起来(单引号或双引号都行);后两个是数字——表示"建在哪",不加引号。三个参数之间用逗号分隔。
hero.buildXY("fence", 40, 53) # ^类型(字符串) ^x ^y
"fence"(围栏)类型比建造 "fire-trap"(火焰陷阱)类型容易得多——围栏直接挡住敌人,简单又可靠。
在"平原森林保卫战"中,食人魔从两个缺口来袭。你需要在两个 X 标记处分别建造一段围栏来堵住它们。把鼠标悬停在 X 标记上,得到坐标,然后写下:
# 在标记上建造两段围栏来保护村民。 # 把鼠标悬停在地图上,以得到 x, y 坐标。 hero.buildXY("fence", 40, 53) hero.buildXY("fence", 40, 21)
注意这两段围栏的 x 坐标都是 40(在同一条竖直线上),只是 y 坐标不同(一个 53、一个 21)——一个堵上方缺口,一个堵下方缺口。这正体现了坐标系的威力:同一个 x、不同的 y,就是竖直方向上的两个不同位置。
和 buildXY 一样,moveXY 也使用坐标,但它的作用是让英雄移动到地图上任意一个 (x, y) 位置。它只需要两个参数:目标的 x 和 y。
hero.moveXY(x, y) # 例如:移动到 x=36, y=59 的位置 hero.moveXY(36, 59)
还记得暗影地牢里那双"又老又单一"的 moveRight 鞋子吗?现在你的新装备能让你用 moveXY 直接到达任何想去的地方。两者的核心区别在于:
| 对比项 | moveRight() 等 | moveXY(x, y) |
|---|---|---|
| 定位方式 | 相对当前位置 | 地图绝对坐标 |
| 参数含义 | 步数(走几格) | 目标的 x 和 y |
| 方向 | 只能上下左右单方向 | 可斜着直奔目标 |
| 适合场景 | 简单的直线小步 | 远距离、精确定位 |
moveRight(3) 像是对司机说"往前开三个路口";moveXY(36, 59) 像是直接输入导航地址"去 36 号大街 59 号"。
moveXY 还内置了一个很贴心的功能:寻路(Pathfinding)。这意味着只要你给出目标坐标,英雄会自动规划一条路线走过去,必要时还会绕开途中的障碍物。你不必再手动一格一格地指挥它——只管告诉它"终点在哪"就好。
在"蜿蜒小径"中,你要带着英雄穿过森林、依次移动到几个目标点,最后在路的尽头建一道围栏挡住食人魔。每到一个目标,就把鼠标悬停查看坐标,写一行 moveXY:
# 到小路的尽头去,并在那儿修一个栅栏。 # 利用你的 moveXY(x, y) 坐标移动功能。 hero.moveXY(36, 59) hero.moveXY(37, 12) hero.moveXY(66, 17) hero.buildXY("fence", 71, 24)
仔细观察这几行:英雄先到 (36, 59),再到 (37, 12)——注意 y 从 59 大幅降到 12,说明它向下走了一大段;接着到 (66, 17),x 大幅增大,说明向右走了一大段。代码依然是顺序执行的:一行走完,才走下一行。
在"一步走错"这一关里,默认代码是食人魔写的——它故意把坐标设成会让英雄踩中地雷的位置!你的任务是修改这些坐标,把英雄引到每一颗宝石那里,安全地走完全程。这一关传递了一个重要观念:示例代码不是命运,你完全可以也应该去修改它。
# 英雄完全糊涂了! # 纠正路径,别让其踩到地雷。 hero.moveXY(11, 35) hero.moveXY(35, 24) hero.moveXY(40, 55) hero.moveXY(77, 57)
规划收集路线时,比较相邻两个坐标点,你就能预判英雄怎么走:x 增大表示向右,x 减小表示向左;y 增大表示向上,y 减小表示向下。在"宝石果子"概念挑战中,你要依次收集所有宝石、最后进入村庄(红色 X)。注意路线里有些坐标会重复出现——这是因为英雄需要"原路返回"经过某个安全的交叉点,再前往下一颗宝石:
# 移动到所有宝石处,然后移动到红色 X 标记。 hero.moveXY(8, 48) hero.moveXY(8, 12) hero.moveXY(12, 26) hero.moveXY(24, 26) hero.moveXY(34, 14) hero.moveXY(24, 26) # 回到交叉点 hero.moveXY(40, 56) hero.moveXY(74, 34) # 进入村庄(红色 X)
到目前为止,你的代码都是"每行执行一次"的顺序结构。但有时候你希望一段动作不停地重复。这时就用到 while True: 循环——它的意思是"永远重复下面缩进的这些代码"。
注意 while True: 后面有个冒号,下一行开始的代码要向右缩进(通常 4 个空格)。缩进的代码才"属于"这个循环、会被反复执行。这是 Python 用缩进表示代码层级的独特设计——别忘了缩进!
致命的猎头者正盯着英雄的脑袋!只要它看不到你,就没法攻击你。解决办法是用 while True 循环,让英雄在森林里沿着一圈坐标不停地跑动,始终保持移动、不被锁定:
# 区域内有一名猎头者! # 在森林附近跑动,以躲避他的视线。 while True: hero.moveXY(56, 44) hero.moveXY(40, 56) # 用 moveXY 在森林中保持移动,以确保存活。 # 不要忘记缩进。 hero.moveXY(24, 44) hero.moveXY(24, 24) hero.moveXY(40, 12) hero.moveXY(56, 24)
这六个坐标点围成了一个大致的环形路线。英雄跑完最后一个点 (56, 24) 后,循环又会从头开始回到 (56, 44),如此周而复始,绕圈奔跑——猎头者永远抓不住一个不停移动的目标!
恭喜你完成了边际森林第一单元!你已经从"相对移动"升级到了"坐标定位",掌握了用一对数字精确指挥英雄的能力。来回顾一下核心概念:
| 核心概念 | 关键点 | 对应关卡 |
|---|---|---|
| 笛卡尔坐标系 | 笛卡尔发明,连接几何与代数 | — |
| 一维数轴 | 原点、正方向、单位长度 | — |
| 二维坐标系 | x 轴水平、y 轴竖直、四象限 | 全部 |
| 坐标 (x, y) | 有序数对,x 在前 y 在后 | 全部 |
| 悬停查坐标 | 鼠标移到地图即可看坐标 | 全部 |
| buildXY | buildXY("fence", x, y) | 平原森林保卫战 |
| moveXY | moveXY(x, y),自带寻路 | 蜿蜒小径 |
| 修改坐标调试 | 纠正路径、避开陷阱 | 一步走错 |
| 收集路线规划 | 依次经过所有目标 | 宝石果子 |
| while True 循环 | 重复动作,注意缩进 | 森林逃避 |
现在你已经能给森林里的每个位置"指路"了。继续加油,勇敢的英雄!🌲🗡️
CodeCombat 编程知识 · 边际森林:第一单元
CodeCombat教研团队