Files
HexTrails/docs/level-design.md
T

73 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 关卡、旋转、碰撞与难度
## 数据格式
平顶六边形使用轴向 `(q,r)`,逻辑 y 轴向下。中心为 `x=1.5qR`、`y=√3(r+q/2)R`。
| 方向 | 朝向 | 邻格变化 |
| --- | --- | --- |
| 0 | ↑ | (0,-1) |
| 1 | ↗ | (1,-1) |
| 2 | ↘ | (1,0) |
| 3 | ↓ | (0,1) |
| 4 | ↙ | (-1,1) |
| 5 | ↖ | (-1,0) |
```json
{
"version": 1,
"id": "my-level",
"name": "转个方向",
"rotationEnabled": true,
"moveLimit": 3,
"cells": [{ "q": 0, "r": 0, "tracks": [3, 4, 5, 0, 1, 2] }],
"tiles": [{ "id": "t0", "q": 0, "r": 0, "dir": 1, "color": 0 }]
}
```
`tracks[i]` 为进入 i 端口后使用的出口,必须满足 `tracks[tracks[i]]===i` 且不能与自己相连。六端口共有 15 种完整配对。`color` 为 0–3。`rotationEnabled` 可选,默认开启;旧版 JSON 仍可导入,但会按新的碰撞规则重新验证。
导入限制:1–61 格、坐标半径不超过 4、步数 1–999。编辑器提供半径 2/3 的常用板型。底板不具备六重旋转对称性时,禁用整体旋转,避免棋子旋转后悬空。
## 旋转是真实的规则变化
顺时针一步将所有底板格子变为 `(q,r) → (-r,q+r)`,同时将轨道端口和对应出口都加 1(模 6)。上层棋子的 `(q,r,dir)` 均不改变。六次旋转恢复原状。
v2 旋转不扣步,点击棋子(含碰撞)扣 1 步。撤销保存并恢复底板、棋子、步数、朝向和旋转计数。重开读取原始关卡,不能从已旋转的中间状态重开。
## 连续碰撞
`path-math.js` 同时服务渲染与碰撞:每条三次贝塞尔轨道采样为 20 段,碰撞针对这些实际绘制的折线进行连续扫掠,而不是仅检查当前动画帧的位置。
每个棋子的顶面碰撞体为半径 `0.88R` 的平顶六边形,阴影和视觉侧壁不计入碰撞。两个同向六边形的 Minkowski 和是半径 `1.76R` 的六边形。将移动中心线的每段与此凸六边形做半平面裁剪,即可得到最早接触时刻。长段移动也不会穿透。
例如:移动棋子在 `(1,1)` 向上走,另一棋子在 `(0,0)`。路线经过的格子不含 `(0,0)`,但经过相邻两格之间时,移动棋子的侧面可能擦到它,因此仍被阻挡。这比仅检查路线格子占用更严格。
判定发生在移动前,动画只展示结果:成功沿线离场;失败走到接触位置再退回。动画期间允许连续点击不同棋子进入队列,执行前按最新棋盘重新判断路线;碰撞会清空等待队列。棋子仍依次运动,避免时序歧义。生成器、求解器、提示与工坊都调用同一个 `trace()`。
## 旋转求解与生成
移走可行棋子只会减少障碍,不改变轨道,因此在任意朝向下先移除所有安全棋子不会损害后续可解性。求解器对“朝向+剩余棋子集合”做 BFS,每次转盘代价为 1,到达一个朝向后先消除所有可离场棋子。
v2 最短扣费步数 = 初始棋子数;最少操作次数 = 初始棋子数 + 最少旋转数。BFS 返回混合 `tap` / `rotate` 动作序列。最大扩展状态数为 20,000;若触及上限,则返回 `exhausted` 表示尚未确定,不能宣称无解。
关卡生成先在碰撞规则下逆向放入可离场棋子,再旋转初始底盘,筛选必须旋转才能完成的布局,最后重新求解验证。主线按评分非递减排列,共 24 关,必要旋转 1–3 次。
## 数字化难度(0–100)
设 N=棋子数、T=忽略占用的平均转弯数、W=解答中的消除层数、F=初始无碰撞可离场数量、R=最少旋转数、K=N=最短扣费步数、M=上限:
```text
规模 = min(1, N/37) × 15
弯道追踪 = min(1, T/5) × 20
解锁层数 = min(1, max(0,W-1)/9) × 20
路线阻塞 = (1-F/N) × 15
必要旋转 = min(1, R/4) × 20
步数压力 = clamp(1-(M-K)/max(1,0.4N), 0, 1) × 10
总分 = 四舍五入各项未取整值之和
```
空关卡为 0 分且不能判为有效可解关卡。不可解、搜索未确定和步数不足分别展示。分项单独取整,显示值之和可能与总分略有差异。
0–29 轻松、30–49 进阶、50–69 烧脑、70–100 挑战。目前 24 关范围为 25–75。它是用于关卡排序的结构估计,没有经过玩家通关率标定;后续可用首次成功率、碰撞率、提示率、旋转次数、撤销率与耗时校准。