Il flipper più spettacolare: anelli e scintille, coriandoli dei rioni, luci che corrono sui ponti, punti che volano, lampo e scossa, scia della pallina, regia e fanfare sul tabellone; ponti, sponde e griglia dei punti in cache (niente arretrato grafico) (Andrea)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
+32
-14
@@ -87,7 +87,7 @@ 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, saveUntil: 0, still: 0, anchor: null, rest: 0, resets: {}, result: null, lastRamp: null, lines: [], lineId: 0, visual: { time: 0 }, ...layout() };
|
||||
ball: null, saveUntil: 0, still: 0, anchor: null, rest: 0, resets: {}, result: null, lastRamp: null, lines: [], lineId: 0, fx: [], trail: [], visual: { time: 0 }, ...layout() };
|
||||
newBall(c);
|
||||
return c;
|
||||
}
|
||||
@@ -107,10 +107,19 @@ function say(c, line, front = false) {
|
||||
if (front || !c.line) { if (front && c.line) c.lines.unshift(c.line); c.line = line; c.lineT = 2; c.lineId++; }
|
||||
else c.lines.push(line);
|
||||
}
|
||||
// I punti; al traguardo la vittoria è acquisita (anche uscendo prima), poi si continua per il record.
|
||||
function add(c, points) {
|
||||
// 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;
|
||||
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.
|
||||
function add(c, points, at) {
|
||||
c.score += points;
|
||||
if (!c.result && c.score >= c.skill.goal) { c.result = { winner: 'player', score: c.score, goal: c.skill.goal }; say(c, 'TRAGUARDO!', true); c.events.push({ kind: 'flipper-palio', power: 1 }); }
|
||||
if (at) fx(c, 'pop', at[0], at[1], { text: `+${points}` });
|
||||
if (!c.result && c.score >= c.skill.goal) {
|
||||
c.result = { winner: 'player', score: c.score, goal: c.skill.goal }; say(c, 'TRAGUARDO!', true);
|
||||
c.events.push({ kind: 'flipper-traguardo', power: 1 }); fx(c, 'burst', 324, 170, { big: true }); fx(c, 'wash', 0, 0, { color: '255,210,80' }); fx(c, 'shake', 0, 0);
|
||||
}
|
||||
if (c.result) c.result.score = c.score;
|
||||
}
|
||||
|
||||
@@ -151,8 +160,11 @@ export function update(c, dt) {
|
||||
if (c.lineT > 0 && (c.lineT -= dt) <= 0) { c.line = c.lines.shift() ?? null; if (c.line) { c.lineT = 2; c.lineId++; } }
|
||||
if (c.holding) { c.holdTime += dt; c.power = powerAt(c.holdTime); }
|
||||
for (const b of c.bumpers) b.hit = Math.max(0, b.hit - dt); c.regolo.hit = Math.max(0, c.regolo.hit - dt);
|
||||
c.fx = c.fx.filter(f => c.visual.time - f.t < FX_LIFE);
|
||||
c.accumulator += dt;
|
||||
while (c.accumulator >= STEP) { c.accumulator -= STEP; step(c); }
|
||||
// La scia della pallina: le ultime posizioni, per la cometa.
|
||||
if (c.ball && !c.ball.waiting) { c.trail.push([c.ball.x, c.ball.y]); if (c.trail.length > 10) c.trail.shift(); } else c.trail.length = 0;
|
||||
}
|
||||
function moveFlippers(c, h) {
|
||||
for (const side of ['left', 'right']) {
|
||||
@@ -205,8 +217,11 @@ function crossLanes(c, from, ball) {
|
||||
const k = (LANE_LINE - from.y) / (ball.y - from.y), x = from.x + (ball.x - from.x) * k;
|
||||
const l = c.lanes.find(l => x >= l.x0 && x < l.x1);
|
||||
if (!l || l.lit) return;
|
||||
l.lit = true; add(c, SCORE.lane); c.events.push({ kind: 'flipper-corsia', power: 0.6 });
|
||||
if (c.lanes.every(k => k.lit)) { add(c, SCORE.palio); say(c, 'PALIO!'); c.events.push({ kind: 'flipper-palio', power: 1 }); for (const k of c.lanes) k.lit = false; }
|
||||
l.lit = true; add(c, SCORE.lane, [l.x, l.y]); c.events.push({ kind: 'flipper-corsia', power: 0.6 }); fx(c, 'spark', l.x, l.y);
|
||||
if (c.lanes.every(k => k.lit)) {
|
||||
add(c, SCORE.palio, [324, 150]); say(c, 'PALIO!'); c.events.push({ kind: 'flipper-palio', power: 1 }); for (const k of c.lanes) k.lit = false;
|
||||
fx(c, 'burst', 324, 110, { big: true }); fx(c, 'wash', 0, 0, { color: '255,80,80' }); fx(c, 'shake', 0, 0);
|
||||
}
|
||||
}
|
||||
// L'imbocco di una rampa: la pallina lo attraversa in salita, abbastanza forte, e sale sul ponte: da lì segue il
|
||||
// percorso (niente urti con la tavola sotto), rallentando in salita e riprendendo in discesa. Troppo piano, torna
|
||||
@@ -240,8 +255,11 @@ function rideRamp(c, ball) {
|
||||
const v = Math.max(on.v, 150);
|
||||
ball.vx = p.dx * v; ball.vy = p.dy * v; ball.ramp = null;
|
||||
for (const o of [...c.bumpers, c.regolo, ...c.targets, ...c.walls]) o.touching = true;
|
||||
add(c, SCORE.ramp); r.lit = 3; c.events.push({ kind: 'flipper-bersaglio', power: 0.9 });
|
||||
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-palio', power: 1 }); c.lastRamp = null; }
|
||||
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 (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;
|
||||
fx(c, 'burst', 324, 60, { big: true }); fx(c, 'wash', 0, 0, { color: '120,200,255' }); fx(c, 'shake', 0, 0); fx(c, 'chase', 0, 0, { ramp: c.ramps.find(k => k.id !== r.id).id });
|
||||
}
|
||||
else { say(c, r.id === 'sinistra' ? 'PONTE SX' : 'PONTE DX'); say(c, `+${SCORE.ramp}`); c.lastRamp = { id: r.id, at: c.sim }; }
|
||||
} else if (on.s <= 0) {
|
||||
// Non ce l'ha fatta: ricasca dall'imbocco, lungo la direzione del ponte.
|
||||
@@ -249,7 +267,7 @@ function rideRamp(c, ball) {
|
||||
} else { ball.vx = p.dx * on.v; ball.vy = p.dy * on.v; }
|
||||
}
|
||||
function drain(c) {
|
||||
c.events.push({ kind: 'flipper-buco', power: 0.8 });
|
||||
c.events.push({ kind: 'flipper-buco', power: 0.8 }); fx(c, 'wash', 0, 0, { color: '40,60,160' });
|
||||
if (c.sim < c.saveUntil) { say(c, 'SALVATA!'); c.saved = true; newBall(c); return; }
|
||||
c.balls--; c.saved = false; c.lastRamp = null;
|
||||
if (c.balls > 0) { say(c, `PALLINE: ${c.balls}`); newBall(c); return; }
|
||||
@@ -280,15 +298,15 @@ function collideSeg(c, ball, w) {
|
||||
const a = Math.atan2(hit.ny, hit.nx) + (random(c) - 0.5) * 0.24, s = 250 + random(c) * 30;
|
||||
const tx = ball.vx + hit.vn * hit.nx, ty = ball.vy + hit.vn * hit.ny; // la parte lungo la sponda
|
||||
ball.vx = Math.cos(a) * s + tx * 0.6; ball.vy = Math.sin(a) * s + ty * 0.6;
|
||||
add(c, SCORE.sling); c.events.push({ kind: 'flipper-sponda', power: 0.6 });
|
||||
add(c, SCORE.sling); c.events.push({ kind: 'flipper-sponda', power: 0.6 }); fx(c, 'spark', ball.x, ball.y);
|
||||
} else { ball.vx += 1.3 * hit.vn * hit.nx; ball.vy += 1.3 * hit.vn * hit.ny; }
|
||||
}
|
||||
function collideTarget(c, ball, t) {
|
||||
const hit = hitSeg(ball, t, 0.5);
|
||||
if (!hit || !hit.fresh || hit.vn < IMPACT) return;
|
||||
t.down = true; add(c, SCORE.target); c.events.push({ kind: 'flipper-bersaglio', power: 0.7 });
|
||||
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); say(c, t.bank === 'pieve' ? 'LA PIEVE!' : 'LA TORRE!'); c.resets[t.bank] = c.sim + BANK_RESET; }
|
||||
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); }
|
||||
}
|
||||
// Pallina contro un cerchio: spinta fuori; torna la velocità d'urto lungo la normale (null se non tocca).
|
||||
function hitCircle(ball, o, e) {
|
||||
@@ -306,12 +324,12 @@ function collideBumper(c, ball, b) {
|
||||
if (!hit || !hit.fresh || hit.vn < IMPACT) return;
|
||||
const a = Math.atan2(hit.ny, hit.nx) + (random(c) - 0.5) * 0.2;
|
||||
ball.vx = Math.cos(a) * c.skill.kick; ball.vy = Math.sin(a) * c.skill.kick;
|
||||
b.hit = 0.15; add(c, SCORE.bumper); c.events.push({ kind: 'flipper-fungo', power: 0.8 });
|
||||
b.hit = 0.15; add(c, SCORE.bumper, [b.x, b.y - 16]); c.events.push({ kind: 'flipper-fungo', power: 0.8 }); fx(c, 'ring', b.x, b.y); fx(c, 'spark', ball.x, ball.y);
|
||||
}
|
||||
function collideRegolo(c, ball) {
|
||||
const hit = hitCircle(ball, c.regolo, 0.7);
|
||||
if (!hit || !hit.fresh || hit.vn < IMPACT) return;
|
||||
c.regolo.hit = 0.3; add(c, SCORE.regolo); say(c, 'IL REGOLO!'); c.events.push({ kind: 'flipper-bersaglio', power: 1 });
|
||||
c.regolo.hit = 0.3; add(c, SCORE.regolo, [c.regolo.x, c.regolo.y - 14]); say(c, 'IL REGOLO!'); c.events.push({ kind: 'flipper-bersaglio', power: 1 }); fx(c, 'burst', c.regolo.x, c.regolo.y); fx(c, 'ring', c.regolo.x, c.regolo.y);
|
||||
}
|
||||
// Il flipper è una capsula che gira: alla pallina si aggiunge la velocità del punto toccato.
|
||||
function collideFlipper(c, ball, side) {
|
||||
|
||||
Reference in New Issue
Block a user