Il flipper col LOCK (idea di Andrea, fatto da Codex e completato): lo stesso ponte tre volte trattiene la pallina e ne dà una nuova, l'altro ponte SPECIAL vale il JACKPOT e il MULTIBALL a due palline; serratura, seconde palline e SPECIAL nel disegno, suoni, aiuto, test; imbocchi delle rampe al centro e uscite laterali più strette (Codex); pallina premio al traguardo; la barra del traguardo sotto la finestra invece del pannello sul Trebbio; il Boccino prudente in curva
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
@@ -170,6 +170,27 @@ 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('il traguardo assegna una sola pallina premio, utilizzabile anche se si era all\'ultima, a tutti i livelli', () => {
|
||||
for (const level of Object.keys(m.LEVELS)) for (const missing of [m.SCORE.bumper, 500]) {
|
||||
const c = m.newFlipper(85, { level }); m.launch(c, 0.8);
|
||||
c.balls = 1; c.score = c.skill.goal - missing;
|
||||
const currentBall = c.ball, b = c.bumpers[0];
|
||||
put(c, { x: b.x, y: b.y + b.r + 6, vx: 0, vy: -200 }); run(c, 0.05);
|
||||
assert.equal(c.balls, 2, `${level}: una pallina in più, raggiungendo o superando la soglia`);
|
||||
assert.equal(c.ball, currentBall, 'il tiro in corso prosegue');
|
||||
assert.equal(c.step, 'play'); assert.equal(c.result.winner, 'player');
|
||||
assert.equal(c.line, 'TRAGUARDO!'); assert.equal(c.lines[0], 'PALLINA +1');
|
||||
assert.match(m.status(c), /pallina premio vinta/);
|
||||
put(c, { x: c.regolo.x, y: c.regolo.y + 12, vx: 0, vy: -200 }); run(c, 0.05);
|
||||
assert.ok(c.score > c.skill.goal); assert.equal(c.balls, 2, 'altri punti non assegnano altri premi');
|
||||
c.saveUntil = 0; put(c, { x: 320, y: 342, vx: 0, vy: 300, ramp: null }); run(c, 0.1);
|
||||
assert.equal(c.balls, 1); assert.ok(m.waiting(c), 'persa l\'ultima originale, si può lanciare la pallina premio');
|
||||
m.launch(c, 0.8); c.saveUntil = 0;
|
||||
put(c, { x: 320, y: 342, vx: 0, vy: 300 }); run(c, 0.1);
|
||||
assert.equal(c.balls, 0); assert.equal(c.step, 'result'); assert.equal(c.result.winner, 'player');
|
||||
}
|
||||
});
|
||||
|
||||
test('i ponti: imboccati forte si sale, si passa sopra la tavola e si scende nella corsia di ritorno; piano si ricasca; di traverso no; tutti e due di fila, i PONTI', () => {
|
||||
const c = m.newFlipper(13, { level: 'bona' }); m.launch(c, 0.9); c.score = 0;
|
||||
const { frames: onRamp, top, exit } = ride(c, { x: 273, y: 240, vx: 0, vy: -470 });
|
||||
@@ -201,6 +222,40 @@ test('i ponti: imboccati forte si sale, si passa sopra la tavola e si scende nel
|
||||
assert.equal(g.balls, 2); assert.equal(g.lastRamp, null, 'azzerata con la pallina persa');
|
||||
});
|
||||
|
||||
test('le rampe centrali si prendono con tiri obliqui dalle palette e anche fuori centro, a tutti i livelli', () => {
|
||||
for (const level of Object.keys(m.LEVELS)) for (const [index, side] of [[0, -1], [1, 1]]) {
|
||||
const c = m.newFlipper(81, { level }); m.launch(c, 0.8);
|
||||
ride(c, { x: 320 + side * 20, y: 290, vx: side * 90, vy: -460 });
|
||||
assert.equal(c.score, m.SCORE.ramp, `${level}: tiro obliquo dalla zona delle palette, lato ${side}`);
|
||||
assert.ok(c.ramps[index].lit > 0);
|
||||
for (const offset of [-12, 12]) {
|
||||
const d = m.newFlipper(82, { level }); m.launch(d, 0.8);
|
||||
const r = d.ramps[index];
|
||||
ride(d, { x: r.path[0][0] + offset, y: r.mouth[0][1] + 1, vx: 0, vy: -330 });
|
||||
assert.equal(d.score, m.SCORE.ramp, `${level}: basta un colpo medio, decentrato di ${offset}`);
|
||||
}
|
||||
const e = m.newFlipper(83, { level }); m.launch(e, 0.8);
|
||||
const r = e.ramps[index];
|
||||
put(e, { x: r.mouth[0][0] + m.R - 1, y: r.mouth[0][1] + 1, vx: 0, vy: -330 }); run(e, 0.05);
|
||||
assert.equal(e.ball.ramp, null, 'fuori dalla bocca disegnata la pallina non viene catturata');
|
||||
}
|
||||
});
|
||||
|
||||
test('le uscite laterali sono più strette: i tiri vicini rientrano verso le palette, quelli esterni si perdono ancora', () => {
|
||||
for (const [x, safe] of [[218, true], [419, true], [207, false], [426, false]]) {
|
||||
const c = m.newFlipper(84); m.launch(c, 0.8); c.saveUntil = 0;
|
||||
put(c, { x, y: 250, vx: 0, vy: 100 });
|
||||
let lower = null;
|
||||
for (let i = 0; i < 2 / m.STEP && c.balls === 3; i++) {
|
||||
m.update(c, m.STEP);
|
||||
if (!lower && c.ball?.y >= 300) lower = { x: c.ball.x };
|
||||
}
|
||||
assert.ok(lower, `la pallina ${x} non resta incastrata nella corsia`);
|
||||
if (safe) assert.ok(lower.x > 250 && lower.x < 390, `${x} torna verso le palette (${lower.x})`);
|
||||
else { assert.equal(lower.x, x, 'la corsia esterna resta aperta'); assert.equal(c.balls, 2); }
|
||||
}
|
||||
});
|
||||
|
||||
test('gli effetti: ogni colpo lascia il suo segno (anello, scintille, punti che volano, coriandoli, luci sui ponti, scossa), e dopo un po\' sparisce', () => {
|
||||
const c = m.newFlipper(17, { level: 'bona' }); m.launch(c, 0.9);
|
||||
const kinds = () => c.fx.map(f => f.kind);
|
||||
@@ -222,6 +277,43 @@ test('gli effetti: ogni colpo lascia il suo segno (anello, scintille, punti che
|
||||
assert.ok(calls.some(([op]) => op === 'translate'), 'la scossa sposta la scena');
|
||||
});
|
||||
|
||||
test('il LOCK: lo stesso ponte tre volte di fila trattiene la pallina e ne dà una nuova; l\'altro ponte SPECIAL vale il jackpot e il multiball', () => {
|
||||
const c = m.newFlipper(21, { level: 'bona' }); m.launch(c, 0.9); c.score = 0;
|
||||
const left = c.ramps[0], right = c.ramps[1];
|
||||
// Un tiro nel ponte: si aspetta che la pallina esca in fondo, prima che vada altrove (così il conto non si azzera).
|
||||
const shoot = r => { const [[x0], [x1]] = r.mouth; put(c, { x: (x0 + x1) / 2, y: r.mouth[0][1] + 14, vx: 0, vy: -470, ramp: null }); let was = false; for (let i = 0; i < 240; i++) { m.update(c, 1 / 60); if (c.ball?.ramp) was = true; else if (was) break; } };
|
||||
shoot(left); assert.equal(c.rampRun.count, 1); assert.ok(!c.locked);
|
||||
shoot(left); assert.equal(c.rampRun.count, 2);
|
||||
shoot(left);
|
||||
assert.ok(c.locked && c.locked.rampId === 'sinistra', 'la pallina è trattenuta');
|
||||
assert.equal(c.special, 'destra', 'l\'altro ponte è SPECIAL');
|
||||
assert.ok(m.waiting(c) && c.ball !== c.locked.ball, 'una pallina nuova sulla molla');
|
||||
assert.ok([c.line, ...c.lines].some(l => /LOCK/.test(l ?? '')) && [c.line, ...c.lines].some(l => /SPECIAL/.test(l ?? '')));
|
||||
assert.ok(c.events.some(e => e.kind === 'flipper-lock'));
|
||||
assert.equal(c.balls, 3, 'il lock non costa una pallina');
|
||||
// Il ponte sbagliato non libera niente.
|
||||
m.launch(c, 0.9); c.events.length = 0; shoot(left);
|
||||
assert.ok(c.locked, 'ancora trattenuta');
|
||||
// Il ponte SPECIAL: jackpot, la pallina liberata, due in gioco.
|
||||
const before = c.score; shoot(right);
|
||||
assert.ok(c.score - before >= m.SCORE.jackpot + m.SCORE.ramp, `jackpot (${c.score - before})`);
|
||||
assert.ok(!c.locked && !c.special, 'liberata');
|
||||
assert.ok(m.multiball(c) && m.activeBalls(c).length === 2, 'multiball');
|
||||
assert.ok(c.events.some(e => e.kind === 'flipper-jackpot'));
|
||||
// In multiball perdere una pallina non costa: resta l'altra; persa anche quella, si scala.
|
||||
const riserva = c.balls; // (il jackpot ha passato il traguardo: una pallina premio in più)
|
||||
Object.assign(c.ball, { x: 320, y: 342, vx: 0, vy: 300, waiting: false, ramp: null }); run(c, 0.5);
|
||||
assert.ok(!m.multiball(c) && c.balls === riserva && m.activeBalls(c).length === 1, 'una in gioco, nessuna persa');
|
||||
c.saveUntil = 0; Object.assign(c.ball, { x: 320, y: 342, vx: 0, vy: 300, waiting: false, ramp: null }); run(c, 0.5);
|
||||
assert.equal(c.balls, riserva - 1, 'ora sì');
|
||||
// Persa la pallina nuova prima dello SPECIAL: quella trattenuta torna, senza scalare.
|
||||
const d = m.newFlipper(22, { level: 'bona' }); m.launch(d, 0.9);
|
||||
const go = r => { const [[x0], [x1]] = r.mouth; put(d, { x: (x0 + x1) / 2, y: r.mouth[0][1] + 14, vx: 0, vy: -470, ramp: null }); let was = false; for (let i = 0; i < 240; i++) { m.update(d, 1 / 60); if (d.ball?.ramp) was = true; else if (was) break; } };
|
||||
go(d.ramps[0]); go(d.ramps[0]); go(d.ramps[0]); assert.ok(d.locked);
|
||||
m.launch(d, 0.9); d.saveUntil = 0; Object.assign(d.ball, { x: 320, y: 342, vx: 0, vy: 300, waiting: false, ramp: null }); run(d, 0.5);
|
||||
assert.ok(!d.locked && d.balls === 3 && m.waiting(d), 'lock libero, pallina di nuovo sulla molla');
|
||||
});
|
||||
|
||||
test('giocando con attenzione, alla bona si arriva al traguardo; nessuna partita si incastra', () => {
|
||||
let won = 0;
|
||||
for (let seed = 1; seed <= 8; seed++) {
|
||||
|
||||
Reference in New Issue
Block a user