Files
NumberSwipe_New/modes/03-hex-walls/tests/layout.test.ts
T
cabinw 8026e4628d 初始提交:三玩法 Web 原型与设计文档
- 玩法一 数字一笔:规则库、求解器、26 关、竖屏触摸 Web 原型、测试
- 玩法二 连连气球:连消畅玩版核心、Web 原型、Cocos 球槽组件与导出脚本
- 玩法三 六边一笔:规则库、求解器、10 关 + 转墙实验包、Web 原型、测试
- 产品文档、UI 设计规范、各玩法 GDD / HANDOFF
- 文档移除全部外部参考作引用;删除玩法二 v0 旧稿
2026-09-16 14:30:59 +08:00

80 lines
3.2 KiB
TypeScript
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.
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { HexBoard } from '../src/board';
import { clipPolygon, roundPolygon, stripesInBand } from '../src/geom';
import { bridgePolygon, cellCenter, fitBoard, hitTest, pixelToAxial, sharedEdge, wallBand } from '../src/layout';
import type { HexCellDef } from '../src/types';
const c = (id: string, q: number, r: number, clue?: number): HexCellDef => ({ id, q, r, clue });
const flower = () =>
HexBoard.fromCells([c('o', 0, 0), c('e', 1, 0), c('ne', 1, -1), c('nw', 0, -1), c('w', -1, 0), c('sw', -1, 1), c('se', 0, 1)], [['o', 'e']]);
test('fitBoard 按短边缩放并居中,不超过 maxSize', () => {
const b = flower();
const L = fitBoard(b, 400, 600, { padding: 10, maxSize: 60 });
assert.ok(L.size <= 60);
// 花形宽 = 3 × √3 × size,应贴合 380 宽或受 maxSize 限制
assert.ok(L.width <= 380 + 1e-6);
const o = cellCenter(b, 0, L);
assert.ok(Math.abs(o.x - 200) < 1e-6);
assert.ok(Math.abs(o.y - 300) < 1e-6);
const small = fitBoard(b, 100, 100, { padding: 0 });
assert.ok(small.width <= 100 + 1e-6 && small.height <= 100 + 1e-6);
});
test('像素 ↔ 轴坐标往返;命中中心;死区与空洞返回 -1', () => {
const b = flower();
const L = fitBoard(b, 400, 600, { padding: 10, maxSize: 60 });
for (const cell of b.cells) {
const p = cellCenter(b, cell.index, L);
assert.deepEqual(pixelToAxial(L, p.x, p.y), { q: cell.q, r: cell.r });
assert.equal(hitTest(b, L, p.x, p.y), cell.index);
}
const o = cellCenter(b, 0, L);
const e = cellCenter(b, 1, L);
// 两格中点在缝里 → 死区
assert.equal(hitTest(b, L, (o.x + e.x) / 2, (o.y + e.y) / 2), -1);
// 盘外
assert.equal(hitTest(b, L, 5, 5), -1);
// (2,0) 不存在
const far = { x: o.x + Math.sqrt(3) * L.size * 2, y: o.y };
assert.equal(hitTest(b, L, far.x, far.y), -1);
});
test('共用边、桥、墙带的几何', () => {
const b = flower();
const L = fitBoard(b, 400, 600, { padding: 10, maxSize: 60 });
const e = sharedEdge(b, 0, 1, L)!;
const o = cellCenter(b, 0, L);
const ea = cellCenter(b, 1, L);
assert.ok(Math.abs(e.mid.x - (o.x + ea.x) / 2) < 1e-6);
assert.ok(Math.abs(e.n.x - 1) < 1e-6, '东邻的法向是 +x');
assert.ok(Math.abs(e.u.y - 1) < 1e-6, '共用边竖直');
assert.equal(sharedEdge(b, 1, 3, L), null, 'ne 与 w 不相邻');
const br = bridgePolygon(b, 0, 1, L)!;
assert.equal(br.length, 4);
const wb = wallBand(b, 0, L);
assert.ok(wb.thick > 0 && wb.len > 0);
});
test('几何工具:圆角多边形顶点数、凸裁剪、斜纹都在带内', () => {
const sq = [
{ x: 0, y: 0 },
{ x: 10, y: 0 },
{ x: 10, y: 10 },
{ x: 0, y: 10 },
];
assert.equal(roundPolygon(sq, 2, 3).length, 16);
const tri = [
{ x: -5, y: 5 },
{ x: 15, y: 5 },
{ x: 5, y: 20 },
];
const clipped = clipPolygon(tri, sq);
assert.ok(clipped.length >= 3);
for (const p of clipped) assert.ok(p.x >= -1e-6 && p.x <= 10 + 1e-6 && p.y >= -1e-6 && p.y <= 10 + 1e-6);
const stripes = stripesInBand({ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, 40, 8, 8, 4);
assert.ok(stripes.length >= 4);
for (const s of stripes) for (const p of s) assert.ok(Math.abs(p.x) <= 20 + 1e-6 && Math.abs(p.y) <= 4 + 1e-6);
});