一个在浏览器里就能玩的几何作图工具站:照着关卡做题、在画板上自由作图、自己出题,还可以让内置求解器自动找出作图步骤。零依赖、零安装,打开网页即可用。
| 入口 | 用途 |
|---|---|
| 画板 | 自由作图,工具全开,适合随手画、研究构造。 |
| 关卡游玩 | 照着收录的关卡做题,作出即过关,可以挑战压步。 |
| 自制关卡 | 制题器:自己出题并标好给定与所求,然后试玩。 |
| 求解器 | 作图后标注给定所求,让程序自动找出作图步骤。 |
- 工具按通用 / 点 / 线 / 圆 / 构造分档;构造档按钮右下角的小角标显示该档当前选中的工具。
- 作图时光标会吸附:先吸到已有的点和交点(含还没作出点的「隐交点」),其次吸到最近的直线 / 圆 —— 吸到哪里就画在哪里。
- 重复的图形不会再作一次:再连一次 AB、以 A 为圆心 AB 为半径再作一个圆,都不会多出第二个同样的图形。隐藏或失效的图形不算重复,也不能被点选。
- 拖动图形:图形的定义点全是自由点时,拖动它就是整体平移;否则拖动是平移画布。移动工具支持多选(
Ctrl/Cmd+A全选、按住Ctrl/Cmd点加选),选中的图形可以一起拖动、一起改样式、一起删除。 - 样式刷先点一个图形当样式来源,之后点到的图形套用它的颜色 / 粗细 / 标签;切换线型点一下就把直线 / 射线 / 线段换成下一种;橡皮点击图形可以删除该图形及关联的图形,有普通的橡皮和点擦橡皮(仅能选中点对象)。
- 元素一览(菜单):看画布上的对象、切换显示或隐藏、查看详情与补标记。
- 快捷键:
Ctrl/Cmd+Z撤回、Ctrl/Cmd+Shift+Z或Ctrl/Cmd+Y重做、Ctrl/Cmd+S保存记录、Delete/Backspace删除选中的图形。
- 关卡包页与搜索页都能按标题 / 副标题 / 关键词 / 步数(
5L、6E、5都行)搜索,空格分隔多个条件是「与」;从关卡包进搜索默认只搜该包。 - 列表每页 20 关,从关卡返回列表会停在原来的位置。
- 目标:作出该关的所求(画布上以金色亮起)即通关。缩略图上的目标步数(如
5L 6E):L 是工具使用数(高级工具计 1L),E 是基础图形(直线与两点圆)数,多解关卡还要作出全部解(V);要先作出解,L / E 才会点亮,所求离开画布(撤销 / 清空)后勾会熄灭,重新作出时再评一次。 - 通关界面:作出新的解时滑入,显示步数与目标对比,5 秒后或撤销回未完成状态时收起。
- 工具限制:部分关卡限定单尺(移动 / 点 / 直线 / 交点)、单规(移动 / 点 / 圆 / 交点)或网格(题目自带一副网格,等于单尺在网格范围内作图),工具栏里只出现允许的工具。
- 撤销:画到一半(画布上还有跟随鼠标的半透明预览)时按撤销只取消这次的半成品,不撤掉上一步。菜单里还有清空画布、探索视图(显示所求的位置方便逆向探索)、设计模式(隐藏的图形全部显示并自动标好给定与所求)、打开求解器(要先作出至少一个解)。
- 查看答案:列出该关收录的解法图,点一下开全屏大图;配了记录的解法图还能点「逐步绘制」一步步看它怎么画出来。
-
标记工具在顶部菜单栏:「标记给定」有 给定 / 带标签给定 / 可移动点,「标记所求」有 所求判定 / 所求显示 / 探索显示。点画布上的对象即可加上或取消标记,标记面板按分组列出、旁边的叉号清空全部标记。
-
可移动点只能是能拖动的自由点(点交点 / 线上点这类跟随别人的点时会弹提示)。制题器与求解器的点默认不显示标签,可在「配置点样式」里打开。
-
多解:选中「标记所求 / 所求显示」后,切换栏里出现解选择器
1、+、−,点数字切换当前解、+增加一组、−删掉最后一组。 -
推荐按照下列规则标记:
- 如果给定图形不具有对称性(比如三点还原三角形的题,已经指定了各点的特征时),则建议使用带标签给定对这些不对称的图形进行标记;
- 如果存在题目没有给定但用于调节给定图形的点(比如给定线的两定义点,或是给定圆的圆上定义点),则建议使用可移动点进行标记;
- 建议将一定需要作出的自然图形标记为所求判定(如需要作出的点、直线、不含圆心的圆)。例如,给定A,H,I还原三角形ABC,则最好仅将直线AB、AC、BC标记为所求判定;
- 建议将完整的所求图形标记为所求显示(如需要作出的及部分给定的点、线段及端点、含圆心的圆)。例如,给定A,H,I还原三角形ABC,则最好将点A、B、C、线段AB、AC、BC标记为所求判定;
- 建议将非给定的所求图形标记为探索显示(如需要作出的点、线段及端点、含圆心的圆)。例如,给定A,H,I还原三角形ABC,则最好将点B、C、线段AB、AC、BC标记为探索显示。
-
导入 / 导出 gmt:菜单里可以查看代码、存成文件、粘贴代码或读取文件。
-
试玩:制题器的菜单里进入,页面与关卡游玩一致 —— 先只显示给定,作出所求后才显示其余内容;返回制题器会还原编辑状态。
-
求解器只认「给定」与「所求判定」。搜索的已知条件只取标为「给定」的对象;一个给定标记都没有时,把画布上除目标以外的对象都当作条件。
-
求解参数可设置最大步数、可用工具(尺规 / 单尺 / 单规 / 网格)、时间上限与要几个解,然后点「开始求解」。步数上限就是你要的步数:搜索是「找到就返回」,想要更短的解就把上限调小。解一找到就立刻显示一条、当场就能点开看,所以不必等搜索跑完。面板里还有进度条、高级选项(并行线程数、容差、解法自检、逐步搜索、启发式搜索等,默认收起)。
-
解法画在画布上:半透明品红按步画出,多个解时右侧列出「解法 1(N 步)…」,点哪一项就换成那一解,「上一步 / 下一步」可逐步看,「清除解法」收掉。解法只是覆盖层,不能选取、不能导出,也不影响你自己的作图;解法允许小范围的拖动;每条解还有「文字步骤」按钮,弹出一份中文说明。
-
搜索到的解不保证正确:它只保证在当前这组数上命中目标,请自己核对每一条解(如稍微拖动或自己重新绘制一遍),或尝试开启「解法自检」。
-
给定点太少导致伪无解:该求解器暂不支持自动标点,若给定图形中的点过少导致很简单的关卡也搜不到解时(如部分单尺关卡),请尝试再绘制1~2个动点(自由点或图形上的点)并加入给定。
-
关系雷达:寻找图上各种关系的辅助功能,勾选类别后点「开始探测」,把画板上多点共线 / 多点共圆 / 平行垂直 / 定点角 / 线上比 / 等距中心 / 角平分线 / 紧圆规这些隐含关系实时逐条列出来,点一条就在画布上用紫色半透明标出来;图形一动标记跟着走,关系不再成立就标「已失效」。它只判定与显示,不改动画布及插入任何点。
-
鸣谢:求解器的搜索内核由 Ander 与 zzzzzz 共同开发,本项目使用它的 JavaScript 移植版;启发式搜索移植自 墨染荷韵 的 bs v12;关系雷达移植自 YX-hueimie 的 Geosolver。
- 画板 / 制题器 / 求解器的菜单右边有「生成网格」,可以设置网格的行列数和单位长度。
- 界面把它当作一整块:点任意一条格线就选中全部,元素一览与标记栏里只占「格线」一行,也不能对它做标记。
- 游玩模式下,网格范围外不能作图。
- 制题器和求解器里,生成网格即将其加入「给定」。
- 菜单「记录」打开记录面板:只列出当前模式或关卡的记录,每条显示序号 / 名称(默认是保存时间,可重命名)/ 步数(作出所求黑、达标的 L / E 金)。左上角加号手动保存,关卡游玩里作出所求时会自动保存一条;右上角「多选」可批量导出 / 删除。
- 一条记录存作图(gmt 文本)+ 可撤回的图形名单 + 样式表 + 基本信息,保存在本机浏览器里;载回来跟保存那一刻一样,也能接着撤销 / 重做。载入时视图会自动适配到图形(或网格)的范围。
- 导入 / 导出可收下 gmt 代码或文件(一次多条),也可看代码或存成
.gmt/.gmts文件;导出文本的格式见「存档格式:GMT 与记录文件」。首页的清除缓存按模式列出本机全部记录。 - 各个列表都会读本机记录:关卡页缩略图与关卡列表 / 关卡包 / 搜索页里,作出过所求的目标步数转成黑色,其中达标的那一项(L 或 E)标金。
- 为能够正确地回退页面,建议始终使用各页面上的「返回」按钮,而不是直接用浏览器的「后退」按钮。
- 帮助:各工作区「菜单 → 帮助」说明当前界面的用法,菜单与按钮悬停时信息栏也会给出说明。
- 项目说明:首页右上角的「项目说明」(手机版在页脚中)取回本文件并渲染成弹窗,也可以查看各版本更改日志。
- 中英双语:首页右上角可切换中文 / English,选择会被记住。
(以下内容面向想自己跑一份站点、增删关卡或直接编辑 gmt 的贡献者 / 制题者,普通玩家无需阅读。)
在仓库根目录运行 python -m http.server 8000,然后访问 http://localhost:8000。GitHub Pages 发布源请选择仓库根目录。
根目录的 package.json 只收纳两个便捷脚本:npm start(起本地静态服务)与 npm run check(校验脚本语法)。
关卡文件载入与各画板的「导入/导出 gmt」,以及历史记录的导入导出功能,均用同一套解析规则。
预绘制指令
| gmt | 画板里的基底 | 说明 |
|---|---|---|
A=[x,y] |
自由点 | 坐标与 gmt 一致(y 轴朝下) |
Line[A,B] / Segment[A,B] / Ray[A,B] |
直线 / 线段 / 射线 | |
Circle[A,B] |
圆(两点基底) | A 为圆心、B 在圆上 |
Compass[A,B,C] |
圆(圆规基底) | C 为圆心、AB 为半径 |
Circle3[A,B,C] |
圆(三点基底) | 过 A、B、C |
#geom=c:SemiCircle[A,B] |
半圆 | c 的基础对象仍写成 Circle[med,A];取 AB 左侧半圆 |
#geom=c:Arc[A,B,C] |
圆弧 | c 的基础对象写成 Circle[A,B];从 B 顺时针到 C |
#geom=c:Arc3[A,B,C] |
三点圆弧 | c 的基础对象写成 Circle3[A,B,C];从 A 到 C 且经过 B |
Intersect[对象1,对象2,x,已知点] |
交点 | 见下方「交点参数」 |
Linepoint[对象,x] |
对象上的点 | 见下方「参数怎么算」 |
Midpoint[A,B] |
中点 | |
Perp[A,s] / Parallel[A,s] |
垂线 / 平行线 | |
PBisect[A,B] |
中垂线 | |
ABisect[A,B,C] |
角平分线 | B 为角的顶点 |
CenterPoint[c] |
圆心 | 不能用于线段中心 |
EdgePoint[s,x] |
无穷远点 | 线 s 上负/正方向(x=0/1)的无穷远假想点,这个点本身不绘制 |
PolarPoint[s,c] |
极点 | 线 s 关于圆 c 的极点(与极线互为逆运算) |
Tangent[A,c,x] |
切线 | 过点 A 作圆 c 的一条切线;x 从圆心到 A 逆时针数 |
PolarLine[A,c] |
极线 | 点 A 关于圆 c 的极线 |
CopyAngle[A,B,C,D,E] |
复制角射线 | 以 E 为顶点、D 为边上一点 |
FixAngle[A,B,x] |
定值角射线 | 以 A 为顶点、AB 为始边逆时针转 x 度 |
#geom= 是画板扩展语义,允许放在 GMT 任意位置;不支持扩展语义的程序会把基础对象当作整圆读取。#internal=id 将对象标记为内部构造对象;id 以 med 开头的对象也自动按内部对象处理。内部对象仍参与依赖和导出,但不显示在画布、元素一览、样式与用户选择中。
暂不支持导入 ShiftSeg 等,遇到这些指令时该对象会被丢掉,依赖它的对象也一并丢掉,其余部分照常解析(不过现行的关卡文件里没有用到 ShiftSeg)。
暂不支持线圆切线、两圆切线、两线角平分线、复制圆等工具导出为 gmt 文件。
参数怎么算(Linepoint[对象,x])
圆上的点按从 x 轴正方向起的弧度(顺时针为正,与画布一致);线上的点按定义点距离倍数,逐类如下:
| 线型 | x 的含义 |
|---|---|
| 直线 / 射线 / 线段 | 第一定义点为起点,1 个单位 = 两定义点之间的距离 |
| 平行线 | 定义点为起点(x=0 就是定义点) |
| 垂线 | 参数从定义点往前退一个单位(x=1 才是定义点 A,x=0 在 A 沿垂线方向退一个单位长处) |
| 中垂线 | 以 AB 中点 M 为圆心、A 顺转 90° 后的位置为起点 |
| 角平分线 | 以顶点为起点,1 个单位随图形变化,见下 |
| 切线 | 1 个单位 = 切圆半径 |
角平分线单位长:不是固定 100。过顶点作角平分线的垂线,取「角的两条边上的定义点到这条垂线的距离」与「画布 50 单位」三者中的最小值,它的两倍就是 1 个单位的长度。
写 gmt 时按这个约定写 x 即可;在画板上直接点出来的点也按同一套换算,所以「自己画的」与「载入关卡里的」位置一致。
交点参数(Intersect[对象1,对象2,x,已知点])
Intersect 的第三个参数 x 就是交点的身份,画板按同一套约定计算与保存,图形移动时交点不会跳到另一个上:
- 线与线:只有一个交点,
x=0; - 线与圆:候选按线的方向(第一定义点 → 第二定义点)升序编号,取哪一个按下面的规则(据原版游戏实测):
- 同一对基底上的第一个
Intersect一律取「非定义点」那个候选 ——x写 0 还是 1 都一样(c1=Circle[A,B]、s1=Line[B,A]时,E=Intersect[c1,s1,0]与F=Intersect[c1,s1,1]取到的都是圆上那个不是 B 的点); - 第二个及以后才按
x正常取(同例改成s1=Line[A,B]:0 号是 B、1 号是另一个点); - 「定义点」指圆的半径端点(
Circle[A,B]的 B)—— 也就是画这个圆时点出来的、圆上本来就有的那个点; - 某个候选位置上是图形的定义点时(这个半径端点 B 被本条线穿过、两圆的公共半径端点 B、直线就是过那个点作出来的…)—— 也就是这一对图形已经有的那个交点 —— 建议把它写成第四个参数(见下),否则可能会出现一系列问题;
- 同一对基底上的第一个
- 圆与圆:候选以对象1圆心 → 对象2圆心的方向起逆时针升序,取法与线与圆相同;
- 线段 / 射线:
x仍按所在直线的候选顺序算,不会因为范围内只剩一个交点就直接把它当成 0 号,一个候选都不在范围内时保留原列表,编号照旧取,最后按「失效但不删除」处理;
Intersect 的第四个参数(如 Intersect[s3,c2,1,F] 里的 F)是已知的那个交点,在正常作图顺序下,通常为这一对图形的定义点。该点会自动记成已知交点,「另一个交点」因此有了固定身份,既然身份已经确定,第三个参数就不再重要,导出 gmt 时写成 Intersect[图形1,图形2,-,已知点](如「圆 AB 上有 C、直线 DC 过 C」时,取到的另一个交点就是 Intersect[s1,c1,-,C])。
配好交点参数之后,交点是被「记住」的:线段、射线的范围变化使交点跑到线外(或两个对象不再相交)时,该点失效但不删除(不显示、不参与作图),重新相交后自动恢复。
设定行
| gmt | 作用 |
|---|---|
initial |
初始显示的对象(黑色,不带标签) |
named |
带标签的初始条件(也算初始显示)。named=A.M 中 A 是变量名、M 是要显示出来的标签名;只写 A 时标签就取 A |
hidden |
作图时隐藏的对象(画板里一律不显示) |
movepoints |
可移动点:选中移动工具时出现(可拖动),切换工具后隐藏 |
result |
所求,可写多条(多解)。a:b,c,d 中冒号前是判定用的对象、冒号后是判定成功后要显示的图形(判直线而不判线段时会用到,详见上文的推荐标记规则);没写冒号后时就把判定对象本身显示出来。每作出一个图形就判定一次:任意一条 result 的判定对象都作出即算过关,并显示对应的显示图形(金色);多条都作出则算多解 |
explore |
探索视图里标金色的内容,与 result 冒号后的显示互不影响 |
rules、ver、check_level |
解析时跳过(rules 的限制条件尚未实现) |
多解标记:解 1 沿用 result,解 k 用 result k(result2、resultShown2…);导入 / 导出时每条 result= 就是一个解。导出格式固定为「每个解一行 result=判定:显示」,空解也留一行空行(第几行就是第几个解);initial= / named= / movepoints= / result= / explore= 这些设定行无论有没有标记都会写出来,没标记的留空,便于对照与手动编辑。
几条与文件一致的显示规则
- 只有
initial、named的对象一开始就显示;gmt 里预绘制出的「解」会被隐藏(画板「导入 gmt」时相反,全部可见,方便编辑); - 标签只给
named的对象,initial里的对象只显示图形、不显示标签; - 关卡加载后会自动把初始图形居中显示(画布坐标原点在左上角,gmt 坐标以图形为中心);
- 点类对象压在线圆之上绘制;线类对象之间线段压在射线之上、射线压在直线之上,与作图先后无关;
- 制题器 / 求解器里对象的「样式色」与「标记色」分开:标成给定 / 所求会换成黑 / 金,取消标记后回到自己的样式色(自由点红、其余点与线圆灰);
initial/named里坐标定义的点与线上点,和可移动点一样能拖(选中移动工具时临时显示成蓝色,退出移动工具还原)。
记录面板导出的文本就是一份可读的 gmt:对象行与标记行之后空一行,接一段 recordname= / time= / saved= / mode= / pack= / levelid= / reached= / steps=5:6 / undolist=3,5,7 / styles=a#ff0000,a~1,a$;多条合并成一份 .gmts 时,每条前面加一行 # ===== 记录 N ===== 隔开。v1.1.3 及以前的老格式(# undolist= / # styles= / # info= 三行 JSON)仍能读。
- 网格由文件头的
#grid=m,n,单位/#gridstyle=…两行带出,不在styles里重复记:它决定网格大小、单位与格线样式,载入时(记录 / 关卡 / 试玩)按这两行把网格还原成一整块。 undolist是可撤回的图形在 gmt 里的对象行序号:游玩模式只列玩家自己画的(关卡自带的图形撤不掉,跟关卡里作图的撤回手感一致),其它模式列全部,所有图形都能逐一撤回。取消撤回后重做会把图形放回来。- 样式表只写非默认项:黑 / 宽 1 / 点显示标签 / 线圆不显示 / 实线 / 可见算默认;
a#ff0000颜色、a~1…a~5点线径(档位号,1 最小 5 最大)、a$显示标签 /a^不显示、a&虚线(线 / 圆)、a@隐藏 /a!可见、a%a1名称为 a1、S1>1线段记号类型(1 到 6)。自由模式导出时,点默认就有的标签也要写a$;游玩模式与制题器导出时,所有非给定对象都要把显隐写出来(a@/a!)。
| 路径 | 内容 |
|---|---|
data/levels/<包id>/<序号>.<关卡id>/… |
关卡文件夹:该关的 gmt、题图、收录的解法图与解法 gmt 全在这里 |
data/thumbs/levels/<包id>/<序号>.<关卡id>/… |
该关题图的缩略图(与题图同名、扩展名 .webp,脚本生成) |
data/packcovers/<包id>.png / .jpg / .webp |
关卡包封面(总览卡片左侧大图) |
data/thumbs/packcovers/… |
关卡包封面的缩略图(脚本生成) |
data/levelpacks.json |
关卡包列表(10 个包) |
data/levels.json |
关卡扁平列表(345 关) |
两个 JSON 各管一件事,不再有额外的覆盖表:一个管关卡包,一个管关卡,页面直接读它们。
直接改上面两个文件即可,写进去的就是页面显示的内容;不写就用页面默认值。
data/levelpacks.json(关卡包,数组,顺序即总览里的顺序)
| 字段 | 作用 | 不写时 |
|---|---|---|
id |
包 id(关卡的 pack 字段对应它) |
— |
name |
关卡包名称(总览卡片、关卡列表标题) | — |
description |
关卡包说明(总览卡片、关卡列表副标题) | 卡片「来自原始资料夹的几何作图题。」;列表「N 个关卡,选择一关开始作图。」 |
cover |
总览卡片封面(相对 data/,可写任意图片路径) |
依次找 data/packcovers/<包id>.png / .jpg / .webp |
icon |
总览卡片图(封面找不到时的后备,相对 data/) |
该包第一个有示意图的关卡 |
source |
原始资料出处,仅备注用 | — |
data/levels.json(关卡,扁平数组,同包内顺序即列表顺序)
| 字段 | 作用 | 不写时 |
|---|---|---|
id |
关卡 id(也是文件名,尽量简短,可以模仿现有id风格) | — |
pack |
所属包 id | — |
file |
GMT 文件路径(相对 data/) |
— |
title |
关卡标题(列表、关卡页缩略图标题、页面标题) | — |
subtitle |
关卡页缩略图(点开卡片)里的说明文字 | 不显示;从画板「试玩」进入时默认「GMT 已加载」 |
targetSteps |
步数(列表右侧、关卡页底部),如 5L / 6E |
列表显示「步数未标注」 |
diagram |
示意图路径(相对 data/) |
列表用 ◇ 占位,卡片无图 |
note |
关卡列表行的悬停提示 | 无 |
tools |
工具限制:"straightedge" 单尺 / "compass" 单规 / "grid" 网格直尺 / 特殊工具组(I直线 O圆 C圆规 L垂线 Z平行线 T中垂线 A角平分线 3三点圆)如 "OC3" |
不限制,给全部工具 |
keywords |
搜索用关键词数组 | 搜索时只用标题与 subtitle |
solutions |
该关收录的解法图:[{"file": "levels/<包id>/<关卡id>/5L.png", "star": "5L", "gmt": "levels/<包id>/<关卡id>/5L.gmt"}, …] |
该关没有收录答案图(「查看答案」置灰) |
示例(data/levels.json 里的一条):
{
"id": "ewp11",
"pack": "ewp",
"file": "levels/ewp/ewp11/ewp11.gmt",
"title": "EWP11",
"subtitle": "圆内有一点A,作过A点的弦,弦长等于半径。",
"targetSteps": "5L / 6E",
"diagram": "levels/ewp/ewp11/ewp11.png",
"note": "",
"tools": "",
"keywords": ["EWP", "弦", "半径", "多解"],
"solutions": [
{"file": "levels/ewp/ewp11/5L.png", "star": "5L", "gmt": "levels/ewp/ewp11/5L.gmt"},
{"file": "levels/ewp/ewp11/6E.png", "star": "6E", "gmt": "levels/ewp/ewp11/6E.gmt"},
{"file": "levels/ewp/ewp11/2V.png", "star": "2V", "gmt": "levels/ewp/ewp11/2V.gmt"}
]
}字段顺序就是上面两个表的顺序;没有的字段写空串 / 空数组(页面按「不写」处理,等同用默认值),tools 这类开关型字段不限制时直接不写、不要留空串。文件与目录名里不出现下划线,一律用连字符 -。
新增关卡包 / 关卡:先建 data/levels/<包id>/<关卡id>/,把该关的 .gmt、题图、解法图都放进去(跑一次 npm run thumbs 在 data/thumbs/levels/… 生成同名 .webp 缩略图),再在 levelpacks.json 加一个包、在 levels.json 加一条关卡即可(file / diagram / solutions 都写 levels/<包id>/<关卡id>/文件名)。
当前标注了工具限制的关卡共 47 关(43 关单尺 + 4 关单规);extra-straightedge-only-pzls 整包 36 关都是单尺。
data/diagrams/ewp/里还留着 5 张当年没用上的题图(EWP021.png/EWP049.png/EWP066.png/EWP088.png/EWP091.png,这几关不在levels.json里),它们没有关卡引用,下划线也一并换成了连字符,需要的话删掉即可。
每条答案可以再带一个 gmt 字段,指向这份解法对应的记录文本。配了它,这条答案的编号那一行右端会多一个「逐步绘制」按钮,点开后在本窗口进入精简画板(board.html?mode=stepview&gmt=…)载入这份记录。
为确保撤回与标记等行为的正确,建议文件内容直接用记录导出格式。原则上用「在求解器里绘出该解法后自动保存的那条记录」即可。
题目与解法图来自「Geommunity Blueprint」造题计划收集的原始资料:每关一个文件夹,里面有题图、当时的最优解图与 gmt 代码(非官方题目的解法单独放在 Solutions 文件夹里,方便玩家先自己思考)。
这些资料已经按上面的表格整理进 data/:每关一个文件夹 data/levels/<包id>/<关卡id>/,里面放该关的 gmt、题图与解法图;levelpacks.json 的 source 字段仍保留原始资料里的目录名作为出处备注。资料与解法图版权归原作者所有。