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 };
|
||||
@@ -0,0 +1,231 @@
|
||||
// Il flipper del River (Andrea, 31 ottobre: «una tavola bellissima, con San Piero dentro»). Visto dall'alto, la tavola
|
||||
// in verticale al centro del primo piano 640×360; la pallina parte dalla corsia di destra con la molla, fa il giro
|
||||
// dell'arco in cima, passa in una delle quattro corsie dei rioni, rimbalza sui tre funghi (i bastioni della Fortezza),
|
||||
// abbatte i bersagli della Pieve a sinistra e della torre a destra, tocca il Regolo in mezzo, e giù dalle sponde
|
||||
// elastiche ai due flipper, con la Sieve dipinta che scorre nel buco in mezzo. Tre palline; si vince arrivando al
|
||||
// traguardo di punti del livello. Unità: i pixel del primo piano. La fisica va a passo fisso (come il biliardo).
|
||||
export const FIELD = { x0: 200, x1: 448, y0: 5, y1: 336 }, LANE = { x0: 432, x1: 448 }, R = 4.5;
|
||||
export const BALLS = 3, STEP = 1 / 240, MAX_SPEED = 980, LAUNCH = [420, 900], CHARGE = 1.2, FLIP = { len: 34, rest: 0.5, up: -0.46, speed: 26, r: 3 };
|
||||
export const PIVOT = { left: [280, 314], right: [360, 314] };
|
||||
export const SCORE = { bumper: 100, sling: 50, lane: 200, palio: 3000, target: 500, bank: 2000, regolo: 1000 };
|
||||
// I livelli: la gravità, quanti secondi dura il salvataggio della pallina appena lanciata, la spinta dei funghi, il traguardo.
|
||||
export const LEVELS = {
|
||||
bona: { g: 620, save: 8, kick: 300, goal: 6000 },
|
||||
seria: { g: 780, save: 3, kick: 340, goal: 12000 },
|
||||
male: { g: 880, save: 0, kick: 380, goal: 25000 },
|
||||
};
|
||||
export const RIONI = ['piazza', 'pieve', 'cardetole', 'campomigliaio'];
|
||||
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
||||
function random(c) { c.seed = (Math.imul(c.seed, 1664525) + 1013904223) >>> 0; return c.seed / 4294967296; }
|
||||
const seg = (a, b, extra = {}) => ({ a, b, e: 0.55, ...extra });
|
||||
|
||||
// La tavola: sponde, corsie, sponde elastiche, funghi, bersagli. Le coordinate seguono il disegno di Codex
|
||||
// (assets/closeups/river-flipper.png): i tre dischi dei funghi, le quattro bandiere dei rioni, la corsia della molla.
|
||||
export function layout() {
|
||||
const arc = [[200, 72], [203, 50], [212, 32], [228, 18], [252, 10], [288, 6], [324, 5], [360, 6], [396, 10], [420, 18], [436, 32], [445, 50], [448, 72]];
|
||||
const walls = [];
|
||||
for (let i = 1; i < arc.length; i++) walls.push(seg(arc[i - 1], arc[i], { e: 0.6 }));
|
||||
walls.push(seg([200, 72], [200, 336]), seg([448, 72], [448, 336]));
|
||||
// La corsia della molla, e in cima il cancelletto che non fa tornare indietro la pallina.
|
||||
walls.push(seg([432, 70], [432, 336]), seg([432, 70], [448, 70], { gate: true }), seg([432, 326], [448, 326], { e: 0.2 }));
|
||||
// Le corsie dei rioni, in cima, fra le bandiere: tre guide.
|
||||
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 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 }));
|
||||
// I bersagli a caduta: la Pieve a sinistra, la torre a destra, con la sponda dietro.
|
||||
walls.push(seg([210, 146], [210, 198]), seg([429, 146], [429, 198]));
|
||||
const targets = [];
|
||||
for (const [bank, x] of [['pieve', 216], ['torre', 423]]) for (let i = 0; i < 3; i++) targets.push({ bank, i, a: [x, 150 + i * 16], b: [x, 162 + i * 16], down: false });
|
||||
const bumpers = [[281, 120], [360, 120], [320, 153]].map(([x, y]) => ({ x, y, r: 12, hit: 0 }));
|
||||
// Il Regolo, in alto a destra sotto l'arco, dove la pallina scende dal giro.
|
||||
const regolo = { x: 404, y: 62, r: 6, hit: 0 };
|
||||
const lanes = [257, 297, 340, 381].map((x, i) => ({ x, y: 88, rione: RIONI[i], lit: false }));
|
||||
return { walls, targets, bumpers, regolo, lanes };
|
||||
}
|
||||
|
||||
export function newFlipper(seed = Date.now() >>> 0, def = {}) {
|
||||
const level = LEVELS[def.level] ? def.level : 'seria';
|
||||
const c = { step: 'play', t: 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: [] }, right: { angle: Math.PI - FLIP.rest, omega: 0, held: [] } },
|
||||
ball: null, saveUntil: 0, still: 0, visual: { time: 0 }, ...layout() };
|
||||
newBall(c);
|
||||
return c;
|
||||
}
|
||||
function newBall(c) { c.ball = { x: (LANE.x0 + LANE.x1) / 2, y: 318, vx: 0, vy: 0, waiting: true }; c.still = 0; c.anchor = null; }
|
||||
export const waiting = c => c.step === 'play' && !!c.ball?.waiting;
|
||||
export const canThrow = c => waiting(c) && !c.holding;
|
||||
export const canPress = canThrow;
|
||||
export const canAdjust = c => c.step !== 'result';
|
||||
export const aimFree = () => true;
|
||||
export const powerAt = time => clamp(time / CHARGE, 0, 1);
|
||||
function say(c, line) { c.line = line; c.lineT = 2; }
|
||||
function add(c, points) { c.score += points; }
|
||||
|
||||
// Il lancio: la molla spinge la pallina su per la corsia.
|
||||
export function launch(c, power) {
|
||||
if (!waiting(c)) return false;
|
||||
c.ball.waiting = false; c.ball.vy = -(LAUNCH[0] + (LAUNCH[1] - LAUNCH[0]) * clamp(power, 0, 1)); c.ball.vx = 0;
|
||||
// Il salvataggio vale una volta per pallina: persa subito, torna; persa di nuovo, è persa.
|
||||
c.saveUntil = c.saved ? 0 : c.t + c.skill.save; c.holding = false; c.holdTime = 0;
|
||||
c.events.push({ kind: 'flipper-lancio', power: 0.5 + 0.5 * power });
|
||||
return true;
|
||||
}
|
||||
// Il flipper: il tasto tenuto lo alza, lasciato ricade. Più dita o tasti insieme: resta su finché ne resta uno.
|
||||
export function hold(c, side, who, down) {
|
||||
const f = c.flippers[side]; if (!f) return false;
|
||||
const was = f.held.length > 0;
|
||||
f.held = f.held.filter(h => h !== who); if (down) f.held.push(who);
|
||||
if (!was && f.held.length && c.step === 'play') c.events.push({ kind: 'flipper-flip', power: 0.6 });
|
||||
return true;
|
||||
}
|
||||
|
||||
// La fisica, a passi fissi.
|
||||
export function update(c, dt) {
|
||||
if (!Number.isFinite(dt) || dt <= 0) return;
|
||||
dt = Math.min(dt, 0.25); c.t += dt; c.visual.time += dt;
|
||||
if (c.lineT > 0 && (c.lineT -= dt) <= 0) c.line = null;
|
||||
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.accumulator += dt;
|
||||
while (c.accumulator >= STEP) { c.accumulator -= STEP; step(c); }
|
||||
}
|
||||
function flipperSeg(c, side) {
|
||||
const f = c.flippers[side], [px, py] = PIVOT[side];
|
||||
return { a: [px, py], b: [px + Math.cos(f.angle) * FLIP.len, py + Math.sin(f.angle) * FLIP.len] };
|
||||
}
|
||||
function step(c) {
|
||||
// I flipper: su se tenuti, giù se no; velocità angolare costante.
|
||||
for (const side of ['left', 'right']) {
|
||||
const f = c.flippers[side];
|
||||
const target = f.held.length ? (side === 'left' ? FLIP.up : Math.PI - FLIP.up) : (side === 'left' ? FLIP.rest : Math.PI - FLIP.rest);
|
||||
const d = target - f.angle, move = FLIP.speed * STEP;
|
||||
f.omega = Math.abs(d) <= move ? 0 : Math.sign(d) * FLIP.speed;
|
||||
f.angle = Math.abs(d) <= move ? target : f.angle + Math.sign(d) * move;
|
||||
}
|
||||
const ball = c.ball;
|
||||
if (c.step !== 'play' || !ball || ball.waiting) return;
|
||||
ball.vy += c.skill.g * STEP;
|
||||
const v = Math.hypot(ball.vx, ball.vy);
|
||||
if (v > MAX_SPEED) { ball.vx *= MAX_SPEED / v; ball.vy *= MAX_SPEED / v; }
|
||||
ball.x += ball.vx * STEP; ball.y += ball.vy * STEP;
|
||||
for (const w of c.walls) collideSeg(c, ball, w);
|
||||
for (const t of c.targets) if (!t.down && collideSeg(c, ball, { a: t.a, b: t.b, e: 0.5 })) dropTarget(c, t);
|
||||
for (const b of c.bumpers) if (collideCircle(ball, b, 0)) { kick(c, ball, b, c.skill.kick); b.hit = 0.15; add(c, SCORE.bumper); c.events.push({ kind: 'flipper-fungo', power: 0.8 }); }
|
||||
if (collideCircle(ball, c.regolo, 0.7)) { c.regolo.hit = 0.3; add(c, SCORE.regolo); say(c, 'Il Regolo!'); c.events.push({ kind: 'flipper-bersaglio', power: 1 }); }
|
||||
for (const side of ['left', 'right']) collideFlipper(c, ball, side);
|
||||
// Le corsie si accendono entrando (non restandoci sopra: dopo il Palio ripartono spente).
|
||||
for (const l of c.lanes) {
|
||||
const inside = Math.hypot(ball.x - l.x, ball.y - l.y) < 7;
|
||||
if (inside && !l.inside && !l.lit) {
|
||||
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! Tutti e quattro i rioni!'); c.events.push({ kind: 'flipper-palio', power: 1 }); for (const k of c.lanes) k.lit = false; }
|
||||
}
|
||||
l.inside = inside;
|
||||
}
|
||||
// La pallina che resta nello stesso punto (ferma, o che trema in un incastro): dopo un po' una scossa.
|
||||
if (!c.anchor || Math.hypot(ball.x - c.anchor.x, ball.y - c.anchor.y) > 3) { c.anchor = { x: ball.x, y: ball.y }; c.still = 0; }
|
||||
else if ((c.still += STEP) > 2.5) { ball.vx += (random(c) - 0.5) * 160; ball.vy -= 90; c.still = 0; c.anchor = null; }
|
||||
// Un lancio fiacco ricasca sulla molla: si rilancia.
|
||||
if (ball.x > LANE.x0 && ball.y > 315 && Math.abs(ball.vy) < 8 && !ball.waiting) newBall(c);
|
||||
if (ball.y > FIELD.y1 + R * 2) drain(c);
|
||||
}
|
||||
function dropTarget(c, t) {
|
||||
t.down = true; add(c, SCORE.target); c.events.push({ kind: 'flipper-bersaglio', power: 0.7 });
|
||||
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!'); for (const k of bank) k.down = false; }
|
||||
}
|
||||
function drain(c) {
|
||||
c.events.push({ kind: 'flipper-buco', power: 0.8 });
|
||||
if (c.t < c.saveUntil) { say(c, 'Pallina salvata!'); c.saved = true; newBall(c); return; }
|
||||
c.balls--; c.saved = false;
|
||||
if (c.balls > 0) { say(c, `Pallina persa. Ne restano ${c.balls}.`); newBall(c); return; }
|
||||
c.ball = null; c.step = 'result';
|
||||
c.result = { winner: c.score >= c.skill.goal ? 'player' : 'cpu', score: c.score, goal: c.skill.goal };
|
||||
}
|
||||
// Pallina contro un segmento: la si spinge fuori e si riflette la velocità. Torna true se ha toccato.
|
||||
function collideSeg(c, ball, w) {
|
||||
const [ax, ay] = w.a, [bx, by] = w.b, dx = bx - ax, dy = by - ay, len2 = dx * dx + dy * dy || 1;
|
||||
const u = clamp(((ball.x - ax) * dx + (ball.y - ay) * dy) / len2, 0, 1), px = ax + dx * u, py = ay + dy * u;
|
||||
let nx = ball.x - px, ny = ball.y - py; const d = Math.hypot(nx, ny);
|
||||
if (d >= R) return false;
|
||||
if (d < 1e-6) { nx = -dy; ny = dx; const n = Math.hypot(nx, ny); nx /= n; ny /= n; } else { nx /= d; ny /= d; }
|
||||
// Il cancelletto ferma solo chi scende dall'alto.
|
||||
if (w.gate && !(ny < 0 && ball.vy > 0)) return false;
|
||||
ball.x = px + nx * R; ball.y = py + ny * R;
|
||||
const vn = ball.vx * nx + ball.vy * ny;
|
||||
if (vn < 0) {
|
||||
// La sponda elastica spinge via, un po' a caso come la gomma vera (così la pallina non fa il ping-pong in eterno).
|
||||
if (w.sling) { const a = Math.atan2(ny, nx) + (random(c) - 0.5) * 0.5, s = 230 + random(c) * 40; ball.vx = Math.cos(a) * s + (ball.vx - vn * nx) * 0.3; ball.vy = Math.sin(a) * s + (ball.vy - vn * ny) * 0.3; add(c, SCORE.sling); c.events.push({ kind: 'flipper-sponda', power: 0.6 }); }
|
||||
else { ball.vx -= (1 + w.e) * vn * nx; ball.vy -= (1 + w.e) * vn * ny; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function collideCircle(ball, o, e) {
|
||||
const dx = ball.x - o.x, dy = ball.y - o.y, d = Math.hypot(dx, dy), min = R + o.r;
|
||||
if (d >= min) return false;
|
||||
const nx = d > 1e-6 ? dx / d : 0, ny = d > 1e-6 ? dy / d : -1;
|
||||
ball.x = o.x + nx * min; ball.y = o.y + ny * min;
|
||||
const vn = ball.vx * nx + ball.vy * ny;
|
||||
if (vn < 0) { ball.vx -= (1 + e) * vn * nx; ball.vy -= (1 + e) * vn * ny; }
|
||||
return true;
|
||||
}
|
||||
function kick(c, ball, o, speed) {
|
||||
const a = Math.atan2(ball.y - o.y, ball.x - o.x) + (random(c) - 0.5) * 0.3;
|
||||
ball.vx = Math.cos(a) * speed; ball.vy = Math.sin(a) * speed;
|
||||
}
|
||||
// Il flipper è una capsula che gira: alla pallina si aggiunge la velocità del punto toccato.
|
||||
function collideFlipper(c, ball, side) {
|
||||
const f = c.flippers[side], { a, b } = flipperSeg(c, side), [ax, ay] = a, dx = b[0] - ax, dy = b[1] - ay, len2 = dx * dx + dy * dy;
|
||||
const u = clamp(((ball.x - ax) * dx + (ball.y - ay) * dy) / len2, 0, 1), px = ax + dx * u, py = ay + dy * u;
|
||||
let nx = ball.x - px, ny = ball.y - py; const d = Math.hypot(nx, ny), min = R + FLIP.r;
|
||||
if (d >= min) return false;
|
||||
if (d < 1e-6) { nx = 0; ny = -1; } else { nx /= d; ny /= d; }
|
||||
ball.x = px + nx * min; ball.y = py + ny * min;
|
||||
// La velocità della superficie: ω × r, col raggio dal perno al punto toccato.
|
||||
const rx = px - ax, ry = py - ay, sx = -f.omega * ry, sy = f.omega * rx;
|
||||
const vn = (ball.vx - sx) * nx + (ball.vy - sy) * ny;
|
||||
if (vn < 0) { ball.vx -= (1 + 0.45) * vn * nx; ball.vy -= (1 + 0.45) * vn * ny; }
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Comandi: col mouse o col dito, prima del lancio tenere premuto carica la molla e lasciare lancia; in gioco
|
||||
// la metà sinistra della scena alza il flipper sinistro, la destra il destro, finché si tiene premuto.
|
||||
export function aimScreen(c, [x, y]) { if (!Number.isFinite(x) || !Number.isFinite(y)) return false; c.cursor = [x, y]; return true; }
|
||||
export function press(c, pointerId = 'ptr') {
|
||||
if (c.step !== 'play') return false;
|
||||
if (waiting(c)) { if (c.holding) return false; c.holding = true; c.holdTime = 0; c.pointerId = pointerId; c.power = 0; return true; }
|
||||
return hold(c, (c.cursor?.[0] ?? 320) < 320 ? 'left' : 'right', pointerId, true);
|
||||
}
|
||||
export function release(c, pointerId = 'ptr') {
|
||||
if (c.holding && c.pointerId === pointerId) return launch(c, powerAt(c.holdTime));
|
||||
let any = false;
|
||||
for (const side of ['left', 'right']) if (c.flippers[side].held.includes(pointerId)) any = hold(c, side, pointerId, false) || any;
|
||||
return any;
|
||||
}
|
||||
export const cancel = (c, pointerId) => pointerId === undefined ? release(c, c.pointerId ?? 'ptr') : release(c, pointerId);
|
||||
// I pulsanti «← Flipper» e «Flipper →» (tenuti premuti), e le frecce.
|
||||
export const turn = (c, dir, who = 'button') => c.step === 'play' && hold(c, dir < 0 ? 'left' : 'right', who, true);
|
||||
export const stopTurn = (c, who = 'button') => hold(c, 'left', who, false) | hold(c, 'right', who, false);
|
||||
export const nudge = () => false;
|
||||
export function setPower(c, v) { if (!canThrow(c) || !Number.isFinite(v)) return false; c.power = clamp(v, 0, 1); return true; }
|
||||
export function key(c, k, down) {
|
||||
if (k === 'ArrowLeft' || k === 'ArrowRight') { hold(c, k === 'ArrowLeft' ? 'left' : 'right', 'key-' + k, down); return true; }
|
||||
if (k === ' ') { if (down) { if (!c.holding && waiting(c)) press(c, 'key'); } else if (c.holding && c.pointerId === 'key') release(c, 'key'); return true; }
|
||||
if (k === 'Enter') { if (down) shoot(c); return true; }
|
||||
return false;
|
||||
}
|
||||
// Il pulsante Lancia: con la forza del cursore.
|
||||
export const shoot = c => canThrow(c) && launch(c, c.power);
|
||||
export const button = c => c.step === 'result' ? 'Finita' : waiting(c) ? 'Lancia' : 'In gioco…';
|
||||
export function status(c) {
|
||||
if (c.step === 'result') return `${c.result.score} punti. ${c.result.winner === 'player' ? `Traguardo ${c.result.goal}: fatto!` : `Ne servivano ${c.result.goal}.`}`;
|
||||
const tally = `Punti ${c.score} (traguardo ${c.skill.goal}), palline ${c.balls}.`;
|
||||
if (c.line) return `${c.line} ${tally}`;
|
||||
return waiting(c) ? `Tieni premuto per caricare la molla e lascia, o premi Lancia. ${tally}` : `Flipper: metà sinistra e destra della scena, o i pulsanti. ${tally}`;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { newTelefono, updateTelefono, tapTelefono } from './telefono.js';
|
||||
import * as condor from './condor.js';
|
||||
import * as campana from './campana.js';
|
||||
import * as bruciate from './bruciate.js';
|
||||
import * as flipper from './flipper.js';
|
||||
import * as poker from './poker.js';
|
||||
|
||||
// I minigiochi in primo piano: comandi opzionali per il tiro o per la scelta di una carta.
|
||||
@@ -36,6 +37,7 @@ export const MINIGAMES = {
|
||||
condor: { create: (seed, level) => condor.newCondor(seed, { level }), update: condor.update, press: condor.press, release: condor.release, cancel: condor.cancel, aim: condor.aimScreen },
|
||||
campana: { create: (seed, level) => campana.newCampana(seed, { level }), update: campana.update, press: campana.press, release: campana.release, cancel: campana.cancel, aim: campana.aimScreen },
|
||||
bruciate: { create: (seed, level) => bruciate.newBruciate(seed, { level }), update: bruciate.update, press: bruciate.press, release: bruciate.release, cancel: bruciate.cancel, aim: bruciate.aimScreen },
|
||||
flipper: { create: (seed, level) => flipper.newFlipper(seed, { level }), update: flipper.update, press: flipper.press, release: flipper.release, cancel: flipper.cancel, aim: flipper.aimScreen },
|
||||
micro: { create: (seed, level) => micro.newMicro(seed, { level }), update: micro.update, press: micro.press, release: micro.release, cancel: micro.cancel, aim: micro.aimScreen },
|
||||
gara: { create: (seed, level, deck, def) => gara.newGara(seed, { level, riders: def.riders }), update: gara.update, press: gara.press, release: gara.release, cancel: gara.cancel, aim: gara.aimScreen },
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ import { drawCorsa } from './corsa.js';
|
||||
import { drawCondor } from './condor-render.js';
|
||||
import { drawCampana } from './campana-render.js';
|
||||
import { drawBruciate } from './bruciate-render.js';
|
||||
import { drawFlipper } from './flipper-render.js';
|
||||
import { drawPoker } from './poker-render.js';
|
||||
import { drawViaggio } from './viaggio.js';
|
||||
import { drawIncanto } from './incanto.js';
|
||||
@@ -1055,6 +1056,11 @@ function drawCloseup(ctx, game, time) {
|
||||
drawCampana(ctx, c, def, backgrounds.get(def.picture));
|
||||
return;
|
||||
}
|
||||
if (def.type === 'flipper') {
|
||||
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
|
||||
drawFlipper(ctx, c, def, backgrounds.get(def.picture));
|
||||
return;
|
||||
}
|
||||
if (def.type === 'bruciate') {
|
||||
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
|
||||
drawBruciate(ctx, c, def, backgrounds.get(def.picture));
|
||||
|
||||
@@ -161,6 +161,15 @@ export class Soundscape {
|
||||
else if (kind === 'bruciate-scoppio') { this.noiseSound(0.04, 2600, 0.05 * strength, { pan }); this.tone(900, 500, 0.03, 0.03, 0, pan); }
|
||||
else if (kind === 'bruciate-cade') { this.noiseSound(0.5, 3200, 0.09, { pan }); this.tone(200, 90, 0.12, 0.05, 0, pan); }
|
||||
else if (kind === 'bruciate-sacco') this.noiseSound(0.7, 1100, 0.1, { paper: true, pan });
|
||||
// Il flipper: la molla, il colpo del flipper, il fungo, la sponda elastica, il bersaglio, la corsia, il Palio, il buco.
|
||||
else if (kind === 'flipper-lancio') { this.noiseSound(0.12, 1800, 0.05 * strength, { pan }); this.tone(180, 600, 0.12, 0.05, 0, pan); }
|
||||
else if (kind === 'flipper-flip') { this.tone(240, 120, 0.05, 0.07, 0, pan); this.noiseSound(0.03, 2200, 0.04, { pan }); }
|
||||
else if (kind === 'flipper-fungo') { this.tone(900, 500, 0.06, 0.06 * strength + 0.02, 0, pan); this.tone(1800, 900, 0.04, 0.03, 0, pan); }
|
||||
else if (kind === 'flipper-sponda') { this.tone(520, 260, 0.05, 0.05, 0, pan); this.noiseSound(0.03, 1600, 0.03, { pan }); }
|
||||
else if (kind === 'flipper-bersaglio') { this.tone(1400, 1200, 0.05, 0.05, 0, pan); this.tone(2100, 1800, 0.08, 0.04, 0.05, pan); }
|
||||
else if (kind === 'flipper-corsia') { this.tone(1046, 1046, 0.08, 0.04, 0, pan); this.tone(1318, 1318, 0.1, 0.04, 0.08, pan); }
|
||||
else if (kind === 'flipper-palio') for (const [i, f] of [523, 659, 784, 1046].entries()) this.tone(f, f, 0.14, 0.06, i * 0.1, pan);
|
||||
else if (kind === 'flipper-buco') { this.tone(220, 80, 0.3, 0.06, 0, pan); this.noiseSound(0.2, 600, 0.04, { pan }); }
|
||||
else if (kind === 'cerbottana-tosse') { this.noiseSound(0.18, 900, 0.08, { pan }); this.tone(260, 140, 0.12, 0.06, 0.25, pan); this.tone(240, 120, 0.1, 0.05, 0.3, pan); }
|
||||
// Le macchinine: il dito che spinge, le ruote sull'asfalto, l'urto di plastica, fuori pista, il traguardo.
|
||||
else if (kind === 'micro-spinta') { this.tone(1200, 500, 0.04, 0.04 + strength * 0.04, 0, pan); this.noiseSound(0.04, 2600, 0.04, { pan }); }
|
||||
|
||||
+8
-5
@@ -16,6 +16,7 @@ import * as cerbottana from './engine/cerbottana.js';
|
||||
import * as condor from './engine/condor.js';
|
||||
import * as campana from './engine/campana.js';
|
||||
import * as bruciate from './engine/bruciate.js';
|
||||
import * as flipper from './engine/flipper.js';
|
||||
import * as poker from './engine/poker.js';
|
||||
import { cardAt as pokerAt } from './engine/poker-render.js';
|
||||
import * as basket from './engine/basket.js';
|
||||
@@ -619,6 +620,7 @@ const MINI = {
|
||||
condor: { ...condor, back: 'Torna al campone', label: 'La Condor: muovi il mouse o tocca il campo per mirare, tieni premuto per trattenere il fiato e lascia per sparare; oppure premi Spara. Frecce per spostare il mirino.' },
|
||||
campana: { ...campana, back: 'Torna al campone', label: 'La campana: muovi il mouse o tocca la campana per mirare, tieni premuto per la forza e lascia per tirare il sassolino; oppure regola la forza e premi Tira (frecce su e giù per mirare, destra e sinistra per la forza). Ai salti premi, o Spazio, quando la lancetta è nel verde.' },
|
||||
bruciate: { ...bruciate, back: 'Torna alla festa', label: 'Le bruciate: tieni premuto e fai dei giri, col mouse o col dito, per girare la manovella; oppure Spazio o le frecce, una spinta a ogni colpo. Né troppo piano né troppo forte; quando son cotte, Rovescia nel sacco (o Invio).' },
|
||||
flipper: { ...flipper, toggle: true, back: 'Torna in strada', label: 'Il flipper del River: tieni premuto per caricare la molla e lascia per lanciare (o regola la forza e premi Lancia, o Spazio). In gioco, tieni premuto sulla metà sinistra o destra della scena per alzare il flipper di quel lato, oppure i pulsanti Flipper o le frecce sinistra e destra.' },
|
||||
micro: { ...micro, back: 'Torna dal Chiaru', label: 'La pista col gesso: muovi il mouse o tocca il parcheggio per mirare, tieni premuto per caricare la spinta e lascia; oppure regola la forza e premi Spingi. Frecce o pulsanti Mira per girare.' },
|
||||
basket: { ...basket, back: 'Torna in strada', label: 'Fai due tiri: muovi il mouse o tocca per mirare, tieni premuto per caricare la forza, lascia per tirare.' },
|
||||
// La gara: niente mira né forza, il pulsante alterna gas e freno (chi non può tenere premuto).
|
||||
@@ -1048,7 +1050,7 @@ function updateUi() {
|
||||
}
|
||||
}
|
||||
if (mg && !cards) {
|
||||
setAttr($('bocce-controls'), 'aria-label', { rinquarto: 'Comandi dei rinquarti', biliardo: 'Comandi del biliardo', basket: 'Comandi del basket', pesca: 'Comandi della pesca', gara: 'Comandi della gara', micro: 'Comandi delle macchinine', cerbottana: 'Comandi della cerbottana', condor: 'Comandi della Condor', campana: 'Comandi della campana', bruciate: 'Comandi delle bruciate' }[def.type] ?? 'Comandi delle bocce');
|
||||
setAttr($('bocce-controls'), 'aria-label', { rinquarto: 'Comandi dei rinquarti', biliardo: 'Comandi del biliardo', basket: 'Comandi del basket', pesca: 'Comandi della pesca', gara: 'Comandi della gara', micro: 'Comandi delle macchinine', cerbottana: 'Comandi della cerbottana', condor: 'Comandi della Condor', campana: 'Comandi della campana', bruciate: 'Comandi delle bruciate', flipper: 'Comandi del flipper' }[def.type] ?? 'Comandi delle bocce');
|
||||
setProp($('panel'), 'hidden', true); setProp($('choices'), 'hidden', true);
|
||||
const line = mg.status(c, def.names);
|
||||
if ($('bocce-status').textContent !== line) $('bocce-status').textContent = line;
|
||||
@@ -1064,7 +1066,7 @@ function updateUi() {
|
||||
const easy = def.type === 'gara' && gara.simple(c);
|
||||
setProp(powerLabel, 'hidden', def.type === 'rinquarto' || def.type === 'condor' || def.type === 'bruciate' || easy);
|
||||
for (const id of ['bocce-left', 'bocce-right']) setProp($(id), 'hidden', easy || def.type === 'campana' || def.type === 'bruciate');
|
||||
const powerName = def.type === 'gara' ? 'Sensibilità ' : def.type === 'cerbottana' ? 'Fiato ' : 'Forza ';
|
||||
const powerName = def.type === 'gara' ? 'Sensibilità ' : def.type === 'cerbottana' ? 'Fiato ' : def.type === 'flipper' ? 'Molla ' : 'Forza ';
|
||||
if (powerLabel.firstChild.nodeValue !== powerName) powerLabel.firstChild.nodeValue = powerName;
|
||||
setAttr($('bocce-power'), 'aria-label', def.type === 'gara' ? 'Sensibilità dello sterzo col dito' : 'Forza del lancio');
|
||||
setProp($('gara-pad'), 'hidden', def.type !== 'gara' || easy);
|
||||
@@ -1072,8 +1074,8 @@ function updateUi() {
|
||||
c.sensLoaded = true;
|
||||
try { const v = localStorage.getItem(GARA_SENS); if (v !== null) gara.setPower(c, Number(v) / 100); } catch { /* si resta al valore di partenza */ }
|
||||
}
|
||||
setText($('bocce-left'), def.type === 'gara' ? '← Sterza' : '← Mira');
|
||||
setText($('bocce-right'), def.type === 'gara' ? 'Sterza →' : 'Mira →');
|
||||
setText($('bocce-left'), def.type === 'gara' ? '← Sterza' : def.type === 'flipper' ? '← Flipper' : '← Mira');
|
||||
setText($('bocce-right'), def.type === 'gara' ? 'Sterza →' : def.type === 'flipper' ? 'Flipper →' : 'Mira →');
|
||||
setProp($('gara-brake'), 'hidden', def.type !== 'gara' || easy);
|
||||
$('bocce-throw').classList.toggle('hold', easy);
|
||||
setProp($('gara-brake'), 'disabled', !(mg.canAdjust?.(c) ?? false) || !!game.speech);
|
||||
@@ -1086,6 +1088,7 @@ function updateUi() {
|
||||
setProp($('condor-help'), 'hidden', def.type !== 'condor');
|
||||
setProp($('campana-help'), 'hidden', def.type !== 'campana');
|
||||
setProp($('bruciate-help'), 'hidden', def.type !== 'bruciate');
|
||||
setProp($('flipper-help'), 'hidden', def.type !== 'flipper');
|
||||
setProp($('basket-help'), 'hidden', def.type !== 'basket');
|
||||
setProp($('pesca-help'), 'hidden', def.type !== 'pesca');
|
||||
setProp($('gara-help'), 'hidden', def.type !== 'gara');
|
||||
@@ -1117,7 +1120,7 @@ function updateUi() {
|
||||
canvas.classList.toggle('torch', !!torch);
|
||||
canvas.classList.toggle('bocce', !!mg);
|
||||
// Biliardo e GP di Fortezza: audio, pausa e uscita fuori dal quadro (al GP la pista passa negli angoli).
|
||||
$('stage').classList.toggle('biliardo', !!c && ['biliardo', 'rinquarto', 'gara', 'micro', 'cerbottana', 'condor', 'campana'].includes(story.closeups[c.id].type));
|
||||
$('stage').classList.toggle('biliardo', !!c && ['biliardo', 'rinquarto', 'gara', 'micro', 'cerbottana', 'condor', 'campana', 'flipper'].includes(story.closeups[c.id].type));
|
||||
$('stage').classList.toggle('briscola', !!cards);
|
||||
setAttr(canvas, 'aria-label', torch ? 'Fessura: sposta la luce con mouse, dito o frecce. Invio cerca il prossimo dettaglio; Esc esce.' : mg ? mg.label : "La scena dell'avventura");
|
||||
// La pagina che comincia a girare fa il suo fruscio.
|
||||
|
||||
+27
-1
@@ -360,6 +360,17 @@ export const story = {
|
||||
draw: [],
|
||||
quit: [{ say: 'Il gesso lo lascio lì. Ci giocherà qualche bambino, che è più in allenamento di me.' }],
|
||||
},
|
||||
// Il flipper del River (Andrea, 31 ottobre: «una tavola bellissima, con San Piero dentro»): la tavola di Codex,
|
||||
// fisica e parti meccaniche in flipper.js. [DA ANDREA: le battute sono provvisorie]
|
||||
flipper: {
|
||||
type: 'flipper',
|
||||
picture: 'assets/closeups/river-flipper.png',
|
||||
names: { player: 'Andrea', cpu: 'il flipper' },
|
||||
win: [{ say: 'Traguardo! Se c\'era la classifica, oggi ci mettevo le mie iniziali.' }, { set: 'flipper_vinto' }],
|
||||
lose: [{ say: 'Tre palline, e tutte nella Sieve. Il flipper del River non perdona.' }],
|
||||
draw: [],
|
||||
quit: [{ say: 'Lascio la pallina lì. Tanto il River non chiude.' }],
|
||||
},
|
||||
// Le bruciate della Smarronata, 1993: la manovella del cilindro sul fuoco, coi volontari della Misericordia
|
||||
// (Andrea, 31 ottobre: «a manovella, a mano»). [DA ANDREA: le battute sono provvisorie]
|
||||
bruciate: {
|
||||
@@ -1667,7 +1678,9 @@ export const story = {
|
||||
id: 'river', name: 'il River', box: [60, 128, 190, 110], at: [160, 320], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'Il River, il bar della rotonda. Fuori gli ombrelloni, dentro i flipper e le figurine.' }],
|
||||
open: [{ say: 'Un ghiacciolo ci starebbe. Ma prima il palio, poi i ghiaccioli.' }],
|
||||
// Dentro, il flipper (Andrea, 31 ottobre): si entra e si sceglie come giocarlo.
|
||||
open: [{ say: 'Un ghiacciolo ci starebbe. Ma prima... il flipper.' }, { dialogue: 'flipper' }],
|
||||
use: [{ say: 'Dentro, in fondo, fra le figurine: il flipper. Con San Piero dipinto sulla tavola.' }, { dialogue: 'flipper' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -4440,6 +4453,19 @@ export const story = {
|
||||
},
|
||||
},
|
||||
// Da solo al campo del Circolo: Andrea decide quanto farsi male.
|
||||
// Il flipper del River: Andrea da solo davanti alla tavola, sceglie il livello. [DA ANDREA: battute provvisorie]
|
||||
flipper: {
|
||||
nodes: {
|
||||
start: {
|
||||
options: [
|
||||
{ text: 'Una partita alla bona, tanto per sentire la molla.', next: 'end', then: [{ say: 'Seimila punti e sono contento.' }, { closeup: 'flipper', level: 'bona' }] },
|
||||
{ text: 'Una partita seria.', next: 'end', then: [{ say: 'Dodicimila. Come quando ci lasciavo le cinquecento lire.' }, { closeup: 'flipper', level: 'seria' }] },
|
||||
{ text: 'A fassi male.', next: 'end', then: [{ say: 'Venticinquemila. Il record del River, o quasi.' }, { closeup: 'flipper', level: 'male' }] },
|
||||
{ text: 'Un\'altra volta.', next: 'end' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
basket: {
|
||||
nodes: {
|
||||
start: {
|
||||
|
||||
Reference in New Issue
Block a user