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:
@@ -4,7 +4,7 @@
|
||||
// ponti (le rampe), flipper, molla e pallina. A sinistra, sul fianco nero del mobile, il tabellone luminoso a punti
|
||||
// come sui flipper degli anni 2000 (Andrea): le cifre che corrono, le scritte che lampeggiano, le lampadine che
|
||||
// girano intorno. Niente filtri né passate a tutto schermo (vedi risveglio.js).
|
||||
import { FIELD, LANE, R, SCORE, flipperSeg, alongRamp, rampHeight, waiting, canThrow, saveLeft } from './flipper.js';
|
||||
import { FIELD, LANE, R, SCORE, flipperSeg, alongRamp, rampHeight, waiting, canThrow, saveLeft, activeBalls, multiball } from './flipper.js';
|
||||
import { ambientHash as hash } from './ambient.js';
|
||||
import { text, panel } from './bocce-render.js';
|
||||
|
||||
@@ -112,7 +112,7 @@ function rampEntrance(ctx, r, time, who) {
|
||||
}
|
||||
const rampCache = new Map();
|
||||
function ramp(ctx, r, time, who) {
|
||||
const state = r.lit > 0 && Math.sin(time * 14) > 0 ? 'lit' : who && who !== r.id && Math.sin(time * 6) > 0 ? 'invite' : 'base';
|
||||
const state = r.lit > 0 && Math.sin(time * 14) > 0 ? 'lit' : (who === r.id + '!' || (who && who !== r.id)) && Math.sin(time * 6) > 0 ? 'invite' : 'base';
|
||||
if (typeof document === 'undefined') { paintRamp(ctx, r, state); return; }
|
||||
const key = `${r.id}:${state}`;
|
||||
let layer = rampCache.get(key);
|
||||
@@ -161,6 +161,14 @@ function plunger(ctx, c) {
|
||||
for (let y = top + pull + 2; y < 346; y += 3) ctx.fillRect(Math.round(x) - 4, Math.round(y), 8, 1);
|
||||
ctx.fillStyle = '#d9463a'; ctx.fillRect(Math.round(x) - 5, Math.round(top + pull) - 3, 10, 4);
|
||||
}
|
||||
// La serratura: la pallina trattenuta in fondo al ponte, dietro due sbarre, con la luce che pulsa.
|
||||
function lock(ctx, b, time) {
|
||||
ball(ctx, b);
|
||||
ctx.strokeStyle = Math.sin(time * 6) > 0 ? '#ffd45a' : '#8a7a5a'; ctx.lineWidth = 1.5;
|
||||
for (const dx of [-3, 3]) { ctx.beginPath(); ctx.moveTo(b.x + dx, b.y - 9); ctx.lineTo(b.x + dx, b.y + 7); ctx.stroke(); }
|
||||
ctx.beginPath(); ctx.moveTo(b.x - 7, b.y - 9); ctx.lineTo(b.x + 7, b.y - 9); ctx.stroke();
|
||||
label(ctx, 'LOCK', b.x, b.y - 15, 5, '#ffe08c');
|
||||
}
|
||||
function ball(ctx, b) {
|
||||
if (!b) return;
|
||||
// Sul ponte la pallina sta più in alto, a seconda della quota: più su, più grande, con l'ombra più lontana.
|
||||
@@ -261,13 +269,11 @@ function board(ctx, c, time, glass) {
|
||||
if (visible) dots(ctx, line.slice(0, 12), wx + ww / 2, wy + 31, 2, flashing ? '#fff6c8' : '#f0a24a', 'rgba(255,170,60,0.08)', 'center');
|
||||
if (flashing) regia(ctx, c.line, time - d.flash, wx, wy, ww, wh);
|
||||
ctx.restore();
|
||||
// Il traguardo resta leggibile anche mentre il display celebra un colpo.
|
||||
// Il traguardo resta leggibile anche mentre il display celebra un colpo: una barra sottile sotto la finestra e,
|
||||
// nella riga in basso, quanto manca (il pannello che c'era prima copriva il Trebbio dipinto).
|
||||
const progress = Math.min(1, c.score / c.skill.goal), remaining = Math.max(0, c.skill.goal - c.score);
|
||||
panel(ctx, 20, 134, 160, 31);
|
||||
label(ctx, `OBIETTIVO ${c.skill.goal.toLocaleString('it-IT')}`, 28, 142, 7, '#f1d7a1', 'left');
|
||||
label(ctx, remaining ? `MANCANO ${remaining.toLocaleString('it-IT')}` : 'TRAGUARDO RAGGIUNTO', 28, 152, 6, remaining ? '#becbd0' : '#a9e7bf', 'left');
|
||||
ctx.fillStyle = '#30434a'; ctx.fillRect(28, 159, 144, 2);
|
||||
ctx.fillStyle = '#edc777'; ctx.fillRect(28, 159, 144 * progress, 2);
|
||||
ctx.fillStyle = '#1a1410'; ctx.fillRect(wx, wy + wh + 3, ww, 3);
|
||||
ctx.fillStyle = remaining ? '#edc777' : '#a9e7bf'; ctx.fillRect(wx, wy + wh + 3, Math.round(ww * progress), 3);
|
||||
// I ponti: due lampade ai lati della finestra (accese se fatte; quella da fare lampeggia).
|
||||
c.ramps.forEach((r, i) => disc(ctx, i ? wx + ww + 8 : wx - 8, wy + wh / 2, 6, r.lit > 0 || c.lastRamp?.id === r.id ? '#ffd45a' : c.lastRamp && Math.sin(time * 6) > 0 ? '#fff6c8' : '#2a2420', '#8a7a5a'));
|
||||
// I cinque dischi in basso: le palline che restano, e i quattro rioni accesi.
|
||||
@@ -276,7 +282,7 @@ function board(ctx, c, time, glass) {
|
||||
rioni.forEach(([x, y], i) => { const l = c.lanes[i]; disc(ctx, x, y, 11, l.lit ? COLORS[l.rione] : '#2a2420', l.lit ? '#fff4c8' : '#8a7a5a'); });
|
||||
// Sotto, in piccolo: il salvataggio o la combo.
|
||||
const save = saveLeft(c);
|
||||
const note = save > 0 ? `PALLINA SALVA ${Math.ceil(save)} s` : c.lastRamp ? `L'ALTRO PONTE ENTRO ${Math.ceil(12 - (c.sim - c.lastRamp.at))} s` : c.ball && !c.ball.waiting && c.saved ? 'SENZA RETE' : '';
|
||||
const note = multiball(c) ? 'MULTIBALL!' : c.locked ? 'LOCK: FAI IL PONTE SPECIAL' : c.rampRun?.count ? `PONTE ${c.rampRun.count}/3 PER IL LOCK` : save > 0 ? `PALLINA SALVA ${Math.ceil(save)} s` : c.lastRamp ? `L'ALTRO PONTE ENTRO ${Math.ceil(12 - (c.sim - c.lastRamp.at))} s` : c.ball && !c.ball.waiting && c.saved ? 'SENZA RETE' : remaining ? `MANCANO ${remaining.toLocaleString('it-IT')}` : 'TRAGUARDO: +1 PALLINA';
|
||||
if (note) text(ctx, note, 100, 352, 6, save > 0 ? '#9fd27a' : '#ffe08c', 'center');
|
||||
}
|
||||
// La piccola regia del tabellone ai colpi grossi: al traguardo la corona del Regolo che si compone a colonne, al
|
||||
@@ -351,9 +357,9 @@ function shake(c, time) {
|
||||
return k > 0 ? [Math.round(Math.sin(time * 90) * 3 * k), Math.round(Math.cos(time * 70) * 2 * k)] : [0, 0];
|
||||
}
|
||||
// La cometa: le ultime posizioni della pallina, sempre più tenui.
|
||||
function trail(ctx, c) {
|
||||
const n = c.trail.length;
|
||||
c.trail.forEach(([x, y], i) => { ctx.globalAlpha = 0.05 + 0.3 * i / n; ctx.fillStyle = '#e8ecf2'; ctx.beginPath(); ctx.arc(x, y, R * (0.3 + 0.6 * i / n), 0, Math.PI * 2); ctx.fill(); });
|
||||
function trail(ctx, b) {
|
||||
const n = b.trail.length;
|
||||
b.trail.forEach(([x, y], i) => { ctx.globalAlpha = 0.05 + 0.3 * i / n; ctx.fillStyle = '#e8ecf2'; ctx.beginPath(); ctx.arc(x, y, R * (0.3 + 0.6 * i / n), 0, Math.PI * 2); ctx.fill(); });
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
function result(ctx, c) {
|
||||
@@ -393,12 +399,16 @@ export function drawFlipper(ctx, c, def, bg, glass) {
|
||||
for (const r of c.ramps) rampEntrance(ctx, r, time, c.lastRamp?.id);
|
||||
for (const side of ['left', 'right']) flipper(ctx, c, side);
|
||||
plunger(ctx, c);
|
||||
// La pallina (con la cometa) sotto i ponti, se è sulla tavola; sopra, se è su un ponte; poi gli effetti.
|
||||
if (c.ball && !c.ball.waiting) trail(ctx, c);
|
||||
if (!c.ball?.ramp) ball(ctx, c.ball);
|
||||
for (const r of c.ramps) ramp(ctx, r, time, c.lastRamp?.id);
|
||||
if (c.ball?.ramp) ball(ctx, c.ball);
|
||||
// Le palline (con la cometa) sotto i ponti, se sono sulla tavola; sopra, se sono su un ponte; la pallina
|
||||
// trattenuta (LOCK) nella serratura in fondo al suo ponte; poi gli effetti.
|
||||
const balls = activeBalls(c), special = c.special ? c.special + '!' : c.lastRamp?.id;
|
||||
for (const b of balls) if (!b.waiting && !b.ramp) { trail(ctx, b); ball(ctx, b); }
|
||||
for (const b of balls) if (b.waiting) ball(ctx, b);
|
||||
for (const r of c.ramps) ramp(ctx, r, time, special);
|
||||
if (c.locked) lock(ctx, c.locked.ball, time);
|
||||
for (const b of balls) if (b.ramp) { trail(ctx, b); ball(ctx, b); }
|
||||
drawFx(ctx, c, time);
|
||||
if (c.special) { const r = c.ramps.find(k => k.id === c.special); if (Math.sin(time * 8) > 0) label(ctx, 'SPECIAL', (r.mouth[0][0] + r.mouth[1][0]) / 2, r.mouth[0][1] - 38, 6, '#fff6d6'); }
|
||||
// Da fermo, l'invito: la molla da caricare.
|
||||
if (waiting(c) && !c.holding && Math.sin(time * 5) > 0) text(ctx, 'LANCIA', (LANE.x0 + LANE.x1) / 2, 300, 5, '#ffe08c', 'center');
|
||||
board(ctx, c, time, glass);
|
||||
|
||||
Reference in New Issue
Block a user