Si sceglie il rione all'inizio: Piazza, Pieve, Cardetole o Campomigliaio

Dopo «Nuova partita» la domanda «Per chi corri quest'anno?» con i quattro rioni nei loro colori; la scelta resta
nella partita (rione:<id>) e cambia la riga dell'introduzione e le battute sul rione di Andrea. Voci nuove.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ApqNBvNbNCwXqR2RLo8QJh
This commit is contained in:
2026-10-24 15:04:15 +02:00
co-authored by Claude Opus 5.5
parent ee6c03a04d
commit 94a5479645
20 changed files with 149 additions and 18 deletions
+12
View File
@@ -77,8 +77,15 @@ try {
await page.keyboard.press('Escape');
assert.ok(await page.isHidden('#credits'), 'Esc chiude i crediti');
await page.click('#new-game');
// Il rione: quattro scelte; la Pieve cambia la riga dell'introduzione e resta nella partita.
await page.waitForSelector('#rione:not([hidden])');
assert.equal(await page.locator('#rione-buttons button').count(), 4);
await page.screenshot({ path: `${OUT}/01a-rione.png` });
await page.click('#rione [data-rione="pieve"]');
// L'introduzione: una riga per clic, poi comincia la partita.
await page.waitForSelector('#intro:not([hidden])');
for (let i = 0; i < 2; i++) await page.click('#intro', { position: { x: 40, y: 40 } });
assert.match(await page.textContent('#intro-text'), /Pieve, i bianchi/, 'la riga del rione scelto');
await page.screenshot({ path: `${OUT}/01b-intro.png` });
for (let i = 0; i < 10 && await page.isVisible('#intro'); i++) await page.click('#intro', { position: { x: 40, y: 40 } });
assert.ok(await page.isHidden('#intro'), 'l\'introduzione non finisce');
@@ -88,6 +95,7 @@ try {
assert.match(await page.evaluate(() => voices.audio.src), /assets\/voices\/andrea-[0-9a-f]+\.mp3$/);
await page.waitForFunction(() => music.duck === 0.45, null, { timeout: 2000 });
await page.screenshot({ path: `${OUT}/02-inizio.png` });
assert.ok(await page.evaluate(() => game.state.flags['rione:pieve']), 'il rione resta nella partita');
const fits = async p => p.evaluate(() => document.querySelector('#panel').getBoundingClientRect().bottom <= innerHeight);
assert.ok(await fits(page), 'verbi e inventario fuori dallo schermo');
await settle(page);
@@ -236,6 +244,7 @@ try {
await phone.click('#splash'); // un clic salta il cartello
assert.ok(await phone.isVisible('#title'), 'il clic sul cartello porta al titolo');
await phone.click('#new-game');
await phone.click('#rione [data-rione="campomigliaio"]');
await phone.click('#skip-intro');
await settle(phone);
const overflow = await phone.evaluate(() => document.documentElement.scrollWidth > innerWidth);
@@ -250,6 +259,7 @@ try {
await small.goto(`http://127.0.0.1:${PORT}/`);
await small.waitForSelector('#splash', { state: 'hidden', timeout: 15000 });
await small.click('#new-game');
await small.click('#rione [data-rione="campomigliaio"]');
await small.click('#skip-intro');
await settle(small);
assert.equal(await small.evaluate(() => innerWidth), 740, 'la pagina è più larga dello schermo del telefono');
@@ -258,6 +268,7 @@ try {
await small.screenshot({ path: `${OUT}/08b-telefono-stretto-pausa.png` });
const landscape = await open({ width: 844, height: 390 });
await landscape.click('#new-game');
await landscape.click('#rione [data-rione="campomigliaio"]');
await landscape.click('#skip-intro');
await settle(landscape);
assert.equal(await landscape.evaluate(() => document.documentElement.scrollWidth > innerWidth), false);
@@ -267,6 +278,7 @@ try {
// Debug (?debug): area calpestabile e hotspot disegnati; Maiusc+clic segna punti senza muovere Andrea, X li azzera.
const dbg = await open({ width: 1280, height: 860 }, '?debug');
await dbg.click('#new-game');
await dbg.click('#rione [data-rione="campomigliaio"]');
await dbg.click('#skip-intro');
await settle(dbg);
const start = await dbg.evaluate(() => [game.state.x, game.state.y]);
+18 -1
View File
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
import { backgroundOrder } from '../src/engine/render.js';
import { story } from '../src/story/story.js';
import { solution } from '../src/story/solution.js';
import { Game, VERBS, checkSave } from '../src/engine/game.js';
import { Game, VERBS, checkSave, introText } from '../src/engine/game.js';
import { inside } from '../src/engine/geometry.js';
import { existsSync, readFileSync } from 'node:fs';
import { settle } from './helpers.mjs';
@@ -391,3 +391,20 @@ test('ogni posto della mappa ha la sua miniatura (make miniature)', () => {
assert.ok(existsSync(new URL('../' + p.mini, import.meta.url)), `manca ${p.mini}: make miniature`);
}
});
test('i quattro rioni: ognuno ha la sua riga nell\'introduzione, e le battute sul rione di Andrea lo seguono', () => {
assert.deepEqual(Object.keys(story.rioni), ['piazza', 'pieve', 'cardetole', 'campomigliaio']);
const line = story.intro.find(l => typeof l !== 'string');
assert.deepEqual(Object.keys(line.rione).sort(), Object.keys(story.rioni).sort(), 'una riga per rione');
for (const [id, r] of Object.entries(story.rioni)) assert.ok(introText(line, id).includes(r.colors.replace('i ', '')), `${id}: ${introText(line, id)}`);
const look = (rione, scene, target) => {
const flags = rione ? { ['rione:' + rione]: true } : {};
const g = new Game(story, { state: { scene, x: 0, y: 0, facing: 1, inventory: [], flags, used: {}, ended: false } });
g.act('look', target); return settle(g).map(s => s.text).join(' ');
};
assert.match(look('pieve', 'pieve', 'chiesa'), /Il mio rione/);
assert.match(look('cardetole', 'pieve', 'chiesa'), /quelli in bianco/);
assert.match(look('piazza', 'piazza', 'colonna'), /noi rossi/);
assert.doesNotMatch(look('pieve', 'piazza', 'colonna'), /noi rossi/);
assert.match(look(null, 'pieve', 'chiesa'), /quelli in bianco/, 'un salvataggio di prima dei rioni funziona');
});