diff --git a/assets/voices/andrea-67afa54e05.mp3 b/assets/voices/andrea-67afa54e05.mp3 new file mode 100644 index 0000000..1b927d5 Binary files /dev/null and b/assets/voices/andrea-67afa54e05.mp3 differ diff --git a/assets/voices/andrea-f52137b691.mp3 b/assets/voices/andrea-f52137b691.mp3 new file mode 100644 index 0000000..a3528a5 Binary files /dev/null and b/assets/voices/andrea-f52137b691.mp3 differ diff --git a/assets/voices/andrea-f6ef96032e.mp3 b/assets/voices/andrea-f6ef96032e.mp3 new file mode 100644 index 0000000..9021f7c Binary files /dev/null and b/assets/voices/andrea-f6ef96032e.mp3 differ diff --git a/assets/voices/index.json b/assets/voices/index.json index c14a982..6ab4a90 100644 --- a/assets/voices/index.json +++ b/assets/voices/index.json @@ -1815,14 +1815,42 @@ "file": "mariano-0eeb942ca2.mp3", "sec": 3.52 }, - "mariano|Il campione non scappa. Tira!": { - "file": "mariano-bdf58ba1be.mp3", - "sec": 3.26 + "mariano|Come la vuoi?": { + "file": "mariano-0288ec460b.mp3", + "sec": 0.7 + }, + "mariano|Il campione non scappa. Come la vuoi?": { + "file": "mariano-1d8388d631.mp3", + "sec": 3.55 }, "mariano|Promesso: ti ci porto. Ma prima ritrova il palio, che senza festa non si parte.": { "file": "mariano-1c9cd0c325.mp3", "sec": 5.96 }, + "andrea|Alla bona.": { + "file": "andrea-67afa54e05.mp3", + "sec": 0.69 + }, + "andrea|Seria.": { + "file": "andrea-f6ef96032e.mp3", + "sec": 0.58 + }, + "andrea|A fassi male.": { + "file": "andrea-f52137b691.mp3", + "sec": 0.78 + }, + "mariano|Alla bona: tiro con gli occhi chiusi.": { + "file": "mariano-b1b1ca273b.mp3", + "sec": 2.2 + }, + "mariano|Seria. Così mi piace.": { + "file": "mariano-accff65835.mp3", + "sec": 2.27 + }, + "mariano|A fassi male? Poi non piangere, eh.": { + "file": "mariano-d65fb6e871.mp3", + "sec": 3.42 + }, "andrea|Ingegnere, una partita ai birilli?": { "file": "andrea-dbcf4d1083.mp3", "sec": 2.49 @@ -1831,17 +1859,29 @@ "file": "andrea-8f63435d69.mp3", "sec": 1.38 }, - "ing_tozzi|Una partita ai birilli? Si va a venti.": { - "file": "ing_tozzi-7aef4a5420.mp3", - "sec": 3.3 + "ing_tozzi|Una partita ai birilli? Si va a venti. Come la vuoi?": { + "file": "ing_tozzi-82e7cc88aa.mp3", + "sec": 4.58 + }, + "ing_tozzi|Ci mancherebbe. Stavolta guardo bene. Come la vuoi?": { + "file": "ing_tozzi-344ac1021f.mp3", + "sec": 4.67 + }, + "ing_tozzi|Alla bona: tiro senza occhiali.": { + "file": "ing_tozzi-17e489814e.mp3", + "sec": 2.19 }, "andrea|Gnamo.": { "file": "andrea-a496b0aca9.mp3", "sec": 0.58 }, - "ing_tozzi|Ci mancherebbe. Stavolta guardo bene.": { - "file": "ing_tozzi-37404495f7.mp3", - "sec": 3.13 + "ing_tozzi|Seria. Dammi il gesso.": { + "file": "ing_tozzi-4b3adfa04a.mp3", + "sec": 2.28 + }, + "ing_tozzi|A fassi male? Allora mi tolgo la giacca. Ah no, non ce l'ho.": { + "file": "ing_tozzi-c365e9f760.mp3", + "sec": 6.05 }, "andrea|Sono bloccato. Mi dai un consiglio?": { "file": "andrea-42b02eb9ea.mp3", diff --git a/assets/voices/ing_tozzi-17e489814e.mp3 b/assets/voices/ing_tozzi-17e489814e.mp3 new file mode 100644 index 0000000..1030f50 Binary files /dev/null and b/assets/voices/ing_tozzi-17e489814e.mp3 differ diff --git a/assets/voices/ing_tozzi-344ac1021f.mp3 b/assets/voices/ing_tozzi-344ac1021f.mp3 new file mode 100644 index 0000000..a083b96 Binary files /dev/null and b/assets/voices/ing_tozzi-344ac1021f.mp3 differ diff --git a/assets/voices/ing_tozzi-37404495f7.mp3 b/assets/voices/ing_tozzi-37404495f7.mp3 deleted file mode 100644 index 8ef84c9..0000000 Binary files a/assets/voices/ing_tozzi-37404495f7.mp3 and /dev/null differ diff --git a/assets/voices/ing_tozzi-4b3adfa04a.mp3 b/assets/voices/ing_tozzi-4b3adfa04a.mp3 new file mode 100644 index 0000000..f425117 Binary files /dev/null and b/assets/voices/ing_tozzi-4b3adfa04a.mp3 differ diff --git a/assets/voices/ing_tozzi-7aef4a5420.mp3 b/assets/voices/ing_tozzi-7aef4a5420.mp3 deleted file mode 100644 index 43f123c..0000000 Binary files a/assets/voices/ing_tozzi-7aef4a5420.mp3 and /dev/null differ diff --git a/assets/voices/ing_tozzi-82e7cc88aa.mp3 b/assets/voices/ing_tozzi-82e7cc88aa.mp3 new file mode 100644 index 0000000..b0ad714 Binary files /dev/null and b/assets/voices/ing_tozzi-82e7cc88aa.mp3 differ diff --git a/assets/voices/ing_tozzi-c365e9f760.mp3 b/assets/voices/ing_tozzi-c365e9f760.mp3 new file mode 100644 index 0000000..fd7dae8 Binary files /dev/null and b/assets/voices/ing_tozzi-c365e9f760.mp3 differ diff --git a/assets/voices/mariano-0288ec460b.mp3 b/assets/voices/mariano-0288ec460b.mp3 new file mode 100644 index 0000000..fa79f79 Binary files /dev/null and b/assets/voices/mariano-0288ec460b.mp3 differ diff --git a/assets/voices/mariano-1d8388d631.mp3 b/assets/voices/mariano-1d8388d631.mp3 new file mode 100644 index 0000000..5d410b5 Binary files /dev/null and b/assets/voices/mariano-1d8388d631.mp3 differ diff --git a/assets/voices/mariano-accff65835.mp3 b/assets/voices/mariano-accff65835.mp3 new file mode 100644 index 0000000..02f9900 Binary files /dev/null and b/assets/voices/mariano-accff65835.mp3 differ diff --git a/assets/voices/mariano-b1b1ca273b.mp3 b/assets/voices/mariano-b1b1ca273b.mp3 new file mode 100644 index 0000000..8fcc2c1 Binary files /dev/null and b/assets/voices/mariano-b1b1ca273b.mp3 differ diff --git a/assets/voices/mariano-bdf58ba1be.mp3 b/assets/voices/mariano-bdf58ba1be.mp3 deleted file mode 100644 index f750487..0000000 Binary files a/assets/voices/mariano-bdf58ba1be.mp3 and /dev/null differ diff --git a/assets/voices/mariano-d65fb6e871.mp3 b/assets/voices/mariano-d65fb6e871.mp3 new file mode 100644 index 0000000..6c83f0d Binary files /dev/null and b/assets/voices/mariano-d65fb6e871.mp3 differ diff --git a/src/engine/biliardo.js b/src/engine/biliardo.js index 2b3e073..aedb85b 100644 --- a/src/engine/biliardo.js +++ b/src/engine/biliardo.js @@ -36,8 +36,16 @@ export function reach(v) { export const canThrow = c => c.step === 'aim' && c.current === 'player'; export const moving = c => list(c).some(b => b.vx || b.vy); +// Quanto è bravo l'Ingegnere: quanti colpi prova a vuoto (direzioni intorno al tavolo, ritocchi vicino alla biglia +// avversaria, forze) e quanto gli trema la mano. «A fassi male» è il campione vero: vince quasi sempre. +export const LEVELS = { + bona: { angles: 0, fine: 0.15, powers: [0.6], wobble: 0.16, powerNoise: 0.2 }, + seria: { angles: 14, fine: 0.09, powers: [0.5, 0.8], wobble: 0.072, powerNoise: 0.1 }, + male: { angles: 64, fine: 0.025, powers: [0.3, 0.5, 0.7, 0.9], wobble: 0.03, powerNoise: 0.05 }, +}; export function newBiliardo(seed = Date.now() >>> 0, def = {}) { - const c = { step: 'aim', t: 0, seed, current: 'player', target: def.target ?? TARGET, power: 0.45, angle: 0, + const level = LEVELS[def.level] ? def.level : 'seria'; + const c = { step: 'aim', t: 0, seed, level, skill: LEVELS[level], current: 'player', target: def.target ?? TARGET, power: 0.45, angle: 0, holding: false, holdTime: 0, accumulator: 0, events: [], points: { player: 0, cpu: 0 }, last: null, shot: null, plan: null }; c.balls = { player: ball('player', LEN * 0.18, WID * 0.5), cpu: ball('cpu', LEN * 0.81, WID * 0.25), pallino: ball('pallino', LEN * 0.88, WID * 0.7) }; c.pins = castle(); @@ -210,10 +218,10 @@ function finish(c) { // L'avversario prova tanti colpi a vuoto e sceglie quello che rende di più; poi la mano gli trema un poco. // Le prove si fanno a pezzi, un po' per fotogramma: tutte insieme fermerebbero il gioco per un decimo di secondo. function cpuCandidates(c) { - const base = angleTo(c.balls.cpu, c.balls.player), angles = []; - for (let i = 0; i < 64; i++) angles.push(i / 64 * Math.PI * 2); - for (let d = -0.3; d <= 0.3; d += 0.025) angles.push(base + d); - return angles.flatMap(angle => [0.3, 0.5, 0.7, 0.9].map(power => ({ angle, power }))); + const k = c.skill ?? LEVELS.male, base = angleTo(c.balls.cpu, c.balls.player), angles = []; + for (let i = 0; i < k.angles; i++) angles.push(i / k.angles * Math.PI * 2); + for (let d = -0.3; d <= 0.3; d += k.fine) angles.push(base + d); + return angles.flatMap(angle => k.powers.map(power => ({ angle, power }))); } function cpuThink(c, budget = 12) { const t = c.thinking ??= { todo: cpuCandidates(c), best: null }; @@ -222,7 +230,8 @@ function cpuThink(c, budget = 12) { if (!t.best || gain > t.best.gain) t.best = { angle, power, gain }; } if (t.todo.length) return; - c.plan = { angle: t.best.angle + deviation(c) * (c.wobble ?? 0.03), power: clamp(t.best.power + deviation(c) * 0.05, 0.05, 1) }; + const k = c.skill ?? LEVELS.male; + c.plan = { angle: t.best.angle + deviation(c) * (c.wobble ?? k.wobble), power: clamp(t.best.power + deviation(c) * k.powerNoise, 0.05, 1) }; c.angle = c.plan.angle; c.power = c.plan.power; c.thinking = null; } diff --git a/src/engine/bocce.js b/src/engine/bocce.js index 0d3c94b..1db8db0 100644 --- a/src/engine/bocce.js +++ b/src/engine/bocce.js @@ -19,8 +19,16 @@ export const powerFor = distance => clamp((distance - 8) / 96, 0, 1); export const moving = c => [c.jack, ...c.balls].some(b => Math.hypot(b.vu, b.vw) > EPS); export const closest = (c, who) => c.balls.filter(b => b.who === who).sort((a, b) => gap(a, c.jack) - gap(b, c.jack))[0]; -export function newBocce(seed = Date.now() >>> 0) { - const c = { step: 'aim', t: 0, age: 0, seed, balls: [], turn: 0, current: 'player', power: 0.5, +// Quanto è bravo Mariano: quanto si allarga il suo tiro e quanto spesso prova a bocciare la boccia migliore. +// «A fassi male» è il campione vero del pallaio. +export const LEVELS = { + bona: { spread: 3, attack: 0.1 }, + seria: { spread: 1.15, attack: 0.4 }, + male: { spread: 0.22, attack: 0.85 }, +}; +export function newBocce(seed = Date.now() >>> 0, def = {}) { + const level = LEVELS[def.level] ? def.level : 'seria'; + const c = { step: 'aim', t: 0, age: 0, seed, level, skill: LEVELS[level], balls: [], turn: 0, current: 'player', power: 0.5, holding: false, holdTime: 0, accumulator: 0, events: [], trails: [], travel: 0, lastThrower: null }; c.jack = { u: 45 + random(c) * 18, w: (random(c) - 0.5) * 5, vu: 0, vw: 0, r: JACK, mass: 0.1, jack: true, spin: 0 }; c.aim = { u: c.jack.u, w: c.jack.w }; @@ -134,10 +142,11 @@ function nextTurn(c) { function cpuThrow(c) { // Mariano cerca un accosto libero; con un avversario attaccato al pallino può // tentare di spostarlo. Usa gli stessi lanci e la stessa fisica del giocatore. - const other = closest(c, 'player'), attack = other && gap(other, c.jack) < 0.6 && random(c) < 0.45; + const k = c.skill ?? LEVELS.male; + const other = closest(c, 'player'), attack = other && gap(other, c.jack) < 0.6 && random(c) < k.attack; const target = attack ? other : c.jack; - const u = clamp(target.u + deviation(c) * (attack ? 0.5 : 2.2), 10, LANE - 2); - const w = clamp(target.w + deviation(c) * (attack ? 0.18 : 0.8), -HALF + BALL, HALF - BALL); + const u = clamp(target.u + deviation(c) * (attack ? 0.5 : 2.2) * k.spread, 10, LANE - 2); + const w = clamp(target.w + deviation(c) * (attack ? 0.18 : 0.8) * k.spread, -HALF + BALL, HALF - BALL); launch(c, 'cpu', u, w, Math.hypot(u, w) + (attack ? 14 : -0.35)); } export function update(c, dt) { diff --git a/src/engine/game.js b/src/engine/game.js index 0a91a28..d57358c 100644 --- a/src/engine/game.js +++ b/src/engine/game.js @@ -6,8 +6,8 @@ import * as biliardo from './biliardo.js'; // I minigiochi in primo piano: stessi comandi (mira, pressione, rilascio), motori diversi. export const MINIGAMES = { - bocce: { create: seed => bocce.newBocce(seed), update: bocce.update, press: bocce.press, release: bocce.release, cancel: bocce.cancel, aim: bocce.aimScreen }, - biliardo: { create: seed => biliardo.newBiliardo(seed), update: biliardo.update, press: biliardo.press, release: biliardo.release, cancel: biliardo.cancel, aim: biliardo.aimScreen }, + bocce: { create: (seed, level) => bocce.newBocce(seed, { level }), update: bocce.update, press: bocce.press, release: bocce.release, cancel: bocce.cancel, aim: bocce.aimScreen }, + biliardo: { create: (seed, level) => biliardo.newBiliardo(seed, { level }), update: biliardo.update, press: biliardo.press, release: biliardo.release, cancel: biliardo.cancel, aim: biliardo.aimScreen }, }; export const VERBS = ['walk', 'look', 'take', 'use', 'talk', 'open', 'give', 'drop']; @@ -439,7 +439,8 @@ export class Game { else if (e.closeup) { const type = this.story.closeups[e.closeup].type; this.closeup = type === 'torch' ? { id: e.closeup, step: 'search', t: 0, age: 0, found: 0, dwell: 0, beam: [320, 180] } - : MINIGAMES[type] ? { id: e.closeup, ...MINIGAMES[type].create(Math.floor(Math.random() * 4294967296)) } + // `level`: «Alla bona», «Seria» o «A fassi male», scelto nel dialogo con l'avversario. + : MINIGAMES[type] ? { id: e.closeup, ...MINIGAMES[type].create(Math.floor(Math.random() * 4294967296), e.level) } : { id: e.closeup, step: 'cover', t: 0, age: 0 }; } else throw new Error('Effetto sconosciuto: ' + JSON.stringify(e)); diff --git a/src/story/story.js b/src/story/story.js index 79e92d7..78c858f 100644 --- a/src/story/story.js +++ b/src/story/story.js @@ -2256,18 +2256,26 @@ export const story = { start: { options: [ { - text: 'Mariano! Facciamo una partita a bocce?', when: '!bocce_vinte', next: 'end', + text: 'Mariano! Facciamo una partita a bocce?', when: '!bocce_vinte', next: 'livello', then: [ { say: 'Con me? Al pallaio sono il campione.', who: 'mariano', pose: 'explain' }, { say: 'Facciamo così: se mi batti, ti porto a Brento Sanico.', who: 'mariano' }, - { closeup: 'bocce' }, + { say: 'Come la vuoi?', who: 'mariano' }, ], }, - { text: 'La rivincita?', when: 'bocce_vinte', next: 'end', then: [{ say: 'Il campione non scappa. Tira!', who: 'mariano', pose: 'throw' }, { closeup: 'bocce' }] }, + { text: 'La rivincita?', when: 'bocce_vinte', next: 'livello', then: [{ say: 'Il campione non scappa. Come la vuoi?', who: 'mariano', pose: 'throw' }] }, { text: 'Allora, Brento Sanico?', when: 'bocce_vinte', then: [{ say: 'Promesso: ti ci porto. Ma prima ritrova il palio, che senza festa non si parte.', who: 'mariano', pose: 'explain' }] }, { text: 'Ciao Mariano.', next: 'end' }, ], }, + // La difficoltà (Andrea, 25 ottobre): quanto si impegna Mariano. + livello: { + options: [ + { text: 'Alla bona.', next: 'end', then: [{ say: 'Alla bona: tiro con gli occhi chiusi.', who: 'mariano', pose: 'laugh' }, { closeup: 'bocce', level: 'bona' }] }, + { text: 'Seria.', next: 'end', then: [{ say: 'Seria. Così mi piace.', who: 'mariano' }, { closeup: 'bocce', level: 'seria' }] }, + { text: 'A fassi male.', next: 'end', then: [{ say: 'A fassi male? Poi non piangere, eh.', who: 'mariano', pose: 'cheer' }, { closeup: 'bocce', level: 'male' }] }, + ], + }, }, }, ing_tozzi: { @@ -2275,17 +2283,21 @@ export const story = { start: { options: [ { - text: 'Ingegnere, una partita ai birilli?', when: '!biliardo_vinto', next: 'end', - then: [ - { say: 'Una partita ai birilli? Si va a venti.', who: 'ing_tozzi', pose: 'chalk' }, - { say: 'Gnamo.' }, - { closeup: 'biliardo' }, - ], + text: 'Ingegnere, una partita ai birilli?', when: '!biliardo_vinto', next: 'livello', + then: [{ say: 'Una partita ai birilli? Si va a venti. Come la vuoi?', who: 'ing_tozzi', pose: 'chalk' }], }, - { text: 'La rivincita?', when: 'biliardo_vinto', next: 'end', then: [{ say: 'Ci mancherebbe. Stavolta guardo bene.', who: 'ing_tozzi', pose: 'explain' }, { closeup: 'biliardo' }] }, + { text: 'La rivincita?', when: 'biliardo_vinto', next: 'livello', then: [{ say: 'Ci mancherebbe. Stavolta guardo bene. Come la vuoi?', who: 'ing_tozzi', pose: 'explain' }] }, { text: 'Arrivederci, Ingegnere.', next: 'end' }, ], }, + // La difficoltà: quanti colpi prova a vuoto l'Ingegnere, e quanto gli trema la mano. + livello: { + options: [ + { text: 'Alla bona.', next: 'end', then: [{ say: 'Alla bona: tiro senza occhiali.', who: 'ing_tozzi', pose: 'laugh' }, { say: 'Gnamo.' }, { closeup: 'biliardo', level: 'bona' }] }, + { text: 'Seria.', next: 'end', then: [{ say: 'Seria. Dammi il gesso.', who: 'ing_tozzi', pose: 'chalk' }, { say: 'Gnamo.' }, { closeup: 'biliardo', level: 'seria' }] }, + { text: 'A fassi male.', next: 'end', then: [{ say: 'A fassi male? Allora mi tolgo la giacca. Ah no, non ce l\'ho.', who: 'ing_tozzi', pose: 'explain' }, { say: 'Gnamo.' }, { closeup: 'biliardo', level: 'male' }] }, + ], + }, }, }, // Lo "psicologo": il castello ascolta e risponde con un indizio su quello che manca. diff --git a/tests/biliardo.browser.mjs b/tests/biliardo.browser.mjs index d503e34..b71b902 100644 --- a/tests/biliardo.browser.mjs +++ b/tests/biliardo.browser.mjs @@ -83,6 +83,7 @@ try { game.go('circolo_4_maggio'); settle(game); game.act('talk', 'ing_tozzi'); settle(game); game.choose(game.options().findIndex(o => o.text.includes('una partita ai birilli'))); settle(game); + game.choose(game.options().findIndex(o => o.text === 'Seria.')); settle(game); }); await page.waitForSelector('#bocce-controls:not([hidden])'); await page.waitForFunction(async () => { diff --git a/tests/biliardo.test.mjs b/tests/biliardo.test.mjs index ee8a779..975ae3b 100644 --- a/tests/biliardo.test.mjs +++ b/tests/biliardo.test.mjs @@ -251,7 +251,11 @@ test('al Circolo 4 Maggio: dal portone in piazza si entra nella sala, e al tavol g.choose(g.options().findIndex(o => o.text === 'Ingegnere, una partita ai birilli?')); const before = settle(g).map(s => s.text); assert.ok(before.some(t => t.includes('Si va a venti'))); + assert.deepEqual(g.options().map(o => o.text), ['Alla bona.', 'Seria.', 'A fassi male.'], 'prima si sceglie quanto fa sul serio'); + g.choose(g.options().findIndex(o => o.text === 'A fassi male.')); + settle(g); assert.equal(g.closeup?.id, 'biliardo'); + assert.equal(g.closeup.level, 'male'); assert.equal(g.act('look', 'stecche'), false, 'durante la partita la scena non risponde'); const c = g.closeup; assert.ok(g.closeupAim([VIEW.x + c.balls.cpu.x * VIEW.k, VIEW.y + c.balls.cpu.y * VIEW.k]), 'si mira sulla biglia avversaria'); @@ -270,7 +274,16 @@ test('al Circolo 4 Maggio: dal portone in piazza si entra nella sala, e al tavol g.act('talk', 'ing_tozzi'); settle(g); assert.ok(g.options().some(o => o.text === 'La rivincita?'), 'vinto, si chiede la rivincita'); g.choose(g.options().findIndex(o => o.text === 'La rivincita?')); settle(g); + g.choose(g.options().findIndex(o => o.text === 'Alla bona.')); settle(g); assert.equal(g.closeup?.id, 'biliardo'); + assert.equal(g.closeup.level, 'bona'); assert.ok(g.closeupClose()); assert.ok(settle(g).some(s => s.text.startsWith('Lasci a metà?'))); }); + +test('i livelli dell\'Ingegnere: «Alla bona» prova pochi colpi e trema, «A fassi male» li prova tutti', () => { + const bona = newBiliardo(1, { level: 'bona' }), male = newBiliardo(1, { level: 'male' }), none = newBiliardo(1); + assert.equal(none.level, 'seria', 'senza scelta si gioca sul serio'); + assert.ok(bona.skill.wobble > male.skill.wobble * 3); + assert.ok(bona.skill.angles < male.skill.angles); +}); diff --git a/tests/bocce.test.mjs b/tests/bocce.test.mjs index f4ced25..6bf925e 100644 --- a/tests/bocce.test.mjs +++ b/tests/bocce.test.mjs @@ -1,6 +1,6 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { newBocce, press, release, update, powerAt, score, thrower, left, project, SWING, LANE, BALLS } from '../src/engine/bocce.js'; +import { LEVELS, newBocce, press, release, update, powerAt, score, thrower, left, project, SWING, LANE, BALLS } from '../src/engine/bocce.js'; import { story } from '../src/story/story.js'; import { Game } from '../src/engine/game.js'; import { settle } from './helpers.mjs'; @@ -83,7 +83,11 @@ test('al pallaio: Mariano sfida, la mano si gioca nel primo piano; vincere vale g.choose(g.options().findIndex(o => o.text.startsWith('Mariano! Facciamo'))); const before = settle(g).map(s => s.text); assert.ok(before.some(t => t.includes('Brento Sanico')), 'la posta la dice prima'); + assert.deepEqual(g.options().map(o => o.text), ['Alla bona.', 'Seria.', 'A fassi male.'], 'prima si sceglie quanto fa sul serio'); + g.choose(g.options().findIndex(o => o.text === 'Seria.')); + settle(g); assert.equal(g.closeup?.id, 'bocce'); + assert.equal(g.closeup.level, 'seria'); assert.equal(g.act('look', 'pallaio'), false, 'durante la partita la scena non risponde'); // Si gioca la mano (le bocce di Andrea mirano al pallino); a mano finita il risultato si forza a favore. const c = g.closeup; @@ -108,7 +112,16 @@ test('al pallaio: Mariano sfida, la mano si gioca nel primo piano; vincere vale assert.ok(g.options().some(o => o.text === 'La rivincita?') && g.options().some(o => o.text === 'Allora, Brento Sanico?')); g.choose(g.options().findIndex(o => o.text === 'La rivincita?')); settle(g); + g.choose(g.options().findIndex(o => o.text === 'A fassi male.')); + settle(g); assert.equal(g.closeup?.id, 'bocce'); + assert.equal(g.closeup.level, 'male'); assert.ok(g.closeupClose()); assert.ok(settle(g).some(s => s.text.startsWith('Già stanco?'))); }); + +test('i livelli di Mariano: alla bona si allarga, a fassi male boccia spesso', () => { + assert.equal(newBocce(1).level, 'seria'); + assert.ok(LEVELS.bona.spread > LEVELS.seria.spread && LEVELS.seria.spread > LEVELS.male.spread); + assert.ok(LEVELS.male.attack > LEVELS.bona.attack); +}); diff --git a/tests/browser.mjs b/tests/browser.mjs index 0f54e87..6e7f82a 100644 --- a/tests/browser.mjs +++ b/tests/browser.mjs @@ -375,6 +375,8 @@ try { await clickScene(bocce, await hotspotCenter(bocce, 'mariano')); await bocce.waitForFunction(() => game.dialogue && !game.busy, null, { timeout: 20000 }); await bocce.locator('#choices li', { hasText: 'Facciamo una partita' }).click(); + await bocce.waitForFunction(() => game.options().some(o => o.text === 'Seria.'), null, { timeout: 30000 }); + await bocce.locator('#choices li', { hasText: 'Seria.' }).click(); await bocce.waitForFunction(() => game.closeup?.id === 'bocce' && game.closeup.step === 'aim' && !game.speech, null, { timeout: 30000 }); const r = await bocce.locator('#scene').boundingBox(); await bocce.mouse.move(r.x + r.width / 2, r.y + r.height / 2); @@ -405,6 +407,8 @@ try { await clickScene(b, await hotspotCenter(b, 'ing_tozzi')); await b.waitForFunction(() => game.dialogue && !game.busy, null, { timeout: 20000 }); await b.locator('#choices li', { hasText: 'una partita ai birilli' }).click(); + await b.waitForFunction(() => game.options().some(o => o.text === 'Seria.'), null, { timeout: 30000 }); + await b.locator('#choices li', { hasText: 'Seria.' }).click(); await b.waitForFunction(() => game.closeup?.id === 'biliardo' && game.closeup.step === 'aim' && !game.speech, null, { timeout: 30000 }); const r = await b.locator('#scene').boundingBox(); const [tx, ty] = await b.evaluate(async () => { const { VIEW } = await import('/src/engine/biliardo.js'); const V = game.closeup.balls.cpu; return [VIEW.x + V.x * VIEW.k, VIEW.y + V.y * VIEW.k]; });