'use strict'; const test = require('node:test'); const assert = require('node:assert'); const { parseMarkers, fillPolygonEvenOdd, pointInPolygon, extractPolygons, parseColor, buildMarkersUrl } = require('../src/core/territoryDataService'); const chunkPos = require('../src/core/chunkPos'); /** 把 [x, z] 坐标对转换为 dynmap 的点对象形式 */ function points(coords) { return coords.map(([x, z]) => ({ x, z })); } /** 构造一个 markers.json 的单个多边形 marker */ function claimMarker(name, polygons, color) { const converted = polygons.map(points); return { popup: `
疆土归属: ${name}
`, color: color || '#75bd20', points: converted.length === 1 ? converted[0] : converted }; } test('pointInPolygon 经典射线法', () => { const square = [[0, 0], [32, 0], [32, 32], [0, 32]]; assert.strictEqual(pointInPolygon(16, 16, square), true); assert.strictEqual(pointInPolygon(-1, 16, square), false); assert.strictEqual(pointInPolygon(33, 16, square), false); }); test('extractPolygons 展平单个多边形与嵌套多边形数组', () => { const single = points([[0, 0], [16, 0], [16, 16]]); assert.strictEqual(extractPolygons(single).length, 1); const nested = [points([[0, 0], [16, 0], [16, 16]]), points([[4, 4], [8, 4], [8, 8]])]; assert.strictEqual(extractPolygons(nested).length, 2); // 少于 3 个点的多边形被丢弃 assert.strictEqual(extractPolygons(points([[0, 0], [1, 1]])).length, 0); }); test('fillPolygonEvenOdd 用内圈孔洞挖空外圈', () => { const outer = [[0, 0], [48, 0], [48, 48], [0, 48]]; const hole = [[16, 16], [32, 16], [32, 32], [16, 32]]; const out = new Set(); fillPolygonEvenOdd(out, [outer, hole]); // 3×3 区块的外圈 = 9 格,挖掉中心 1 格 = 8 格 assert.strictEqual(out.size, 8); assert.ok(!out.has(chunkPos.key(1, 1)), '孔洞中心区块不应被填充'); assert.ok(out.has(chunkPos.key(0, 0))); assert.ok(out.has(chunkPos.key(2, 2))); }); test('parseMarkers 解析邦国领土与王城', () => { const json = JSON.stringify([ { id: 'union_claims', markers: [ claimMarker('测试国', [[[0, 0], [32, 0], [32, 32], [0, 32]]], '#ff0000'), claimMarker('另一国', [[[64, 64], [96, 64], [96, 96], [64, 96]]], '#00ff00') ] }, { id: 'union_capitals', markers: [ { popup: '
「测试国」的王城
', point: { x: 8, y: 70, z: 8 } } ] } ]); const model = parseMarkers(json); assert.strictEqual(model.stateCount, 2); const state = model.state('测试国'); assert.ok(state); assert.strictEqual(state.color, 0xff0000); assert.deepStrictEqual(state.capital, { x: 0, z: 0 }, '王城应由方块坐标 8,8 换算为区块 0,0'); assert.strictEqual(state.chunks.size, 4, '2×2 区块应被填充'); // 没有王城 marker 的邦国回退到第一个区块 const other = model.state('另一国'); assert.ok(other.capital); }); test('parseMarkers 忽略弹窗中没有邦国名的 marker', () => { const json = JSON.stringify([ { id: 'union_claims', markers: [{ popup: '
无归属
', points: points([[0, 0], [16, 0], [16, 16]]) }] }, { id: 'other_layer', markers: [claimMarker('忽略我', [[[0, 0], [16, 0], [16, 16]]])] } ]); const model = parseMarkers(json); assert.strictEqual(model.stateCount, 0); }); test('parseMarkers 顶层必须是数组', () => { assert.throws(() => parseMarkers('{}'), /数组/); }); test('parseColor 支持 #RRGGBB 与 #AARRGGBB', () => { assert.strictEqual(parseColor('#ff0000'), 0xff0000); assert.strictEqual(parseColor('#80ff0000'), 0xff0000); assert.strictEqual(parseColor('75bd20'), 0x75bd20); // 非法输入回退到默认邦国绿 assert.strictEqual(parseColor('#zzzzzz'), 0x75bd20); assert.strictEqual(parseColor(''), 0x75bd20); }); test('buildMarkersUrl 拼接地图站点与世界目录', () => { assert.strictEqual( buildMarkersUrl('http://bgjq.simpfun.cn', 'minecraft_overworld'), 'http://bgjq.simpfun.cn/tiles/minecraft_overworld/markers.json' ); // 末尾斜杠应被去掉 assert.strictEqual( buildMarkersUrl('https://map.example.com/', 'world'), 'https://map.example.com/tiles/world/markers.json' ); // 缺省值 assert.strictEqual(buildMarkersUrl(), 'http://bgjq.simpfun.cn/tiles/minecraft_overworld/markers.json'); });