去做题

第十三单元:坐标遍历 · 用嵌套循环扫遍二维网格

Unit 13: Grid Traversal — Nested Loops over 2D Coordinates
计算机科学 5 · 安息云峰 Cloudrip Mountain · 第 20–22 关:网格搜索 → 网格雷区 → 拿起武器!

13.1 什么是坐标遍历

What Is Grid Traversal

一张旧藏宝图上写着"宝藏在 x:?0, y:?0",可关键数字看不清;食人魔大军排着整齐的队形逼近村庄,等着被一整片地雷伺候;沉睡的营地里四排帐篷、每排五顶,士兵还在梦乡……这三个任务有一个共同点:你要处理的不是一条线上的点,而是一整片区域里的每一个点。要走遍一个二维平面上的所有位置,就需要把上一单元的嵌套循环用到坐标上——这就是本单元的主题:坐标遍历(grid traversal)。

本单元主线:用两层嵌套 for 循环遍历二维坐标空间——外层循环控制一个坐标(行 / X),内层循环控制另一个坐标(列 / Y);用步长 step 设定相邻点的间距;用"起点 + 步长 × 数量"算出遍历范围;从对象属性(如 tentDistanceX/Y)读取真实间距而非硬编码;并懂得逐行遍历比逐列遍历移动更省时。

13.1.1 从一维到二维(From a Line to a Plane)

13.1.1.1 一个循环走一条线,两个循环铺一片 One loop for a line, two for a plane

第九单元你用单个 for 循环沿一条边界巡逻——那是一维的:只有一个坐标在变。而一片区域是二维的:X 和 Y 都要变。关卡概览一语点破:"你可以用 for 循环映射出不止一维的位置!嵌套 for 循环将覆盖每个单独的方块。"外层循环每固定一个 X,内层循环就把所有 Y 走一遍,两层配合,平面上的每个格点都会被访问到。

扣哒世界 坐标遍历示意 - koudashijie.com
图1:嵌套循环把二维区域"织"成一张网——外层定一列/行,内层走完该列/行的每个点

13.1.2 嵌套循环画网格(Drawing a Grid)

13.1.2.1 每个 (x, y) 组合都是一个格点 Every (x, y) pair is a grid point

嵌套坐标循环产生的所有 (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)
嵌套循环踏遍每个格点 Y↓ X→

13.2 外层行 · 内层列

Outer Row, Inner Column

13.2.1 外循环 X、内循环 Y(Outer X, Inner Y)

13.2.1.1 谁在外、谁在内,决定走法 Which loop is outer decides the path

两层循环分别管一个坐标。若外层管 X、内层管 Y(如"网格搜索"),英雄会先固定一个 X,把这一竖列的所有 Y 走完,再换下一个 X——即逐列移动。反过来外层管 Y、内层管 X(如"拿起武器!"),就是逐行移动。哪个在外由任务需要决定,效率也可能因此不同(见 13.5)。

外层管一个坐标,内层管另一个坐标 for x in range(...): for y in range(...): hero.moveXY(x, y) ← 外层:固定 X(选一列) ← 内层:走完这一列所有 Y ← 到达每个 (x, y) 点

13.2.2 遍历顺序(The Order of Visits)

13.2.2.1 内层转完一整圈,外层才走一步 Inner finishes before outer advances

和所有嵌套循环一样:内层要完整跑完,外层才前进一格。以"网格搜索"为例(外 X 内 Y),执行轨迹是:X=20 时 Y 从 20 走到 50;然后 X=30,Y 又从 20 走到 50……如此往复。理解这个顺序,才能预测英雄的实际走位路线。

步骤xy到达点
12020(20, 20)
22030(20, 30)
32040(20, 40)
42050(20, 50)
53020(30, 20) ← 换列,Y 重头开始

13.3 坐标增量步长

Coordinate Step Increments

13.3.1 用 step 控制间距(Spacing with step)

13.3.1.1 步长就是相邻点的距离 The step is the gap between points

坐标遍历里,range 的第三个参数 step 直接决定相邻两个格点之间的距离。"网格搜索"用 step = 10,格点每隔 10 个单位一个;"网格雷区"用步长 8,让地雷密密麻麻布满,不留缝隙。步长选多大,取决于任务对"疏密"的要求——搜宝要覆盖所有可能点,布雷则要密到无法穿越。

13.3.2 起点+步长×数量(start + step × count)

13.3.2.1 用"数量"反推终点 Derive the end from the count

当你知道"从哪开始、每格多宽、一共几格"时,终点可以算出来:终点 = 起点 + 步长 × 数量。"网格雷区"就用了这个技巧——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,别人(和几天后的你)就得反推它是怎么来的。把意图写进公式里,代码才好懂、好改。

13.4 读取对象属性

Reading Object Attributes

13.4.1 tentDistanceX/Y(Getting Spacing from Data)

13.4.1.1 从单位身上读取真实间距 Read the real spacing from a unit

"拿起武器!"里,帐篷间距不是固定数字,而是藏在军士(sergeant)这个对象的属性里。你先找到军士,再读它的 tentDistanceX 和 tentDistanceY,就得到了横向、纵向的真实间距,用它们当步长。这体现了一个重要思想:数据可以来自游戏对象,而不必写死在代码里。

# 从军士对象读取帐篷间距
sergeant = hero.findNearest(hero.findFriends())
stepX = sergeant.tentDistanceX   # 横向间距
stepY = sergeant.tentDistanceY   # 纵向间距

13.4.2 拒绝硬编码(Avoid Hard-Coding)

13.4.2.1 每个营地间距不同,写死会失败 Hard-coded values break on other maps

关卡特别警告:"每个营地会略有不同,所以不要对值进行硬编码!"如果你把间距写成固定的数字,换一个营地布局就全乱套。用 tentDistanceX/Y 读取当前营地的真实间距,代码就能自动适配任何营地——这正是可复用、可泛化的程序应有的样子。

❌ 硬编码(换营地就错)
stepX = 18   # 写死
stepY = 16   # 换图失效
✅ 读属性(自动适配)
stepX = sergeant.tentDistanceX
stepY = sergeant.tentDistanceY

13.5 逐行遍历优化

Row-by-Row Efficiency

13.5.1 逐行比逐列快(Rows Beat Columns Here)

13.5.1.1 让英雄少走冤枉路 Minimize the hero's travel

"拿起武器!"里关卡反复提醒:"逐行去叫士兵要比逐列叫更快。"为什么?因为营地是宽 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!")
效率也是策略:算法正确只是第一步,路径是否高效常常决定成败。在限时关卡里("拿起武器!"要赶在食人魔进攻前叫醒所有士兵),选对遍历方向,就能省下宝贵的时间。这提醒我们:同一个网格,遍历顺序不同,代价可能天差地别。

13.6 实战关卡精讲

Level Strategy & Analysis

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

第 20 关 · 网格搜索

🗺️ 网格搜索 —— 走遍每个可能的藏宝点

扣哒世界 网格搜索关卡 - koudashijie.com
图2:网格搜索——藏宝坐标看不清,就用嵌套循环把边界内每个可能的点都走一遍

关卡背景与目标

藏宝图上的坐标 "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)。用变量的代码含义更清楚,改边界时只需改一处。这是好习惯的起点。
第 21 关 · 网格雷区

💣 网格雷区 —— 密布地雷等敌人踏入

扣哒世界 网格雷区关卡 - koudashijie.com
图3:网格雷区——用嵌套循环把火焰陷阱密密麻麻铺成一片,等食人魔队形踏入后一起引爆

关卡背景与目标

食人魔排着整齐的队形进军,正好利用这一点:在他们必经之处铺一整片地雷,等全体踏入再一起引爆。你有 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。这把本单元的坐标遍历和前面学的"距离判断 + 循环控制"串在了一起。
第 22 关 · 拿起武器!

📢 拿起武器! —— 逐行叫醒沉睡的军队

扣哒世界 拿起武器关卡 - koudashijie.com
图4:拿起武器!——4 排 5 列共 20 顶帐篷,逐行走到每个 X 标记处用 say 唤醒士兵

关卡背景与目标

食人魔即将进攻,你的军队却在睡觉!营地有 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 排 → Y 方向循环 × 4;5 顶 → X 方向循环 × 5。数量乘错(比如 Y 乘 5、X 乘 4)会漏叫或多跑。动手前先数清"几排几列",把数量填对。
战斗开始要撤离:唤醒全部士兵后,别让英雄留在营地——食人魔看见英雄会派增援。及时离开是这关的隐藏要求,算法完成后还要顾及走位安全。

13.7 常见错误

Common Mistakes

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

13.7.1.1 坐标遍历的典型问题 Typical issues in grid traversal

扣哒世界 坐标遍历常见错误速查 - koudashijie.com
图5:内外循环坐标弄反、终点漏格、硬编码间距,是本单元最常见的失误
易错点清单:
  • 内外循环坐标弄反——想逐行却把 X 放外层,导致逐列移动、路径绕远甚至超时。
  • 内层用错变量——内层循环体里仍用外层变量、忘了用内层的,格点全落在一条线上。
  • 终点漏一格——range 左闭右开,想包含最后一列/行要把终点写足(如 rightBorder 取 61)。
  • 数量乘错——"起点+步长×数量"里数量填错,会漏掉或多走整行整列。
  • 硬编码间距——把 tentDistanceX/Y 写成固定数字,换营地立刻失效。
  • step 与坐标不匹配——X 和 Y 的步长可能不同(如 tentDistanceX ≠ tentDistanceY),别用同一个。
  • 把自己困进陷阱——布雷时忘了每列后右移避险,英雄被自建陷阱围住。
  • 忘记引爆/撤离——坐标遍历完成后,还要处理引爆时机或战斗开始时离场等收尾动作。

本单元小结 ✨

恭喜你把嵌套循环从"数组"推广到了"整片二维空间"!这一单元你掌握了坐标遍历:用两层嵌套 for 循环扫遍网格上每个 (x, y) 点,理解了外层控制一个坐标(行/X)、内层控制另一个坐标(列/Y)以及"内层跑完外层才前进"的顺序;你会用 step 设定格点间距、用"起点 + 步长 × 数量"清晰地界定范围;学会了从对象属性 tentDistanceX/Y 读取真实数据、拒绝硬编码,让程序自动适配不同营地;还懂得了逐行遍历比逐列更省路的效率之道。你在"网格搜索"里地毯式寻宝、在"网格雷区"里密布陷阱、在"拿起武器!"里逐行唤醒全军。来回顾一下核心概念:

核心概念关键点对应关卡
坐标遍历嵌套循环扫遍二维网格三关
外行内列外层一坐标、内层另一坐标三关
遍历顺序内层跑完外层才前进三关
坐标步长step = 相邻点间距三关
起点+步长×数量用数量反推终点网格雷区·拿起武器
读取对象属性tentDistanceX/Y拿起武器
拒绝硬编码数据来自对象,自动适配拿起武器
逐行遍历优化沿更长方向走更省路拿起武器
buildXY 布点网格化建造陷阱网格雷区
收尾动作避险/引爆/撤离网格雷区·拿起武器

懂得用两层循环织出整片网格、又能读活数据、走对路线的英雄,
才是云峰之上真正掌控空间的程序员。继续向雪山之巅挺进吧,勇敢的程序员!🏔️🗺️💣

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

扣哒世界教研团队