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); });