Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1dxvtsZx8NE9fwjXUNDBr
93 lines
4.7 KiB
JavaScript
93 lines
4.7 KiB
JavaScript
// Copione del video «Come si gioca: il biliardo» (video/SCALETTA-CARTE.md, clip 5).
|
|
// Giro: video/narra.py, poi rec.mjs come in video/SCALETTA.md, poi video/monta.py <cartella> --tutorial.
|
|
import { c, sleep, setup as base, regista } from "./tutorial-comune.mjs";
|
|
|
|
export const title = { it: "Come si gioca · il biliardo", en: "Come si gioca · il biliardo" };
|
|
export const cues = [
|
|
c("dove", "Il biliardo è al Circolo 4 Maggio, in piazza. L'Ingegner Tozzi gioca ai cinque birilli, e si va a venti"),
|
|
c("livello", "Anche qui si sceglie quanto deve giocare forte"),
|
|
c("tavolo", "Il tavolo dall'alto: la tua biglia, quella dell'avversario, il pallino, e in mezzo il castello dei birilli"),
|
|
c("scopo", "Si colpisce la propria biglia e la si manda su quella dell'avversario: i birilli che lei abbatte fanno punti a chi tira"),
|
|
c("mira", "La stecca segue il mouse, e un clic fissa la mira. La linea tratteggiata è la strada della biglia; le frecce, dove vanno le due biglie dopo l'urto"),
|
|
c("tira", "Con le frecce si gira la stecca, con Maiuscolo ancora più piano. Poi si regola la forza, e si preme Tira"),
|
|
c("valori", "Il birillo bianco vale due, il rosso quattro, il rosso da solo dieci. Il pallino toccato dalla biglia avversaria, tre"),
|
|
c("fallo", "Attenzione ai falli: mancare la biglia avversaria, toccare prima il pallino, o buttare giù birilli con la propria. I punti vanno all'avversario"),
|
|
c("fine", "Un colpo a testa, finché qualcuno arriva a venti. Buona partita!"),
|
|
];
|
|
|
|
export const setup = base;
|
|
|
|
export async function run(d) {
|
|
const p = d.page, r = regista(d, "tutorial-biliardo");
|
|
await r.ascolta();
|
|
await r.vaiA("circolo_4_maggio", [470, 300]);
|
|
await r.cue("dove");
|
|
await r.parlaCon("ing_tozzi");
|
|
await r.scegli("una partita ai birilli");
|
|
await r.cue("livello");
|
|
await r.scegli("Alla bona.");
|
|
// Una partita fissa: un colpo da otto, un fallo voluto per farlo vedere, poi quattro e tre; vince Andrea 21 a 2.
|
|
await p.evaluate(async () => {
|
|
const { newBiliardo } = await import("/src/engine/biliardo.js");
|
|
game.closeup = { id: "biliardo", ...newBiliardo(2, { level: "bona" }) };
|
|
});
|
|
await sleep(800);
|
|
await r.cue("tavolo");
|
|
await r.muovi([200, 200], 0.9); await sleep(300); await r.muovi([400, 180], 0.9);
|
|
await r.cue("scopo");
|
|
|
|
// Il colpo: il migliore fra quelli provati a vuoto (o, la seconda volta, un fallo apposta). Si mira col clic sul
|
|
// tavolo lungo la direzione scelta, poi forza col cursore e Tira.
|
|
const colpo = (sbaglia) => p.evaluate(async (sbaglia) => {
|
|
const L = await import("/src/engine/biliardo.js"), c = game.closeup, b = c.balls.player, o = c.balls.cpu;
|
|
let top = null;
|
|
if (sbaglia) for (let a = 0; a < Math.PI * 2 && !top; a += 0.05) { if (L.simulate(c, "player", a, 0.12).foul === "miss") top = { angle: a, power: 0.12 }; }
|
|
else {
|
|
const base = Math.atan2(o.y - b.y, o.x - b.x);
|
|
for (let d = -0.35; d <= 0.35; d += 0.01) for (const power of [0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) {
|
|
const j = L.simulate(c, "player", base + d, power), gain = j.to === "player" ? j.points : -j.points;
|
|
if (!top || gain > top.gain) top = { angle: base + d, power, gain };
|
|
}
|
|
}
|
|
const x = b.x + Math.cos(top.angle) * 45, y = b.y + Math.sin(top.angle) * 45;
|
|
return { ...top, at: [L.VIEW.x + x * L.VIEW.k, L.VIEW.y + y * L.VIEW.k] };
|
|
}, sbaglia);
|
|
const tocca = async () => {
|
|
for (let i = 0; i < 900; i++) {
|
|
const s = await p.evaluate(async () => {
|
|
const { canThrow } = await import("/src/engine/biliardo.js");
|
|
return game.closeup.step === "result" ? "fine" : canThrow(game.closeup) && !game.speech ? "tocca" : "aspetta";
|
|
});
|
|
if (s !== "aspetta") return s === "tocca";
|
|
await sleep(150);
|
|
}
|
|
return false;
|
|
};
|
|
let n = 0;
|
|
while (await tocca()) {
|
|
n++;
|
|
if (n === 1) await r.cue("mira");
|
|
if (n === 2) await r.cue("fallo");
|
|
const m = await colpo(n === 2);
|
|
await r.muovi(m.at, 0.9); await sleep(400);
|
|
await r.clicca(m.at, 0.3);
|
|
await p.evaluate(({ angle }) => { game.closeup.angle = angle; game.closeup.aimLocked = true; }, m);
|
|
if (n === 1) await r.cue("tira");
|
|
await d.hover(p.locator("#bocce-power"), 0.6);
|
|
await p.locator("#bocce-power").fill(String(Math.round(m.power * 100)));
|
|
await p.evaluate((v) => { game.closeup.power = v; }, m.power);
|
|
await sleep(500);
|
|
await d.click(p.locator("#bocce-throw"), 0.6);
|
|
await p.locator("#scene").scrollIntoViewIfNeeded();
|
|
if (n === 1) await r.cue("valori");
|
|
}
|
|
await p.waitForFunction(() => game.closeup.step === "result", null, { timeout: 120000 });
|
|
await sleep(1500);
|
|
await r.cue("fine");
|
|
await r.aspetta();
|
|
await d.click(p.locator("#closeup-close"), 0.6);
|
|
await r.fermi(800);
|
|
await r.fine();
|
|
await r.salvaAudio();
|
|
}
|