Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1dxvtsZx8NE9fwjXUNDBr
129 lines
8.1 KiB
JavaScript
129 lines
8.1 KiB
JavaScript
// Tressette ai 21 nel browser: carte e segni con mouse, tocco e tastiera; si accelerano solo le attese.
|
|
// npm run browser:tressette — /tmp/regolo-shots/tressette-*.png
|
|
import { chromium } from 'playwright';
|
|
import { mkdirSync } 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 tap(page, mode, selector) {
|
|
if (mode === 'touch') { const r = await page.locator(selector).boundingBox(); await page.touchscreen.tap(r.x + r.width / 2, r.y + r.height / 2); }
|
|
else await page.click(selector);
|
|
}
|
|
async function playCard(page, mode, index, signal) {
|
|
if (signal) {
|
|
if (mode === 'keyboard') await page.keyboard.press(signal[0]);
|
|
else await tap(page, mode, `#tressette-${signal}`);
|
|
assert.equal(await page.evaluate(() => game.closeup.signal), signal);
|
|
}
|
|
if (mode === 'keyboard') {
|
|
await page.keyboard.press(String((index + 1) % 10));
|
|
assert.equal(await page.evaluate(() => game.closeup.selected), index);
|
|
await page.keyboard.press('Enter');
|
|
return;
|
|
}
|
|
const at = await page.evaluate(async i => {
|
|
const { handX, CARD } = await import('/src/engine/tressette-render.js');
|
|
return [handX(game.closeup.hands[0].length, i) + 21, CARD.y + 30];
|
|
}, index);
|
|
const r = await page.locator('#scene').boundingBox(), x = r.x + at[0] / 640 * r.width, y = r.y + at[1] / 360 * r.height;
|
|
if (mode === 'touch') await page.touchscreen.tap(x, y); else await page.mouse.click(x, y);
|
|
}
|
|
async function wait(page) {
|
|
return page.evaluate(async () => {
|
|
const { canPlay } = await import('/src/engine/tressette.js');
|
|
for (let i = 0; i < 12 && !canPlay(game.closeup) && !['smazzata', 'result'].includes(game.closeup.step); i++) game.update(1.3);
|
|
return game.closeup.step;
|
|
});
|
|
}
|
|
try {
|
|
browser = await chromium.launch({ args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
|
|
const scenarios = [
|
|
{ name: 'desktop-mouse', mode: 'mouse', viewport: { width: 1280, height: 860 }, full: true },
|
|
{ name: 'telefono-touch', mode: 'touch', viewport: { width: 390, height: 844 } },
|
|
{ name: 'desktop-keyboard', mode: 'keyboard', viewport: { width: 1280, height: 860 } },
|
|
{ name: 'orizzontale-touch', mode: 'touch', viewport: { width: 844, height: 390 } },
|
|
];
|
|
for (const { name, mode, viewport, full } of scenarios) {
|
|
const page = await browser.newPage({ viewport, hasTouch: true, isMobile: viewport.width < 900 });
|
|
page.on('pageerror', e => errors.push(`${name}: ${e.message}`));
|
|
page.on('console', e => { if (e.type() === 'error') errors.push(`${name}: ${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); game.act('talk', 'giocatore_carte'); settle(game); });
|
|
await page.locator('#choices li', { hasText: 'Un tressette ai 21?' }).click();
|
|
await page.evaluate(async () => (await import('/tests/helpers.mjs')).settle(game));
|
|
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: 'Seria.' }).click();
|
|
await page.evaluate(async () => (await import('/tests/helpers.mjs')).settle(game));
|
|
assert.deepEqual(await page.evaluate(() => [game.closeup.deck, game.closeup.level]), [deck, 'seria']);
|
|
await page.waitForSelector('#tressette-controls:not([hidden])');
|
|
assert.ok(await page.isHidden('#briscola-controls')); assert.ok(await page.isHidden('#bocce-controls'));
|
|
const scene = await page.locator('#scene').boundingBox();
|
|
assert.ok(scene.width * 42 / 640 >= 18, 'carte toccabili anche sul telefono');
|
|
await wait(page);
|
|
await page.screenshot({ path: `${out}/tressette-${name}-inizio.png` });
|
|
// Una carta che non risponde al seme non parte: resta in mano e il motivo si legge.
|
|
let refusedChecked = false, signals = 0, played = 0, shotSignal = false;
|
|
for (let steps = 0; steps < 400; steps++) {
|
|
const step = await wait(page);
|
|
if (step === 'result') break;
|
|
if (step === 'smazzata') {
|
|
await page.screenshot({ path: `${out}/tressette-${name}-smazzata.png` });
|
|
if (!full) break;
|
|
if (mode === 'keyboard') await page.keyboard.press('Enter'); else await tap(page, mode, '#tressette-next');
|
|
continue;
|
|
}
|
|
if (!refusedChecked) {
|
|
const bad = await page.evaluate(async () => {
|
|
const { legal } = await import('/src/engine/tressette.js');
|
|
const c = game.closeup, ok = legal(c.hands[0], c.table);
|
|
return c.hands[0].findIndex((_, i) => !ok.includes(i));
|
|
});
|
|
if (bad >= 0) {
|
|
const held = await page.evaluate(() => game.closeup.hands[0].length);
|
|
await playCard(page, mode, bad);
|
|
assert.match(await page.evaluate(() => game.closeup.refused), /Devi rispondere/);
|
|
await page.waitForFunction(() => /Devi rispondere/.test(document.getElementById('tressette-status').textContent));
|
|
assert.equal(await page.evaluate(() => game.closeup.hands[0].length), held);
|
|
refusedChecked = true;
|
|
}
|
|
}
|
|
const move = await page.evaluate(async () => {
|
|
const { observation, choose } = await import('/src/engine/tressette.js');
|
|
return choose(observation(game.closeup, 0), 'male', 7);
|
|
});
|
|
const before = await page.evaluate(() => game.closeup.hands[0].length);
|
|
await playCard(page, mode, move.index, move.signal); played++;
|
|
if (move.signal) {
|
|
signals++;
|
|
assert.equal(await page.evaluate(() => game.closeup.table.find(p => p.who === 0)?.signal), move.signal);
|
|
if (!shotSignal) { await page.waitForTimeout(60); await page.screenshot({ path: `${out}/tressette-${name}-segno.png` }); shotSignal = true; }
|
|
}
|
|
assert.equal(await page.evaluate(() => game.closeup.hands[0].length), before - 1, 'un gesto gioca una carta');
|
|
if (played === 1) { await page.evaluate(() => game.update(0.8)); await page.screenshot({ path: `${out}/tressette-${name}-presa.png` }); }
|
|
}
|
|
if (full) {
|
|
assert.equal(await page.evaluate(() => game.closeup.step), 'result');
|
|
await page.screenshot({ path: `${out}/tressette-${name}-risultato.png` });
|
|
const winner = await page.evaluate(() => game.closeup.result.winner);
|
|
await page.keyboard.press('Enter');
|
|
await page.evaluate(async () => (await import('/tests/helpers.mjs')).settle(game));
|
|
assert.equal(await page.evaluate(() => game.closeup), null);
|
|
assert.equal(await page.evaluate(() => !!game.state.flags.tressette_vinto), winner === 'player');
|
|
} else {
|
|
await page.keyboard.press('Escape');
|
|
assert.equal(await page.evaluate(() => game.closeup), null);
|
|
assert.equal(await page.evaluate(() => !!game.state.flags.tressette_vinto), false, 'chi lascia non vince');
|
|
}
|
|
console.log(`${name}: ${played} carte giocate (${signals} con un segno)${full ? ', partita ai 21 finita' : ', una smazzata'} OK`);
|
|
await page.close();
|
|
}
|
|
assert.deepEqual(errors, []); console.log(`Schermate: ${out}/tressette-*.png`);
|
|
} finally { await browser?.close(); server.kill(); }
|