Mappa: filtrando, i posti trovati si vedono (la mappa non resta lunga e scorsa in fondo); il Barone e le sue due donne e due jack; prove nel browser su una porta libera (con la 8080 giravano sul container)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ks2K99uqCZjAxvy7Rdj1R
This commit is contained in:
2026-10-25 12:40:48 +01:00
co-authored by Claude Opus 5.5
parent 53da1e48f8
commit af6861350d
11 changed files with 49 additions and 16 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+6 -6
View File
@@ -627,9 +627,9 @@
"file": "andrea-0bd1b0f32e.mp3", "file": "andrea-0bd1b0f32e.mp3",
"sec": 8.7 "sec": 8.7
}, },
"andrea|Baron! Che ci fai qui?": { "andrea|Barone! Che ci fai qui?": {
"file": "andrea-1b59141c98.mp3", "file": "andrea-83abae1419.mp3",
"sec": 2.59 "sec": 2.91
}, },
"barondeduke|Aspetto uno che mi deve riportare le mutande.": { "barondeduke|Aspetto uno che mi deve riportare le mutande.": {
"file": "barondeduke-3ea60b7083.mp3", "file": "barondeduke-3ea60b7083.mp3",
@@ -643,9 +643,9 @@
"file": "barondeduke-5542a06624.mp3", "file": "barondeduke-5542a06624.mp3",
"sec": 1.65 "sec": 1.65
}, },
"barondeduke|In mano avevo full. E ho detto: «Male male si divide».": { "barondeduke|Avevo due donne e due jack. Entra la terza donna, e penso: «Male male si divide».": {
"file": "barondeduke-944c3e57dc.mp3", "file": "barondeduke-d4ff967179.mp3",
"sec": 5.03 "sec": 7.64
}, },
"andrea|E invece?": { "andrea|E invece?": {
"file": "andrea-435306c242.mp3", "file": "andrea-435306c242.mp3",
+6 -1
View File
@@ -217,6 +217,7 @@ function filterMap() {
$('map-none').hidden = shown > 0; $('map-none').hidden = shown > 0;
$('map-none').textContent = `Nessun posto con «${query.trim()}»`; $('map-none').textContent = `Nessun posto con «${query.trim()}»`;
drawRoads(); drawRoads();
$('map-scroll').scrollTo(0, 0); // i posti rimasti stanno in cima: niente mappa vuota scorsa in fondo
} }
$('map-search').addEventListener('input', filterMap); $('map-search').addEventListener('input', filterMap);
$('map-search').addEventListener('keydown', e => { $('map-search').addEventListener('keydown', e => {
@@ -226,7 +227,11 @@ $('map-search').addEventListener('keydown', e => {
}); });
// Le strade fra le schede, misurate dopo l'impaginazione (e di nuovo se la finestra cambia misura). // Le strade fra le schede, misurate dopo l'impaginazione (e di nuovo se la finestra cambia misura).
function drawRoads() { function drawRoads() {
const board = $('map-board'), svg = $('map-roads'), origin = board.getBoundingClientRect(); const board = $('map-board'), svg = $('map-roads');
// Prima si toglie la misura vecchia: il disegno delle strade, grande quanto la mappa intera, la terrebbe lunga
// anche dopo un filtro.
svg.style.width = svg.style.height = '0';
const origin = board.getBoundingClientRect();
const center = id => { const center = id => {
const card = board.querySelector(`[data-scene="${id}"]`); const card = board.querySelector(`[data-scene="${id}"]`);
const r = card && !card.hidden && !card.parentElement.hidden && card.querySelector('img').getBoundingClientRect(); const r = card && !card.hidden && !card.parentElement.hidden && card.querySelector('img').getBoundingClientRect();
+2 -2
View File
@@ -1018,11 +1018,11 @@ export const story = {
actions: { actions: {
look: [{ say: 'Il BaronDeDuke, col cappello da cowboy. Ha l\'aria di uno che aspetta qualcuno, e che spera non lo veda nessuno.' }], look: [{ say: 'Il BaronDeDuke, col cappello da cowboy. Ha l\'aria di uno che aspetta qualcuno, e che spera non lo veda nessuno.' }],
talk: [ talk: [
{ say: 'Baron! Che ci fai qui?' }, { say: 'Barone! Che ci fai qui?' },
{ say: 'Aspetto uno che mi deve riportare le mutande.', who: 'barondeduke' }, { say: 'Aspetto uno che mi deve riportare le mutande.', who: 'barondeduke' },
{ say: 'Le... mutande?' }, { say: 'Le... mutande?' },
{ say: 'Le ho perse a poker, ieri sera.', who: 'barondeduke', pose: 'facepalm' }, { say: 'Le ho perse a poker, ieri sera.', who: 'barondeduke', pose: 'facepalm' },
{ say: 'In mano avevo full. E ho detto: «Male male si divide».', who: 'barondeduke', pose: 'cards' }, { say: 'Avevo due donne e due jack. Entra la terza donna, e penso: «Male male si divide».', who: 'barondeduke', pose: 'cards' },
{ say: 'E invece?' }, { say: 'E invece?' },
{ say: 'L\'altro aveva colore.', who: 'barondeduke', pose: 'shrug' }, { say: 'L\'altro aveva colore.', who: 'barondeduke', pose: 'shrug' },
], ],
+2 -3
View File
@@ -2,14 +2,13 @@
// ritagli dei nuovi effetti, pause degli animali e nessuna modifica alla partita. // ritagli dei nuovi effetti, pause degli animali e nessuna modifica alla partita.
// npm run browser:effects — immagini in /tmp/regolo-effects (oppure $OUT). // npm run browser:effects — immagini in /tmp/regolo-effects (oppure $OUT).
import { chromium } from 'playwright'; import { chromium } from 'playwright';
import { spawn } from 'node:child_process'; import { staticServer } from './helpers.mjs';
import { mkdirSync, writeFileSync } from 'node:fs'; import { mkdirSync, writeFileSync } from 'node:fs';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
const out = process.env.OUT ?? '/tmp/regolo-effects'; const out = process.env.OUT ?? '/tmp/regolo-effects';
mkdirSync(out, { recursive: true }); mkdirSync(out, { recursive: true });
const port = 9000 + Math.floor(Math.random() * 900); const server = await staticServer(), port = server.port;
const server = spawn('python3', ['-m', 'http.server', String(port), '--bind', '127.0.0.1'], { stdio: 'ignore' });
let browser; let browser;
try { try {
browser = await chromium.launch({ args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] }); browser = await chromium.launch({ args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
+19 -3
View File
@@ -2,14 +2,14 @@
// fino alla fine, salvataggio e impaginazione su telefono. Schermate in $OUT (predefinito: /tmp/regolo-shots). // fino alla fine, salvataggio e impaginazione su telefono. Schermate in $OUT (predefinito: /tmp/regolo-shots).
// npm run browser // npm run browser
import { chromium } from 'playwright'; import { chromium } from 'playwright';
import { spawn } from 'node:child_process'; import { staticServer } from './helpers.mjs';
import { mkdirSync, readFileSync } from 'node:fs'; import { mkdirSync, readFileSync } from 'node:fs';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { solution, route } from '../src/story/solution.js'; import { solution, route } from '../src/story/solution.js';
const OUT = process.env.OUT ?? '/tmp/regolo-shots', PORT = 8000 + Math.floor(Math.random() * 900); const OUT = process.env.OUT ?? '/tmp/regolo-shots';
mkdirSync(OUT, { recursive: true }); mkdirSync(OUT, { recursive: true });
const server = spawn('python3', ['-m', 'http.server', String(PORT), '--bind', '127.0.0.1'], { stdio: 'ignore' }); const server = await staticServer(), PORT = server.port;
const browser = await chromium.launch({ args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] }); const browser = await chromium.launch({ args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
const errors = []; const errors = [];
@@ -276,6 +276,22 @@ try {
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
assert.ok(await page.isHidden('#map')); assert.ok(await page.isHidden('#map'));
assert.equal(await page.evaluate(() => game.state.scene), 'piazza'); assert.equal(await page.evaluate(() => game.state.scene), 'piazza');
// Con tutti i posti visti e la finestra piccola la mappa si scorre: filtrando da in fondo, i posti trovati si vedono
// (prima la mappa restava lunga quanto quella intera, scorsa su un fondo vuoto).
await page.setViewportSize({ width: 560, height: 340 });
await page.evaluate(() => { for (const id of Object.keys(game.story.scenes)) game.state.flags['visto:' + id] = true; });
await page.click('#map-button');
assert.ok(await page.evaluate(() => { const s = document.getElementById('map-scroll'); s.scrollTop = s.scrollHeight; return s.scrollTop > 0; }), 'la mappa piena si scorre');
await page.fill('#map-search', 'campo');
const found = await page.evaluate(() => {
const s = document.getElementById('map-scroll'), r = s.getBoundingClientRect();
const c = [...document.querySelectorAll('#map-board button')].find(b => !b.hidden && !b.parentElement.hidden).getBoundingClientRect();
return { inside: c.top >= r.top && c.bottom <= r.bottom, scrollTop: s.scrollTop, h: s.scrollHeight, ch: s.clientHeight, top: c.top - r.top, q: document.getElementById('map-search').value };
});
assert.ok(found.inside, `filtrando, il primo posto trovato non si vede: ${JSON.stringify(found)}`);
await page.screenshot({ path: `${OUT}/07d-mappa-ricerca-piccola.png` });
await page.click('#close-map');
await page.setViewportSize({ width: 1280, height: 860 });
const phone = await open({ width: 390, height: 844 }); const phone = await open({ width: 390, height: 844 });
await phone.click('#splash'); // un clic salta il cartello await phone.click('#splash'); // un clic salta il cartello
+13
View File
@@ -16,3 +16,16 @@ export function settle(game, { maxSteps = 20000 } = {}) {
export function memoryStorage() { export function memoryStorage() {
return { data: {}, getItem(k) { return this.data[k] ?? null; }, setItem(k, v) { this.data[k] = String(v); }, removeItem(k) { delete this.data[k]; } }; return { data: {}, getItem(k) { return this.data[k] ?? null; }, setItem(k, v) { this.data[k] = String(v); }, removeItem(k) { delete this.data[k]; } };
} }
// Il server statico delle prove nel browser, su una porta libera scelta dal sistema: con una porta a caso poteva
// capitare la 8080 del gioco in Docker (make up), e la prova girava sul codice vecchio del container.
export async function staticServer() {
const { spawn } = await import('node:child_process');
const server = spawn('python3', ['-u', '-m', 'http.server', '0', '--bind', '127.0.0.1'], { stdio: ['ignore', 'pipe', 'ignore'] });
const port = await new Promise((resolve, reject) => {
let out = '';
server.stdout.on('data', d => { out += d; const m = out.match(/port (\d+)/); if (m) resolve(Number(m[1])); });
server.on('exit', () => reject(new Error('il server di prova non è partito')));
});
return { port, kill: () => server.kill() };
}
+1 -1
View File
@@ -478,6 +478,6 @@ test('gli amici in giro: l\'Arcu al circolino, il Tozzino al cancello del tennis
assert.deepEqual(talk('campomigliaio_circolo', 'arcu'), ['andrea: Arcu! Ma che è sempre là, Borgo?', 'arcu: E chi lo sposta???']); assert.deepEqual(talk('campomigliaio_circolo', 'arcu'), ['andrea: Arcu! Ma che è sempre là, Borgo?', 'arcu: E chi lo sposta???']);
assert.ok(talk('carza', 'tozzino').includes('tozzino: E chi sennò? Ha chiamato ora: è imbottigliato in Bolognese.')); assert.ok(talk('carza', 'tozzino').includes('tozzino: E chi sennò? Ha chiamato ora: è imbottigliato in Bolognese.'));
const baron = talk('polisportiva', 'barondeduke'); const baron = talk('polisportiva', 'barondeduke');
assert.ok(baron.includes('barondeduke: In mano avevo full. E ho detto: «Male male si divide».')); assert.ok(baron.includes('barondeduke: Avevo due donne e due jack. Entra la terza donna, e penso: «Male male si divide».'));
assert.equal(baron.at(-1), 'barondeduke: L\'altro aveva colore.'); assert.equal(baron.at(-1), 'barondeduke: L\'altro aveva colore.');
}); });