56 lines
4.8 KiB
HTML
56 lines
4.8 KiB
HTML
<!doctype html><meta charset="utf-8"><title>指路与玩家填色像素回归</title>
|
||
<style>body{font:16px sans-serif;background:#eee}#views{display:flex;gap:12px}canvas{width:375px;height:812px;background:white}h1{font-size:20px}</style>
|
||
<style>
|
||
@font-face{font-family:NumberSwipeSans500;src:url(../fonts/NumberSwipeSans500.ttf)}
|
||
@font-face{font-family:NumberSwipeSans600;src:url(../fonts/NumberSwipeSans600.ttf)}
|
||
@font-face{font-family:NumberSwipeSans700;src:url(../fonts/NumberSwipeSans700.ttf)}
|
||
@font-face{font-family:NumberSwipeDigits800;src:url(../fonts/NumberSwipeDigits800.ttf)}
|
||
@font-face{font-family:NumberSwipePlayful;src:url(../fonts/NumberSwipePlayful.ttf)}
|
||
</style>
|
||
<h1>指路与玩家填色像素回归</h1><p id="result">检查中…</p><div id="views"></div>
|
||
<script type="module">
|
||
import {solve} from '../src/upstream/rules.mjs';
|
||
await Promise.all(['NumberSwipeSans500','NumberSwipeSans600','NumberSwipeSans700','NumberSwipeDigits800','NumberSwipePlayful'].map(f=>document.fonts.load('16px '+f)));
|
||
const pack=await(await fetch('../src/upstream/levels.json')).json();
|
||
const source=await(await fetch('../game.js')).text();
|
||
const levels=pack.playOrder.map(id=>pack.levels.find(l=>l.id===id));
|
||
const canvases=[],handlers={},frames=new Map(),storage=new Map();let frameId=0,now=100000;
|
||
class Clock extends Date{static now(){return now;}}
|
||
const wx={
|
||
loadFont:p=>p.split('/').at(-1).replace('.ttf',''),
|
||
createCanvas(){const c=document.createElement('canvas');if(!canvases.length)canvases.push(c);return c;},
|
||
getWindowInfo:()=>({windowWidth:375,windowHeight:812,pixelRatio:1,safeArea:{top:44,bottom:778}}),
|
||
getMenuButtonBoundingClientRect:()=>({bottom:80}),getPerformance:()=>({now:()=>now}),
|
||
getStorageSync:k=>storage.get(k)||'',setStorageSync:(k,v)=>storage.set(k,v),removeStorageSync:k=>storage.delete(k),
|
||
setInnerAudioOption(){},createInnerAudioContext:()=>({stop(){},onError(){},play(){}}),showModal(){},env:{USER_DATA_PATH:'/user'}
|
||
};
|
||
for(const n of ['TouchStart','TouchMove','TouchEnd','TouchCancel','Hide','Show','WindowResize'])wx['on'+n]=cb=>handlers[n]=cb;
|
||
const global={};
|
||
new Function('wx','GameGlobal','requestAnimationFrame','cancelAnimationFrame','setTimeout','clearTimeout','Date',source)(wx,global,cb=>{frames.set(++frameId,cb);return frameId;},id=>frames.delete(id),()=>1,()=>{},Clock);
|
||
const api=global.numberSwipeR2;
|
||
const tick=(ms=16)=>{now+=ms;const entry=frames.entries().next().value;if(!entry)return;const [id,fn]=entry;frames.delete(id);fn();};tick();
|
||
const tap=(x,y)=>{const e={changedTouches:[{identifier:1,clientX:x,clientY:y}],touches:[{identifier:1,clientX:x,clientY:y}]};handlers.TouchStart(e);handlers.TouchEnd(e);tick();};
|
||
const button=label=>{const b=api.buttons().find(b=>b.label===label);if(!b||!b.enabled)throw Error('缺少按钮 '+label);tap(b.x+b.w/2,b.y+b.h/2);};
|
||
const cell=id=>{const s=api.snapshot(),[x,y]=s.centers[id];tap(x+s.boardRect.x,y+s.boardRect.y);};
|
||
const select=i=>{if(api.snapshot().screen!=='select')button('选关');let n=0;while(!api.buttons().some(b=>b.label===String(i+1)||b.label===`${i+1} ✓`)){button('下一页');if(++n>25)throw Error('分页失败');}button(api.buttons().some(b=>b.label===String(i+1))?String(i+1):`${i+1} ✓`);if(api.buttons().some(b=>b.label==='返回游戏'))button('返回游戏');};
|
||
const capture=()=>{const c=document.createElement('canvas');c.width=375;c.height=812;c.getContext('2d').drawImage(canvases[0],0,0);return c;};
|
||
const samples=(route)=>{const s=api.snapshot(),ctx=canvases[0].getContext('2d');return route.map(id=>{const p=s.centers[id];return Array.from(ctx.getImageData(Math.round(p[0]-s.unit*.08),Math.round(p[1]+s.boardRect.y+s.unit*.18),5,5).data);});};
|
||
let checked=0;const result=document.querySelector('#result');
|
||
try{
|
||
for(let i=0;i<levels.length;i++){
|
||
select(i);const route=solve(levels[i])[0],n=Math.min(2,route.length-1);
|
||
for(const id of route.slice(0,n))cell(id);
|
||
const before=samples(route.slice(0,n));const base=capture();
|
||
button('重开');button('指路');for(const id of route.slice(0,n))cell(id);tick(300);
|
||
const after=samples(route.slice(0,n));
|
||
if(JSON.stringify(before)!==JSON.stringify(after))throw Error('玩家填色像素变化:第 '+(i+1)+' 关');
|
||
button('撤销');cell(route[n-1]);tick(300);
|
||
if(JSON.stringify(before)!==JSON.stringify(samples(route.slice(0,n))))throw Error('撤销后填色变化:'+(i+1));
|
||
checked++;
|
||
if(i%4===0){result.textContent=`正在检查 ${checked}/200 关…`;await new Promise(resolve=>requestAnimationFrame(resolve));}
|
||
if(i===3){document.querySelector('#views').replaceChildren(base,capture());}
|
||
}
|
||
result.textContent=`PASS:${checked}/200 关,点击指路前后和撤销重画后,玩家已走格子的实际 Canvas 填色像素一致。左图无提示,右图开启高对比提示。`;
|
||
}catch(e){result.textContent='FAIL:'+e.message;console.error(e);}
|
||
</script>
|