Il Boccino (Gabriele Chiesi, consenso del 31 ottobre) corre il GP di Fortezza col suo NRG bianco e rosso numero 1 (motorino e ritratto di Codex dalle sue foto) (Andrea)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
+116
-37
@@ -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, flipperSeg, alongRamp, rampHeight, waiting, canThrow, saveLeft } from './flipper.js';
|
||||
import { FIELD, LANE, R, SCORE, flipperSeg, alongRamp, rampHeight, waiting, canThrow, saveLeft } from './flipper.js';
|
||||
import { ambientHash as hash } from './ambient.js';
|
||||
import { text, panel } from './bocce-render.js';
|
||||
|
||||
@@ -16,47 +16,99 @@ const rail = (ctx, [ax, ay], [bx, by], dark = '#2a2622', light = '#c9c3b6') => {
|
||||
};
|
||||
function disc(ctx, x, y, r, fill, rim) {
|
||||
ctx.fillStyle = rim; ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.fillStyle = fill; ctx.beginPath(); ctx.arc(x, y, r - 2, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.fillStyle = fill; ctx.beginPath(); ctx.arc(x, y, r - Math.min(2, r * 0.45), 0, Math.PI * 2); ctx.fill();
|
||||
}
|
||||
function bumper(ctx, b) {
|
||||
const r = b.r * (b.hit > 0 ? 1 + b.hit * 1.2 : 1);
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.25)'; ctx.beginPath(); ctx.arc(b.x + 2, b.y + 3, r, 0, Math.PI * 2); ctx.fill();
|
||||
disc(ctx, b.x, b.y, r, b.hit > 0 ? '#ffe9a8' : '#d9463a', '#5a1d16');
|
||||
disc(ctx, b.x, b.y, r * 0.55, b.hit > 0 ? '#fff6d8' : '#f0a24a', '#8a3a1e');
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.55)'; ctx.fillRect(Math.round(b.x - r * 0.4), Math.round(b.y - r * 0.6), 3, 2);
|
||||
const r = b.r, lit = b.hit > 0;
|
||||
disc(ctx, b.x + 1, b.y + 3, r + 1, '#172127', '#131a1e');
|
||||
const metal = ctx.createLinearGradient(b.x - r, b.y - r, b.x + r, b.y + r);
|
||||
metal.addColorStop(0, '#fff3cc'); metal.addColorStop(0.35, '#bda16d');
|
||||
metal.addColorStop(0.55, '#fff1cc'); metal.addColorStop(1, '#665238');
|
||||
disc(ctx, b.x, b.y, r, metal, '#111d25');
|
||||
disc(ctx, b.x, b.y, r - 2.5, lit ? '#fff3bd' : '#e3b658', '#654223');
|
||||
const cap = ctx.createRadialGradient(b.x - 3, b.y - 4, 1, b.x, b.y, r - 4);
|
||||
cap.addColorStop(0, lit ? '#fffef0' : '#e87c61'); cap.addColorStop(0.45, lit ? '#ffe39a' : '#ba4037'); cap.addColorStop(1, '#612b2b');
|
||||
disc(ctx, b.x, b.y, r - 4, cap, '#602c27');
|
||||
label(ctx, `${SCORE.bumper / 1000}K`, b.x, b.y + 0.5, 5, '#fff2d5');
|
||||
ctx.strokeStyle = '#ffe6ae'; ctx.lineWidth = 0.8;
|
||||
ctx.beginPath(); ctx.arc(b.x, b.y, r - 1, Math.PI * 1.08, Math.PI * 1.65); ctx.stroke();
|
||||
}
|
||||
// Incisioni con un carattere compatto, leggibile alla risoluzione della scena.
|
||||
function label(ctx, line, x, y, size = 6, color = '#f8e6bc', align = 'center') {
|
||||
ctx.font = `bold ${size}px monospace`; ctx.textAlign = align; ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = color; ctx.fillText(line, x, y);
|
||||
}
|
||||
function polygon(ctx, points, fill, stroke) {
|
||||
ctx.beginPath(); points.forEach(([x, y], i) => i ? ctx.lineTo(x, y) : ctx.moveTo(x, y)); ctx.closePath();
|
||||
ctx.fillStyle = fill; ctx.fill();
|
||||
if (stroke) { ctx.strokeStyle = stroke; ctx.lineWidth = 1; ctx.stroke(); }
|
||||
}
|
||||
function screw(ctx, x, y) {
|
||||
disc(ctx, x, y, 1.7, '#d1d9d5', '#25333a');
|
||||
ctx.strokeStyle = '#53636b'; ctx.lineWidth = 0.6; ctx.beginPath(); ctx.moveTo(x - 0.7, y + 0.7); ctx.lineTo(x + 0.7, y - 0.7); ctx.stroke();
|
||||
}
|
||||
// Il Regolo: un bersaglio tondo d'oro con la corona e il bordo scuro, che non si confonde col drago dipinto.
|
||||
function regolo(ctx, o) {
|
||||
const r = o.r + (o.hit > 0 ? 2 : 0);
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.3)'; ctx.beginPath(); ctx.arc(o.x + 1, o.y + 2, r + 1, 0, Math.PI * 2); ctx.fill();
|
||||
disc(ctx, o.x, o.y, r + 1, o.hit > 0 ? '#fff6c8' : '#f0c040', '#3a2a08');
|
||||
ctx.fillStyle = '#3fa34d'; ctx.beginPath(); ctx.arc(o.x, o.y, r - 3, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.fillStyle = '#ffd45a';
|
||||
for (const [dx, h] of [[-4, 4], [-1, 6], [2, 4]]) ctx.fillRect(o.x + dx, o.y - r - 2 - h, 2, h);
|
||||
ctx.fillRect(o.x - 5, o.y - r - 2, 10, 2);
|
||||
const gem = ctx.createRadialGradient(o.x - 2, o.y - 2, 0, o.x, o.y, r - 1);
|
||||
gem.addColorStop(0, '#bceca0'); gem.addColorStop(0.35, '#3f9c77'); gem.addColorStop(1, '#153f42');
|
||||
disc(ctx, o.x, o.y, r - 1.5, gem, '#65522c');
|
||||
polygon(ctx, [[o.x - 5, o.y - r - 1], [o.x - 6, o.y - r - 6], [o.x - 2, o.y - r - 4], [o.x, o.y - r - 8], [o.x + 2, o.y - r - 4], [o.x + 6, o.y - r - 6], [o.x + 5, o.y - r - 1]], '#edc877', '#65522c');
|
||||
label(ctx, `${SCORE.regolo / 1000}K`, o.x, o.y + 0.5, 5, '#fff4c8');
|
||||
}
|
||||
// I ponti: una striscia di pietra lungo il percorso (il sinistro caldo, il destro freddo, per seguirli), più alta
|
||||
// in cima (la quota), con l'ombra, il bordo scuro e la luce; accesi, brillano; l'altro, in attesa della combo, invita.
|
||||
// I ponti: canaline metalliche, ambra a sinistra e azzurre a destra, sollevate secondo la quota della pallina.
|
||||
// Accesi, brillano; l'altro, in attesa della combo, invita.
|
||||
// Si dipingono una volta per stato su una tela a parte e poi si copiano (i tratti larghi e trasparenti a ogni
|
||||
// fotogramma lasciavano alla grafica un arretrato di secondi, vedi effetti-leggeri).
|
||||
const RAMP_LOOK = { sinistra: ['#c9b89a', '#8a7a5a'], destra: ['#b8c0c9', '#6a7a8a'] };
|
||||
const RAMP_LOOK = { sinistra: ['#ffd57a', '#95713d'], destra: ['#9ce9f2', '#497d92'] };
|
||||
function rampPoint(r, s) { const p = alongRamp(r, s); return { ...p, y: p.y - 2 - 4 * rampHeight(r, s) }; }
|
||||
function paintRamp(ctx, r, state) {
|
||||
const path = () => { ctx.beginPath(); for (let s = 0; s <= r.len; s += 4) { const p = rampPoint(r, Math.min(s, r.len)); s ? ctx.lineTo(p.x, p.y) : ctx.moveTo(p.x, p.y); } };
|
||||
const [light, dark] = RAMP_LOOK[r.id];
|
||||
const [light, dark] = RAMP_LOOK[r.id], active = state !== 'base';
|
||||
// Il fondo è una canalina scura; i due parapetti sono fili cromati separati.
|
||||
// Si segue esattamente il percorso della fisica, anche ai cambi di direzione.
|
||||
const points = r.cum.map(s => rampPoint(r, s));
|
||||
const path = () => { ctx.beginPath(); points.forEach((p, i) => i ? ctx.lineTo(p.x, p.y) : ctx.moveTo(p.x, p.y)); };
|
||||
const stroke = (width, color) => { ctx.lineWidth = width; ctx.strokeStyle = color; path(); ctx.stroke(); };
|
||||
ctx.lineCap = 'round'; ctx.lineJoin = 'round';
|
||||
ctx.globalAlpha = 0.45; ctx.strokeStyle = '#000'; ctx.lineWidth = 12; ctx.beginPath(); r.path.forEach(([x, y], i) => i ? ctx.lineTo(x, y + 4) : ctx.moveTo(x, y + 4)); ctx.stroke();
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.strokeStyle = state === 'lit' ? '#ffd45a' : state === 'invite' ? '#ffe9a8' : '#2a2420'; ctx.lineWidth = 12; path(); ctx.stroke();
|
||||
ctx.strokeStyle = dark; ctx.lineWidth = 9; path(); ctx.stroke();
|
||||
ctx.strokeStyle = light; ctx.lineWidth = 6; path(); ctx.stroke();
|
||||
// Le pietre: trattini scuri a passo regolare lungo il ponte.
|
||||
ctx.strokeStyle = 'rgba(60,50,40,0.45)'; ctx.lineWidth = 1;
|
||||
for (let s = 6; s < r.len; s += 9) { const p = rampPoint(r, s); ctx.beginPath(); ctx.moveTo(p.x - p.dy * 3, p.y + p.dx * 3); ctx.lineTo(p.x + p.dy * 3, p.y - p.dx * 3); ctx.stroke(); }
|
||||
// L'imbocco: la rampetta che sale, con la freccia.
|
||||
const [[x0, y], [x1]] = r.mouth;
|
||||
ctx.fillStyle = '#6a5a4a'; ctx.fillRect(x0, y - 2, x1 - x0, 6);
|
||||
ctx.fillStyle = state === 'invite' ? '#fff6c8' : '#ffe08c'; ctx.beginPath(); ctx.moveTo((x0 + x1) / 2, y - 1); ctx.lineTo((x0 + x1) / 2 - 3, y + 3); ctx.lineTo((x0 + x1) / 2 + 3, y + 3); ctx.closePath(); ctx.fill();
|
||||
ctx.save(); ctx.translate(2, 4); stroke(17, 'rgba(3,12,19,0.48)'); ctx.restore();
|
||||
stroke(16, '#12212a'); stroke(14, active ? light : dark);
|
||||
stroke(12, '#e0e4d6'); stroke(10, '#32454e'); stroke(7, '#192e39');
|
||||
stroke(2, r.id === 'sinistra' ? '#4e4940' : '#284957');
|
||||
// Traverse distanziate e piccoli inserti luminosi sui parapetti.
|
||||
for (let s = 18; s < r.len - 8; s += 26) {
|
||||
const p = rampPoint(r, s);
|
||||
ctx.strokeStyle = '#62727a'; ctx.lineWidth = 0.7;
|
||||
ctx.beginPath(); ctx.moveTo(p.x - p.dy * 4, p.y + p.dx * 4); ctx.lineTo(p.x + p.dy * 4, p.y - p.dx * 4); ctx.stroke();
|
||||
for (const side of [-1, 1]) {
|
||||
ctx.fillStyle = active ? '#fff6d8' : light;
|
||||
ctx.beginPath(); ctx.arc(p.x + p.dy * 6.5 * side, p.y - p.dx * 6.5 * side, 1, 0, Math.PI * 2); ctx.fill();
|
||||
}
|
||||
}
|
||||
// L'apertura coincide con il segmento d'ingresso: una pedana svasata e una soglia a terra.
|
||||
const [[x0, y], [x1]] = r.mouth, cx = (x0 + x1) / 2;
|
||||
const deck = ctx.createLinearGradient(0, y - 30, 0, y);
|
||||
deck.addColorStop(0, '#1b303b'); deck.addColorStop(0.75, '#455860'); deck.addColorStop(1, '#b8c3bd');
|
||||
polygon(ctx, [[cx - 5, y - 30], [cx + 5, y - 30], [x1, y], [x0, y]], deck, '#18232b');
|
||||
for (const side of [-1, 1]) {
|
||||
rail(ctx, [cx + side * 6, y - 30], [cx + side * (x1 - x0) / 2, y], '#18232b', light);
|
||||
screw(ctx, cx + side * (x1 - x0) / 2, y);
|
||||
}
|
||||
ctx.fillStyle = '#edf0d9'; ctx.fillRect(x0 + 1, y - 1, x1 - x0 - 2, 1);
|
||||
label(ctx, `${SCORE.ramp / 1000}K`, cx, y - 18, 5, light);
|
||||
}
|
||||
function rampEntrance(ctx, r, time, who) {
|
||||
const [[x0, y], [x1]] = r.mouth, x = (x0 + x1) / 2;
|
||||
const color = RAMP_LOOK[r.id][0], invite = who && who !== r.id;
|
||||
// Un inserto scuro isola le frecce dai dettagli del ponte dipinto sotto.
|
||||
polygon(ctx, [[x - 6, y + 1], [x + 6, y + 1], [x + 7, y + 22], [x - 7, y + 22]], '#10222e', color);
|
||||
// Una freccia piena, leggibile anche sul telefono; pulsa più rapida per la combo.
|
||||
const on = Math.sin(time * (invite ? 9 : 4)) > 0;
|
||||
polygon(ctx, [[x, y + 4], [x + 5, y + 11], [x + 2, y + 11], [x + 2, y + 20], [x - 2, y + 20], [x - 2, y + 11], [x - 5, y + 11]], on ? '#fff6d6' : color);
|
||||
ctx.fillStyle = '#112530'; ctx.fillRect(x - 14, y + 22, 28, 10);
|
||||
ctx.strokeStyle = color; ctx.lineWidth = 0.7; ctx.strokeRect(x - 14, y + 22, 28, 10);
|
||||
label(ctx, 'RAMPA', x, y + 27, 6, color);
|
||||
}
|
||||
const rampCache = new Map();
|
||||
function ramp(ctx, r, time, who) {
|
||||
@@ -73,19 +125,34 @@ function ramp(ctx, r, time, who) {
|
||||
}
|
||||
let railLayer = null;
|
||||
function rails(ctx, c) {
|
||||
const paint = g => { for (const w of c.walls) rail(g, w.a, w.b, w.sling ? '#5a1d16' : '#2a2622', w.sling ? '#e8584a' : '#c9c3b6'); };
|
||||
const paint = g => {
|
||||
// Plastiche smaltate sopra gli elastici, con bordi e viti di fissaggio.
|
||||
for (const side of [-1, 1]) {
|
||||
const x = side < 0 ? 236 : 404, tip = x - side * 26;
|
||||
polygon(g, [[x, 246], [x, 284], [tip, 290]], '#152f3c', '#c1a273');
|
||||
polygon(g, [[x - side * 4, 260], [x - side * 4, 280], [tip + side * 7, 284]], '#294c5b', '#597c83');
|
||||
screw(g, x, 250); screw(g, x, 281); screw(g, tip + side * 4, 286);
|
||||
}
|
||||
for (const w of c.walls) rail(g, w.a, w.b, w.sling ? '#582723' : '#18262d', w.sling ? '#f09a70' : '#d2d9cb');
|
||||
};
|
||||
if (typeof document === 'undefined') { paint(ctx); return; }
|
||||
if (!railLayer) { railLayer = Object.assign(document.createElement('canvas'), { width: 640, height: 360 }); paint(railLayer.getContext('2d')); }
|
||||
ctx.drawImage(railLayer, 0, 0);
|
||||
}
|
||||
function flipper(ctx, c, side) {
|
||||
const f = c.flippers[side], { a: [px, py], b: [tx, ty] } = flipperSeg(c, side);
|
||||
ctx.lineCap = 'round';
|
||||
ctx.strokeStyle = 'rgba(0,0,0,0.3)'; ctx.lineWidth = 7; ctx.beginPath(); ctx.moveTo(px + 1, py + 3); ctx.lineTo(tx + 1, ty + 3); ctx.stroke();
|
||||
ctx.strokeStyle = '#3a2a1a'; ctx.lineWidth = 7; ctx.beginPath(); ctx.moveTo(px, py); ctx.lineTo(tx, ty); ctx.stroke();
|
||||
ctx.strokeStyle = f.held.length ? '#ffd45a' : '#e8b640'; ctx.lineWidth = 5; ctx.beginPath(); ctx.moveTo(px, py); ctx.lineTo(tx, ty); ctx.stroke();
|
||||
ctx.strokeStyle = '#fff1b8'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(px, py - 1.5); ctx.lineTo(tx, ty - 1.5); ctx.stroke();
|
||||
disc(ctx, px, py, 4, '#8a7a6a', '#2a2622');
|
||||
ctx.save(); ctx.translate(px, py); ctx.rotate(Math.atan2(ty - py, tx - px));
|
||||
const len = Math.hypot(tx - px, ty - py);
|
||||
const shape = () => {
|
||||
ctx.beginPath(); ctx.moveTo(0, -3); ctx.lineTo(len, -2.5);
|
||||
ctx.quadraticCurveTo(len + 3, 0, len, 2.5); ctx.lineTo(0, 3);
|
||||
ctx.arc(0, 0, 3, Math.PI / 2, Math.PI * 1.5); ctx.closePath();
|
||||
};
|
||||
ctx.save(); ctx.translate(1, 3); shape(); ctx.fillStyle = '#142029'; ctx.fill(); ctx.restore();
|
||||
shape(); ctx.fillStyle = f.held.length ? '#fff6da' : '#f0e4c6'; ctx.fill();
|
||||
ctx.strokeStyle = '#bb503c'; ctx.lineWidth = 1.5; ctx.stroke();
|
||||
ctx.strokeStyle = '#fffdf0'; ctx.lineWidth = 0.8; ctx.beginPath(); ctx.moveTo(3, -1.7); ctx.lineTo(len - 2, -1.2); ctx.stroke();
|
||||
screw(ctx, 0, 0); ctx.restore();
|
||||
}
|
||||
function plunger(ctx, c) {
|
||||
const x = (LANE.x0 + LANE.x1) / 2, top = 326, pull = c.holding ? c.power * 14 : canThrow(c) ? c.power * 6 : 0;
|
||||
@@ -194,6 +261,13 @@ 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.
|
||||
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);
|
||||
// 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.
|
||||
@@ -294,10 +368,12 @@ export function drawFlipper(ctx, c, def, bg, glass) {
|
||||
if (sx || sy) { ctx.fillStyle = '#0c0806'; ctx.fillRect(0, 0, 640, 360); ctx.translate(sx, sy); }
|
||||
if (bg) ctx.drawImage(bg, 0, 0, 640, 360);
|
||||
else { ctx.fillStyle = '#17110c'; ctx.fillRect(0, 0, 640, 360); ctx.fillStyle = '#e9d9b4'; ctx.fillRect(FIELD.x0, FIELD.y0, FIELD.x1 - FIELD.x0, FIELD.y1 - FIELD.y0); }
|
||||
// La stampa resta visibile, ma le parti giocabili emergono dal paesaggio.
|
||||
ctx.fillStyle = 'rgba(8,25,34,0.22)'; ctx.fillRect(201, 73, 230, 261);
|
||||
// Le luci delle corsie dei rioni, sulla riga che conta, sotto le bandiere.
|
||||
for (const l of c.lanes) {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.35)'; ctx.fillRect(l.x - 7, l.y - 3, 14, 7);
|
||||
disc(ctx, l.x, l.y, 6, l.lit ? COLORS[l.rione] : '#2a2622', l.lit ? '#fff4c8' : '#6a6050');
|
||||
disc(ctx, l.x, l.y, 6, l.lit ? COLORS[l.rione] : '#273a3e', l.lit ? '#fff4c8' : '#b5a278');
|
||||
disc(ctx, l.x, l.y, 3.5, l.lit ? '#fff2c6' : COLORS[l.rione], '#182c32');
|
||||
}
|
||||
// Le sponde: tutte le pareti della fisica, cromate; le elastiche con la gomma rossa. Non cambiano mai: dipinte
|
||||
// una volta su una tela a parte.
|
||||
@@ -309,9 +385,12 @@ export function drawFlipper(ctx, c, def, bg, glass) {
|
||||
ctx.fillStyle = '#2a2622'; ctx.fillRect(x - 3, y0 - 1, 6, y1 - y0 + 2);
|
||||
ctx.fillStyle = t.bank === 'pieve' ? '#f2eee2' : '#e8b640'; ctx.fillRect(x - 2, y0, 4, y1 - y0);
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.25)'; ctx.fillRect(x - 2, y1 - 3, 4, 3);
|
||||
ctx.fillStyle = '#fff8d7'; ctx.fillRect(x - 2, y0, 4, 1);
|
||||
ctx.fillStyle = '#3a5864'; ctx.fillRect(x - 1, y0 + 3, 2, 4);
|
||||
}
|
||||
for (const b of c.bumpers) bumper(ctx, b);
|
||||
regolo(ctx, c.regolo);
|
||||
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.
|
||||
|
||||
@@ -15,7 +15,7 @@ export const PIVOT = { left: [280, 314], right: [360, 314] };
|
||||
export const SCORE = { bumper: 1000, sling: 500, lane: 2500, palio: 30000, target: 5000, bank: 25000, regolo: 10000, ramp: 15000, ponti: 50000 };
|
||||
// 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: 300, slope: 260, drag: 30, combo: 12, blend: 30 };
|
||||
export const RAMP = { speed: 240, slope: 155, drag: 20, combo: 12, blend: 30 };
|
||||
// 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
|
||||
@@ -44,8 +44,9 @@ export function layout() {
|
||||
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
|
||||
// po' lungo il flipper: finendo sul perno la pallina ci restava incastrata).
|
||||
walls.push(seg([222, 240], [222, 290]), seg([222, 290], [292, 314], { e: 0.3 }));
|
||||
walls.push(seg([418, 240], [418, 290]), seg([418, 290], [348, 314], { e: 0.3 }));
|
||||
// Le uscite lasciano solo 14/12 pixel; lo spazio recuperato allarga le corsie di ritorno.
|
||||
walls.push(seg([214, 240], [214, 290]), seg([214, 290], [292, 314], { e: 0.3 }));
|
||||
walls.push(seg([420, 240], [420, 290]), seg([420, 290], [348, 314], { e: 0.3 }));
|
||||
// Le sponde elastiche sopra i flipper: il lato lungo spinge.
|
||||
walls.push(seg([236, 246], [236, 284]), seg([236, 284], [262, 290]), seg([236, 246], [262, 290], { sling: true }));
|
||||
walls.push(seg([404, 246], [404, 284]), seg([404, 284], [378, 290]), seg([404, 246], [378, 290], { sling: true }));
|
||||
@@ -63,8 +64,9 @@ export function layout() {
|
||||
// fondo al percorso, dall'altra parte, dritta nella corsia di ritorno verso il flipper. Si incrociano una volta sola,
|
||||
// in cima, come due ponti.
|
||||
const ramps = [
|
||||
{ id: 'sinistra', mouth: [[236, 236], [258, 236]], path: [[247, 236], [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: [[382, 236], [404, 236]], path: [[393, 236], [393, 150], [390, 96], [378, 52], [350, 30], [318, 34], [288, 52], [262, 84], [246, 116], [229, 128], [229, 142]], lit: 0, touching: false },
|
||||
// Imbocchi verso il centro, davanti alle sponde elastiche: 33 pixel utili alla pallina anziché 13.
|
||||
{ id: 'sinistra', mouth: [[252, 226], [294, 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: [[346, 226], [388, 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 },
|
||||
];
|
||||
// Lunghezze, e la cima (il punto più alto del disegno): fin lì si sale, poi si scende.
|
||||
for (const r of ramps) {
|
||||
|
||||
@@ -23,6 +23,9 @@ export const piloti = [
|
||||
casco: 'assets/closeups/gp/casco-tozzino.png', helmet: '#131c3c', stripe: '#f4f4f4' },
|
||||
// Yuri de' Motori (Juri Lascialfari, consenso del 28 ottobre) col suo Yamaha Spy bianco e rosso, il «Mazinga Spy» (foto di Andrea).
|
||||
{ id: 'yuri', name: 'Yuri de\' Motori', bike: 'il Mazinga Spy', motore: 3, testa: 5, color: '#f0f0ee', trim: '#d8302a', moto: 'assets/closeups/gp/spy.png', face: 'assets/closeups/gp/yuri.png', consenso: '2026-10-28' },
|
||||
// Il Boccino (Gabriele Chiesi, consenso del 31 ottobre) col suo Piaggio NRG bianco e rosso col numero 1 (foto di
|
||||
// Andrea); il ritratto di Codex dalla sua foto. [DA ANDREA: motore e testa]
|
||||
{ id: 'boccino', name: 'Il Boccino', bike: 'l\'NRG numero 1', motore: 3, testa: 3, color: '#f4f4f2', trim: '#d8302a', helmet: '#d8302a', stripe: '#f4f4f2', moto: 'assets/closeups/gp/nrg.png', face: 'assets/closeups/gp/boccino.png', consenso: '2026-10-31' },
|
||||
// Il Gianno (il Giannini, consenso del 28 ottobre) col suo Piaggio Typhoon rosso e nero (foto di Andrea).
|
||||
{ id: 'gianno', name: 'Il Gianno', bike: 'il Typhoon', motore: 3, testa: 3, color: '#9e1b22', trim: '#151515', moto: 'assets/closeups/gp/typhoon.png', face: 'assets/closeups/gp/gianno.png', consenso: '2026-10-28' },
|
||||
// La Brauda (Giody Brunetti, consenso del 28 ottobre) col suo Aprilia Rally nero (foto di Andrea). [DA ANDREA: motore e testa]
|
||||
|
||||
Reference in New Issue
Block a user