Bocce: il clic fissa la mira come al biliardo, così andando verso la forza o «Lancia» il puntatore non la sposta più (scoperto girando il video: la boccia partiva a due passi). Copioni dei video «Come si gioca» di scopa, tressette, bocce e biliardo, e video/sovrapposte.py per controllare che il narratore non parli sopra i personaggi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1dxvtsZx8NE9fwjXUNDBr
This commit is contained in:
+6
-4
@@ -457,6 +457,7 @@ canvas.addEventListener('pointerdown', e => {
|
||||
if (!aimed && !game.speech) return;
|
||||
if (aimed) game.closeup.aimLocked = true; // il clic fissa la mira
|
||||
}
|
||||
if (story.closeups[game.closeup.id].type === 'bocce' && aimed) game.closeup.aimLocked = true;
|
||||
press = { closeup: true };
|
||||
// Alle bocce si tiene premuto per caricare la forza (la battuta in corso, un tocco la salta).
|
||||
if (!game.skip()) game.closeupPress();
|
||||
@@ -503,10 +504,11 @@ canvas.addEventListener('contextmenu', e => {
|
||||
canvas.addEventListener('pointermove', e => {
|
||||
if (!running) return;
|
||||
mouse = e.pointerType === 'mouse' ? scenePoint(e) : null;
|
||||
// Al biliardo la stecca segue il mouse finché un clic (o una regolazione fine) non fissa la mira: dopo,
|
||||
// attraversare il tavolo per raggiungere «Tira» non cambia il colpo. Al tiro successivo torna libera.
|
||||
const billiards = game.closeup && story.closeups[game.closeup.id].type === 'biliardo';
|
||||
if (billiards) { if (e.pointerType === 'mouse' && !press && !game.closeup.aimLocked) game.closeupAim(scenePoint(e)); }
|
||||
// Al biliardo e alle bocce la mira segue il mouse finché un clic (o una regolazione fine) non la fissa: dopo,
|
||||
// attraversare il campo per raggiungere la forza, «Tira» o «Lancia» non cambia il colpo. Al tiro dopo torna libera.
|
||||
// (Bocce: scoperto girando il video «Come si gioca», la mira finiva a due passi andando verso il cursore.)
|
||||
const aiming = game.closeup && ['biliardo', 'bocce'].includes(story.closeups[game.closeup.id].type);
|
||||
if (aiming) { if (e.pointerType === 'mouse' && !press && !game.closeup.aimLocked) game.closeupAim(scenePoint(e)); }
|
||||
else if (e.pointerType === 'mouse' || press?.closeup) game.closeupAim(scenePoint(e));
|
||||
});
|
||||
canvas.addEventListener('pointerleave', () => { mouse = null; });
|
||||
|
||||
@@ -387,6 +387,12 @@ try {
|
||||
await bocce.mouse.up();
|
||||
assert.equal(await bocce.evaluate(() => game.closeup.balls.length), 1, 'lasciando, la boccia parte');
|
||||
await bocce.waitForFunction(() => game.closeup.balls.length === 2, null, { timeout: 15000 });
|
||||
// Il clic fissa la mira: andando verso la forza o «Lancia» il puntatore attraversa il campo e non la sposta.
|
||||
await bocce.waitForFunction(() => game.closeup.step === 'aim' && game.closeup.current === 'player', null, { timeout: 30000 });
|
||||
await bocce.mouse.click(r.x + r.width * 0.45, r.y + r.height * 0.6);
|
||||
const fixed = await bocce.evaluate(() => JSON.stringify(game.closeup.aim));
|
||||
await bocce.mouse.move(r.x + r.width * 0.5, r.y + r.height * 0.95, { steps: 8 });
|
||||
assert.equal(await bocce.evaluate(() => JSON.stringify(game.closeup.aim)), fixed, 'la mira resta dove si è cliccato');
|
||||
await bocce.keyboard.press('Escape');
|
||||
await bocce.waitForFunction(() => game.speech?.text.startsWith('Già stanco?'), null, { timeout: 5000 });
|
||||
await bocce.close();
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Controlla che nel video «Come si gioca» il narratore non parli sopra i personaggi del gioco.
|
||||
python3 video/sovrapposte.py tutorial-briscola (dopo la ripresa: cues.json, registro audio, narrazione)
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
VIDEO = Path(__file__).resolve().parent
|
||||
clip = sys.argv[1]
|
||||
cues = json.loads((Path.home() / "Workspaces/qemu/qemu-iso-lab/artifacts/tour/out" / clip / "cues.json").read_text())["cues"]
|
||||
narr = {v["id"]: v["sec"] for v in json.loads((VIDEO / "out" / f"{clip}-narrazione.json").read_text())["voci"]}
|
||||
voci, aperta = [], None
|
||||
for e in json.loads((VIDEO / "out" / f"{clip}-audio.json").read_text())["eventi"]:
|
||||
if "voice" in e:
|
||||
aperta = [e["t"], None, e["voice"]]
|
||||
voci.append(aperta)
|
||||
elif "voiceEnd" in e and aperta:
|
||||
aperta[1], aperta = e["t"], None
|
||||
male = [(c["id"], v[2]) for c in cues for v in voci if v[1] and v[0] < c["start"] + narr[c["id"]] and v[1] > c["start"]]
|
||||
for id_, voce in male:
|
||||
print(f"✗ «{id_}» sopra {voce}")
|
||||
print(f"{clip}: {len(voci)} voci del gioco, {len(cues)} frasi del narratore, {len(male)} sovrapposte")
|
||||
sys.exit(1 if male else 0)
|
||||
@@ -0,0 +1,92 @@
|
||||
// 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();
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copione del video «Come si gioca: le bocce» (video/SCALETTA-CARTE.md, clip 4).
|
||||
// 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 · le bocce", en: "Come si gioca · le bocce" };
|
||||
export const cues = [
|
||||
c("dove", "Le bocce si giocano al pallaio, accanto al tennis della chiesa. Mariano, il campione, sfida chiunque"),
|
||||
c("livello", "Si sceglie quanto deve impegnarsi: alla bona, seria, o a fassi male"),
|
||||
c("campo", "Il campo visto da dietro: in fondo c'è il pallino, e a sinistra il dettaglio, per vedere da vicino chi è più accosto"),
|
||||
c("mira", "Per mirare si tocca il campo, o il dettaglio vicino al pallino. Il cerchio azzurro dice dove si fermerebbe la boccia"),
|
||||
c("forza", "Poi si regola la forza col cursore, e si preme Lancia"),
|
||||
c("turno", "Tira sempre chi non ha il punto. Chi è dietro può accostare, oppure bocciare: colpire la boccia dell'altro per toglierla di lì"),
|
||||
c("tieni", "C'è anche un altro modo: si tiene premuto sul campo, la forza sale, e lasciando parte la boccia. Vanno bene anche le frecce e la barra spaziatrice"),
|
||||
c("punti", "Tre bocce a testa. Alla fine fa un punto ogni boccia più vicina al pallino della migliore dell'avversario"),
|
||||
c("fine", "E se batti Mariano, ti porta a Brento Sanico. Buona partita!"),
|
||||
];
|
||||
|
||||
export const setup = base;
|
||||
|
||||
export async function run(d) {
|
||||
const p = d.page, r = regista(d, "tutorial-bocce");
|
||||
await r.ascolta();
|
||||
await r.vaiA("tennis_chiesa", [430, 310]);
|
||||
await r.cue("dove");
|
||||
await r.parlaCon("mariano");
|
||||
await r.scegli("Facciamo una partita");
|
||||
await r.cue("livello");
|
||||
await r.scegli("Alla bona.");
|
||||
// Una mano fissa: accostando bene, Andrea fa tre punti (anche se il tiro a pressione viene un po' corto o lungo).
|
||||
await p.evaluate(async () => {
|
||||
const { newBocce } = await import("/src/engine/bocce.js");
|
||||
game.closeup = { id: "bocce", ...newBocce(9, { level: "bona" }) };
|
||||
});
|
||||
await sleep(800);
|
||||
await p.waitForFunction(() => window.music?.want === "assets/audio/bocce.mp3", null, { timeout: 10000 });
|
||||
await r.cue("campo");
|
||||
await r.muovi([300, 200], 1.0); await sleep(400); await r.muovi([90, 170], 1.0);
|
||||
|
||||
// Dove mirare: poco prima del pallino, così la boccia si ferma accosto (il cerchio azzurro ci finisce sopra).
|
||||
const mira = () => p.evaluate(async () => {
|
||||
const B = await import("/src/engine/bocce.js"), c = game.closeup, u = c.jack.u - 0.55, w = c.jack.w;
|
||||
const s = B.project(u, w);
|
||||
return { at: [s.x, s.y], u, w, power: B.powerFor(Math.hypot(u, w) - 0.29) };
|
||||
});
|
||||
const tocca = async () => {
|
||||
for (let i = 0; i < 600; i++) {
|
||||
const s = await p.evaluate(async () => {
|
||||
const { canThrow } = await import("/src/engine/bocce.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 tiro = 0;
|
||||
while (await tocca()) {
|
||||
tiro++;
|
||||
const m = await mira();
|
||||
if (tiro === 1) await r.cue("mira");
|
||||
if (tiro < 3) {
|
||||
// Mira col clic, forza col cursore, poi Lancia.
|
||||
await r.clicca(m.at, 0.8);
|
||||
await p.evaluate(async ({ u, w }) => (await import("/src/engine/bocce.js")).aim(game.closeup, u, w), m);
|
||||
if (tiro === 1) await r.cue("forza");
|
||||
await d.hover(p.locator("#bocce-power"), 0.6);
|
||||
await p.locator("#bocce-power").fill(String(Math.round(m.power * 100)));
|
||||
await p.evaluate(async (v) => (await import("/src/engine/bocce.js")).setPower(game.closeup, v), m.power);
|
||||
await sleep(600);
|
||||
await d.click(p.locator("#bocce-throw"), 0.6);
|
||||
if (tiro === 1) await r.cue("turno");
|
||||
} else {
|
||||
// L'ultima tenendo premuto: la forza sale finché non si lascia.
|
||||
await r.cue("tieni");
|
||||
await r.muovi(m.at, 0.8);
|
||||
const [a, b] = await r.punto(...m.at);
|
||||
d.vm(`DISPLAY=:0 xdotool mousemove ${Math.round(a)} ${Math.round(b)} mousedown 1 sleep ${(m.power * 1.6).toFixed(2)} mouseup 1`);
|
||||
}
|
||||
await p.locator("#scene").scrollIntoViewIfNeeded();
|
||||
}
|
||||
await p.waitForFunction(() => game.closeup.step === "result", null, { timeout: 90000 });
|
||||
await sleep(1000);
|
||||
await r.cue("punti");
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copione del video «Come si gioca: la scopa» (video/SCALETTA-CARTE.md, clip 3).
|
||||
// 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 · la scopa", en: "Come si gioca · la scopa" };
|
||||
export const cues = [
|
||||
c("dove", "Allo stesso tavolo dell'oratorio si gioca anche a scopa, in due: basta chiedere «Una scopa?»"),
|
||||
c("mazzo", "Mazzo e livello si scelgono come per la briscola"),
|
||||
c("valori", "Quattro carte in tavola e tre in mano. L'Asso vale uno, le altre il loro numero, il gobbo otto, la donna nove e il regio dieci"),
|
||||
c("presa", "Con una carta prendi quella dello stesso valore; se non c'è, un gruppo di carte che fa la stessa somma. Se puoi prendere, devi"),
|
||||
c("scelta", "Quando le prese possibili sono più d'una, scegli tu: tocca una carta in tavola, oppure usa i pulsanti qui sotto"),
|
||||
c("scopa", "Se con la tua carta pulisci tutta la tavola, è scopa: un punto in più"),
|
||||
c("resto", "Finite le tre carte se ne danno altre tre, finché c'è mazzo. Quel che resta in tavola alla fine va all'ultimo che ha preso"),
|
||||
c("conto", "A fine smazzata un punto a chi ha più carte, uno a chi ha più mattoni, uno per il settebello, il sette di mattoni, e uno per la primiera"),
|
||||
c("primiera", "La primiera somma la carta migliore di ogni seme: il sette vale ventuno, il sei diciotto, l'Asso sedici"),
|
||||
c("fine", "Si va agli undici punti, una smazzata dopo l'altra. Buona scopa!"),
|
||||
];
|
||||
|
||||
export const setup = base;
|
||||
|
||||
export async function run(d) {
|
||||
const p = d.page, r = regista(d, "tutorial-scopa");
|
||||
await r.ascolta();
|
||||
await r.vaiA("oratorio", [470, 300]);
|
||||
await r.cue("dove");
|
||||
await r.parlaCon("giocatore_carte");
|
||||
await r.scegli("Una scopa?");
|
||||
await r.cue("mazzo");
|
||||
await r.scegli("Con le toscane.");
|
||||
await r.scegli("Seria.");
|
||||
// Una smazzata fissa: Andrea sceglie fra due prese, fa due scope e la vince 5 a 1.
|
||||
await p.evaluate(async () => {
|
||||
const { newScopa } = await import("/src/engine/scopa.js");
|
||||
game.closeup = { id: "scopa", ...newScopa(73, { level: "seria", deck: "toscane" }) };
|
||||
});
|
||||
await sleep(800);
|
||||
await p.waitForFunction(() => window.music?.want === "assets/audio/carte.mp3", null, { timeout: 10000 });
|
||||
await r.cue("valori");
|
||||
await r.muovi([320, 160], 1.0); await sleep(500); await r.muovi([320, 300], 1.0);
|
||||
await r.cue("presa");
|
||||
|
||||
// Una mossa di Andrea, come la sceglierebbe un giocatore attento; la scelta fra più prese si fa col pulsante.
|
||||
let k = 1, scelta = false, scopa = false;
|
||||
for (let i = 0; i < 400; i++) {
|
||||
const s = await p.evaluate(async () => {
|
||||
const { canPlay } = await import("/src/engine/scopa.js");
|
||||
return ["smazzata", "result"].includes(game.closeup.step) ? "fine" : canPlay(game.closeup) && !game.speech ? "tocca" : "aspetta";
|
||||
});
|
||||
if (s === "fine") break;
|
||||
if (s === "aspetta") { await sleep(150); continue; }
|
||||
const m = await p.evaluate(async (k) => {
|
||||
const S = await import("/src/engine/scopa.js"), c = game.closeup, m = S.choose(S.observation(c, "player"), "male", k);
|
||||
const card = c.hands.player[m.index], many = S.captures(c.table, card).length > 1;
|
||||
const clean = !!m.captured && m.captured.length === c.table.length;
|
||||
const { handX, HAND } = await import("/src/engine/scopa-render.js");
|
||||
return { ...m, many, clean, at: [handX(c.hands.player.length, m.index) + 32, HAND.y + 46] };
|
||||
}, k++);
|
||||
if (m.many && !scelta) { scelta = true; await r.cue("scelta"); }
|
||||
if (m.clean && !scopa) { scopa = true; await r.cue("scopa"); }
|
||||
await r.clicca(m.at, 0.7);
|
||||
if (m.many) {
|
||||
await p.waitForFunction(() => game.closeup.step === "choose", null, { timeout: 5000 });
|
||||
await sleep(900);
|
||||
await d.click(p.locator(`#scopa-options [data-option="${m.option}"]`), 0.7);
|
||||
await p.locator("#scene").scrollIntoViewIfNeeded();
|
||||
}
|
||||
if (k === 9) await r.cue("resto");
|
||||
}
|
||||
await p.waitForFunction(() => game.closeup.step === "smazzata", null, { timeout: 60000 });
|
||||
await sleep(1200);
|
||||
await r.cue("conto");
|
||||
await r.cue("primiera");
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copione del video «Come si gioca: il tressette ai 21» (video/SCALETTA-CARTE.md, clip 2).
|
||||
// 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 tressette ai 21", en: "Come si gioca · il tressette ai 21" };
|
||||
export const cues = [
|
||||
c("dove", "Per il tressette ai ventuno servono quattro giocatori: il giocatore di carte chiama due amici, e uno fa da socio ad Andrea"),
|
||||
c("mazzo", "Mazzo e livello si scelgono come per la briscola"),
|
||||
c("tavolo", "Il socio è di fronte, gli avversari ai lati. Dieci carte a testa, e si gira verso destra"),
|
||||
c("accuse", "In alto le accuse: la napoletana, cioè Asso, due e tre dello stesso seme, oppure tre Assi, tre due o tre tre. Valgono punti subito"),
|
||||
c("seme", "Chi esce gioca quel che vuole; gli altri devono rispondere al seme, se ce l'hanno. Le carte che non si possono giocare diventano scure"),
|
||||
c("ordine", "Prende la più alta del seme di uscita: il tre, poi il due, l'Asso, il regio, la donna, il gobbo, e giù fino al quattro"),
|
||||
c("segni", "Chi esce può fare un segno al socio. Si sceglie coi pulsanti qui sotto, o coi tasti B, S e V, e poi si gioca la carta"),
|
||||
c("striscio", "Striscio vuol dire: di questo seme ne ho ancora, non è l'ultima"),
|
||||
c("busso", "Busso: prendi con la migliore che hai, e poi ritornami il seme"),
|
||||
c("volo", "Volo: era l'ultima carta di questo seme"),
|
||||
c("punti", "L'Asso vale un punto; il tre, il due e le figure un terzo di punto. L'ultima presa vale un punto in più"),
|
||||
c("smazzata", "A fine smazzata si contano i punti interi, e le accuse si sommano"),
|
||||
c("fine", "Si gioca smazzata dopo smazzata: la coppia che arriva per prima a ventuno è fuori, e ha vinto. Buon tressette!"),
|
||||
];
|
||||
|
||||
export const setup = base;
|
||||
|
||||
export async function run(d) {
|
||||
const p = d.page, r = regista(d, "tutorial-tressette");
|
||||
await r.ascolta();
|
||||
await r.vaiA("oratorio", [470, 300]);
|
||||
await r.cue("dove");
|
||||
await r.parlaCon("giocatore_carte");
|
||||
await r.scegli("Un tressette ai 21?");
|
||||
await r.cue("mazzo");
|
||||
await r.scegli("Con le toscane.");
|
||||
await r.scegli("Seria.");
|
||||
// Una smazzata fissa: accuse, e Andrea che usa striscio, busso e volo.
|
||||
await p.evaluate(async () => {
|
||||
const { newTressette } = await import("/src/engine/tressette.js");
|
||||
game.closeup = { id: "tressette", ...newTressette(11, { level: "seria", deck: "toscane" }) };
|
||||
});
|
||||
await sleep(800);
|
||||
await p.waitForFunction(() => window.music?.want === "assets/audio/carte.mp3", null, { timeout: 10000 });
|
||||
await r.cue("tavolo");
|
||||
await r.muovi([320, 60], 0.9); await sleep(300); await r.muovi([600, 150], 0.9); await sleep(300); await r.muovi([40, 150], 0.9);
|
||||
|
||||
let k = 1, detto = new Set(), seme = false, accuse = false;
|
||||
for (let i = 0; i < 600; i++) {
|
||||
const s = await p.evaluate(async () => {
|
||||
const { canPlay } = await import("/src/engine/tressette.js");
|
||||
return ["smazzata", "result"].includes(game.closeup.step) ? "fine" : canPlay(game.closeup) && !game.speech ? "tocca" : "aspetta";
|
||||
});
|
||||
if (s === "fine") break;
|
||||
if (s === "aspetta") { await sleep(150); continue; }
|
||||
// Le accuse si dicono giocando la prima carta: se ne parla quando sono scritte in alto.
|
||||
if (!accuse && await p.evaluate(() => game.closeup.accuse.length > 0)) { accuse = true; await r.cue("accuse"); }
|
||||
const m = await p.evaluate(async (k) => {
|
||||
const T = await import("/src/engine/tressette.js"), c = game.closeup, m = T.choose(T.observation(c, 0), "male", k);
|
||||
const { handX, CARD } = await import("/src/engine/tressette-render.js");
|
||||
return { ...m, follow: c.table.length > 0, at: [handX(c.hands[0].length, m.index) + 21, CARD.y + 30] };
|
||||
}, k++);
|
||||
if (m.follow && !seme) { seme = true; await r.cue("seme"); await r.cue("ordine"); }
|
||||
if (m.signal && !detto.has(m.signal)) {
|
||||
if (!detto.size) await r.cue("segni");
|
||||
detto.add(m.signal);
|
||||
await r.cue(m.signal);
|
||||
}
|
||||
if (m.signal) await d.click(p.locator(`#tressette-${m.signal}`), 0.6);
|
||||
await p.locator("#scene").scrollIntoViewIfNeeded();
|
||||
await r.clicca(m.at, 0.7);
|
||||
if (k === 7) await r.cue("punti");
|
||||
}
|
||||
await p.waitForFunction(() => ["smazzata", "result"].includes(game.closeup.step), null, { timeout: 60000 });
|
||||
await sleep(1200);
|
||||
await r.cue("smazzata");
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user