欢迎来到计算机科学 5 的第十七单元!在这一单元里,几何不再是纸上的公式,而是拯救村庄的魔法。你要用代码计算矩形的周长与面积、用坐标偏移把圣骑士摆成正方形、用圆面积公式为天选之子注入呐喊之力。更重要的是,你会遇到一个真实编程中无处不在的难题——计算机对小数的处理并不完美,于是你将学到专业程序员的应对之道:近似比较(approximate comparison)。
(a+b)×2、矩形面积 a×b、圆面积 π×r²;用坐标偏移由已知角点算出正方形的其余顶点;并用3% 容差的 almostEqual 函数解决浮点数"几乎相等但不完全相等"的比较难题。
游戏世界里每个单位都有一个位置 pos,包含横坐标 pos.x 和纵坐标 pos.y。两点之间的距离可以直接用 distanceTo 测量。本单元的几何计算,正是建立在"用坐标表示位置、用距离表示边长"之上——比如把两个圣骑士之间的距离当作矩形的一条边。
# 用某个单位作为测量的"基准点" base = hero.findNearest(hero.findFriends()) # 测量基准点到两个圣骑士的距离,作为矩形的两条边 sideSN = base.distanceTo("Femae") # 南北方向的边 sideWE = base.distanceTo("Illumina") # 东西方向的边
回忆第一单元学的函数——把"做某件事"的代码打包命名。几何公式是函数的绝佳用武之地:把"算周长"写成 perimeter(a, b)、"算面积"写成 area(a, b),之后需要时喊一声名字、传入边长,就能拿到结果。这让主循环里的代码清晰得像数学公式本身。别忘了函数要用 return 把算出的结果返回出来。
return 把结果送回调用处,否则函数默认返回 None。比如 return side1 * side2——算出面积并交回去。
矩形有四条边,对边相等:两条长为 a、两条长为 b。所以周长就是四条边的总和 a + a + b + b,可以更简洁地写成 a×2 + b×2,或者提取公因数写成 (a + b)×2——三种写法完全等价,选你觉得最清晰的即可。关卡"矩形仪式"要求你补全这个函数。
# 周长 = 四条边之和 def perimeter(side1, side2): return side1 * 2 + side2 * 2 # 等价写法:return (side1 + side2) * 2
矩形的面积是两条相邻边的乘积 a × b。注意是"相邻"两边(一长一宽),不是两条对边。这个公式最简单,一行 return 就够了。
# 面积 = 相邻两边相乘 def area(side1, side2): return side1 * side2
def perimeter(a, b): return a*2 + b*2
def area(a, b): return a * b
关卡"矩形仪式"点破了一个关键真相:"计算机很难处理精确的数字。"这有两层原因:其一,计算机用二进制存储小数,很多十进制小数(如 0.1)根本无法精确表示,运算后会产生微小误差(这叫浮点误差);其二,圣骑士在不停移动,边长时刻在变,几乎不可能刚好卡在某个精确值上。如果你用 == 去判断"面积是否等于 660",可能永远也等不到那一刻。
解决办法是写一个"近似相等"函数 almostEqual。它不要求两个值分毫不差,而是检查一个值是否落在另一个值的 97% 到 103% 区间内——也就是允许上下 3% 的误差。关卡给出的核心判断是:valueA > 0.97 * valueB and valueA < 1.03 * valueB。两个条件用 and 连接,必须同时满足才算"几乎相等"。
# 在 3% 误差内比较 A 和 B def almostEqual(valueA, valueB): # A 大于 B 的 0.97 倍,且小于 B 的 1.03 倍 if valueA > 0.97 * valueB and valueA < 1.03 * valueB: return True # 否则退回到精确相等判断 return valueA == valueB
min/max 的思路:先算出 min = numberA * 0.97 和 max = numberA * 1.03,再判断 numberB 是否 > min and < max。本质完全一样——都是"围绕目标值画一个 ±3% 的窗口,看另一个值有没有落进去"。
0.1 + 0.2 == 0.3 在很多语言里竟然是 False!所以凡是涉及小数(尤其是距离、面积这类计算结果)的相等判断,都应该用"近似相等"而不是 ==。
关卡"方盾"要你把圣骑士摆成正方形。要算出顶点坐标,先要吃透正方形的两条性质:"正方形的角与邻近的角共享 x 或 y 坐标",以及"正方形是等边的"。也就是说,如果左上角在 (x, y),右上角与它共享同一个 y(只是 x 增加了一个边长);左下角与它共享同一个 x(只是 y 变化了一个边长)。
核心算法叫坐标偏移:以一个已知角点(比如左上角 celadia)为基准,通过给它的 x 或 y 加减一个 sideLength,就能算出其他角点的位置。在"方盾"里,两位圣骑士已在上方两角,你要算出下方两角:左下角与左上角共享 x、y 减去边长;右下角的 x 是左上角 x 加边长、y 同样减去边长。
# 左下角:x 与左上角相同,y 向下偏移一个边长 leftBottom = {"x": celadia.pos.x, "y": celadia.pos.y - sideLength} # 右下角:x 偏移一个边长,y 也向下偏移一个边长 rightBottom = {"x": celadia.pos.x + sideLength, "y": celadia.pos.y - sideLength}
sideLength = celadia.distanceTo(dedalia)。这个边长既决定了正方形有多大,也是所有偏移量的来源。
关卡"雪人面积"用到圆面积公式:半径为 r 的圆,面积等于 π × r × r(即 π×r²)。在游戏里,天选之子呐喊的力度正好等于这个圆的面积,而半径就是他到最近敌人的距离。写成代码就是把半径乘两次、再乘以 π。
# 半径 = 天选之子到最近敌人的距离 radius = chosen.distanceTo(chosen.findNearestEnemy()) # 圆的面积 = π × r × r area = radius * radius * Math.PI
π 是一个无限不循环小数,约等于 3.14159……你不需要自己敲一长串数字,游戏提供了内置常量 Math.PI,它就代表这个精确的圆周率。用它比手写 3.14 更准确、更清晰。关卡说明也强调:π"等于任何圆周的周长与其直径的比值",是数学中最著名的常数之一。
# 直接用内置常量 Math.PI,别手写 3.14 area = Math.PI * radius * radius # 更精确 # hero.say(Math.PI) # 大约 3.141592653589793
Math.PI 是一个"常量"——它的值固定不变,永远是圆周率。用常量代替写死的数字("魔法数字"),能让代码更易读、更不容易出错。这是良好的编程习惯。
这一节聚焦每一关的策略、思路与算法设计,只展示用于说明的关键片段,完整通关程序请你自己动手完成。
本关是本单元的核心关。四位圣骑士会不停移动,你要在他们恰好围出指定周长(104)和指定面积(660)的矩形时喊出 "VENITE!" 召唤守护者。由于骑士一直在动、且计算机算不准小数,精确相等几乎不可能,所以策略是:在 while 循环里持续测量、用近似比较判断,一旦周长和面积都落在 3% 误差内就立刻念咒。
算法分三部分。第一,补全两个几何函数:perimeter 返回 side1*2 + side2*2,area 返回 side1 * side2。第二,在循环里实时计算:每一帧用 distanceTo 量出当前两条边长 sideSN、sideWE,代入函数得到 currentPerimeter 和 currentArea。第三,用近似比较双重判断:同时用 almostEqual 检查当前面积接近目标面积、当前周长接近目标周长,两个条件用 and 连接,都满足才 say("VENITE!") 并 break。
# 关键片段:循环监测 + 近似双重判断 while True: sideSN = base.distanceTo("Femae") sideWE = base.distanceTo("Illumina") currentPerimeter = perimeter(sideSN, sideWE) currentArea = area(sideSN, sideWE) if almostEqual(currentArea, requiredArea) and \ almostEqual(currentPerimeter, requiredPerimeter): hero.say("VENITE!") break
and 把两个 almostEqual 连起来。
almostEqual 换成 currentArea == 660,由于骑士移动和浮点误差,这个条件几乎永远为假,程序会陷入死循环、咒语永远念不出来。这正是本关设计 almostEqual 的用意。
本关要把四位圣骑士摆成正方形,用方阵挡住有侵略性的野雪人。其中 Celadia(左上角)和 Dedalia(右上角)已就位,你要计算并命令 Vaelia(左下角)和 Illumina(右下角)移动到正确位置。策略核心是利用正方形的坐标规律,从已知角点偏移出未知角点。
算法分两步。第一,求边长:上方两角的距离就是边长,sideLength = celadia.distanceTo(dedalia)。第二,用坐标偏移算下方两角并命令移动:左下角与左上角 Celadia 共享 x,y 向下偏移一个边长(y - sideLength);右下角的 x 在 Celadia 基础上加一个边长(x + sideLength),y 同样减一个边长。用 hero.command(单位, "move", 目标坐标字典) 把两位圣骑士派过去。
# 关键片段:坐标偏移 + 命令移动 sideLength = celadia.distanceTo(dedalia) # Vaelia → 左下角(x 与左上角相同,y 减边长) hero.command(vaelia, "move", {"x": celadia.pos.x, "y": celadia.pos.y - sideLength}) # Illumina → 右下角(x 加边长,y 减边长) hero.command(illumina, "move", {"x": celadia.pos.x + sideLength, "y": celadia.pos.y - sideLength})
findByName 辅助函数遍历 friends、按 id 找到指定名字的圣骑士。这样才能精确拿到 Celadia、Vaelia 等特定单位,再对它们分别下命令。
y - sideLength),因为在这张地图的坐标系里"下方"对应更小的 y 值。方向的正负取决于坐标系,动手时要根据实际地图确认是加还是减。
本关是本单元的收尾关,聚焦圆面积公式。天选之子能用呐喊震晕雪人,但呐喊的力度必须等于一个圆的面积,而这个圆的半径就是他到最近敌人的距离。策略很直接:先算出圆面积并"说"出来(作为呐喊力度),然后进入战斗循环持续攻击。
算法分两部分。第一,计算并说出圆面积:用 distanceTo(findNearestEnemy()) 得到半径 radius,代入圆面积公式 area = radius * radius * Math.PI,再用 hero.say(area) 说出来。第二,投入战斗:用循环不断找到最近敌人并攻击,直到敌人被击败——"不要只站在那,开始战斗吧!"
# 关键片段:算圆面积并说出,再进入战斗 chosen = hero.findFriends()[0] radius = chosen.distanceTo(chosen.findNearestEnemy()) area = radius * radius * Math.PI # π×r² hero.say(area) # 说出力度值 while True: enemy = hero.findNearestEnemy() while enemy and enemy.health > 0: hero.attack(enemy)
radius * radius 就等于 r²。乘法顺序不影响结果,写成 Math.PI * radius * radius 也完全一样。
say 出来(这一步是"注入呐喊力度"的关键),之后才进入攻击循环。顺序不能乱——没说出正确力度,呐喊就不会生效。
== 比较小数——距离、面积等浮点结果几乎不可能精确相等,必须用近似比较 almostEqual。perimeter、area 算完必须 return 结果,否则返回 None,后续计算全错。(a+b)×2(相加),面积是 a×b(相乘),别把加号乘号搞反。almostEqual 必须用 and 连接。y - sideLength 还是 y + sideLength 取决于坐标系,方向弄反正方形就歪了。Math.PI 更精确、更清晰,别自己敲近似值。radius * radius 写成 radius * 2——平方是自乘一次(r×r),不是乘以 2。恭喜你把数学几何变成了实实在在的代码魔法!这一单元你学会了用函数封装几何公式——矩形周长 (a+b)×2、矩形面积 a×b、圆面积 π×r²;掌握了一个真实编程中至关重要的技巧——用 3% 容差的近似比较解决浮点数与移动目标"无法精确相等"的难题;还学会了用坐标偏移从已知角点推算正方形的其余顶点,并认识了内置常量 Math.PI。在"矩形仪式、方盾、雪人面积"三关里,你把几何知识、函数封装和条件判断融会贯通。来回顾一下核心概念:
| 核心概念 | 关键点 | 对应关卡 |
|---|---|---|
| 矩形周长 | (a+b)×2 或 a*2+b*2 | 矩形仪式 |
| 矩形面积 | 相邻两边相乘 a×b | 矩形仪式 |
| 近似比较容错 | 落在 0.97~1.03 倍即视为相等 | 矩形仪式 |
| 避免 == 比小数 | 浮点误差,用 almostEqual | 矩形仪式 |
| 正方形性质 | 邻角共享坐标、四边等长 | 方盾 |
| 坐标偏移算顶点 | 已知角 ± sideLength 得其余角 | 方盾 |
| 圆的面积 | π × r × r | 雪人面积 |
| Math.PI 常量 | 内置圆周率,胜过手写 3.14 | 雪人面积 |
懂得把几何公式写成代码、用容差驯服不精确的小数、用坐标偏移精准布阵的英雄,才是云峰之上真正睿智的程序员。
继续向雪山之巅挺进吧,勇敢的程序员!🏔️📐⭕
扣哒世界 编程知识 · 计算机科学 5(安息云峰):第十七单元
扣哒世界教研团队