Il flipper rifatto da Codex: tavola ad alta risoluzione con lacca, cromature e luci animate; due LOCK (tre passaggi sulla stessa rampa in venti secondi, Fortezza e Trebbio), la SUPER SPECIAL al centro che si apre con entrambi i lock e dà pallina premio e multiball a tre; jackpot fino a x3 coi bersagli, nuove fanfare; la valvola contro il rientro nella molla; display del punteggio e pannello della caccia al jackpot ai lati; test unitari e nel browser (lock, super, multiball) (Andrea)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
+91
-40
@@ -7,15 +7,15 @@
|
||||
// fisso (come il biliardo), con sottopassi quando pallina e flipper vanno forte.
|
||||
// Rivista con la revisione di Codex del 31 ottobre: punti solo a un urto nuovo (non al contatto che dura), due dita
|
||||
// sulla scena, annullare non lancia, niente cancelletto-ripiano, salvataggio col tempo della simulazione, corsie che
|
||||
// contano quando si attraversano, traguardo acquisito appena raggiunto.
|
||||
// contano quando si attraversano, traguardo acquisito appena raggiunto. Valvola di lancio a senso unico.
|
||||
export const FIELD = { x0: 200, x1: 448, y0: 5, y1: 336 }, LANE = { x0: 432, x1: 448 }, R = 4.5;
|
||||
export const BALLS = 3, STEP = 1 / 240, MAX_SPEED = 980, CHARGE = 1.2, FLIP = { len: 34, rest: 0.5, up: -0.46, speed: 26, r: 3 };
|
||||
export const PIVOT = { left: [280, 314], right: [360, 314] };
|
||||
// I punti, generosi come sui flipper degli anni 2000 (Andrea: «aumenta i punti»).
|
||||
export const SCORE = { bumper: 1000, sling: 500, lane: 2500, palio: 30000, target: 5000, bank: 25000, regolo: 10000, ramp: 15000, ponti: 50000, jackpot: 100000 };
|
||||
export const SCORE = { bumper: 1000, sling: 500, lane: 2500, palio: 30000, target: 5000, bank: 25000, regolo: 10000, ramp: 15000, ponti: 50000, jackpot: 100000, super: 250000 };
|
||||
// Le rampe (Andrea: «un paio di ponti sui quali sale la pallina»): si entra dal basso con abbastanza velocità, si
|
||||
// sale, si passa sopra la tavola e si scende dall'altra parte. Fatte tutte e due di fila: i PONTI.
|
||||
export const RAMP = { speed: 240, slope: 155, drag: 20, combo: 12, blend: 30 };
|
||||
export const RAMP = { speed: 240, slope: 155, drag: 20, combo: 12, blend: 30, lockWindow: 20 };
|
||||
// Quanto deve picchiare un urto perché conti (velocità lungo la normale, px/s): il tremolio da gravità non conta.
|
||||
const IMPACT = 40, LANE_LINE = 104, BANK_RESET = 0.8, CLICK_HOLD = 0.16, NUDGE_AFTER = 2.5;
|
||||
// I livelli: la stessa fisica, quasi; cambiano il traguardo, il salvataggio della pallina appena lanciata e un po' la
|
||||
@@ -37,9 +37,11 @@ export function layout() {
|
||||
const walls = [];
|
||||
for (let i = 1; i < arc.length; i++) walls.push(seg(arc[i - 1], arc[i], { e: 0.6 }));
|
||||
walls.push(seg([200, 72], [200, 336]), seg([448, 72], [448, 336]));
|
||||
// La corsia della molla, col fondo. Niente cancelletto: una pallina che ricasca nella corsia si rilancia e basta
|
||||
// (il cancelletto a senso unico faceva da ripiano ai lanci a metà).
|
||||
// La corsia della molla, col fondo. Un lancio che non è ancora entrato in campo si può ritentare;
|
||||
// dopo l'ingresso la valvola inclinata impedisce il ritorno, senza creare un ripiano orizzontale.
|
||||
walls.push(seg([432, 70], [432, 336]), seg([432, 326], [448, 326], { e: 0.2 }));
|
||||
// Valvola inclinata: lascia uscire il lancio e devia a sinistra ogni rientro dal campo.
|
||||
walls.push(seg([432, 70], [448, 60], { gate: true, e: 0.7 }));
|
||||
// Le corsie dei rioni, in cima, fra le bandiere: tre guide.
|
||||
for (const x of [277, 319, 361]) walls.push(seg([x, 70], [x, 102], { e: 0.4 }));
|
||||
// I divisori delle uscite laterali e le guide che portano ai flipper (la guida passa sopra il perno e continua un
|
||||
@@ -67,6 +69,7 @@ export function layout() {
|
||||
// Imbocchi verso il centro, davanti alle sponde elastiche: 27 pixel utili alla pallina anziché 13.
|
||||
{ id: 'sinistra', mouth: [[255, 226], [291, 226]], path: [[273, 226], [273, 196], [247, 150], [250, 96], [262, 52], [290, 30], [322, 34], [352, 52], [378, 84], [394, 116], [411, 128], [411, 142]], lit: 0, touching: false },
|
||||
{ id: 'destra', mouth: [[349, 226], [385, 226]], path: [[367, 226], [367, 196], [393, 150], [390, 96], [378, 52], [350, 30], [318, 34], [288, 52], [262, 84], [246, 116], [229, 128], [229, 142]], lit: 0, touching: false },
|
||||
{ id: 'super', mouth: [[303, 232], [337, 232]], path: [[320, 232], [320, 188], [333, 145], [333, 96], [320, 72], [307, 96], [307, 145], [287, 168], [246, 180], [229, 198], [229, 220]], lit: 0, touching: false },
|
||||
];
|
||||
// Lunghezze, e la cima (il punto più alto del disegno): fin lì si sale, poi si scende.
|
||||
for (const r of ramps) {
|
||||
@@ -91,15 +94,17 @@ export function newFlipper(seed = Date.now() >>> 0, def = {}) {
|
||||
const c = { step: 'play', t: 0, sim: 0, seed, level, skill: LEVELS[level], current: 'player', balls: BALLS, score: 0, power: 0.7,
|
||||
holding: false, holdTime: 0, pointerId: null, cursor: null, accumulator: 0, events: [], line: null, lineT: 0, saved: false,
|
||||
flippers: { left: { angle: FLIP.rest, omega: 0, held: [], click: 0 }, right: { angle: Math.PI - FLIP.rest, omega: 0, held: [], click: 0 } },
|
||||
ball: null, extraBalls: [], locked: null, special: null, rampRun: { id: null, count: 0 },
|
||||
ball: null, extraBalls: [], locks: [], special: null, specialPaid: false, jackpotX: 1,
|
||||
rampRun: { id: null, count: 0, until: 0 }, superOpenedAt: null,
|
||||
saveUntil: 0, resets: {}, result: null, lastRamp: null, lines: [], lineId: 0, fx: [], trail: [], visual: { time: 0 }, ...layout() };
|
||||
newBall(c);
|
||||
return c;
|
||||
}
|
||||
function makeBall() { return { x: (LANE.x0 + LANE.x1) / 2, y: 318, vx: 0, vy: 0, waiting: true, ramp: null, contacts: [], trail: [], still: 0, anchor: null, rest: 0 }; }
|
||||
function makeBall() { return { x: (LANE.x0 + LANE.x1) / 2, y: 318, vx: 0, vy: 0, waiting: true, inPlay: false, releaseAt: 0, ramp: null, contacts: [], trail: [], still: 0, anchor: null, rest: 0 }; }
|
||||
function newBall(c) { c.ball = makeBall(); c.trail = c.ball.trail; }
|
||||
export const activeBalls = c => [c.ball, ...c.extraBalls].filter(Boolean);
|
||||
export const multiball = c => c.extraBalls.length > 0;
|
||||
export const rampOpen = (c, r) => r.id !== 'super' || c.special === 'super';
|
||||
export const waiting = c => c.step === 'play' && !!c.ball?.waiting;
|
||||
export const canThrow = c => waiting(c) && !c.holding;
|
||||
export const canPress = canThrow;
|
||||
@@ -117,7 +122,7 @@ function say(c, line, front = false) {
|
||||
}
|
||||
// Gli effetti sulla tavola (Andrea: «più spettacolare»): anelli, scintille, coriandoli, luci che corrono, punti che
|
||||
// volano, il lampo colorato e la scossa. Qui si segnano col loro tempo; li disegna flipper-render.js, e durano FX_LIFE.
|
||||
export const FX_LIFE = 2;
|
||||
export const FX_LIFE = 3;
|
||||
function fx(c, kind, x, y, extra = {}) { c.fx.push({ kind, x, y, t: c.visual.time, ...extra }); }
|
||||
// I punti, che volano via dal punto colpito; al traguardo la vittoria è acquisita (anche uscendo prima), poi si
|
||||
// continua per il record.
|
||||
@@ -175,7 +180,7 @@ export function update(c, dt) {
|
||||
while (c.accumulator >= STEP) { c.accumulator -= STEP; step(c); }
|
||||
// La scia della pallina: le ultime posizioni, per la cometa.
|
||||
for (const b of activeBalls(c)) {
|
||||
if (!b.waiting) { b.trail.push([b.x, b.y]); if (b.trail.length > 10) b.trail.shift(); } else b.trail.length = 0;
|
||||
if (!b.waiting && b.releaseAt <= c.sim) { b.trail.push([b.x, b.y]); if (b.trail.length > 10) b.trail.shift(); } else b.trail.length = 0;
|
||||
}
|
||||
c.trail = c.ball?.trail ?? [];
|
||||
}
|
||||
@@ -193,10 +198,11 @@ function step(c) {
|
||||
c.sim += STEP;
|
||||
for (const r of c.ramps) r.lit = Math.max(0, r.lit - STEP);
|
||||
if (c.lastRamp && c.sim - c.lastRamp.at > RAMP.combo) c.lastRamp = null;
|
||||
if (c.rampRun.count && c.sim > c.rampRun.until) c.rampRun = { id: null, count: 0, until: 0 };
|
||||
if (c.step !== 'play') { moveFlippers(c, STEP); return; }
|
||||
for (const bank of Object.keys(c.resets)) if (c.sim >= c.resets[bank]) { for (const t of c.targets) if (t.bank === bank) t.down = false; delete c.resets[bank]; }
|
||||
// Una fotografia delle palline all'inizio del passo: quelle appena liberate si muovono dal prossimo.
|
||||
const balls = activeBalls(c), ground = balls.filter(b => !b.waiting && !b.ramp);
|
||||
const balls = activeBalls(c), ground = balls.filter(b => !b.waiting && !b.ramp && b.releaseAt <= c.sim);
|
||||
const from = new Map(ground.map(b => [b, { x: b.x, y: b.y }]));
|
||||
for (const b of ground) { b.vy += c.skill.g * STEP; limit(b); }
|
||||
const speed = Math.max(0, ...ground.map(b => Math.hypot(b.vx, b.vy)));
|
||||
@@ -206,6 +212,7 @@ function step(c) {
|
||||
moveFlippers(c, h);
|
||||
for (const ball of ground) {
|
||||
ball.x += ball.vx * h; ball.y += ball.vy * h;
|
||||
if (ball.x < LANE.x0 - R) ball.inPlay = true;
|
||||
for (const w of c.walls) collideSeg(c, ball, w);
|
||||
for (const t of c.targets) if (!t.down) collideTarget(c, ball, t);
|
||||
for (const b of c.bumpers) collideBumper(c, ball, b);
|
||||
@@ -216,7 +223,7 @@ function step(c) {
|
||||
for (let a = 0; a < ground.length; a++) for (let b = a + 1; b < ground.length; b++) collideBalls(ground[a], ground[b]);
|
||||
}
|
||||
for (const ball of balls) {
|
||||
if (ball.waiting) continue;
|
||||
if (ball.waiting || ball.releaseAt > c.sim) continue;
|
||||
if (ball.ramp) { rideRamp(c, ball); continue; }
|
||||
crossLanes(c, from.get(ball), ball);
|
||||
enterRamps(c, from.get(ball), ball);
|
||||
@@ -261,6 +268,7 @@ function crossLanes(c, from, ball) {
|
||||
// indietro e ricasca dall'imbocco.
|
||||
function enterRamps(c, from, ball) {
|
||||
for (const r of c.ramps) {
|
||||
if (!rampOpen(c, r)) continue;
|
||||
const [[x0, y], [x1]] = r.mouth;
|
||||
if (!(from.y > y && ball.y <= y && ball.vy < 0)) continue;
|
||||
const k = (y - from.y) / (ball.y - from.y), x = from.x + (ball.x - from.x) * k;
|
||||
@@ -288,7 +296,8 @@ function rideRamp(c, ball) {
|
||||
const v = Math.max(on.v, 150);
|
||||
ball.vx = p.dx * v; ball.vy = p.dy * v; ball.ramp = null;
|
||||
ball.contacts = [...c.bumpers, c.regolo, ...c.targets, ...c.walls].map(o => o.contactId);
|
||||
add(c, SCORE.ramp, [p.x, p.y]); r.lit = 3; c.events.push({ kind: 'flipper-bersaglio', power: 0.9 }); fx(c, 'chase', 0, 0, { ramp: r.id });
|
||||
if (r.id !== 'super') add(c, SCORE.ramp, [p.x, p.y]);
|
||||
r.lit = 3; c.events.push({ kind: 'flipper-bersaglio', power: 0.9 }); fx(c, 'chase', 0, 0, { ramp: r.id });
|
||||
if (rampFeature(c, r, ball)) return;
|
||||
if (c.lastRamp && c.lastRamp.id !== r.id) {
|
||||
add(c, SCORE.ponti); say(c, 'I PONTI!'); say(c, `BONUS +${SCORE.ponti}`); c.events.push({ kind: 'flipper-ponti', power: 1 }); c.lastRamp = null;
|
||||
@@ -300,29 +309,62 @@ function rideRamp(c, ball) {
|
||||
ball.vx = -p.dx * on.v; ball.vy = Math.max(60, -p.dy * on.v); ball.ramp = null; ball.y = r.mouth[0][1] + 1;
|
||||
} else { ball.vx = p.dx * on.v; ball.vy = p.dy * on.v; }
|
||||
}
|
||||
// La sequenza considera i passaggi completi: bersagli e rimbalzi non la interrompono, l'altra rampa sì.
|
||||
// Tre passaggi completi sulla stessa rampa entro venti secondi. La finestra parte dal primo.
|
||||
function announce(c, line) {
|
||||
// Gli eventi di fase sostituiscono i vecchi annunci dei ponti; il premio del traguardo mantiene la precedenza.
|
||||
const prize = c.lines.includes('PALLINA +1'); c.lines = [];
|
||||
if (c.line === 'TRAGUARDO!') { if (prize) c.lines.push('PALLINA +1'); c.lines.push(line); }
|
||||
else { c.line = null; say(c, line, true); if (prize) c.lines.push('PALLINA +1'); }
|
||||
}
|
||||
function celebrate(c, title, points, color) {
|
||||
if (points) add(c, points, [320, 184]);
|
||||
announce(c, title);
|
||||
fx(c, 'celebrate', 320, 170, { text: title, points, color });
|
||||
fx(c, 'burst', 320, 150, { big: true }); fx(c, 'shake', 0, 0);
|
||||
}
|
||||
function rampFeature(c, r, ball) {
|
||||
if (c.locked) {
|
||||
if (c.special !== r.id) { say(c, 'TENTA SPECIAL'); return true; }
|
||||
const released = c.locked.ball;
|
||||
released.vx = 0; released.vy = 170; released.waiting = false;
|
||||
released.trail = []; released.still = 0; released.anchor = null; released.rest = 0;
|
||||
c.extraBalls.push(released); c.locked = null; c.special = null; c.lastRamp = null;
|
||||
c.rampRun = { id: null, count: 0 }; c.saveUntil = 0; c.saved = true;
|
||||
add(c, SCORE.jackpot, [324, 190]); say(c, 'JACKPOT!', c.line !== 'TRAGUARDO!'); say(c, 'MULTIBALL!');
|
||||
c.events.push({ kind: 'flipper-jackpot', power: 1 }); fx(c, 'burst', 324, 130, { big: true }); fx(c, 'shake', 0, 0);
|
||||
if (r.id === 'super') {
|
||||
// La rampa centrale esiste solo con entrambi i lock: premio e tre palline sul campo.
|
||||
if (c.locks.length !== 2) return true;
|
||||
for (const [i, lock] of c.locks.entries()) {
|
||||
const released = lock.ball;
|
||||
// Le uscite verticali centrano le corsie interne: nessuna spinta verso le buche laterali.
|
||||
released.vx = 0; released.vy = 170; released.waiting = false;
|
||||
released.trail = []; released.still = 0; released.anchor = null; released.rest = 0;
|
||||
released.releaseAt = c.sim + i * 0.45; released.releasedFrom = lock.rampId;
|
||||
c.extraBalls.push(released);
|
||||
}
|
||||
// Uscita laterale sopra la paletta sinistra, con velocità moderata; niente tiro dritto nella buca.
|
||||
ball.vx = 0; ball.vy = 160;
|
||||
c.locks = []; c.special = null; c.specialPaid = false; c.superOpenedAt = null; c.lastRamp = null;
|
||||
c.rampRun = { id: null, count: 0, until: 0 }; c.saveUntil = 0; c.saved = true; c.balls++;
|
||||
celebrate(c, 'SUPER SPECIAL', SCORE.super * c.jackpotX, '#e1a0ff');
|
||||
say(c, 'PALLINA +1'); say(c, 'MULTIBALL!');
|
||||
c.events.push({ kind: 'flipper-super', power: 1 });
|
||||
for (const ramp of c.ramps) { ramp.lit = 5; fx(c, 'chase', 0, 0, { ramp: ramp.id }); }
|
||||
return true;
|
||||
}
|
||||
if (multiball(c)) return false;
|
||||
c.rampRun = { id: r.id, count: c.rampRun.id === r.id ? c.rampRun.count + 1 : 1 };
|
||||
if (c.rampRun.count < 3) return false;
|
||||
// La pallina resta visibile nella serratura all'uscita della rampa. Il nuovo lancio è gratuito.
|
||||
c.locked = { rampId: r.id, ball }; ball.vx = 0; ball.vy = 0; ball.trail = [];
|
||||
c.special = c.ramps.find(other => other.id !== r.id).id; c.lastRamp = null;
|
||||
if (multiball(c)) {
|
||||
celebrate(c, 'JACKPOT!', SCORE.jackpot * c.jackpotX, '#ffcf75');
|
||||
c.events.push({ kind: 'flipper-jackpot', power: 1 }); return true;
|
||||
}
|
||||
if (c.locks.some(lock => lock.rampId === r.id)) { c.rampRun = { id: null, count: 0, until: 0 }; say(c, 'LOCK OCCUPATO'); return true; }
|
||||
if (c.special === r.id && !c.specialPaid) {
|
||||
c.specialPaid = true;
|
||||
celebrate(c, 'JACKPOT!', SCORE.jackpot * c.jackpotX, '#ffcf75');
|
||||
c.events.push({ kind: 'flipper-jackpot', power: 1 });
|
||||
}
|
||||
const continuing = c.rampRun.id === r.id && c.sim <= c.rampRun.until;
|
||||
c.rampRun = { id: r.id, count: continuing ? c.rampRun.count + 1 : 1, until: continuing ? c.rampRun.until : c.sim + RAMP.lockWindow };
|
||||
if (c.rampRun.count < 3) return c.locks.length > 0;
|
||||
c.locks.push({ rampId: r.id, ball }); ball.vx = 0; ball.vy = 0; ball.trail = [];
|
||||
c.special = c.locks.length === 2 ? 'super' : c.ramps.find(other => other.id !== 'super' && other.id !== r.id).id;
|
||||
c.rampRun = { id: null, count: 0, until: 0 }; c.lastRamp = null;
|
||||
c.saveUntil = 0; c.saved = false; c.holding = false; c.holdTime = 0; c.pointerId = null;
|
||||
newBall(c); say(c, 'LOCK!', c.line !== 'TRAGUARDO!'); say(c, 'SPECIAL!');
|
||||
c.events.push({ kind: 'flipper-lock', power: 1 }); fx(c, 'ring', ball.x, ball.y);
|
||||
newBall(c);
|
||||
celebrate(c, c.locks.length === 2 ? 'SUPER APERTA' : 'BALL LOCKED', 0, c.locks.length === 2 ? '#e1a0ff' : '#72edff');
|
||||
c.events.push({ kind: 'flipper-lock', power: 1 });
|
||||
if (c.special === 'super') { c.superOpenedAt = c.visual.time; c.events.push({ kind: 'flipper-super-open', power: 1 }); }
|
||||
return true;
|
||||
}
|
||||
function drain(c, ball) {
|
||||
@@ -330,17 +372,20 @@ function drain(c, ball) {
|
||||
if (multiball(c)) {
|
||||
if (c.ball === ball) c.ball = c.extraBalls.shift();
|
||||
else c.extraBalls = c.extraBalls.filter(b => b !== ball);
|
||||
c.saveUntil = 0; c.saved = true; c.rampRun = { id: null, count: 0 };
|
||||
say(c, 'UNA IN GIOCO'); return;
|
||||
c.saveUntil = 0; c.saved = true; c.rampRun = { id: null, count: 0, until: 0 };
|
||||
say(c, multiball(c) ? 'DUE IN GIOCO' : 'UNA IN GIOCO'); return;
|
||||
}
|
||||
if (c.sim < c.saveUntil) { say(c, 'SALVATA!'); c.saved = true; newBall(c); return; }
|
||||
c.rampRun = { id: null, count: 0 };
|
||||
if (c.locked) {
|
||||
// Perduta la sostitutiva: si recupera quella trattenuta, senza scalare una vita o lasciare lock sospesi.
|
||||
c.locked = null; c.special = null; c.lastRamp = null; c.saved = false; c.saveUntil = 0;
|
||||
say(c, 'LOCK LIBERO', true); newBall(c); return;
|
||||
c.rampRun = { id: null, count: 0, until: 0 };
|
||||
if (c.locks.length) {
|
||||
// Si recupera l'ultimo lock. L'eventuale primo resta acquisito e non si scala la riserva.
|
||||
c.locks.pop(); c.superOpenedAt = null;
|
||||
c.special = c.locks.length ? c.ramps.find(r => r.id !== 'super' && r.id !== c.locks[0].rampId).id : null;
|
||||
if (!c.locks.length) c.specialPaid = false;
|
||||
c.lastRamp = null; c.saved = false; c.saveUntil = 0;
|
||||
announce(c, 'LOCK LIBERO'); newBall(c); return;
|
||||
}
|
||||
c.balls--; c.saved = false; c.lastRamp = null;
|
||||
c.balls--; c.saved = false; c.lastRamp = null; c.jackpotX = 1;
|
||||
if (c.balls > 0) { say(c, `PALLINE: ${c.balls}`); newBall(c); return; }
|
||||
c.ball = null; c.step = 'result';
|
||||
c.result = { winner: c.score >= c.skill.goal ? 'player' : 'cpu', score: c.score, goal: c.skill.goal };
|
||||
@@ -366,6 +411,12 @@ function hitSeg(ball, w, e) {
|
||||
return { vn: -vn, nx, ny, fresh };
|
||||
}
|
||||
function collideSeg(c, ball, w) {
|
||||
if (w.gate) {
|
||||
if (!ball.inPlay) return;
|
||||
const hit = hitSeg(ball, w, w.e);
|
||||
if (hit && hit.vn > 0) ball.vx = Math.min(-100, ball.vx);
|
||||
return;
|
||||
}
|
||||
if (!w.sling) { hitSeg(ball, w, w.e); return; }
|
||||
// La sponda elastica: spinge via lungo la normale, tenendo la componente di strisciamento, e un po' a caso come
|
||||
// la gomma vera; solo a un urto nuovo, e solo se picchia.
|
||||
@@ -383,7 +434,7 @@ function collideTarget(c, ball, t) {
|
||||
if (!hit || !hit.fresh || hit.vn < IMPACT) return;
|
||||
t.down = true; add(c, SCORE.target, [t.a[0] + (t.bank === 'pieve' ? 14 : -14), (t.a[1] + t.b[1]) / 2]); c.events.push({ kind: 'flipper-bersaglio', power: 0.7 }); fx(c, 'spark', t.a[0], (t.a[1] + t.b[1]) / 2);
|
||||
const bank = c.targets.filter(k => k.bank === t.bank);
|
||||
if (bank.every(k => k.down)) { add(c, SCORE.bank, [t.a[0] + (t.bank === 'pieve' ? 30 : -30), 174]); say(c, t.bank === 'pieve' ? 'LA PIEVE!' : 'LA TORRE!'); c.resets[t.bank] = c.sim + BANK_RESET; fx(c, 'burst', t.a[0] + (t.bank === 'pieve' ? 20 : -20), 174); }
|
||||
if (bank.every(k => k.down)) { c.jackpotX = Math.min(3, c.jackpotX + 1); fx(c, 'pop', 320, 205, { text: `JACKPOT X${c.jackpotX}` }); add(c, SCORE.bank, [t.a[0] + (t.bank === 'pieve' ? 30 : -30), 174]); say(c, t.bank === 'pieve' ? 'LA PIEVE!' : 'IL TREBBIO!'); c.resets[t.bank] = c.sim + BANK_RESET; fx(c, 'burst', t.a[0] + (t.bank === 'pieve' ? 20 : -20), 174); }
|
||||
}
|
||||
// Pallina contro un cerchio: spinta fuori; torna la velocità d'urto lungo la normale (null se non tocca).
|
||||
function hitCircle(ball, o, e) {
|
||||
@@ -465,7 +516,7 @@ export const button = c => c.step === 'result' ? 'Finita' : waiting(c) ? 'Lancia
|
||||
export function status(c) {
|
||||
if (c.step === 'result') return `${c.result.score} punti. ${c.result.winner === 'player' ? `Traguardo ${c.result.goal}: fatto!` : `Ne servivano ${c.result.goal}.`}`;
|
||||
const goal = c.result ? 'traguardo fatto, pallina premio vinta, ora il record' : `traguardo ${c.skill.goal}`;
|
||||
const feature = c.locked ? `LOCK: pallina trattenuta. Rampa ${c.special === 'sinistra' ? 'sinistra' : 'destra'} SPECIAL: jackpot ${SCORE.jackpot} e multiball.` : multiball(c) ? 'MULTIBALL: due palline in gioco!' : c.rampRun.count ? `Rampa ${c.rampRun.id}: ${c.rampRun.count}/3 per il LOCK.` : '';
|
||||
const feature = c.special === 'super' ? 'SUPER SPECIAL aperta al centro: pallina premio e multiball!' : multiball(c) ? `MULTIBALL: ${activeBalls(c).length} palline. Le rampe valgono JACKPOT!` : c.locks.length ? `LOCK ${c.locks.length}/2. Completa tre passaggi sulla rampa ${c.special} per aprire la SUPER SPECIAL.` : c.rampRun.count ? `Rampa ${c.rampRun.id}: ${c.rampRun.count}/3, restano ${Math.ceil(c.rampRun.until - c.sim)} secondi.` : 'Tre passaggi sulla stessa rampa in 20 secondi: LOCK.';
|
||||
const tally = `Punti ${c.score} (${goal}), palline ${c.balls}.${feature ? ' ' + feature : ''}`;
|
||||
if (c.line) return `${c.line} ${tally}`;
|
||||
return waiting(c) ? `Tieni premuto per caricare la molla e lascia, o premi Lancia. ${tally}` : `Flipper: metà sinistra e destra della scena, o i pulsanti. ${tally}`;
|
||||
|
||||
Reference in New Issue
Block a user