Files
shanhe-puzzle/tests/pockets.test.cjs
codex-bot 4845826deb
Shanhe source and offline package checks / verify (pull_request) Failing after 5s
feat: sync Shanhe 2.2 source and add remote package verification
2026-11-01 17:13:46 +08:00

22 lines
5.4 KiB
JavaScript

const test=require('node:test'),assert=require('node:assert/strict'),C=require('../src/core.js'),M=require('matter-js'),{Game}=require('../src/game.js');
const rectangle=(id,x,y,w,h)=>({id:String(id),name:'区域'+id,center:[x+w/2,y+h/2],rings:[[[x,y],[x+w,y],[x+w,y+h],[x,y+h]]]});
const level={id:'pocket',name:'封口补位',width:500,height:500,regions:[rectangle(0,50,50,300,50),rectangle(1,50,100,50,200),rectangle(2,300,100,50,200),rectangle(3,50,300,300,50),rectangle(4,100,100,200,200),rectangle(5,360,360,40,40)],edges:[[0,1,0],[0,2,0],[1,3,0],[2,3,0],[0,4,0],[1,4,0],[2,4,0],[3,4,0]]};
function game(snap=2){const g=new Game(level,420,620,4,()=>{},()=>.5,1,snap);g.status='playing';g.queue=[];return g;}
function depth(a,b){let max=0;for(const x of a.parts.length>1?a.parts.slice(1):[a])for(const y of b.parts.length>1?b.parts.slice(1):[b]){if(!M.Bounds.overlaps(x.bounds,y.bounds))continue;const collision=M.Collision.collides(x,y);if(collision)max=Math.max(max,collision.depth);}return max;}
test('真实封口才生成缺口,开口海湾和外侧地区不算',()=>{const atlas=C.pocketAtlas(level);assert.deepEqual(C.enclosedRegions(atlas,[0,1,2,3]),[4]);assert.deepEqual(C.enclosedRegions(atlas,[0,1,2]),[]);assert.deepEqual(C.enclosedRegions(atlas,[0,1,3]),[]);assert.deepEqual(C.enclosedRegions(atlas,[0,1,2,3,4]),[]);});
test('地图源内环中的内嵌城市也支持补位,海面不会被当作缺失地区',()=>{const outer=rectangle(0,0,0,300,300);outer.holes=[[[80,80],[220,80],[220,220],[80,220]]];const atlas=C.pocketAtlas({...level,width:300,height:300,regions:[outer,rectangle(1,80,80,140,140)]});assert.deepEqual(C.enclosedRegions(atlas,[0]),[1]);});
test('正确缺块在外圈附近可跨过封闭边缘补位,沿用组合的自然角度',()=>{
const g=game(),a=g.createGroup([0,1,2,3],{tx:60,ty:200,angle:.35}),b=g.createGroup([4],{tx:60,ty:200,angle:.35});M.Body.translate(b.body,{x:120,y:0});assert.ok(Math.abs(g.pose(a).tx-g.pose(b).tx)>g.snap.magnet);assert.equal(g.snapPlan(g.pose(a),g.pose(b),g.snap.distance),null);const plan=g.pocketPlan(a,b);assert.ok(plan);assert.equal(plan.anchor,a);assert.equal(g.tryMerges(),true);const merged=g.groups[0];assert.equal(g.pocketFillCount,1);assert.equal(g.pocketTransfers.length,1);assert.ok(g.fx.at(-1).text.includes('留洞补位'));assert.ok(Math.abs(merged.body.angle-.35)<1e-8);assert.deepEqual(g.pocketHints(),[]);g.destroy();
});
test('补位仍拒绝太远、方向不对、非缺口及非邻接地区',()=>{
const g=game(),a=g.createGroup([0,1,2,3],{tx:60,ty:200,angle:0}),b=g.createGroup([4],{tx:60,ty:200,angle:0}),other=g.createGroup([5],{tx:60,ty:200,angle:0});M.Body.translate(b.body,{x:1000,y:0});assert.equal(g.pocketPlan(a,b),null);M.Body.translate(b.body,{x:-900,y:0});M.Body.setAngle(b.body,1);assert.equal(g.pocketPlan(a,b),null);assert.equal(g.pocketPlan(a,other),null);M.Body.setAngle(b.body,0);g.level={...level,edges:[]};assert.equal(g.pocketPlan(a,b),null);g.destroy();
});
test('补位不会吞掉占据缺口的第三块;空间腾出后可继续补',()=>{
const g=game(),pose={tx:60,ty:200,angle:0},a=g.createGroup([0,1,2,3],pose),b=g.createGroup([4],pose),other=g.createGroup([5],pose);M.Body.translate(b.body,{x:120,y:0});M.Body.setPosition(other.body,g.worldPoint(a,level.regions[4].center));const pos={...other.body.position},plan=g.pocketPlan(a,b);assert.ok(plan);assert.equal(g.merge(a,b,plan.edge,plan),null);assert.equal(g.pocketFillCount,0);assert.equal(g.pocketTransfers.length,0);assert.equal(g.score,0);assert.deepEqual(other.body.position,pos);M.Body.translate(other.body,{x:0,y:200});const merged=g.merge(a,b,plan.edge,plan);assert.ok(merged);assert.ok(depth(merged.body,other.body)<=1.2);g.destroy();
});
test('普通外侧邻块不会因全局已拼得多而触发补位',()=>{const g=game();const a=g.createGroup([0,1,2,4],{tx:60,ty:200,angle:0}),b=g.createGroup([3],{tx:200,ty:200,angle:0});assert.equal(g.pocketPlan(a,b),null);g.destroy();});
test('真实全国省级地图能识别湖北内侧缺口,沿海海南不算被包围',()=>{const fs=require('node:fs'),vm=require('node:vm'),box={window:{}};vm.runInNewContext(fs.readFileSync('src/maps.js','utf8'),box);const map=box.window.MAP_DATA.levels[0],atlas=C.pocketAtlas(map),hubei=map.regions.findIndex(r=>r.id==='420000'),hainan=map.regions.findIndex(r=>r.id==='460000'),rest=id=>map.regions.map((r,i)=>i).filter(i=>i!==id);assert.ok(C.enclosedRegions(atlas,rest(hubei)).includes(hubei));assert.ok(!C.enclosedRegions(atlas,rest(hainan)).includes(hainan));});
test('真实湖北被六个邻省先围住后,可在外圈附近通过正常合并流程补位',()=>{
const fs=require('node:fs'),vm=require('node:vm'),box={window:{}};vm.runInNewContext(fs.readFileSync('src/maps.js','utf8'),box);const map=box.window.MAP_DATA.levels[0],id=map.regions.findIndex(r=>r.id==='420000'),neighbours=map.edges.filter(e=>!e[2]&&(e[0]===id||e[1]===id)).map(e=>e[0]===id?e[1]:e[0]),g=new Game(map,420,620,4,()=>{},()=>.5,1,2);g.status='playing';g.queue=[];const a=g.createGroup(neighbours,{tx:10,ty:140,angle:0});g.fitGroup(a);assert.ok(g.pocketIds(a).includes(id));const b=g.createGroup([id],g.pose(a));M.Body.setPosition(b.body,{x:a.body.bounds.min.x-(b.body.bounds.max.x-b.body.bounds.min.x)/2-4,y:a.body.position.y});assert.equal(g.snapPlan(g.pose(a),g.pose(b),g.snap.distance),null);assert.equal(g.tryMerges(),true);assert.equal(g.pocketFillCount,1);assert.equal(g.groups[0].ids.length,neighbours.length+1);g.destroy();
});