Files
leggenda-del-regolo/video/tutorial-biliardo.mjs
T

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();
}