Flipper: il tabellone cambia illustrazione con la zona che la pallina attraversa (il Regolo coronato, la Fortezza, la Pieve, la Sieve, il Trebbio, i rioni) e il simbolo della zona sul campo (Codex, flipper-art.js); «TIENI PREMUTO QUI» per la molla solo col dito
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
// Illustrazioni vettoriali del backglass, riusate come inserti in lacca sul campo.
|
||||
import { activeBalls } from './flipper.js';
|
||||
|
||||
export const SCENES = {
|
||||
regolo: { color: '#91f5b1', title: 'IL REGOLO SI RISVEGLIA', note: 'IL CUSTODE DELLA SIEVE', at: [320, 193] },
|
||||
fortezza: { color: '#65ddec', title: 'LA FORTEZZA SI ACCENDE', note: 'SOPRA I BASTIONI', at: [320, 119] },
|
||||
trebbio: { color: '#ffc16c', title: 'LA TORRE DEL TREBBIO', note: 'UN PASSAGGIO NELLA STORIA', at: [365, 164] },
|
||||
pieve: { color: '#ffaccc', title: 'LE LUCI DELLA PIEVE', note: 'NEL CUORE DEL PAESE', at: [240, 174] },
|
||||
rioni: { color: '#ffe6a0', title: 'I COLORI DEI RIONI', note: 'QUATTRO COLORI, UN PAESE', at: [320, 88] },
|
||||
sieve: { color: '#6fcfff', title: 'SULLE ONDE DELLA SIEVE', note: 'IL VIAGGIO CONTINUA', at: [320, 280] },
|
||||
cosmo: { color: '#dba5ff', title: 'IL REGOLO DEL FUTURO', note: 'OLTRE IL TEMPO', at: [320, 193] },
|
||||
spira: { color: '#ff729e', title: 'NELLA SPIRA AL NEON', note: 'ENERGIA IN MOVIMENTO', at: [376, 170] },
|
||||
};
|
||||
const RAMP_SCENE = { sinistra: 'fortezza', destra: 'trebbio', super: 'cosmo', orbita: 'cosmo', ponte: 'fortezza', spira: 'spira' };
|
||||
|
||||
// Solo lettura: nessun evento, punteggio o numero casuale della simulazione viene consumato dal disegno.
|
||||
export function flipperScene(c) {
|
||||
const balls = activeBalls(c).filter(b => !b.waiting && b.releaseAt <= c.sim);
|
||||
if (c.regolo.hit > 0 || balls.some(b => !b.ramp && Math.hypot(b.x - c.regolo.x, b.y - c.regolo.y) < 33)) return 'regolo';
|
||||
const ramp = Object.keys(RAMP_SCENE).find(id => balls.some(b => b.ramp?.id === id));
|
||||
if (ramp) return RAMP_SCENE[ramp];
|
||||
if (!balls.length) return c.table === 'future' ? 'cosmo' : 'regolo';
|
||||
if (c.bumpers.some(b => b.hit > 0)) return 'fortezza';
|
||||
// Priorità stabile anche in multiball, indipendente dall'ordine delle palline.
|
||||
if (balls.some(b => b.y < 106)) return c.table === 'future' ? 'cosmo' : 'rioni';
|
||||
if (balls.some(b => b.x < 242 && b.y < 218)) return 'pieve';
|
||||
if (balls.some(b => b.x > 398 && b.x < 432 && b.y < 218)) return 'trebbio';
|
||||
if (balls.some(b => b.y < 180)) return 'fortezza';
|
||||
if (balls.some(b => b.y < 244)) return c.table === 'future' ? 'cosmo' : 'regolo';
|
||||
return 'sieve';
|
||||
}
|
||||
const TAU = Math.PI * 2;
|
||||
function stroke(g, pts, color, width = 1) {
|
||||
g.beginPath(); pts.forEach(([x, y], i) => i ? g.lineTo(x, y) : g.moveTo(x, y));
|
||||
g.strokeStyle = color; g.lineWidth = width; g.stroke();
|
||||
}
|
||||
function fill(g, pts, color) {
|
||||
g.beginPath(); pts.forEach(([x, y], i) => i ? g.lineTo(x, y) : g.moveTo(x, y));
|
||||
g.closePath(); g.fillStyle = color; g.fill();
|
||||
}
|
||||
function circle(g, x, y, r, color) { g.beginPath(); g.arc(x, y, r, 0, TAU); g.fillStyle = color; g.fill(); }
|
||||
|
||||
export function paintRegolo(g, x, y, size, color = '#91f5b1') {
|
||||
g.save(); g.translate(x, y); g.scale(size / 30, size / 30); g.lineCap = 'round'; g.lineJoin = 'round';
|
||||
// Un serpente a spire, con ventre dorato, cresta, occhio e corona: leggibile anche come piccolo emblema.
|
||||
const body = () => {
|
||||
g.beginPath(); g.moveTo(-29, 22);
|
||||
g.bezierCurveTo(-8, 28, 29, 23, 23, 7);
|
||||
g.bezierCurveTo(17, -9, -24, 0, -18, 13);
|
||||
g.bezierCurveTo(-13, 24, 13, 12, 9, 0);
|
||||
g.bezierCurveTo(5, -11, -9, -9, -4, -23);
|
||||
};
|
||||
body(); g.strokeStyle = '#061d26'; g.lineWidth = 12; g.stroke();
|
||||
body(); g.strokeStyle = color; g.lineWidth = 8; g.stroke();
|
||||
body(); g.strokeStyle = '#153f43'; g.lineWidth = 4.6; g.stroke();
|
||||
g.save(); g.translate(0, 2); body(); g.strokeStyle = '#ffe5a6'; g.lineWidth = 1.1; g.stroke(); g.restore();
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const a = i * 0.36;
|
||||
stroke(g, [[-16 + i * 5, 20 - Math.sin(a) * 3], [-15 + i * 5, 17 - Math.sin(a) * 3]], color, 1);
|
||||
}
|
||||
fill(g, [[-9, -26], [-4, -31], [5, -29], [12, -23], [9, -18], [0, -18], [-5, -14]], color);
|
||||
stroke(g, [[3, -18], [11, -19], [15, -17], [18, -19]], '#ff92a7', 1);
|
||||
circle(g, 4, -25, 2.1, '#071821'); circle(g, 4.5, -25.6, 0.8, '#ffffff');
|
||||
fill(g, [[-8, -33], [-11, -43], [-4, -38], [0, -47], [5, -38], [12, -43], [8, -33]], '#ffdc88');
|
||||
stroke(g, [[-8, -32], [8, -32]], '#fff5d5', 1.6); circle(g, 0, -38, 1.4, '#ff729e');
|
||||
g.restore();
|
||||
}
|
||||
|
||||
export function paintZoneSymbol(g, id) {
|
||||
const { color, at: [x, y] } = SCENES[id];
|
||||
g.save(); g.translate(x, y); g.lineJoin = 'round';
|
||||
if (id === 'fortezza' || id === 'trebbio' || id === 'pieve') {
|
||||
const roof = id === 'pieve' ? [[-12, -2], [0, -13], [12, -2]]
|
||||
: id === 'trebbio' ? [[-7, 8], [-7, -14], [-3, -14], [-3, -10], [3, -10], [3, -14], [7, -14], [7, 8]]
|
||||
: [[-14, 7], [-14, -7], [-9, -7], [-9, -3], [-4, -3], [-4, -10], [4, -10], [4, -3], [9, -3], [9, -7], [14, -7], [14, 7]];
|
||||
stroke(g, roof, color, 1.3);
|
||||
stroke(g, [[-10, 0], [-10, 9], [10, 9], [10, 0]], color, 0.8);
|
||||
stroke(g, [[-3, 8], [-3, 2], [0, -1], [3, 2], [3, 8]], '#fff1c1', 0.8);
|
||||
} else if (id === 'rioni') {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const px = -15 + i * 10;
|
||||
fill(g, [[px, -10], [px + 7, -10], [px + 7, 3], [px + 3.5, 7], [px, 3]], ['#f97980', '#f7eac9', '#78e2ad', '#7bb6ff'][i]);
|
||||
}
|
||||
} else if (id === 'sieve') {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
g.beginPath(); g.moveTo(-19, -6 + i * 6); g.bezierCurveTo(-8, -14 + i * 6, 8, 2 + i * 6, 19, -6 + i * 6);
|
||||
g.strokeStyle = color; g.lineWidth = 1.2; g.stroke();
|
||||
}
|
||||
} else if (id === 'spira') {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
g.beginPath(); g.ellipse(0, 0, 6 + i * 5, 4 + i * 4, -0.5, 0, TAU);
|
||||
g.strokeStyle = color; g.lineWidth = 0.9; g.stroke();
|
||||
}
|
||||
}
|
||||
g.restore();
|
||||
}
|
||||
|
||||
function fortress(g, tower, color) {
|
||||
fill(g, [[18, 69], [18, 43], [28, 39], [28, 32], [35, 32], [35, 41], [51, 40], [51, 29], [61, 29], [61, 39], [87, 37], [87, 26], [97, 26], [97, 36], [119, 34], [119, 29], [129, 29], [129, 65]], '#163648');
|
||||
stroke(g, [[18, 43], [35, 41], [51, 40], [61, 39], [87, 37], [97, 36], [129, 34]], color, 1.5);
|
||||
if (tower) {
|
||||
fill(g, [[61, 63], [61, 14], [66, 14], [66, 19], [73, 19], [73, 12], [79, 12], [79, 19], [86, 19], [86, 14], [91, 14], [91, 63]], '#365062');
|
||||
stroke(g, [[61, 63], [61, 14], [66, 14]], color, 1.3);
|
||||
for (const y of [27, 40, 53]) { g.fillStyle = '#ffd590'; g.fillRect(73, y, 5, 7); }
|
||||
}
|
||||
for (let y = 46; y < 67; y += 7) stroke(g, [[22, y], [126, y - 6]], '#426076', 0.5);
|
||||
for (const x of [33, 49, 101, 117]) { g.fillStyle = color; g.fillRect(x, 47, 2, 6); }
|
||||
fill(g, [[67, 70], [67, 55], [73, 50], [80, 55], [80, 70]], '#071927');
|
||||
stroke(g, [[14, 72], [74, 65], [135, 70]], color, 1);
|
||||
}
|
||||
|
||||
// Una scena di 150 × 88, interamente in cache nel renderer.
|
||||
export function paintFlipperScene(g, id) {
|
||||
const color = SCENES[id].color;
|
||||
const sky = g.createLinearGradient(0, 0, 150, 88);
|
||||
sky.addColorStop(0, '#071125'); sky.addColorStop(0.6, id === 'trebbio' ? '#38273b' : '#15364a'); sky.addColorStop(1, '#071925');
|
||||
g.fillStyle = sky; g.fillRect(0, 0, 150, 88);
|
||||
circle(g, 120, 19, 13, color + '18'); circle(g, 120, 19, 8, '#fff0be'); circle(g, 117, 16, 7, '#153044');
|
||||
for (let i = 0; i < 20; i++) circle(g, (i * 47 + 13) % 150, (i * 23 + 9) % 57, i % 3 ? 0.5 : 0.9, color + 'aa');
|
||||
fill(g, [[0, 59], [21, 37], [44, 53], [77, 27], [108, 51], [139, 33], [150, 40], [150, 88], [0, 88]], '#142b3c');
|
||||
fill(g, [[0, 74], [39, 53], [67, 64], [99, 44], [150, 62], [150, 88], [0, 88]], '#1f4350');
|
||||
for (let i = 0; i < 5; i++) {
|
||||
g.beginPath(); g.moveTo(0, 74 + i * 3); g.bezierCurveTo(52, 58 + i * 3, 78, 91 + i * 2, 150, 67 + i * 4);
|
||||
g.strokeStyle = i % 2 ? '#69b6ce66' : '#102837'; g.lineWidth = 1; g.stroke();
|
||||
}
|
||||
if (id === 'fortezza' || id === 'trebbio') fortress(g, id === 'trebbio', color);
|
||||
else if (id === 'pieve') {
|
||||
fill(g, [[30, 69], [30, 43], [59, 25], [87, 43], [87, 69]], '#406077');
|
||||
fill(g, [[87, 68], [87, 23], [94, 13], [101, 23], [101, 68]], '#29495e');
|
||||
stroke(g, [[26, 44], [59, 22], [90, 44]], color, 2);
|
||||
circle(g, 59, 43, 7, '#142a3e'); circle(g, 59, 43, 4, '#ffc8de');
|
||||
fill(g, [[52, 69], [52, 58], [59, 53], [66, 58], [66, 69]], '#102334');
|
||||
g.fillStyle = '#ffdfa2'; g.fillRect(91, 28, 6, 9);
|
||||
} else if (id === 'rioni') {
|
||||
['#f97980', '#f7eac9', '#78e2ad', '#7bb6ff'].forEach((c, i) => {
|
||||
const x = 24 + i * 29, y = 23 + (i % 2) * 5;
|
||||
stroke(g, [[x, y - 4], [x, 69]], '#d5c7a1', 1.2);
|
||||
fill(g, [[x + 2, y], [x + 23, y + 3], [x + 23, y + 30], [x + 12, y + 38], [x + 2, y + 30]], c);
|
||||
stroke(g, [[x + 12, y + 7], [x + 12, y + 27]], '#213347', 2);
|
||||
stroke(g, [[x + 6, y + 17], [x + 18, y + 17]], '#213347', 2);
|
||||
});
|
||||
} else if (id === 'sieve') {
|
||||
g.beginPath(); g.moveTo(10, 65); g.quadraticCurveTo(74, 9, 140, 65);
|
||||
g.strokeStyle = '#0a1b2c'; g.lineWidth = 10; g.stroke(); g.strokeStyle = color; g.lineWidth = 2; g.stroke();
|
||||
for (const x of [28, 49, 74, 99, 122]) stroke(g, [[x, 49 - Math.sin(x / 150 * Math.PI) * 10], [x, 73]], '#5f8b9f', 3);
|
||||
paintRegolo(g, 77, 70, 12, '#91f5b1');
|
||||
} else {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
g.beginPath(); g.ellipse(75, 44, 28 + i * 12, 23 + i * 8, -0.4 + i * 0.3, 0, TAU);
|
||||
g.strokeStyle = color + (i ? '55' : 'aa'); g.lineWidth = 0.8; g.stroke();
|
||||
}
|
||||
paintRegolo(g, 75, 51, id === 'spira' ? 26 : 30, color);
|
||||
}
|
||||
// Riflessi e retino del vetro inciso.
|
||||
for (let y = 2; y < 88; y += 3) stroke(g, [[0, y], [150, y]], '#050f1c22', 0.5);
|
||||
}
|
||||
|
||||
export function paintPlayfieldArt(g, future = false) {
|
||||
const color = future ? '#ad8cff' : '#74c5bd';
|
||||
g.save();
|
||||
// Rosone centrale, smalti e serpente coronato sotto il piano di gioco.
|
||||
for (const r of [30, 33, 43]) {
|
||||
g.beginPath(); g.ellipse(320, 194, r, r * 0.83, 0, 0, TAU);
|
||||
g.strokeStyle = color + '55'; g.lineWidth = r === 33 ? 1.4 : 0.6; g.stroke();
|
||||
}
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const a = i / 16 * TAU;
|
||||
stroke(g, [[320 + Math.cos(a) * 37, 194 + Math.sin(a) * 31], [320 + Math.cos(a) * 41, 194 + Math.sin(a) * 34]], '#d6be7855', 0.8);
|
||||
}
|
||||
g.globalAlpha = 0.46; paintRegolo(g, 320, 202, 25, color); g.globalAlpha = 1;
|
||||
for (const [x, side, tint] of [[239, -1, '#65ddec'], [402, 1, '#ffc16c']]) {
|
||||
stroke(g, [[x, 217], [x - side * 10, 230], [x - side * 10, 242]], tint + '66', 1);
|
||||
for (let i = 0; i < 3; i++) fill(g, [[x, 209 + i * 8], [x + 2, 212 + i * 8], [x, 215 + i * 8], [x - 2, 212 + i * 8]], tint + '88');
|
||||
}
|
||||
// Filetti dorati delle sponde e frecce smaltate verso le palette.
|
||||
for (const side of [-1, 1]) {
|
||||
const x = 320 + side * 67;
|
||||
stroke(g, [[x, 263], [x - side * 14, 279], [x - side * 22, 300]], '#e7c88955', 0.8);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const cx = x - side * i * 7, y = 274 + i * 10;
|
||||
stroke(g, [[cx - 3, y - 2], [cx, y + 1], [cx + 3, y - 2]], color + '77', 1);
|
||||
}
|
||||
}
|
||||
g.restore();
|
||||
}
|
||||
Reference in New Issue
Block a user