Giochi di carte dopo la revisione di Codex sui tre insieme; player dei video «Come si gioca» e primo copione
Esito che vale e si salva appena deciso, per tutti i minigiochi (anche tornando al titolo dalla schermata del risultato); briscola: uscire durante il conteggio non perde più la vittoria; tressette: si esce ai 21 col primo evento che ci porta, accusa compresa (prima vinceva sempre Andrea se ci arrivavano tutte e due), il carico che fa vincere si dà anche se è il 2, accuse che non escono dal tavolo, convenzioni scritte nell'aiuto; scopa: primiera valutata per quel che migliora, finale esatto a mazzo finito, presa sulla tavola al rilascio con scelta e conferma, frecce e Invio sempre sulla presa scelta. Tastiera: Invio e Spazio giocano sempre la carta scelta. Tavola descritta a parole. Classi CSS comuni ai tre giochi. Video: player nel gioco (pulsante «Guarda il video» nel «Come si gioca», compare quando il video esiste), video/narra.py per la voce del narratore, pezzi comuni dei copioni e il copione della briscola; scaletta in video/SCALETTA-CARTE.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1dxvtsZx8NE9fwjXUNDBr
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
// Pezzi comuni dei copioni «Come si gioca» (video/SCALETTA-CARTE.md): briscola, tressette, scopa, bocce, biliardo.
|
||||
// Si girano come demo-prototipo.mjs (video/SCALETTA.md, «Come si rigira»); prima `python3 video/narra.py <copione>`
|
||||
// per la voce del narratore, dopo `python3 video/monta.py <cartella> --tutorial`.
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
|
||||
export const URL_GIOCO = "http://127.0.0.1:8000/";
|
||||
export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
export const c = (id, it) => ({ id, it, en: it, top: true });
|
||||
|
||||
// La durata di ogni frase del narratore (video/narra.py): la battuta resta finché la voce non ha finito.
|
||||
export function narrazione(clip) {
|
||||
const f = new URL(`./out/${clip}-narrazione.json`, import.meta.url);
|
||||
return existsSync(f) ? Object.fromEntries(JSON.parse(readFileSync(f, "utf8")).voci.map((v) => [v.id, v.sec * 1000])) : {};
|
||||
}
|
||||
|
||||
export async function setup(d) {
|
||||
const p = d.page;
|
||||
await p.goto(URL_GIOCO);
|
||||
await p.evaluate(() => localStorage.clear());
|
||||
await p.reload();
|
||||
await p.waitForSelector("#title:not([hidden])");
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
export function regista(d, clip) {
|
||||
const p = d.page, voce = narrazione(clip);
|
||||
let inizio = 0, dura = 0;
|
||||
const r = {
|
||||
// Una battuta del narratore: prima si lascia finire quella di prima.
|
||||
async cue(id) {
|
||||
const resto = inizio + dura + 350 - Date.now();
|
||||
if (resto > 0) await sleep(resto);
|
||||
await d.cue(id);
|
||||
inizio = Date.now(); dura = voce[id] ?? 0;
|
||||
},
|
||||
async fine() { const resto = inizio + dura + 600 - Date.now(); if (resto > 0) await sleep(resto); },
|
||||
// Puntatore vero sulla scena (coordinate 640×360).
|
||||
async punto(x, y) {
|
||||
const b = await p.locator("#scene").boundingBox(), k = await p.evaluate(() => devicePixelRatio);
|
||||
return [(b.x + (x / 640) * b.width) * k, (b.y + (y / 360) * b.height) * k];
|
||||
},
|
||||
async clicca([x, y], dur = 0.8) { const [a, b] = await r.punto(x, y); d.vm(`DISPLAY=:0 ~/lab/video/mv.py ${a} ${b} ${dur} click`); await sleep(300); },
|
||||
async muovi([x, y], dur = 0.8) { const [a, b] = await r.punto(x, y); d.vm(`DISPLAY=:0 ~/lab/video/mv.py ${a} ${b} ${dur}`); await sleep(200); },
|
||||
// Aspetta che l'omino abbia finito di camminare e di parlare; un dialogo o un primo piano aperti fermano l'attesa.
|
||||
async fermi(pausa = 500) {
|
||||
await sleep(150);
|
||||
for (let i = 0; i < 1500; i++) {
|
||||
const ok = await p.evaluate(() => {
|
||||
if (game.speech || (game.queue.length && !game.closeup) || game.walking || game.reach > 0) return false;
|
||||
return !!game.dialogue || !!game.closeup || (!game.busy && !game.task);
|
||||
});
|
||||
if (ok) break;
|
||||
await sleep(100);
|
||||
}
|
||||
await sleep(pausa);
|
||||
},
|
||||
async scegli(testo) { await d.click(p.locator("#choices li", { hasText: testo }), 0.6); await r.fermi(); },
|
||||
// Partita nuova per Campomigliaio, introduzione saltata, dritti nella scena del gioco.
|
||||
async vaiA(scena, at) {
|
||||
await d.click(p.locator("#new-game"));
|
||||
await sleep(1200);
|
||||
await d.click(p.locator('#rione [data-rione="campomigliaio"]'));
|
||||
await sleep(800);
|
||||
if (await p.isVisible("#skip-intro")) await d.click(p.locator("#skip-intro"), 0.5);
|
||||
await p.evaluate(([s, a]) => { game.go(s, a); }, [scena, at]);
|
||||
await r.fermi(1200);
|
||||
},
|
||||
async parlaCon(id) {
|
||||
const [x, y] = await p.evaluate((id) => { const h = game.hotspot(id); return [h.box[0] + h.box[2] / 2, h.box[1] + h.box[3] / 2]; }, id);
|
||||
await r.clicca([x, y]);
|
||||
await r.fermi();
|
||||
},
|
||||
// L'audio annotato dentro la pagina (musica voluta e voci del gioco), per rimontarlo (monta.py).
|
||||
async ascolta() {
|
||||
await p.evaluate(() => {
|
||||
window.__t0 = Date.now(); window.__audio = [];
|
||||
let music = undefined, voice = null;
|
||||
const t = () => (Date.now() - window.__t0) / 1000;
|
||||
const path = (src) => decodeURI(new URL(src, location.href).pathname.slice(1));
|
||||
const look = () => {
|
||||
if (window.music && window.music.want !== music) { music = window.music.want; window.__audio.push({ t: t(), music: music ?? null }); }
|
||||
const a = window.voices?.audio, now = a && !a.paused && !a.ended ? a : null;
|
||||
if (now !== voice) {
|
||||
if (voice) window.__audio.push({ t: t(), voiceEnd: true });
|
||||
if (now) window.__audio.push({ t: t(), voice: path(now.src) });
|
||||
voice = now;
|
||||
}
|
||||
requestAnimationFrame(look);
|
||||
};
|
||||
look();
|
||||
});
|
||||
},
|
||||
async salvaAudio() {
|
||||
const audio = await p.evaluate(() => window.__audio);
|
||||
const out = new URL("./out/", import.meta.url);
|
||||
mkdirSync(out, { recursive: true });
|
||||
writeFileSync(new URL(`${clip}-audio.json`, out), JSON.stringify({ eventi: audio }, null, 2));
|
||||
},
|
||||
};
|
||||
return r;
|
||||
}
|
||||
Reference in New Issue
Block a user