30 lines
4.0 KiB
JavaScript
30 lines
4.0 KiB
JavaScript
import fs from 'node:fs';import vm from 'node:vm';import assert from 'node:assert/strict';import path from 'node:path';import {fileURLToPath} from 'node:url';
|
|
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');const pack=JSON.parse(fs.readFileSync(path.join(root,'src/upstream/levels.json'))),ordered=pack.playOrder.map(id=>pack.levels.find(l=>l.id===id));
|
|
export function boot(storage = new Map(), width = 375, height = 812) {
|
|
const handlers = {}, frames = new Map(), played=[],vibrated=[];
|
|
let frameId = 0,now=100000;
|
|
class Clock extends Date {static now(){return now;}}
|
|
const text = [],images=[];
|
|
const ctx = new Proxy({ drawImage:(image,x,y,w,h)=>images.push({source:image.source||null,x,y,w,h}),measureText: s => ({ width: [...String(s)].reduce((n,c)=>n+(/[^\x00-\x7f]/.test(c)?1:.55),0)*(Number(/([\d.]+)px/.exec(ctx.font)?.[1])||16) }), fillText: (s, x, y) => text.push({ s, x, y }) }, { get: (o, k) => o[k] ?? (() => { }), set: (o, k, v) => (o[k] = v, true) });
|
|
const wx = {createImage:()=>({set src(value){const b=fs.readFileSync(path.join(root,value));this.width=b.readUInt32BE(16);this.height=b.readUInt32BE(20);this.source=value;this.onload?.();}}),vibrateShort:o=>vibrated.push({type:o.type,at:now}),setInnerAudioOption:()=>{},createInnerAudioContext:()=>({stop(){},onError(){},play(){assert.ok(fs.existsSync(path.join(root,this.src)),this.src);played.push(this.src);}}), createCanvas: () => ({ getContext: () => ctx }), getWindowInfo: () => ({ windowWidth: width, windowHeight: height, pixelRatio: 2, safeArea: { top: 44, bottom: height - 34 } }), getMenuButtonBoundingClientRect: () => ({ bottom: 80 }), getStorageSync: k => storage.get(k) || '', setStorageSync: (k, v) => storage.set(k, v), removeStorageSync: k => storage.delete(k), getPerformance: () => ({ now: () => Date.now() }), showModal: () => { }, env: { USER_DATA_PATH: '/user' } };
|
|
wx.loadFont=p=>{assert.ok(fs.existsSync(path.join(root,p)),'字体必须随包提供 '+p);return path.basename(p,'.ttf');};
|
|
for (const name of ['TouchStart', 'TouchMove', 'TouchEnd', 'TouchCancel', 'Hide', 'Show', 'WindowResize'])
|
|
wx['on' + name] = cb => handlers[name] = cb;
|
|
const global = {};
|
|
vm.runInNewContext(fs.readFileSync(path.join(root, 'game.js'), 'utf8'), {Date:Clock, wx, GameGlobal: global, requestAnimationFrame: cb => { frames.set(++frameId, cb); return frameId; }, cancelAnimationFrame: id => frames.delete(id), setTimeout: () => 1, clearTimeout: () => { } }, { timeout: 10000 });
|
|
const tick = () => { const entry=frames.entries().next().value;if(!entry)return;const [id,fn]=entry; frames.delete(id);text.length=0;images.length=0; fn(); };
|
|
tick();
|
|
const touch = (kind, x, y) => handlers[kind]({ changedTouches: [{ identifier: 1, clientX: x, clientY: y }], touches: [{ identifier: 1, clientX: x, clientY: y }] });
|
|
const tap = (x, y) => { touch('TouchStart', x, y); touch('TouchEnd', x, y); tick(); };
|
|
const api = global.numberSwipeR2;
|
|
const button = label => { const b = api.buttons().find(b => b.label === label); assert.ok(b, '缺少按钮 ' + label); assert.ok(b.enabled, '按钮不可用 ' + 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.buttons().some(b=>b.label==='返回游戏'))button('返回游戏'); if (api.snapshot().screen !== 'select')
|
|
button('选关'); const label = String(i + 1); let steps = 0; while (!api.buttons().some(b => b.label === label || b.label === label + ' ✓')) {
|
|
button(api.snapshot().selection.page>Math.floor(i/15)+1?'上一页':'下一页');
|
|
assert.ok(++steps < 25);
|
|
} button(api.buttons().some(b => b.label === label) ? label : label + ' ✓'); if (api.buttons().some(b => b.label === '返回游戏'))
|
|
button('返回游戏'); assert.equal(api.snapshot().level, ordered[i].id); };
|
|
return { advance(ms){now+=ms;tick();}, api, button, cell, select, tick, handlers, frames, storage, text, played,images,vibrated };
|
|
}
|