去做题

第十七单元:圆形迷宫 · 陷阱规避与几何路径规划

Unit 17: Circular Maze — Hazard Avoidance & Geometric Path Planning
计算机科学 6 · 封神冰川 Deified Glacier · 破碎的圈子

17.1 圆形迷宫概述

Overview of the Circular Maze

封神冰川的最深处,冰封的宝箱被六道同心的火焰陷阱圆环重重守护。每一圈陷阱看似密不透风,却都藏着一道缺口(opening)——只要你能沿着圆周找到它,就能一层层向内推进,直抵中心的宝藏。可这里的空中守卫盯得极紧:不许跳跃、不许召唤、不许放置标记、不许使用魔法,任何违规都会招致惩罚。你能做的,只有用最纯粹的移动与几何计算,在火焰的缝隙中舞出一条生路。本单元只有一关,却把陷阱规避与几何路径规划这两项 AI 导航的硬核技能演绎到了极致。

本单元主线:学会用 findHazards() 获取全部陷阱数组;用 distanceTo 计算与陷阱的距离并找出最近的那个;理解陷阱的攻击范围(trapAttackRange)并始终保持在其之外;用极坐标与三角函数沿圆周规划路线;通过沿圆周扫描 + 安全阈值判断找到每一圈的缺口,层层向内安全抵达宝箱。这些正是 AI 在带危险源的环境中做路径规划的核心方法。

17.1.1 同心圆陷阱(Concentric Rings of Traps)

17.1.1.1 一层套一层的火焰圆环 Ring after ring of fire traps

与上一单元的方格迷宫不同,这一关的迷宫是圆形的:陷阱以中心宝箱 center 为圆心,排成六个同心圆(concentric circles),每个圆的半径依次递减。这种结构决定了我们不能再用"上下左右走格子"的思路,而要改用圆周运动——沿着某一圈的半径转圈,去寻找那圈上唯一的缺口。

🎯 就像层层设防的冰环靶心
想象一个巨大的靶子,由外到内一圈圈火环包围着靶心的宝箱。每一圈火环都不是完整的——总有一处熄灭的缺口。你要做的,是沿着当前这圈的圆周慢慢挪动,一边走一边"听"最近的火焰离你多远;当火焰突然远去,说明你正对着缺口,趁机向内一步跨进下一圈。如此六次,便能踏入靶心。
宝箱 六层带缺口的火焰陷阱圆环
火焰陷阱环(带缺口)
中心宝箱 center

17.1.2 缺口与安全路(Openings and Safe Paths)

17.1.2.1 找到缺口才能向内一层 Only through the opening can you go inward

每一圈陷阱都有一个缺口。缺口的位置就是那圈上"离陷阱最远"的地方——当你站在缺口正对面时,最近的火焰会明显远离你。抓住这个特征,策略就清晰了:沿着当前半径的圆周一点点转动,持续测量到最近陷阱的距离,一旦距离超过某个安全阈值,就说明缺口到了,立刻沿半径向内跨进下一圈。

四条铁律不可违反:本关明确禁止跳跃、召唤、放置标记、使用魔法。这意味着你不能靠传送或飞跃硬闯过火环,唯一的通路就是脚踏实地地绕、精确地算。约束越严,越考验你的几何与算法功底——这正是本关的魅力所在。

17.2 感知陷阱

Sensing the Hazards

17.2.1 findHazards 获取陷阱(Getting the Hazard Array)

17.2.1.1 一次拿到全部陷阱 Retrieve all traps at once

hero.findHazards() 是本关的核心感知工具——它返回一个数组,包含场上所有的火焰陷阱("fire-traps")。数组里的每个元素都是一个陷阱对象,带有 pos(位置,含 x / y)等属性。有了这份"陷阱清单",你就能计算它们的位置、距离,进而规划安全路线。

# 获取所有火焰陷阱组成的数组
hazards = hero.findHazards()

# 遍历每个陷阱,读取它的位置
for h in hazards:
    hx = h.pos.x   # 陷阱的 x 坐标
    hy = h.pos.y   # 陷阱的 y 坐标
findHazards 与 findEnemies 的区别:findHazards 专门返回环境陷阱(如火焰陷阱),而不是会走动的敌人。陷阱通常固定不动,但有一个"攻击范围"——一旦你踏入,它就会被触发。把陷阱当作"静态危险源"来处理,是路径规划里非常常见的建模方式。

17.2.2 陷阱攻击范围(Trap Attack Range)

17.2.2.1 靠得太近就会被触发 Get too close and it triggers

每个火焰陷阱都有一个攻击范围 trapAttackRange(本关约为 3 米)。只要你移动到距陷阱小于这个范围的地方,陷阱就会被脚步声激活并喷出火焰。因此规避的本质就是:始终让自己与任何陷阱的距离大于攻击范围。关卡还给了 radiusStep(圆环半径间隔)、center(圆心)等常量,帮你推算每一圈的几何位置。

# 关卡预置的常用常量
trapAttackRange = 3          # 陷阱攻击范围:靠近 3 米内会触发
radiusStep = 10              # 相邻两圈之间的半径间隔
center = {"x": 68, "y": 68}   # 圆心(宝箱位置)
陷阱 攻击范围(危险区) 你 安全:距离 > 范围 distanceTo

17.3 计算与陷阱的距离

Computing Distance to Hazards

17.3.1 distanceTo 测距(Measuring Distance)

17.3.1.1 我离那个陷阱有多远 How far am I from that trap

hero.distanceTo(target) 返回英雄当前位置到目标之间的直线距离。它背后其实就是欧几里得距离公式——两点坐标差的平方和再开方。有了它,我们就能量化"我离危险有多近",从而做出"走还是不走"的判断。这是所有基于距离的规避决策的基础。

distanceTo 背后的欧几里得距离 distance = √( (x₂-x₁)² + (y₂-y₁)² ) 两点横纵坐标之差,平方相加再开方,即为直线距离
# 计算到某个陷阱的距离
for h in hazards:
    d = hero.distanceTo(h)
    if d < trapAttackRange:
        # 太危险!这个位置会触发陷阱
        pass

17.3.2 找最近的陷阱(Finding the Nearest Trap)

17.3.2.1 只需盯住最危险的那一个 Just watch the most dangerous one

要判断当前位置是否安全,并不需要逐一比较所有陷阱——只要盯住离你最近的那个即可。因为如果连最近的陷阱都在安全距离之外,其余更远的自然更安全。hero.findNearest(hero.findHazards()) 就能从陷阱数组里挑出最近的一个,再用 distanceTo 测它的距离,就得到了"当前最危险的距离"。

# 关键片段:找到最近的陷阱并测距
nearest = hero.findNearest(hero.findHazards())
if hero.distanceTo(nearest) >= threshold:
    # 最近的陷阱都够远 → 此处正对缺口,可以向内走
    pass
"盯最近"是规避问题的通用简化:面对一群危险源,与其算清和每一个的关系,不如只关注"最迫近的威胁"。只要保证与最近危险的安全距离,整体就安全。这个"最近邻(nearest neighbor)"思想在避障、碰撞检测、聚类算法里反复出现。

17.4 基于几何位置的路线规划

Geometric Route Planning

17.4.1 极坐标与圆周(Polar Coordinates & Circles)

17.4.1.1 用"角度+半径"描述圆上的点 Describe points on a circle by angle and radius

要沿圆周移动,笛卡尔坐标(x, y)不好使,极坐标(角度 + 半径)才是天生的语言。给定圆心 (cx, cy)、半径 R 和角度 angle,圆上任意一点都能算出来。让角度从 0 逐渐增大到 360,点就会沿圆周转一整圈——这正是我们"绕圈找缺口"所需要的运动。

# 圆心与半径设定
cx = 68
cy = 68
rad = Math.PI * 2 / 360   # 角度转弧度的系数(1度对应的弧度)
角度要先转成弧度:Math.cos / Math.sin 接受的是弧度而非角度。一整圈 360 度等于 2π 弧度,所以 1 度 = 2π / 360 弧度。代码里先算出这个系数 rad,用角度乘以它就得到弧度。忘了这步转换是最常见的坑。

17.4.2 三角函数算坐标(Trig for Coordinates)

17.4.2.1 cos 管 x,sin 管 y Cosine for x, sine for y

把极坐标转回英雄能用的 (x, y),靠的就是三角函数:x = cx + cos(角) × 半径,y = cy + sin(角) × 半径。本关的巧妙之处在于同时算两个半径的点:当前圈的外半径 R 上的点 (X, y) 用来绕圈行走;比它小一圈的内半径 iR 上的点 (ix, iy) 则是"缺口对齐后向内跨进"的落脚点。

# 关键片段:同时算当前圈与内圈的点坐标
angle = (angle + 2) % 360          # 角度每次+2度,绕圈扫描
X = cx + Math.cos(angle * rad) * R    # 当前圈上点的 x
y = cy + Math.sin(angle * rad) * R    # 当前圈上点的 y
ix = cx + Math.cos(angle * rad) * iR   # 内圈同角度点的 x
iy = cy + Math.sin(angle * rad) * iR   # 内圈同角度点的 y
圆心(cx,cy) 外圈(X,y) 内圈(ix,iy) angle 同一角度上的外圈点与内圈点
角度步长决定"扫描精度":代码里角度每次 +2 度。步长越小,绕圈越平滑、越不容易错过狭窄的缺口,但移动次数更多、更慢;步长越大越快,却可能"跳过"缺口。这是精度与效率的权衡——几乎所有采样式算法都要面对它。

17.5 绕开陷阱寻找安全路径

Navigating Around Hazards

17.5.1 沿圆周扫描(Scanning Along the Circle)

17.5.1.1 一边转圈,一边探火焰 Circle around while sensing the flames

规避的核心动作是"沿当前圈扫描":让角度每次增加一点,英雄就沿圆周挪一小步,每挪一步都用 findNearest + distanceTo 测一下离最近陷阱的距离。绝大多数位置都紧贴火环(距离小),只有走到缺口正对面时,最近陷阱才会陡然变远——这就是"该向内走"的信号。

角度 +2° moveXY 到圆上点 测最近陷阱距离 ≥阈值?向内 / 继续转 未到缺口 → 继续转圈扫描

17.5.2 安全阈值判断(The Safety Threshold)

17.5.2.1 距离够大就向内跨进 Distance big enough? Step inward

判断"是否到了缺口",靠的是一个安全阈值 threshold(本关约 5.8,略大于攻击范围)。当最近陷阱的距离 >= threshold 时,说明当前角度正对缺口且向内走也不会踩到火——于是移动到内圈的点 (ix, iy),圈数减一,进入下一层继续扫描。如此循环六次,当所有圈都穿过后,直接走向圆心的宝箱。

# 关键片段:阈值判断 + 向内推进(结构示意)
hero.moveXY(X, y)                       # 沿当前圈走到扫描点
nearest = hero.findNearest(hero.findHazards())
if hero.distanceTo(nearest) >= threshold:
    hero.moveXY(ix, iy)              # 缺口!向内跨进下一圈
    circle -= 1                   # 剩余圈数减一
    break                          # 跳出本圈扫描,换到内圈重新绕
阈值必须大于攻击范围:阈值 threshold 要设得比 trapAttackRange 稍大,留出安全余量。如果阈值太小、几乎等于攻击范围,英雄可能在"擦边"处向内走时正好蹭到火焰而触发陷阱;太大又可能永远等不到满足条件的位置、卡在原地绕不停。调对这个阈值,是通关的关键。
这就是"势场/距离场"避障的雏形:持续测量到最近危险的距离、把它和一个安全阈值比较来决定动作——这正是机器人学里人工势场法(artificial potential field)的核心直觉:危险源产生"排斥力",目标产生"吸引力",智能体在两种力的合成下找到安全路径。本关用最朴素的方式,让你亲手实现了这一思想。

17.6 实战关卡精讲

Level Strategy & Analysis

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

第 35 关 · 破碎的圈子

🎯 破碎的圈子 —— 绕过火环直取宝箱

扣哒世界 破碎的圈子关卡 - koudashijie.com
图2:破碎的圈子——六层带缺口的火焰陷阱环绕着中心宝箱,禁止跳跃/召唤/标记/魔法,只能靠几何计算逐层穿越

策略:逐圈绕行,遇缺口即向内一层

本关的整体策略可以概括为一句话:从最外圈开始,沿圆周绕行扫描,一旦探到缺口就向内跨一圈,重复直到抵达圆心。由于禁止一切"取巧"手段(跳跃、召唤、标记、魔法),唯一可行的就是老老实实用 moveXY 沿几何路线走,并用 findHazards + distanceTo 时刻感知火焰、绕开攻击范围。关卡把陷阱布成同心圆,恰好把问题化归为"在每一圈上找那个距离陷阱最远的角度"。

思路拆解一:用常量描述几何结构

先把迷宫的几何参数固化成常量:圆心 (cx, cy) = (68, 68)、单圈半径基数 r、总圈数 circle = 5(从外向内递减)、微调量 diff 与安全阈值 threshold。角度转弧度系数 rad = Math.PI * 2 / 360。这些常量把"迷宫长什么样"和"怎么走"清晰地分开,便于调参。

# 关键片段:几何常量
r = 18                 # 半径步长基数
diff = 0.5            # 落点微调,避免贴边触发
threshold = 5.8       # 安全阈值(略大于攻击范围)
circle = 5             # 待穿越的圈数(由外向内)
cx = 68
cy = 68
rad = Math.PI * 2 / 360   # 1度对应的弧度

思路拆解二:外层循环控制"还剩几圈"

最外层用 while True 循环,每次处理"当前这一圈"。开头先判断:如果 circle 已经为 0(所有圈都穿完),就直接 moveXY 到圆心宝箱并 break 结束。否则,根据当前 circle 算出这一圈的外半径 R 和向内一圈的内半径 iR = R - r,准备开始沿这圈扫描。

# 关键片段:外层循环——逐圈处理
while True:
    if not circle:                  # 圈数为0 → 全部穿过
        hero.moveXY(68, 67)          # 直取中心宝箱
        break
    R = r * circle + r / 2 - diff  # 当前圈的外半径
    iR = R - r                    # 向内一圈的半径
    # ……进入内层:沿这圈扫描找缺口……

思路拆解三:内层循环沿圆周扫描找缺口

内层同样是循环,让 angle 每次 +2 度绕圈。每个角度都算出当前圈点 (X, y) 与内圈点 (ix, iy),先 moveXY(X, y) 走到扫描位置,再用 findNearest 找最近陷阱、distanceTo 测距。若距离 ≥ 阈值,说明正对缺口:立刻 moveXY(ix, iy) 向内跨进,circle -= 1,break 回到外层去处理内一圈;否则继续增大角度扫描下一个点。

# 关键片段:内层扫描——找到缺口就向内
while True:
    angle = (angle + 2) % 360
    X = cx + Math.cos(angle * rad) * R
    y = cy + Math.sin(angle * rad) * R
    ix = cx + Math.cos(angle * rad) * iR
    iy = cy + Math.sin(angle * rad) * iR
    hero.moveXY(X, y)
    nearest = hero.findNearest(hero.findHazards())
    if hero.distanceTo(nearest) >= threshold:
        hero.moveXY(ix, iy)   # 缺口对齐,向内一层
        circle -= 1
        break
为什么"距离变远"就意味着缺口?陷阱沿圆环密布,所以在圆周上大部分角度,最近陷阱都近在咫尺。唯独在缺口处,那一段没有陷阱,最近的火焰要么是缺口两侧较远的,要么是别的圈——距离自然更大。用"最近陷阱距离突然增大"来定位缺口,是把几何特征转化为可判断条件的漂亮一招。
起手先走到最外圈:循环开始前,代码先 moveXY 到最外圈的一个安全起点(如 128 - r/2 - diff 处),再开始绕圈。先站到正确的半径上,再沿圆周扫描——这个"先就位、再扫描"的两步式起手,能让后续循环逻辑保持干净统一。
微调量 diff 别忽视:代码里到处减去一个小小的 diff(0.5)。它的作用是让落点稍微"内缩"一点,避免因浮点误差恰好停在陷阱攻击范围的边界上而意外触发。处理连续几何时,预留一点安全余量是非常实用的工程技巧。

17.7 常见错误

Common Mistakes

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

17.7.1.1 陷阱规避与几何计算的典型问题 Typical issues with hazard avoidance and geometry

扣哒世界 陷阱规避常见错误速查 - koudashijie.com
图3:忘转弧度、阈值设错、圈数没递减、贴边触发陷阱,是本单元最常见的失误
易错点清单:
  • 角度没转弧度——Math.cos/sin 要弧度,直接传角度会算出完全错误的坐标,英雄乱跑撞火。
  • 阈值设得不对——threshold 太小会贴边触发陷阱,太大会永远找不到缺口卡死在原地。
  • 忘记递减圈数——穿过一圈后没 circle -= 1,会在同一圈无限绕圈出不去。
  • 只看单个陷阱——应对整群陷阱要用 findNearest 盯最近的那个,而不是随便挑一个测距。
  • 忽略攻击范围——移动落点距陷阱必须 大于 trapAttackRange,擦边即触发。
  • 缺少安全余量——不留 diff 微调,浮点误差可能让你恰好停在危险边界上。
  • 违反关卡禁令——用了跳跃/召唤/标记/魔法会被守卫惩罚,本关只能纯移动+几何计算。
  • 角度步长太大——步长过大会"跳过"狭窄缺口;太小又太慢,要权衡精度与效率。

本单元小结 ✨

恭喜你穿越了封神冰川最凶险的火焰圆环,取得了中心的宝藏!这一关虽只有一个,却把 AI 导航中最实用的两大技能锤炼得淋漓尽致。你学会了用 findHazards() 一次性获取全部陷阱,用 distanceTo 和 findNearest 计算并盯住最近的危险,理解了陷阱的攻击范围并始终保持在安全距离之外;更掌握了用极坐标 + 三角函数沿圆周做几何路线规划,通过沿圆周扫描 + 安全阈值判断精准定位每一圈的缺口、逐层向内推进。从"感知危险"到"量化距离",再到"几何规划"与"阈值决策",你已经亲手实现了一套完整的避障导航逻辑——这正是机器人与自动驾驶背后的核心思想。来回顾一下核心概念:

核心概念关键点对应写法
获取陷阱返回陷阱数组hero.findHazards()
攻击范围靠近即触发trapAttackRange
计算距离欧几里得直线距离hero.distanceTo(h)
找最近陷阱只盯最迫近威胁findNearest(findHazards())
极坐标角度+半径描述圆angle, R
三角函数算点cos管x,sin管ycx+cos(a)*R, cy+sin(a)*R
沿圆周扫描角度递增找缺口angle = (angle+2)%360
阈值判断够远才向内跨distanceTo >= threshold

懂得"感知危险、量化距离、几何规划、留足余量"——在火焰缝隙中舞出安全路径的智慧,正是智能体驾驭危险世界的通行证。
封神冰川的圆环试炼圆满完成,恭喜你走完这段冰与火的算法之旅!🎯🔥❄️

扣哒世界 编程知识 · 计算机科学 6(封神冰川):第十七单元

扣哒世界教研团队