import test from 'node:test'; import assert from 'node:assert/strict'; import * as m from '../src/engine/campana.js'; import { drawCampana } from '../src/engine/campana-render.js'; import { Game, MINIGAMES } from '../src/engine/game.js'; import { story } from '../src/story/story.js'; import { settle } from './helpers.mjs'; // Tira senza sparpaglio, con la forza giusta per arrivare al punto, e aspetta che il sassolino cada. function toss(c, [x, y]) { c.skill = { ...c.skill, scatter: 0 }; c.aim = { x, y }; c.power = Math.hypot(x - m.FROM.x, y - m.FROM.y) / m.REACH; assert.ok(m.shoot(c)); for (let i = 0; i < 200 && c.step === 'flying'; i++) m.update(c, 1 / 60); } // Salta ogni volta che la lancetta passa nel mezzo. function hopAll(c) { for (let i = 0; i < 20000 && c.step === 'hop'; i++) { m.update(c, 1 / 120); if (c.step === 'hop' && (!c.hop || c.hop.t >= m.HOP) && Math.abs(m.needle(c)) < 0.03 && c.meter.t > 0) m.press(c, 1); } } const at = id => { const p = m.center(id); return [p.x, p.y]; }; test('le caselle: 1-2-3, la coppia 4-5, la 6, la coppia 7-8 e il cielo; le righe', () => { for (let n = 1; n <= 8; n++) assert.equal(m.boxAt(at(n), 3).id, n); assert.equal(m.boxAt(at('cielo')).id, 'cielo'); assert.ok(m.center(4).y < m.center(5).y, 'la 4 a sinistra di chi salta'); const b = m.BOXES[2]; assert.ok(m.boxAt([b.x + 1, m.CY], 3).line, 'sul bordo si tocca la riga'); assert.ok(!m.boxAt([b.x + 1, m.CY], 0).line, 'alla bona le righe non contano'); assert.equal(m.boxAt([20, 20]), null); }); test('i salti: si scavalca la casella col sassolino, nella coppia un piede solo; tornando lo si raccoglie', () => { const ids = list => list.map(b => b.pick ? 'prendi' : b.out ? 'fuori' : b.boxes.join('+')); assert.deepEqual(ids(m.beats(1)), ['2', '3', '4+5', '6', '7+8', 'cielo', '7+8', '6', '4+5', '3', '2', 'prendi', 'fuori']); assert.deepEqual(ids(m.beats(5)), ['1', '2', '3', '4', '6', '7+8', 'cielo', '7+8', '6', 'prendi', '4', '3', '2', '1', 'fuori']); assert.deepEqual(ids(m.beats(8)), ['1', '2', '3', '4+5', '6', '7', 'cielo', 'prendi', '7', '6', '4+5', '3', '2', '1', 'fuori']); assert.ok(m.beats(3).every(b => !b.boxes.includes(3)), 'mai il piede dove sta il sassolino'); }); test('il sassolino nella casella giusta fa saltare; tutta la campana fatta, si passa alla dopo', () => { const c = m.newCampana(1, { level: 'seria' }); toss(c, at(1)); assert.equal(c.step, 'hop'); assert.match(c.line, /nella 1/); hopAll(c); assert.equal(c.step, 'done'); assert.equal(c.target.player, 2); assert.equal(c.stone, null, 'raccolto'); for (let i = 0; i < 120 && c.step !== 'aim'; i++) m.update(c, 1 / 60); assert.equal(c.current, 'player', 'chi ce la fa ritira'); }); test('fuori, sulla riga o nella casella sbagliata passa la mano al Taglia', () => { for (const [point, why] of [[at(2), /Nella 2, non nella 1/], [[m.START + 1, m.CY], /riga/], [[60, 40], /fuori/i]]) { const c = m.newCampana(2, { level: 'seria' }); toss(c, point); assert.equal(c.step, 'rest'); assert.match(c.line, why); for (let i = 0; i < 200 && c.step === 'rest'; i++) m.update(c, 1 / 60); assert.equal(c.current, 'cpu'); assert.equal(c.target.player, 1); } }); test('l\'equilibrio: troppo presto o troppo tardi si sbaglia', () => { const early = m.newCampana(3, { level: 'male' }); toss(early, at(1)); while (early.meter.t < 0.05) m.update(early, 1 / 120); m.press(early, 1); assert.equal(early.step, 'rest'); assert.match(early.line, /presto/); const late = m.newCampana(3, { level: 'male' }); toss(late, at(1)); for (let i = 0; i < 1000 && late.step === 'hop'; i++) m.update(late, 1 / 60); assert.match(late.line, /equilibrio/); }); test('il tiro col dito: un tocco secco mira soltanto; tenendo premuto la forza va e torna', () => { const c = m.newCampana(4, { level: 'bona' }); assert.ok(m.aimScreen(c, [300, 150])); m.press(c, 1); m.update(c, 0.05); m.release(c, 1); assert.equal(c.step, 'aim'); m.press(c, 1); for (let i = 0; i < 60 * m.CHARGE; i++) m.update(c, 1 / 60); assert.ok(c.power > 0.95); for (let i = 0; i < 30 * m.CHARGE; i++) m.update(c, 1 / 60); assert.ok(c.power < 0.6, 'e poi torna giù'); assert.ok(m.release(c, 1)); assert.equal(c.step, 'flying'); assert.ok(m.key(c, 'ArrowUp', true)); }); test('il Taglia gioca da solo; chi finisce la 8 vince', () => { const c = m.newCampana(5, { level: 'seria' }); toss(c, [60, 40]); let turns = 0; for (let i = 0; i < 60 * 600 && c.step !== 'result'; i++) { m.update(c, 1 / 60); if (c.current === 'player' && c.step === 'aim') { turns++; toss(c, [60, 40]); } } assert.equal(c.step, 'result'); assert.equal(c.result.winner, 'cpu'); assert.equal(c.target.cpu, 9); assert.ok(turns > 0, 'il Taglia ogni tanto sbaglia e ripassa la mano'); const w = m.newCampana(6, { level: 'bona' }); w.target.player = 8; toss(w, at(8)); hopAll(w); assert.equal(w.step, 'result'); assert.equal(w.result.winner, 'player'); }); test('il disegno della campana non cambia lo stato', () => { const calls = [], ctx = new Proxy({}, { get: (o, k) => k in o ? o[k] : (...args) => calls.push([k, ...args]) }); const c = m.newCampana(7, { level: 'bona' }), bg = {}; const draw = () => { const before = structuredClone(c); drawCampana(ctx, c, story.closeups.campana, bg); assert.deepEqual(c, before); }; draw(); assert.ok(calls.some(([op, img]) => op === 'drawImage' && img === bg)); assert.ok(calls.some(([op, t]) => op === 'fillText' && t === 'LA CAMPANA')); toss(c, at(1)); for (let i = 0; i < 40; i++) m.update(c, 1 / 60); draw(); assert.ok(calls.some(([op, t]) => op === 'fillText' && t === 'EQUILIBRIO')); c.step = 'result'; c.result = { winner: 'player' }; calls.length = 0; draw(); assert.ok(calls.some(([op, t]) => op === 'fillText' && t === 'BATTUTO IL TAGLIA!')); assert.ok(MINIGAMES.campana); }); test('dal Taglia, al campone delle poste: la campana, il livello e la vittoria', () => { const g = new Game(story); g.go('giardini_poste'); settle(g); g.act('talk', 'taglia'); settle(g); g.choose(g.options().findIndex(o => o.text.startsWith('E una partita a campana'))); settle(g); g.choose(g.options().findIndex(o => o.text === 'Seria.')); settle(g); assert.equal(g.closeup?.id, 'campana'); assert.equal(g.closeup.level, 'seria'); g.closeup.step = 'result'; g.closeup.result = { winner: 'player' }; g.closeupClose(); settle(g); assert.ok(g.state.flags.campana_vinta); });