Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1dxvtsZx8NE9fwjXUNDBr
175 lines
11 KiB
JavaScript
175 lines
11 KiB
JavaScript
// Partite complete con eventi reali. Si accelerano solo le attese, mai le mosse di Andrea.
|
|
// npm run browser:briscola — /tmp/regolo-shots/briscola-*.png
|
|
import { chromium } from 'playwright';
|
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
import assert from 'node:assert/strict';
|
|
import { staticServer } from './helpers.mjs';
|
|
|
|
const out = '/tmp/regolo-shots'; mkdirSync(out, { recursive: true });
|
|
const server = await staticServer(), errors = [];
|
|
let browser;
|
|
async function clickCard(page, mode, index) {
|
|
const r = await page.locator('#scene').boundingBox();
|
|
const x = r.x + (176 + index * 102 + 42) / 640 * r.width, y = r.y + 290 / 360 * r.height;
|
|
if (mode === 'touch') await page.touchscreen.tap(x, y);
|
|
else if (mode === 'keyboard') {
|
|
await page.keyboard.press(String(index + 1));
|
|
assert.equal(await page.evaluate(() => game.closeup.selected), index);
|
|
await page.keyboard.press('ArrowRight'); await page.keyboard.press('ArrowLeft');
|
|
assert.equal(await page.evaluate(() => game.closeup.selected), index);
|
|
await page.keyboard.press('Enter');
|
|
} else await page.mouse.click(x, y);
|
|
}
|
|
async function advance(page) {
|
|
return page.evaluate(async () => {
|
|
const { canPlay } = await import('/src/engine/briscola.js');
|
|
for (let i = 0; i < 6 && !canPlay(game.closeup) && !['count', 'result'].includes(game.closeup.step); i++) game.update(1.3);
|
|
return game.closeup.step;
|
|
});
|
|
}
|
|
async function artwork(page) {
|
|
const pictures = await page.evaluate(async () => {
|
|
await document.fonts.ready;
|
|
const { DECK_CARDS, newBriscola } = await import('/src/engine/briscola.js');
|
|
const { drawCard, drawBriscola } = await import('/src/engine/briscola-render.js');
|
|
const { story } = await import('/src/story/story.js');
|
|
const canvas = document.createElement('canvas'), ctx = canvas.getContext('2d'), pictures = {};
|
|
for (const [deck, cards] of Object.entries(DECK_CARDS)) {
|
|
canvas.width = 900; canvas.height = 560;
|
|
ctx.imageSmoothingEnabled = false;
|
|
ctx.fillStyle = '#234c40'; ctx.fillRect(0, 0, 900, 560);
|
|
cards.forEach((card, i) => drawCard(ctx, card, 8 + (i % 10) * 89, 8 + Math.floor(i / 10) * 136));
|
|
pictures[`${deck}-mazzo`] = canvas.toDataURL().split(',')[1];
|
|
canvas.width = 640; canvas.height = 360;
|
|
const c = newBriscola(41, { deck }), before = JSON.stringify(c);
|
|
drawBriscola(ctx, c, story.closeups.briscola);
|
|
if (before !== JSON.stringify(c)) throw new Error('Il renderer modifica lo stato');
|
|
pictures[`${deck}-tavolo`] = canvas.toDataURL().split(',')[1];
|
|
}
|
|
return pictures;
|
|
});
|
|
for (const [name, png] of Object.entries(pictures)) writeFileSync(`${out}/briscola-${name}.png`, Buffer.from(png, 'base64'));
|
|
}
|
|
try {
|
|
browser = await chromium.launch({ args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
|
|
const scenarios = ['mouse', 'touch', 'keyboard'].flatMap(mode => [
|
|
{ name: `desktop-${mode}`, mode, viewport: { width: 1280, height: 860 } },
|
|
{ name: `telefono-${mode}`, mode, viewport: { width: 390, height: 844 } },
|
|
]);
|
|
scenarios.push({ name: 'orizzontale-touch', mode: 'touch', viewport: { width: 844, height: 390 } });
|
|
for (const { name, mode, viewport } of scenarios) {
|
|
const page = await browser.newPage({ viewport, hasTouch: true, isMobile: viewport.width < 900 });
|
|
page.on('pageerror', e => errors.push(e.message));
|
|
page.on('console', e => { if (e.type() === 'error') errors.push(e.text()); });
|
|
await page.goto(`http://127.0.0.1:${server.port}/`);
|
|
await page.click('#splash'); await page.click('#new-game');
|
|
await page.click('#rione [data-rione="campomigliaio"]'); await page.click('#skip-intro');
|
|
await page.evaluate(async () => {
|
|
const { settle } = await import('/tests/helpers.mjs'); settle(game); game.go('oratorio'); settle(game);
|
|
});
|
|
await page.waitForTimeout(1500);
|
|
if (name === 'desktop-mouse') {
|
|
await page.screenshot({ path: `${out}/briscola-oratorio.png` });
|
|
await artwork(page);
|
|
}
|
|
await page.evaluate(async () => {
|
|
const { settle } = await import('/tests/helpers.mjs');
|
|
game.act('talk', 'giocatore_carte'); settle(game);
|
|
});
|
|
await page.locator('#choices li', { hasText: 'Una briscola?' }).click();
|
|
await page.evaluate(async () => (await import('/tests/helpers.mjs')).settle(game));
|
|
// Sul telefono le napoletane, altrove le toscane: tutti e due i mazzi passano dal dialogo.
|
|
const deck = name.startsWith('telefono') ? 'napoletane' : 'toscane';
|
|
await page.locator('#choices li', { hasText: deck === 'napoletane' ? 'Con le napoletane.' : 'Con le toscane.' }).click();
|
|
await page.evaluate(async () => (await import('/tests/helpers.mjs')).settle(game));
|
|
await page.locator('#choices li', { hasText: 'A fassi male.' }).click();
|
|
assert.equal(await page.evaluate(async () => { (await import('/tests/helpers.mjs')).settle(game); return game.closeup.deck; }), deck);
|
|
await page.evaluate(async deck => {
|
|
const { newBriscola } = await import('/src/engine/briscola.js');
|
|
game.closeup = { id: 'briscola', ...newBriscola(91421, { level: 'male', deck }) };
|
|
}, deck);
|
|
await page.waitForSelector('#briscola-controls:not([hidden])');
|
|
assert.ok(await page.isHidden('#bocce-controls'));
|
|
const scene = await page.locator('#scene').boundingBox(), exit = await page.locator('#closeup-close').boundingBox();
|
|
assert.ok(exit.y >= scene.y + scene.height, 'uscita fuori dal tavolo');
|
|
for (const id of ['#corner', '#sound']) {
|
|
const r = await page.locator(id).boundingBox(); assert.ok(r.y + r.height <= scene.y, `${id} fuori dal tavolo`);
|
|
}
|
|
assert.ok(scene.width * 84 / 640 >= 44, 'carte toccabili sul telefono');
|
|
await advance(page);
|
|
await page.screenshot({ path: `${out}/briscola-${name}-inizio.png` });
|
|
if (name === 'desktop-mouse') {
|
|
// Il tempo premuto non carica nulla; uscire dal bersaglio o annullare non gioca.
|
|
const r = await page.locator('#scene').boundingBox(), x = r.x + 218 / 640 * r.width, y = r.y + 290 / 360 * r.height;
|
|
const before = await page.evaluate(() => game.closeup.hands.player.length);
|
|
await page.mouse.move(x, y); await page.mouse.down(); await page.waitForTimeout(400);
|
|
assert.equal(await page.evaluate(() => game.closeup.hands.player.length), before);
|
|
await page.locator('#scene').dispatchEvent('pointercancel', { pointerId: 1 }); await page.mouse.up();
|
|
assert.equal(await page.evaluate(() => game.closeup.hands.player.length), before);
|
|
// Anche la selezione da tastiera sui pulsanti segue il focus.
|
|
await page.locator('#briscola-card-0').focus(); await page.keyboard.press('ArrowRight');
|
|
assert.equal(await page.evaluate(() => game.closeup.selected), 1);
|
|
assert.equal(await page.evaluate(() => document.activeElement.id), 'briscola-card-1');
|
|
await page.locator('#briscola-card-1').evaluate(b => b.blur());
|
|
// Muto e pausa fermano anche le attese della partita.
|
|
await page.keyboard.press('m'); assert.equal(await page.evaluate(() => music.muted), true);
|
|
assert.equal(await page.evaluate(() => ambience.nodes.size), 0);
|
|
await page.keyboard.press('m');
|
|
await page.click('#pause-button');
|
|
const paused = await page.evaluate(() => JSON.stringify(game.closeup)); await page.waitForTimeout(250);
|
|
assert.equal(await page.evaluate(() => JSON.stringify(game.closeup)), paused);
|
|
await page.keyboard.press('Escape');
|
|
}
|
|
let played = 0, capturedTrick = false, capturedEnd = false;
|
|
while (!['count', 'result'].includes(await advance(page))) {
|
|
const index = await page.evaluate(async () => {
|
|
const { observation, attentive } = await import('/src/engine/briscola.js');
|
|
return attentive(observation(game.closeup, 'player'));
|
|
});
|
|
const before = await page.evaluate(() => game.closeup.hands.player.length);
|
|
await clickCard(page, mode, index); played++;
|
|
assert.equal(await page.evaluate(() => game.closeup.hands.player.length), before - 1, 'un gesto gioca esattamente una carta');
|
|
if (!capturedTrick) {
|
|
await page.evaluate(() => { if (game.closeup.step === 'play') game.update(0.75); });
|
|
assert.equal(await page.evaluate(() => game.closeup.step), 'trick');
|
|
assert.equal(await page.evaluate(() => game.closeup.table.length), 2);
|
|
await page.waitForTimeout(200);
|
|
await page.screenshot({ path: `${out}/briscola-${name}-presa.png` }); capturedTrick = true;
|
|
}
|
|
if (!capturedEnd && await page.evaluate(() => !game.closeup.stock.length)) {
|
|
await page.screenshot({ path: `${out}/briscola-${name}-fine-mazzo.png` }); capturedEnd = true;
|
|
}
|
|
assert.ok(played <= 20);
|
|
}
|
|
assert.equal(played, 20);
|
|
await page.evaluate(() => game.update(3));
|
|
await page.screenshot({ path: `${out}/briscola-${name}-conteggio.png` });
|
|
await page.evaluate(() => game.update(15));
|
|
await page.waitForFunction(() => game.closeup.step === 'result');
|
|
await page.waitForTimeout(50);
|
|
const result = await page.evaluate(() => game.closeup.result);
|
|
assert.equal(result.player + result.cpu, 120);
|
|
await page.screenshot({ path: `${out}/briscola-${name}-risultato.png` });
|
|
if (mode === 'keyboard') await page.keyboard.press('Enter');
|
|
else if (mode === 'touch') await page.tap('#closeup-close');
|
|
else await page.click('#closeup-close');
|
|
await page.evaluate(async () => (await import('/tests/helpers.mjs')).settle(game));
|
|
assert.equal(await page.evaluate(() => !!game.state.flags.briscola_vinta), result.winner === 'player');
|
|
assert.ok(await page.evaluate(() => game.state.flags.briscola_giocata));
|
|
// Rivincita e abbandono con Esc: nessun flag di vittoria regalato.
|
|
await page.evaluate(async () => {
|
|
const { settle } = await import('/tests/helpers.mjs'); game.act('talk', 'giocatore_carte'); settle(game);
|
|
});
|
|
await page.locator('#choices li', { hasText: 'La rivincita?' }).click();
|
|
await page.evaluate(async () => (await import('/tests/helpers.mjs')).settle(game));
|
|
await page.locator('#choices li', { hasText: 'Con le toscane.' }).click();
|
|
await page.evaluate(async () => (await import('/tests/helpers.mjs')).settle(game));
|
|
await page.locator('#choices li', { hasText: 'Alla bona.' }).click();
|
|
await page.evaluate(async () => (await import('/tests/helpers.mjs')).settle(game));
|
|
await page.keyboard.press('Escape'); assert.equal(await page.evaluate(() => game.closeup), null);
|
|
console.log(`${name}: 20 carte giocate, prese, conteggio, risultato e rivincita OK`);
|
|
await page.close();
|
|
}
|
|
assert.deepEqual(errors, []); console.log(`Schermate: ${out}/briscola-*.png`);
|
|
} finally { await browser?.close(); server.kill(); }
|