Il flipper coi due ponti (le rampe che si incrociano sopra la tavola, i PONTI se fatti di fila), i punti ×10 come sui flipper degli anni 2000, il tabellone a punti luminosi con le cifre che corrono e le scritte che lampeggiano, e un clic sul River che entra e va al flipper (Andrea)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
2026-10-31 21:20:53 +01:00
co-authored by Claude Fable 5.1
parent a50847da2b
commit 0a8b0310d3
13 changed files with 227 additions and 59 deletions
+26 -7
View File
@@ -55,11 +55,11 @@ test('la pallina persa: tre palline, poi il risultato; il salvataggio vale una v
put(c, { x: 320, y: 320, vx: 0, vy: 200 }); run(c, 1);
}
assert.equal(c.step, 'result'); assert.equal(c.result.winner, 'cpu'); assert.ok(!m.canThrow(c));
assert.match(m.status(c), /Ne servivano 10000/);
assert.match(m.status(c), /Ne servivano 120000/);
const d = m.newFlipper(5, { level: 'bona' });
m.launch(d, 0.9); assert.ok(m.saveLeft(d) > 7); put(d, { x: 320, y: 320, vx: 0, vy: 200 }); run(d, 1);
assert.ok(m.waiting(d) && d.balls === 3, 'salvata: la pallina torna sulla molla');
assert.match(d.line ?? '', /salvata/);
assert.match(d.line ?? '', /SALVATA/);
m.launch(d, 0.9); assert.equal(m.saveLeft(d), 0); put(d, { x: 320, y: 320, vx: 0, vy: 200 }); run(d, 1);
assert.equal(d.balls, 2, 'la seconda volta è persa davvero');
// Lo stesso quarto di secondo, a passi piccoli o in un fotogramma solo: stesso esito (Codex).
@@ -139,7 +139,7 @@ test('comandi: la molla si carica e si lancia lasciando; annullare non lancia; d
// Spazio: carica e lancia; Invio: lancia con la forza del cursore; a partita finita i tasti non sono suoi.
const d = m.newFlipper(9); assert.ok(m.key(d, ' ', true)); run(d, 0.3); assert.ok(d.holding); m.key(d, ' ', false); assert.ok(!m.waiting(d));
const e = m.newFlipper(10); assert.ok(m.setPower(e, 0.5)); assert.equal(m.button(e), 'Lancia'); m.key(e, 'Enter', true); assert.ok(!m.waiting(e)); assert.equal(m.button(e), 'In gioco…');
e.step = 'result'; e.result = { winner: 'cpu', score: 0, goal: 10000 }; assert.ok(!m.key(e, ' ', true) && !m.key(e, 'Enter', true));
e.step = 'result'; e.result = { winner: 'cpu', score: 0, goal: 120000 }; assert.ok(!m.key(e, ' ', true) && !m.key(e, 'Enter', true));
});
test('il traguardo è acquisito appena raggiunto: anche uscendo prima, si vince; la trattenuta sul flipper non viene scossa', () => {
@@ -149,7 +149,7 @@ test('il traguardo è acquisito appena raggiunto: anche uscendo prima, si vince;
assert.match(m.status(c), /record/);
const g = new Game(story); g.go('via_provinciale'); settle(g);
g.act('use', 'river'); settle(g); g.choose(g.options().findIndex(o => o.text.startsWith('Una partita alla bona'))); settle(g);
g.closeup.score = 7000; g.closeup.result = { winner: 'player', score: 7000, goal: 6000 };
g.closeup.score = 70000; g.closeup.result = { winner: 'player', score: 70000, goal: 60000 };
g.closeupClose(); settle(g);
assert.ok(g.state.flags.flipper_vinto, 'uscito dopo il traguardo: vinto');
// La pallina trattenuta sul flipper alzato resta lì (niente scossa): è una mossa del giocatore.
@@ -158,6 +158,24 @@ test('il traguardo è acquisito appena raggiunto: anche uscendo prima, si vince;
assert.ok(h.ball && !h.ball.waiting && h.ball.y > 280 && h.ball.x < 320, `ancora sul flipper (${h.ball?.x.toFixed(0)}, ${h.ball?.y.toFixed(0)})`);
});
test('i ponti: imboccati forte si sale e si scende dall\'altra parte (e la pallina non urta la tavola sotto); piano si ricasca; tutti e due di fila, i PONTI', () => {
const c = m.newFlipper(13, { level: 'bona' }); m.launch(c, 0.9); c.score = 0;
put(c, { x: 247, y: 250, vx: 0, vy: -420 });
let onRamp = 0, top = 999; for (let i = 0; i < 150; i++) { m.update(c, 1 / 60); if (c.ball.ramp) { onRamp++; top = Math.min(top, c.ball.y); } }
assert.ok(onRamp > 20, 'è salita sul ponte'); assert.ok(top < 50, 'fin sopra la tavola');
assert.ok(!c.ball.ramp && c.ball.x > 380, `scesa dall'altra parte (${c.ball.x.toFixed(0)}, ${c.ball.y.toFixed(0)})`);
assert.equal(c.score, m.SCORE.ramp); assert.match(c.line, /PONTE SINISTRO/); assert.ok(c.ramps[0].lit > 0);
assert.ok(c.events.some(e => e.kind === 'flipper-rampa'));
// Il secondo ponte subito dopo: i PONTI.
put(c, { x: 393, y: 250, vx: 0, vy: -420, ramp: null }); run(c, 2.5);
assert.equal(c.score, 3 * m.SCORE.ramp - m.SCORE.ramp + m.SCORE.ponti, `i ponti (${c.score})`); assert.match(c.line ?? '', /PONTI/);
// Troppo piano: non ci arriva, ricasca dall'imbocco senza punti.
const d = m.newFlipper(14, { level: 'bona' }); m.launch(d, 0.9); d.score = 0;
put(d, { x: 247, y: 250, vx: 0, vy: -300 }); let climbed = false; for (let i = 0; i < 90; i++) { m.update(d, 1 / 60); if (d.ball.ramp) climbed = true; }
assert.ok(!d.ball.ramp && d.ball.y > 236, 'ricaduta'); assert.equal(d.score % m.SCORE.ramp === 0 && d.score >= m.SCORE.ramp, false, 'niente punti del ponte');
void climbed;
});
test('giocando con attenzione, alla bona si arriva al traguardo; nessuna partita si incastra', () => {
let won = 0;
for (let seed = 1; seed <= 8; seed++) {
@@ -177,13 +195,14 @@ test('il disegno del flipper non cambia lo stato', () => {
draw(); assert.ok(calls.some(([op, img]) => op === 'drawImage' && img === bg));
assert.ok(calls.some(([op, t]) => op === 'fillText' && t === 'FLIPPER DEL RIVER'));
m.launch(c, 0.8); run(c, 1); calls.length = 0; draw();
assert.ok(calls.some(([op, t]) => op === 'fillText' && /^SALVATA \d s$/.test(t)), 'il salvataggio si vede');
c.step = 'result'; c.result = { winner: 'player', score: 7000, goal: 6000 }; calls.length = 0; draw();
assert.ok(calls.filter(([op]) => op === 'fillRect').length > 300, 'il tabellone a punti luminosi');
c.step = 'result'; c.result = { winner: 'player', score: 70000, goal: 60000 }; calls.length = 0; draw();
assert.ok(calls.some(([op, t]) => op === 'fillText' && t === 'TRAGUARDO FATTO!'));
});
test('al River: «Usa» o «Apri» il bar, il livello, la partita; uscendo senza traguardo, la battuta di chi lascia', () => {
test('al River: un clic sul bar (o «Usa», «Apri»), il livello, la partita; uscendo senza traguardo, la battuta di chi lascia', () => {
const g = new Game(story); g.go('via_provinciale'); settle(g);
assert.equal(g.hotspot('river').default, 'open');
g.act('use', 'river'); settle(g);
assert.ok(g.options().some(o => o.text === 'Una partita seria.'));
g.choose(g.options().findIndex(o => o.text === 'Una partita seria.')); settle(g);