一张旧藏宝图上写着"宝藏在 x:?0, y:?0",可关键数字看不清;食人魔大军排着整齐的队形逼近村庄,等着被一整片地雷伺候;沉睡的营地里四排帐篷、每排五顶,士兵还在梦乡……这三个任务有一个共同点:你要处理的不是一条线上的点,而是一整片区域里的每一个点。要走遍一个二维平面上的所有位置,就需要把上一单元的嵌套循环用到坐标上——这就是本单元的主题:坐标遍历(grid traversal)。
for 循环遍历二维坐标空间——外层循环控制一个坐标(行 / X),内层循环控制另一个坐标(列 / Y);用步长 step 设定相邻点的间距;用"起点 + 步长 × 数量"算出遍历范围;从对象属性(如 tentDistanceX/Y)读取真实间距而非硬编码;并懂得逐行遍历比逐列遍历移动更省时。
第九单元你用单个 for 循环沿一条边界巡逻——那是一维的:只有一个坐标在变。而一片区域是二维的:X 和 Y 都要变。关卡概览一语点破:"你可以用 for 循环映射出不止一维的位置!嵌套 for 循环将覆盖每个单独的方块。"外层循环每固定一个 X,内层循环就把所有 Y 走一遍,两层配合,平面上的每个格点都会被访问到。
嵌套坐标循环产生的所有 (x, y) 组合,正好铺满一个规则的点阵。关卡概览给了最经典的示范:在 100×100 的方形里,从 (10,10) 一直走到 (90,90),把每个格点都踏一遍。
# 走遍 100x100 里每个格点 for x in range(10, 100, 10): for y in range(10, 100, 10): hero.moveXY(x, y) # 英雄依次到达 (10,10)…(10,90)…(90,90)
两层循环分别管一个坐标。若外层管 X、内层管 Y(如"网格搜索"),英雄会先固定一个 X,把这一竖列的所有 Y 走完,再换下一个 X——即逐列移动。反过来外层管 Y、内层管 X(如"拿起武器!"),就是逐行移动。哪个在外由任务需要决定,效率也可能因此不同(见 13.5)。
和所有嵌套循环一样:内层要完整跑完,外层才前进一格。以"网格搜索"为例(外 X 内 Y),执行轨迹是:X=20 时 Y 从 20 走到 50;然后 X=30,Y 又从 20 走到 50……如此往复。理解这个顺序,才能预测英雄的实际走位路线。
| 步骤 | x | y | 到达点 |
|---|---|---|---|
| 1 | 20 | 20 | (20, 20) |
| 2 | 20 | 30 | (20, 30) |
| 3 | 20 | 40 | (20, 40) |
| 4 | 20 | 50 | (20, 50) |
| 5 | 30 | 20 | (30, 20) ← 换列,Y 重头开始 |
坐标遍历里,range 的第三个参数 step 直接决定相邻两个格点之间的距离。"网格搜索"用 step = 10,格点每隔 10 个单位一个;"网格雷区"用步长 8,让地雷密密麻麻布满,不留缝隙。步长选多大,取决于任务对"疏密"的要求——搜宝要覆盖所有可能点,布雷则要密到无法穿越。
当你知道"从哪开始、每格多宽、一共几格"时,终点可以算出来:终点 = 起点 + 步长 × 数量。"网格雷区"就用了这个技巧——range(12, 12 + 8*6, 8) 表示"从 12 开始,步长 8,共 6 列";range(12, 12 + 8*7, 8) 表示"7 行"。这样写比手算终点更清晰,一眼就能看出布了几行几列。
# 起点12,步长8,X方向6列、Y方向7行 for x in range(12, 12 + 8*6, 8): # 12,20,28,36,44,52 → 6 个 for y in range(12, 12 + 8*7, 8): # 12,20,…,60 → 7 个 hero.buildXY("fire-trap", x, y)
12 + 8*6 让"12 是起点、8 是间距、6 是数量"三个信息一目了然。若直接写死终点 60,别人(和几天后的你)就得反推它是怎么来的。把意图写进公式里,代码才好懂、好改。
"拿起武器!"里,帐篷间距不是固定数字,而是藏在军士(sergeant)这个对象的属性里。你先找到军士,再读它的 tentDistanceX 和 tentDistanceY,就得到了横向、纵向的真实间距,用它们当步长。这体现了一个重要思想:数据可以来自游戏对象,而不必写死在代码里。
# 从军士对象读取帐篷间距 sergeant = hero.findNearest(hero.findFriends()) stepX = sergeant.tentDistanceX # 横向间距 stepY = sergeant.tentDistanceY # 纵向间距
关卡特别警告:"每个营地会略有不同,所以不要对值进行硬编码!"如果你把间距写成固定的数字,换一个营地布局就全乱套。用 tentDistanceX/Y 读取当前营地的真实间距,代码就能自动适配任何营地——这正是可复用、可泛化的程序应有的样子。
stepX = 18 # 写死 stepY = 16 # 换图失效
stepX = sergeant.tentDistanceX stepY = sergeant.tentDistanceY
"拿起武器!"里关卡反复提醒:"逐行去叫士兵要比逐列叫更快。"为什么?因为营地是宽 5 顶、高 4 排的横向布局。若逐行走(外层 Y、内层 X),英雄在每一排里横向连续移动、走完一排换下一排,路径顺畅;若逐列走(外层 X、内层 Y),英雄要在窄窄的纵向来回蹿、频繁跨排,绕远得多。让内层循环沿"更长的那个方向"走,能减少来回折返。
# 逐行遍历:外层 Y(4 排),内层 X(5 顶) for y in range(firstY, firstY + stepY*4, stepY): for x in range(firstX, firstX + stepX*5, stepX): hero.moveXY(x, y) hero.say("Wake UP!")
这一节聚焦每一关的策略、思路与算法设计,只展示用于说明的关键片段,完整通关程序请你自己动手完成。
藏宝图上的坐标 "x:?0, y:?0" 数字模糊,只知道 ?0 可能是 10、20、30……树木和石头的位置已找到,圈定了一个矩形范围。策略很直接:既然不知道确切点,就把范围内所有可能的点都搜一遍,在每个点用 say 提示农民试挖。
关卡给好了边界变量:leftBorder=20、rightBorder=61、bottomBorder=20、topBorder=51、step=10,还写好了外层 X 循环,留下内层 Y 循环让你补全。思路就是在外层 X 循环里嵌套一个 Y 循环:for y in range(bottomBorder, topBorder, step),每到一个 (x, y) 就 hero.moveXY(x, y) 再 hero.say("试试这里!")。注意用给定的边界变量而不是写死数字,rightBorder 取 61(比 60 多 1)是为了让 60 也被走到。
# 关键片段:需要补全的内层 Y 循环 for x in range(leftBorder, rightBorder, step): for y in range(bottomBorder, topBorder, step): hero.moveXY(x, y) hero.say("试试这里!")
range(bottomBorder, topBorder, step) 而不是 range(20, 51, 10)。用变量的代码含义更清楚,改边界时只需改一处。这是好习惯的起点。
食人魔排着整齐的队形进军,正好利用这一点:在他们必经之处铺一整片地雷,等全体踏入再一起引爆。你有 90 秒建雷区。地雷(fire-trap)只有人类踩到才炸,所以布雷时英雄自己走上去也不怕。
核心是用嵌套循环 + buildXY 铺一片密集网格。关卡给了范围写法 range(12, 12+8*6, 8)(X 方向 6 列)和 range(12, 12+8*7, 8)(Y 方向 7 行),你要在内层循环里 hero.buildXY("fire-trap", x, y)。有一个巧妙细节:每建完一列,让英雄向右挪一步 hero.moveXY(hero.pos.x + 8, hero.pos.y),避免把自己困在刚布好的陷阱堆里。布完雷后进入等待——用 while True 监测最近的敌人,当 hero.distanceTo(nearest) < 20 时移动到雷区中心引爆并 break。
# 关键片段:嵌套循环布雷 + 建完一列右移避险 for x in range(12, 12 + 8*6, 8): for y in range(12, 12 + 8*7, 8): hero.buildXY("fire-trap", x, y) # 一列建完向右走,别困住自己 hero.moveXY(hero.pos.x + 8, hero.pos.y)
moveXY(hero.pos.x + 8, hero.pos.y) 放在外层循环内、内层循环后——每铺完一整列就右移一步,让英雄始终站在还没布雷的空地上。漏了它,英雄可能被自己刚建的陷阱团团围住动弹不得。
while True 持续找最近的敌人,if nearest and hero.distanceTo(nearest) < 20 说明敌军已踏入雷区范围,此时英雄冲进去触发引爆再 break。这把本单元的坐标遍历和前面学的"距离判断 + 循环控制"串在了一起。
食人魔即将进攻,你的军队却在睡觉!营地有 4 排、每排 5 顶共 20 顶帐篷,每顶前有个 X 标记。你要走到每个标记处用 hero.say() 喊醒士兵。要抢时间,还要注意——战斗打响时赶紧离开营地,否则被食人魔看见会招来增援。
这一关综合了本单元所有要点。第一,读属性不硬编码:sergeant = hero.findNearest(hero.findFriends()),再取 stepX = sergeant.tentDistanceX、stepY = sergeant.tentDistanceY 作步长。第二,用起点+步长×数量定范围:起点 firstX=10, firstY=14,X 方向 5 顶、Y 方向 4 排。第三,逐行遍历更快:外层 Y(4 排)、内层 X(5 顶),每到一个标记 moveXY(x, y) 后 say("Wake UP!")。
# 关键片段:读属性 + 逐行遍历唤醒 stepX = sergeant.tentDistanceX stepY = sergeant.tentDistanceY # 外层 Y 走 4 排,内层 X 走 5 顶(逐行更快) for y in range(firstY, firstY + stepY*4, stepY): for x in range(firstX, firstX + stepX*5, stepX): hero.moveXY(x, y) hero.say("Wake UP!")
× 4;5 顶 → X 方向循环 × 5。数量乘错(比如 Y 乘 5、X 乘 4)会漏叫或多跑。动手前先数清"几排几列",把数量填对。
range 左闭右开,想包含最后一列/行要把终点写足(如 rightBorder 取 61)。tentDistanceX/Y 写成固定数字,换营地立刻失效。恭喜你把嵌套循环从"数组"推广到了"整片二维空间"!这一单元你掌握了坐标遍历:用两层嵌套 for 循环扫遍网格上每个 (x, y) 点,理解了外层控制一个坐标(行/X)、内层控制另一个坐标(列/Y)以及"内层跑完外层才前进"的顺序;你会用 step 设定格点间距、用"起点 + 步长 × 数量"清晰地界定范围;学会了从对象属性 tentDistanceX/Y 读取真实数据、拒绝硬编码,让程序自动适配不同营地;还懂得了逐行遍历比逐列更省路的效率之道。你在"网格搜索"里地毯式寻宝、在"网格雷区"里密布陷阱、在"拿起武器!"里逐行唤醒全军。来回顾一下核心概念:
| 核心概念 | 关键点 | 对应关卡 |
|---|---|---|
| 坐标遍历 | 嵌套循环扫遍二维网格 | 三关 |
| 外行内列 | 外层一坐标、内层另一坐标 | 三关 |
| 遍历顺序 | 内层跑完外层才前进 | 三关 |
| 坐标步长 | step = 相邻点间距 | 三关 |
| 起点+步长×数量 | 用数量反推终点 | 网格雷区·拿起武器 |
| 读取对象属性 | tentDistanceX/Y | 拿起武器 |
| 拒绝硬编码 | 数据来自对象,自动适配 | 拿起武器 |
| 逐行遍历优化 | 沿更长方向走更省路 | 拿起武器 |
| buildXY 布点 | 网格化建造陷阱 | 网格雷区 |
| 收尾动作 | 避险/引爆/撤离 | 网格雷区·拿起武器 |
懂得用两层循环织出整片网格、又能读活数据、走对路线的英雄,
才是云峰之上真正掌控空间的程序员。继续向雪山之巅挺进吧,勇敢的程序员!🏔️🗺️💣
扣哒世界 编程知识 · 计算机科学 5(安息云峰):第十三单元
扣哒世界教研团队