- 玩法一 数字一笔:规则库、求解器、26 关、竖屏触摸 Web 原型、测试 - 玩法二 连连气球:连消畅玩版核心、Web 原型、Cocos 球槽组件与导出脚本 - 玩法三 六边一笔:规则库、求解器、10 关 + 转墙实验包、Web 原型、测试 - 产品文档、UI 设计规范、各玩法 GDD / HANDOFF - 文档移除全部外部参考作引用;删除玩法二 v0 旧稿
141 lines
6.4 KiB
TypeScript
141 lines
6.4 KiB
TypeScript
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 中心');
|
||
});
|