Start the jigsaw puzzle picker on a random image
The menu always opened on the first artwork; now imageIndex is randomised right after loadArtwork(), so each visit to the picker shows a different initial puzzle. The existing Random/prev/next buttons are unchanged (previous random-start button reverted in 4f2d299). Test page + CDP driver verify 8 fresh page loads all land on a valid in-range image with its preview loaded, with variety across loads.
This commit is contained in:
parent
4f2d29942d
commit
25f5e05a96
|
|
@ -0,0 +1,60 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html><head><meta charset="utf-8">
|
||||||
|
<script type="importmap">{"imports":{"phaser":"/phaser.esm.js"}}</script>
|
||||||
|
<style>html,body{margin:0;background:#111}canvas{display:block}</style>
|
||||||
|
</head><body>
|
||||||
|
<div id="log"></div>
|
||||||
|
<script type="module">
|
||||||
|
import * as Phaser from 'phaser';
|
||||||
|
import JigsawGame from './src/games/jigsaw/JigsawGame.js';
|
||||||
|
|
||||||
|
const log = (m) => { document.getElementById('log').textContent += m + '\n'; console.log('[t]', m); };
|
||||||
|
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||||
|
|
||||||
|
const ART = [
|
||||||
|
{ name: 'Alien World', path: 'assets/images/shift/alien-world.png' },
|
||||||
|
{ name: 'Aquaroom', path: 'assets/images/shift/aquaroom.png' },
|
||||||
|
{ name: 'Aztec Warrior', path: 'assets/images/shift/aztec-warrior.png' },
|
||||||
|
{ name: 'Cat On Tiger', path: 'assets/images/shift/cat-on-tiger.png' },
|
||||||
|
{ name: 'Cockpit', path: 'assets/images/shift/cockpit.png' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
type: Phaser.AUTO,
|
||||||
|
width: 1920, height: 1080,
|
||||||
|
parent: document.body,
|
||||||
|
backgroundColor: '#000',
|
||||||
|
scene: [ { key: 'boot', create() {
|
||||||
|
this.cache.json.add('shift-artwork', { artwork: ART });
|
||||||
|
this.cache.json.add('music', { tracks: [] });
|
||||||
|
this.scene.start('jigsaw-game');
|
||||||
|
} }, JigsawGame ],
|
||||||
|
};
|
||||||
|
const game = new Phaser.Game(config);
|
||||||
|
const s = () => game.scene.getScene('jigsaw-game');
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
for (let i = 0; i < 400 && !s(); i++) await wait(50); // boot → jigsaw start is async
|
||||||
|
if (!s()) throw new Error('jigsaw scene never started');
|
||||||
|
for (let i = 0; i < 400 && !s().menu; i++) await wait(50);
|
||||||
|
if (!s().menu) throw new Error('menu never built');
|
||||||
|
// The preview image is loaded asynchronously; give it a moment.
|
||||||
|
for (let i = 0; i < 200 && !s().previewImg; i++) await wait(25);
|
||||||
|
|
||||||
|
const sc = s();
|
||||||
|
const item = sc.currentImage();
|
||||||
|
document.title = 'RESULT:' + JSON.stringify({
|
||||||
|
idx: sc.imageIndex,
|
||||||
|
n: sc.artwork.length,
|
||||||
|
name: item && item.name,
|
||||||
|
validName: ART.some((a) => a.name === item.name),
|
||||||
|
preview: !!sc.previewImg,
|
||||||
|
menuVisible: sc.menu.visible,
|
||||||
|
});
|
||||||
|
log('initial image: ' + item.name + ' (index ' + sc.imageIndex + ' of ' + sc.artwork.length + ')');
|
||||||
|
})().catch((e) => {
|
||||||
|
log('ERROR: ' + ((e && e.stack) || e));
|
||||||
|
document.title = 'FAIL:' + ((e && e.message) || e);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body></html>
|
||||||
|
|
@ -77,6 +77,10 @@ export default class JigsawGame extends Phaser.Scene {
|
||||||
this.selectedDiff = 'easy';
|
this.selectedDiff = 'easy';
|
||||||
|
|
||||||
this.loadArtwork();
|
this.loadArtwork();
|
||||||
|
// Open the menu on a random picture, not always the first one, so each
|
||||||
|
// visit to the puzzle picker starts on a different puzzle. (loadArtwork
|
||||||
|
// guarantees at least one entry, so this index is always valid.)
|
||||||
|
this.imageIndex = Math.floor(Math.random() * this.artwork.length);
|
||||||
this.buildBackground();
|
this.buildBackground();
|
||||||
this.buildHUD();
|
this.buildHUD();
|
||||||
this.buildMenu();
|
this.buildMenu();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
// Headless-chromium CDP driver: loads a page repeatedly and reads the result
|
||||||
|
// the page publishes in document.title (RESULT:<json> or FAIL:<msg>).
|
||||||
|
// Usage: node tools/__jig_init_driver.mjs <url> [loads=8]
|
||||||
|
import { spawn } from 'node:child_process';
|
||||||
|
import { setTimeout as sleep } from 'node:timers/promises';
|
||||||
|
|
||||||
|
const url = process.argv[2];
|
||||||
|
const loads = Math.max(1, parseInt(process.argv[3] || '8', 10));
|
||||||
|
if (!url) { console.error('usage: driver <url> [loads]'); process.exit(2); }
|
||||||
|
|
||||||
|
const BIN = process.env.HOME + '/.cache/ms-playwright/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell';
|
||||||
|
const PORT = 9333;
|
||||||
|
const chrome = spawn(BIN, [
|
||||||
|
'--headless', '--no-sandbox', '--disable-gpu',
|
||||||
|
`--remote-debugging-port=${PORT}`,
|
||||||
|
'about:blank',
|
||||||
|
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||||
|
chrome.stderr.on('data', (d) => { const t = d.toString(); if (!/Fontconfig|dbus|DBus|ozone|sandbox|WebGL|GL Driver/i.test(t)) process.stderr.write(t); });
|
||||||
|
|
||||||
|
async function httpJson(path) {
|
||||||
|
const r = await fetch(`http://127.0.0.1:${PORT}${path}`);
|
||||||
|
return r.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
let ws, idc = 0;
|
||||||
|
const pending = new Map();
|
||||||
|
const send = (method, params = {}) => new Promise((resolve, reject) => {
|
||||||
|
const id = ++idc;
|
||||||
|
pending.set(id, { resolve, reject });
|
||||||
|
ws.send(JSON.stringify({ id, method, params }));
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
let targets = null;
|
||||||
|
for (let i = 0; i < 60; i++) {
|
||||||
|
try { targets = await httpJson('/json/list'); break; } catch { await sleep(250); }
|
||||||
|
}
|
||||||
|
if (!targets) throw new Error('chrome devtools endpoint never came up');
|
||||||
|
const page = targets.find((t) => t.type === 'page');
|
||||||
|
if (!page) throw new Error('no page target');
|
||||||
|
ws = new WebSocket(page.webSocketDebuggerUrl);
|
||||||
|
await new Promise((res, rej) => { ws.onopen = res; ws.onerror = rej; });
|
||||||
|
ws.onmessage = (m) => {
|
||||||
|
const msg = JSON.parse(m.data);
|
||||||
|
if (msg.id && pending.has(msg.id)) {
|
||||||
|
const { resolve, reject } = pending.get(msg.id);
|
||||||
|
pending.delete(msg.id);
|
||||||
|
if (msg.error) reject(new Error(msg.error.message)); else resolve(msg.result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await send('Runtime.enable');
|
||||||
|
await send('Page.enable');
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
for (let k = 0; k < loads; k++) {
|
||||||
|
await send('Page.navigate', { url });
|
||||||
|
const t0 = Date.now();
|
||||||
|
let title = '';
|
||||||
|
while (Date.now() - t0 < 30000) {
|
||||||
|
const out = await send('Runtime.evaluate', { expression: 'document.title', returnByValue: true });
|
||||||
|
title = out.result.value || '';
|
||||||
|
if (title.startsWith('RESULT:') || title.startsWith('FAIL')) break;
|
||||||
|
await sleep(250);
|
||||||
|
}
|
||||||
|
results.push(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ok = true;
|
||||||
|
const idxs = [];
|
||||||
|
for (const t of results) {
|
||||||
|
if (!t.startsWith('RESULT:')) { ok = false; console.error('bad title: ' + t); continue; }
|
||||||
|
const r = JSON.parse(t.slice('RESULT:'.length));
|
||||||
|
const valid = Number.isInteger(r.idx) && r.idx >= 0 && r.idx < r.n && r.n > 1 &&
|
||||||
|
r.validName && r.preview && r.menuVisible;
|
||||||
|
if (!valid) { ok = false; console.error('bad result: ' + JSON.stringify(r)); }
|
||||||
|
idxs.push(r.idx);
|
||||||
|
}
|
||||||
|
const distinct = new Set(idxs).size;
|
||||||
|
console.log(`initial indices over ${loads} fresh page loads: ${idxs.join(', ')}`);
|
||||||
|
console.log(`distinct initial images: ${distinct}/${loads}`);
|
||||||
|
if (distinct < 2) { ok = false; console.error('expected variety in the initial image'); }
|
||||||
|
console.log(ok ? 'ALL PASS' : 'FAIL');
|
||||||
|
process.exitCode = ok ? 0 : 1;
|
||||||
|
} finally {
|
||||||
|
try { ws && ws.close(); } catch {}
|
||||||
|
chrome.kill('SIGKILL');
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue