import assert from 'node:assert/strict'; import { test } from 'node:test'; import { Board } from '../src/board'; import { Game } from '../src/game'; import { PointerInput } from '../src/input'; 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 }); // 一横四格:a(1) b c d function setup() { const b = Board.fromCells([c('a', 0, 0, 1, 1, 1), c('b', 1, 0), c('c', 2, 0), c('d', 3, 0)]); const g = new Game(b); return { b, g, input: new PointerInput(g) }; } test('按下起点后滑动追加;滑回上一格撤销;松手保留路径', () => { const { g, input } = setup(); input.down(0); assert.equal(g.path.length, 1); input.move(1); input.move(2); assert.deepEqual([...g.path], [0, 1, 2]); input.move(1); // 滑回上一格 = 撤销 assert.deepEqual([...g.path], [0, 1]); input.up(); assert.equal(g.status, 'idle'); assert.deepEqual([...g.path], [0, 1]); }); test('松手后从末端按下可继续;按不相邻的空格不生效', () => { const { g, input } = setup(); input.down(0); input.move(1); input.up(); input.down(3); // 不相邻的空格 assert.equal(input.isDrawing, false); input.move(2); assert.equal(g.path.length, 2); input.up(); input.down(1); // 末端 assert.equal(input.isDrawing, true); input.move(2); assert.deepEqual([...g.path], [0, 1, 2]); }); test('点末端的前一格 = 撤销一步,且可以接着滑', () => { const { g, input } = setup(); input.down(0); input.move(1); input.move(2); input.up(); input.down(1); assert.deepEqual([...g.path], [0, 1]); assert.equal(input.isDrawing, true); input.move(0); assert.deepEqual([...g.path], [0]); }); test('点末端旁边的空格直接追加(点着走)', () => { const { g, input } = setup(); input.down(0); input.up(); input.down(1); assert.deepEqual([...g.path], [0, 1]); }); test('有线索 1 时点别的格起手无效并产生 rejectStart', () => { const { g, input } = setup(); const ev = input.down(2); assert.equal(ev[0].type, 'rejectStart'); assert.equal(g.path.length, 0); assert.equal(input.isDrawing, false); assert.deepEqual(input.move(1), []); }); test('滑到不相邻格忽略;空白 -1 不影响;同一格不重复触发', () => { const { g, input } = setup(); input.down(0); input.move(-1); input.move(2); // 不相邻 assert.deepEqual([...g.path], [0]); input.move(1); input.move(1); assert.deepEqual([...g.path], [0, 1]); }); test('过关后停止响应', () => { const { g, input } = setup(); input.down(0); input.move(1); input.move(2); const ev = input.move(3); assert.ok(ev.some((e) => e.type === 'cleared')); assert.equal(input.isDrawing, false); assert.deepEqual(input.down(3), []); assert.deepEqual(input.move(2), []); assert.equal(g.path.length, 4); }); test('点更早的历史格一次撤掉多步,不松手即可沿新分支继续', () => { const b = Board.fromCells([ c('a', 0, 0, 1, 1, 1), c('b', 1, 0), c('c', 2, 0), c('d', 2, 1), c('e', 1, 1), c('f', 0, 1), ]); const g = new Game(b); const input = new PointerInput(g); input.down(0); [1, 2, 3, 4].forEach(cell => input.move(cell)); input.up(); const events = input.down(1); assert.deepEqual(g.path, [0, 1]); assert.deepEqual(events, [ { type: 'unstep', cell: 4, step: 5 }, { type: 'unstep', cell: 3, step: 4 }, { type: 'unstep', cell: 2, step: 3 }, { type: 'rewound', cell: 1, removed: 3 }, ]); assert.equal(input.isDrawing, true); assert.equal(g.status, 'drawing'); input.move(4); // 从第 2 格直接转入之前的第 5 格 input.move(5); input.up(); assert.deepEqual(g.path, [0, 1, 4, 5]); assert.equal(g.stepOf(4), 3); assert.equal(g.stepOf(2), 0); assert.equal(g.status, 'idle'); }); test('不松手滑到不相邻历史格可直接回退;同格抖动不会重复撤销', () => { const { g, input } = setup(); input.down(0); input.move(1); input.move(2); input.move(0); // 与末端不相邻,仍能一次回到起点 assert.deepEqual(g.path, [0]); assert.deepEqual(input.move(0), []); input.move(1); assert.deepEqual(g.path, [0, 1]); });