Il flipper del River: la tavola di Codex con San Piero dipinto (Fortezza, Pieve, torre, la Sieve fra i flipper, il Regolo con la corona, le bandiere dei rioni) e sopra le parti meccaniche del gioco: molla, arco, corsie dei rioni (tutte e quattro: PALIO), funghi, bersagli a caduta, Regolo, sponde elastiche, due flipper; fisica a passo fisso, tre palline, tre livelli (Andrea)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
// Il flipper del River in primo piano (flipper.js): la tavola dipinta da Codex con San Piero, e sopra le parti
|
||||
// meccaniche disegnate dal gioco, nelle stesse coordinate della fisica: sponde cromate, corsie dei rioni con le luci,
|
||||
// funghi, bersagli a caduta, il Regolo, sponde elastiche, flipper, molla e pallina. A sinistra, sul fianco nero del
|
||||
// mobile, il quadro dei punti. Niente filtri né passate a tutto schermo (vedi risveglio.js).
|
||||
import { FIELD, LANE, R, FLIP, PIVOT, SCORE, waiting, canThrow } from './flipper.js';
|
||||
import { text, panel } from './bocce-render.js';
|
||||
|
||||
const COLORS = { piazza: '#d0453a', pieve: '#f2eee2', cardetole: '#3fa34d', campomigliaio: '#2f6fd0' };
|
||||
const rail = (ctx, [ax, ay], [bx, by], dark = '#2a2622', light = '#c9c3b6') => {
|
||||
ctx.lineCap = 'round';
|
||||
ctx.strokeStyle = dark; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(ax, ay); ctx.lineTo(bx, by); ctx.stroke();
|
||||
ctx.strokeStyle = light; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(ax, ay); ctx.lineTo(bx, by); ctx.stroke();
|
||||
};
|
||||
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();
|
||||
}
|
||||
function bumper(ctx, b, time) {
|
||||
const k = b.hit > 0 ? 1 + b.hit * 1.2 : 1, r = b.r * k;
|
||||
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);
|
||||
void time;
|
||||
}
|
||||
function flipper(ctx, c, side) {
|
||||
const f = c.flippers[side], [px, py] = PIVOT[side], tx = px + Math.cos(f.angle) * FLIP.len, ty = py + Math.sin(f.angle) * FLIP.len;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.strokeStyle = 'rgba(0,0,0,0.3)'; ctx.lineWidth = FLIP.r * 2 + 2; ctx.beginPath(); ctx.moveTo(px + 1, py + 3); ctx.lineTo(tx + 1, ty + 3); ctx.stroke();
|
||||
ctx.strokeStyle = '#3a2a1a'; ctx.lineWidth = FLIP.r * 2 + 2; ctx.beginPath(); ctx.moveTo(px, py); ctx.lineTo(tx, ty); ctx.stroke();
|
||||
ctx.strokeStyle = f.held.length ? '#ffd45a' : '#e8b640'; ctx.lineWidth = FLIP.r * 2 - 1; 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');
|
||||
}
|
||||
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;
|
||||
ctx.fillStyle = '#4a4440'; ctx.fillRect(Math.round(x) - 3, top + pull, 6, 336 - top - pull + 10);
|
||||
ctx.fillStyle = '#9a9088';
|
||||
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);
|
||||
}
|
||||
function ball(ctx, b) {
|
||||
if (!b) return;
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.35)'; ctx.beginPath(); ctx.arc(b.x + 2, b.y + 3, R, 0, Math.PI * 2); ctx.fill();
|
||||
const g = ctx.createRadialGradient(b.x - 1.5, b.y - 1.5, 0.5, b.x, b.y, R);
|
||||
g.addColorStop(0, '#ffffff'); g.addColorStop(0.4, '#cfd3da'); g.addColorStop(1, '#5a6068');
|
||||
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(b.x, b.y, R, 0, Math.PI * 2); ctx.fill();
|
||||
}
|
||||
// Il quadro dei punti sul fianco del mobile: punti, palline, i rioni accesi, il traguardo, l'ultimo avviso.
|
||||
function board(ctx, c, def) {
|
||||
panel(ctx, 22, 60, 160, 150);
|
||||
text(ctx, 'FLIPPER DEL RIVER', 102, 76, 7, '#efd49e', 'center');
|
||||
text(ctx, String(c.step === 'result' ? c.result.score : c.score).padStart(6, '0'), 102, 100, 14, '#fff4c8', 'center');
|
||||
text(ctx, 'PUNTI', 102, 116, 5, '#a9a08a', 'center');
|
||||
text(ctx, `PALLINE ${c.step === 'result' ? 0 : c.balls}`, 34, 136, 6, '#d7ccb4');
|
||||
text(ctx, `TRAGUARDO ${c.skill.goal}`, 34, 150, 6, '#d7ccb4');
|
||||
text(ctx, 'RIONI', 34, 168, 6, '#d7ccb4');
|
||||
c.lanes.forEach((l, i) => disc(ctx, 80 + i * 20, 168, 6, l.lit ? COLORS[l.rione] : '#3a3630', l.lit ? '#fff4c8' : '#5a5650'));
|
||||
text(ctx, (def.names?.player ?? 'Andrea').toUpperCase(), 34, 190, 6, '#efd49e');
|
||||
if (c.line) text(ctx, c.line.toUpperCase(), 102, 203, 5, '#ffe08c', 'center');
|
||||
}
|
||||
function result(ctx, c) {
|
||||
panel(ctx, 20, 230, 164, 56);
|
||||
text(ctx, c.result.winner === 'player' ? 'TRAGUARDO FATTO!' : 'FINE PARTITA', 102, 246, 7, '#efd49e', 'center');
|
||||
text(ctx, `${c.result.score} su ${c.result.goal}`, 102, 266, 7, c.result.winner === 'player' ? '#9fd27a' : '#e0b07a', 'center');
|
||||
}
|
||||
|
||||
export function drawFlipper(ctx, c, def, bg) {
|
||||
const time = c.visual.time;
|
||||
ctx.save();
|
||||
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); }
|
||||
// Le luci delle corsie dei rioni, sotto le bandiere.
|
||||
for (const l of c.lanes) disc(ctx, l.x, l.y + 18, 4, l.lit ? COLORS[l.rione] : 'rgba(40,36,30,0.6)', l.lit ? '#fff4c8' : 'rgba(0,0,0,0.4)');
|
||||
// Le sponde: tutte le pareti della fisica, cromate; le elastiche con la gomma rossa.
|
||||
for (const w of c.walls) {
|
||||
if (w.gate) { ctx.strokeStyle = '#8a8078'; ctx.lineWidth = 1; ctx.setLineDash([2, 2]); ctx.beginPath(); ctx.moveTo(...w.a); ctx.lineTo(...w.b); ctx.stroke(); ctx.setLineDash([]); continue; }
|
||||
if (w.sling) { rail(ctx, w.a, w.b, '#5a1d16', '#e8584a'); continue; }
|
||||
rail(ctx, w.a, w.b);
|
||||
}
|
||||
// I bersagli a caduta: in piedi colorati, caduti un segno a terra.
|
||||
for (const t of c.targets) {
|
||||
const [x, y0] = t.a, y1 = t.b[1];
|
||||
if (t.down) { ctx.fillStyle = 'rgba(0,0,0,0.35)'; ctx.fillRect(x - 2, y0, 4, y1 - y0); continue; }
|
||||
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);
|
||||
}
|
||||
for (const b of c.bumpers) bumper(ctx, b, time);
|
||||
// Il Regolo: il bersaglio verde con la corona, che brilla quando è colpito.
|
||||
disc(ctx, c.regolo.x, c.regolo.y, c.regolo.r + (c.regolo.hit > 0 ? 2 : 0), c.regolo.hit > 0 ? '#c9ff9a' : '#3fa34d', '#1e4a24');
|
||||
ctx.fillStyle = '#ffd45a'; ctx.fillRect(c.regolo.x - 3, c.regolo.y - c.regolo.r - 4, 6, 3);
|
||||
for (const side of ['left', 'right']) flipper(ctx, c, side);
|
||||
plunger(ctx, c);
|
||||
ball(ctx, c.ball);
|
||||
// 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, def);
|
||||
if (c.step === 'result') result(ctx, c);
|
||||
ctx.restore();
|
||||
}
|
||||
export { SCORE };
|
||||
Reference in New Issue
Block a user