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

141 lines
6.4 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 { Board } from '../src/board';
import type { CellDef } from '../src/types';
const c = (id: string, x: number, y: number, w = 1, h = 1, clue?: number): CellDef => ({ id, x, y, w, h, clue });
test('只认四向邻接,角对角不算', () => {
const b = Board.fromCells([c('a', 0, 0), c('b', 1, 1), c('c', 1, 0)]);
assert.equal(b.areAdjacent(0, 1), false);
assert.equal(b.areAdjacent(0, 2), true);
assert.equal(b.areAdjacent(2, 1), true);
});
test('2×2 可以同时贴多个 1×1;只碰角的不算', () => {
// 1-4 的盘:start(0,0) 与 big(1,1,2,2) 只碰角
const b = Board.fromCells([c('start', 0, 0, 1, 1, 1), c('a', 1, 0), c('b', 2, 0), c('big', 1, 1, 2, 2, 3)]);
const big = b.cellById('big').index;
assert.deepEqual([...b.adj[big]].sort(), [1, 2]);
assert.equal(b.areAdjacent(0, big), false);
});
test('横长条与其长边上的两个格都相邻', () => {
const b = Board.fromCells([c('H', 1, 1, 2, 1), c('p', 1, 0), c('q', 2, 0), c('r', 3, 1), c('s', 0, 2)]);
const H = b.cellById('H').index;
assert.equal(b.areAdjacent(H, b.cellById('p').index), true);
assert.equal(b.areAdjacent(H, b.cellById('q').index), true);
assert.equal(b.areAdjacent(H, b.cellById('r').index), true);
assert.equal(b.areAdjacent(H, b.cellById('s').index), false);
});
test('空洞返回 -1,坐标会平移到原点', () => {
const b = Board.fromCells([c('a', 2, 3), c('b', 3, 3), c('d', 3, 4)]);
assert.equal(b.cols, 2);
assert.equal(b.rows, 2);
assert.equal(b.cellAtUnit(0, 0), 0);
assert.equal(b.cellAtUnit(0, 1), -1);
assert.equal(b.cellAtUnit(5, 5), -1);
});
test('共享边线段', () => {
const b = Board.fromCells([c('B', 0, 0, 2, 2), c('x', 2, 1), c('y', 1, 2)]);
assert.deepEqual(b.sharedEdge(0, 1), { x1: 2, y1: 1, x2: 2, y2: 2, dir: 'v' });
assert.deepEqual(b.sharedEdge(0, 2), { x1: 1, y1: 2, x2: 2, y2: 2, dir: 'h' });
assert.equal(b.sharedEdge(1, 2), null);
});
test('3×3 大连块:合法、长边邻接、共享边合并', () => {
const b = Board.fromCells([c('O', 0, 0, 3, 3), c('a', 3, 1), c('d', 1, 3), c('e', 3, 3)]);
const O = b.cellById('O');
assert.equal(O.isRect, true);
assert.equal(b.areAdjacent(O.index, b.cellById('a').index), true);
assert.equal(b.areAdjacent(O.index, b.cellById('d').index), true);
assert.equal(b.areAdjacent(O.index, b.cellById('e').index), false, '角对角不算');
assert.equal(b.cellAtUnit(2, 2), O.index);
assert.deepEqual(b.sharedEdge(O.index, b.cellById('a').index), { x1: 3, y1: 1, x2: 3, y2: 2, dir: 'v' });
assert.deepEqual(b.sharedEdge(O.index, b.cellById('d').index), { x1: 1, y1: 3, x2: 2, y2: 3, dir: 'h' });
});
test('非法盘面直接抛错', () => {
assert.throws(() => Board.fromCells([c('a', 0, 0), c('b', 0, 0)]), /重叠/);
assert.throws(() => Board.fromCells([c('a', 0, 0, 3, 1)]), /尺寸/);
assert.throws(() => Board.fromCells([c('a', 0, 0, 1, 1, 5)]), /线索/);
assert.throws(() => Board.fromCells([c('a', 0, 0, 1, 1, 1), c('b', 1, 0, 1, 1, 1)]), /重复/);
});
// ---------- 异形格(俄罗斯方块拼块) ----------
const s = (id: string, x: number, y: number, w: number, h: number, shape: Array<[number, number]>, clue?: number): CellDef => ({
id,
x,
y,
w,
h,
shape,
clue,
});
test('异形格:L4 展开成单位集合,缺口是空洞', () => {
const b = Board.fromCells([s('L', 1, 1, 2, 3, [[0, 0], [0, 1], [0, 2], [1, 2]]), c('a', 0, 0)]);
const L = b.cellById('L');
assert.equal(L.isRect, false);
assert.deepEqual([...L.units].sort(), [[1, 1], [1, 2], [1, 3], [2, 3]].sort());
assert.equal(b.cellAtUnit(2, 1), -1, 'bbox 里缺口 (2,1) 是空洞');
assert.equal(b.cellAtUnit(2, 3), L.index);
assert.equal(b.cellById('a').isRect, true);
});
test('异形格:旋转与镜像都在目录内', () => {
const variants: Array<Array<[number, number]>> = [
[[0, 0], [0, 1], [0, 2], [1, 2]], // L
[[0, 0], [1, 0], [2, 0], [0, 1]], // J(L 的镜像)
[[0, 0], [1, 0], [1, 1], [2, 1]], // L 旋转 90°
[[1, 0], [2, 0], [0, 1], [1, 1]], // S
[[0, 0], [0, 1], [1, 1], [1, 2]], // Z(S 的镜像)
[[0, 0], [1, 0], [2, 0], [1, 1]], // T
[[0, 0], [0, 1], [1, 0]], // L3
];
for (const shape of variants) {
const w = Math.max(...shape.map(([x]) => x)) + 1;
const h = Math.max(...shape.map(([, y]) => y)) + 1;
assert.doesNotThrow(() => Board.fromCells([s('P', 0, 1, w, h, shape), c('a', 0, 0)]), JSON.stringify(shape));
}
});
test('异形格:非法 shape 抛错', () => {
assert.throws(() => Board.fromCells([s('L', 0, 0, 2, 2, [[0, 0], [1, 1]])]), /不连通/);
assert.throws(() => Board.fromCells([s('L', 0, 0, 2, 2, [[0, 0], [0, 0]])]), /重复/);
assert.throws(() => Board.fromCells([s('L', 0, 0, 2, 2, [[0, 0], [-1, 0]])]), /为负/);
assert.throws(() => Board.fromCells([s('L', 0, 0, 2, 2, [[0, 0], [0, 1], [0, 2]])]), /不符/);
assert.throws(() => Board.fromCells([s('L', 0, 0, 3, 3, [[0, 0], [1, 0], [2, 0], [2, 1], [0, 1]])]), /单位数/);
});
test('异形格邻接:认每一条共享单位边', () => {
// L 占 (1,1)(1,2)(1,3)(2,3),缺口在 (2,1)(2,2)
const b = Board.fromCells([
s('L', 1, 1, 2, 3, [[0, 0], [0, 1], [0, 2], [1, 2]]),
c('a', 2, 1), // 缺口里的格:与 (1,1) 共边 → 相邻
c('d', 2, 2), // 与 (1,2) 共边 → 相邻
c('e', 3, 1), // 只挨到缺口,不与任何 L 单位共边 → 不相邻
]);
const L = b.cellById('L').index;
assert.equal(b.areAdjacent(L, b.cellById('a').index), true);
assert.equal(b.areAdjacent(L, b.cellById('d').index), true);
assert.equal(b.areAdjacent(L, b.cellById('e').index), false);
});
test('异形格共享边:共线单位边合并成一整段;自身返回 null', () => {
const b = Board.fromCells([c('B', 0, 0, 2, 2), c('V', 2, 0, 1, 2)]);
assert.deepEqual(b.sharedEdge(0, 1), { x1: 2, y1: 0, x2: 2, y2: 2, dir: 'v' }, '2×2 与竖条的两条单位边合并');
assert.equal(b.sharedEdge(0, 0), null);
});
test('异形格锚点在形内', () => {
const b = Board.fromCells([s('L', 1, 1, 2, 3, [[0, 0], [0, 1], [0, 2], [1, 2]]), c('a', 0, 0)]);
const [ux, uy] = b.anchorUnit(b.cellById('L').index);
assert.ok(b.cellById('L').units.some(([x, y]) => x === ux && y === uy), `锚点 (${ux},${uy}) 必须在 L 内`);
const [cx, cy] = b.anchorCenter(b.cellById('a').index);
assert.deepEqual([cx, cy], [0.5, 0.5], '矩形格锚点 = bbox 中心');
});