La macchina del tempo: dall'Arcu («Bei tempi quando qui si faceva i pokerini…») al Barrettino del 10 luglio 1997 col viaggio sul cruscotto, Vannino e Pierino Parigi, e il pokerino a cinque carte col cambio; l'Arcu in Via Liguri Magelli più grande (Andrea)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
This commit is contained in:
2026-10-29 20:30:05 +01:00
co-authored by Claude Opus 5.5
parent bb8e6a8885
commit a699115a03
50 changed files with 941 additions and 13 deletions
+21
View File
@@ -12,6 +12,7 @@ import * as scopa from './scopa.js';
import * as gara from './gara.js';
import * as micro from './micro.js';
import * as cerbottana from './cerbottana.js';
import * as poker from './poker.js';
// I minigiochi in primo piano: comandi opzionali per il tiro o per la scelta di una carta.
export const MINIGAMES = {
@@ -23,6 +24,7 @@ export const MINIGAMES = {
rinquarto: { create: (seed, level) => rinquarto.newRinquarto(seed, { level }), update: rinquarto.update, press: rinquarto.press, release: rinquarto.release, cancel: rinquarto.cancel, aim: rinquarto.aimScreen },
basket: { create: (seed, level) => basket.newBasket(seed, { level }), update: basket.update, press: basket.press, release: basket.release, cancel: basket.cancel, aim: basket.aimScreen },
pesca: { create: (seed, level) => pesca.newPesca(seed, { level }), update: pesca.update, press: pesca.press, release: pesca.release, cancel: pesca.cancel, aim: pesca.aimScreen },
poker: { create: (seed, level) => poker.newPoker(seed, { level }), update: poker.update, play: poker.play },
cerbottana: { create: (seed, level) => cerbottana.newCerbottana(seed, { level }), update: cerbottana.update, press: cerbottana.press, release: cerbottana.release, cancel: cerbottana.cancel, aim: cerbottana.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 },
@@ -51,6 +53,9 @@ const WALK = ['walk_1', 'walk_2', 'walk_3', 'walk_4'], STEP = 15, REACH = 0.45;
// Il primo piano di un libro (story.closeups, effetto `closeup`): copertina → frontespizio → una pagina girata →
// l'ultima pagina, poi dopo una pausa il commento. Tempi in secondi.
export const PAGE_TURN = 0.7, CLOSEUP_PAUSE = 1.4;
// I tempi del viaggio nel tempo, in secondi: quando comincia a comparire la data, quanto dura ogni tasto, quando si
// carica (tremori e lampi), quando arriva il bianco, quando si chiude.
export const TRIP = { type: 0.8, key: 0.16, charge: 3.6, flash: 5.4, end: 6.6 };
// Condizioni: 'flag', '!flag', 'has:oggetto', '!has:oggetto', oppure un elenco (tutte vere).
export function check(cond, state) {
@@ -306,6 +311,8 @@ export class Game {
if (c.step === 'done') this.queue.unshift(...def.complete);
return true;
}
// Il viaggio si chiude da solo alla fine (o uscendo prima): si arriva comunque.
if (def?.type === 'viaggio') { this.queue.unshift(...def.complete); return true; }
// I minigiochi: a partita finita (risultato deciso, anche se il conteggio si sta ancora mostrando) la battuta
// di chi ha vinto; uscendo prima, quella di chi lascia.
if (MINIGAMES[def?.type]) {
@@ -336,6 +343,18 @@ export class Game {
return true;
}
// Il viaggio nel tempo (story.closeups, type 'viaggio'): la data si compone sul cruscotto una cifra alla volta, poi
// tremori, lampi e il bianco; alla fine si chiude da solo e parte `complete` (di solito il `go` nella scena d'arrivo).
updateTrip(dt) {
const c = this.closeup, def = this.story.closeups[c.id], T = TRIP;
c.t += dt;
const typed = Math.max(0, Math.min(def.date.length, Math.floor((c.t - T.type) / T.key) + 1));
for (; c.typed < typed; c.typed++) if (def.date[c.typed] !== ' ') c.events.push({ kind: 'card-tap', power: 0.7 });
if (!c.charge && c.t >= T.charge) { c.charge = true; c.events.push({ kind: 'gara-turbo', power: 1 }); }
if (!c.boom && c.t >= T.flash) { c.boom = true; c.events.push({ kind: 'gara-buca', power: 1 }); }
if (c.t >= T.end) this.closeupClose();
}
updateTorch(dt) {
const c = this.closeup, def = this.story.closeups[c.id];
c.t += dt;
@@ -453,6 +472,7 @@ export class Game {
const c = this.closeup;
if (c) {
if (this.story.closeups[c.id].type === 'torch') { this.updateTorch(dt); return; }
if (this.story.closeups[c.id].type === 'viaggio') { this.updateTrip(dt); return; }
const m = this.minigame();
if (m) {
m.update(c, dt);
@@ -494,6 +514,7 @@ export class Game {
else if (e.closeup) {
const type = this.story.closeups[e.closeup].type;
this.closeup = type === 'torch' ? { id: e.closeup, step: 'search', t: 0, age: 0, found: 0, dwell: 0, beam: [320, 180] }
: type === 'viaggio' ? { id: e.closeup, t: 0, age: 0, events: [], typed: 0 }
// `level`: «Alla bona», «Seria» o «A fassi male», scelto nel dialogo con l'avversario.
// `decks`: il mazzo da usare quando il suo flag è acceso (briscola: le napoletane al posto delle toscane).
: MINIGAMES[type] ? { id: e.closeup, ...MINIGAMES[type].create(Math.floor(Math.random() * 4294967296), e.level,
+89
View File
@@ -0,0 +1,89 @@
import { SEATS, NAMES, MANI, canDraw, myTurn, evaluate, CATEGORIES, red } from './poker.js';
import { drawTable, drawCard } from './briscola-render.js';
import { text, panel } from './bocce-render.js';
// Le carte di Andrea, in basso: coordinate condivise da disegno e tocco (bersagli larghi anche sul telefono).
export const HAND = { x: 150, y: 222, gap: 70, w: 62, h: 90 };
export function cardAt(c, [x, y]) {
if (!canDraw(c)) return -1;
return c.cards.andrea.findIndex((_, i) => x >= HAND.x + i * HAND.gap && x < HAND.x + i * HAND.gap + HAND.w && y >= HAND.y - 14 && y < HAND.y + HAND.h);
}
const ink = '#2a1f1a', paper = '#fff4dc';
function rect(ctx, x, y, w, h, color) { ctx.fillStyle = color; ctx.fillRect(Math.round(x), Math.round(y), w, h); }
// I semi francesi a pixel (il carattere del gioco non ha ♥♦♣♠): cuore, quadro, fiore, picca, larghi 2·s.
function pip(ctx, suit, x, y, s) {
const color = suit === 'cuori' || suit === 'quadri' ? '#c0392b' : ink;
ctx.fillStyle = color; ctx.beginPath();
if (suit === 'quadri') { ctx.moveTo(x, y - s); ctx.lineTo(x + s * 0.75, y); ctx.lineTo(x, y + s); ctx.lineTo(x - s * 0.75, y); }
else if (suit === 'cuori') {
ctx.moveTo(x, y + s); ctx.lineTo(x - s, y - s * 0.1); ctx.arc(x - s * 0.5, y - s * 0.35, s * 0.52, Math.PI * 0.85, Math.PI * 1.95);
ctx.arc(x + s * 0.5, y - s * 0.35, s * 0.52, Math.PI * 1.05, Math.PI * 0.15); ctx.lineTo(x, y + s);
} else if (suit === 'picche') {
ctx.moveTo(x, y - s); ctx.lineTo(x + s, y + s * 0.15); ctx.arc(x + s * 0.5, y + s * 0.3, s * 0.5, 0, Math.PI * 0.9);
ctx.lineTo(x, y + s * 0.2); ctx.arc(x - s * 0.5, y + s * 0.3, s * 0.5, Math.PI * 0.1, Math.PI); ctx.closePath();
ctx.fill(); ctx.beginPath(); ctx.moveTo(x, y + s * 0.3); ctx.lineTo(x + s * 0.35, y + s); ctx.lineTo(x - s * 0.35, y + s);
} else {
for (const [dx, dy] of [[0, -0.45], [-0.48, 0.15], [0.48, 0.15]]) { ctx.moveTo(x + dx * s + s * 0.42, y + dy * s); ctx.arc(x + dx * s, y + dy * s, s * 0.42, 0, Math.PI * 2); }
ctx.fill(); ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + s * 0.35, y + s); ctx.lineTo(x - s * 0.35, y + s);
}
ctx.closePath(); ctx.fill();
}
// Una carta francese: angolo col valore e il seme, il seme grande in mezzo (le figure con una cornice).
export function drawFrench(ctx, card, x, y, w, h, back = false) {
if (back) { drawCard(ctx, null, x, y, w, h, true); return; }
rect(ctx, x + 2, y + 3, w, h, '#15231d'); rect(ctx, x, y, w, h, ink); rect(ctx, x + 1, y + 1, w - 2, h - 2, paper);
const color = red(card) ? '#c0392b' : ink, k = w / 62;
text(ctx, card.rank, x + 5 * k, y + 9 * k, Math.round(9 * k), color);
pip(ctx, card.suit, x + 9 * k, y + 22 * k, 5 * k);
if (['J', 'Q', 'K'].includes(card.rank)) {
rect(ctx, x + 14 * k, y + 28 * k, 34 * k, 50 * k, red(card) ? '#f3d6c4' : '#d8dfe6');
ctx.strokeStyle = color; ctx.lineWidth = 1; ctx.strokeRect(Math.round(x + 14 * k) + 0.5, Math.round(y + 28 * k) + 0.5, Math.round(34 * k), Math.round(50 * k));
text(ctx, card.rank, x + 31 * k, y + 46 * k, Math.round(16 * k), color, 'center');
pip(ctx, card.suit, x + 31 * k, y + 66 * k, 7 * k);
} else pip(ctx, card.suit, x + 31 * k, y + 52 * k, 14 * k);
}
// Le fiches del piatto: pile colorate, una ogni cinque.
function chips(ctx, n, x, y) {
const colors = ['#c0392b', '#2f6fb0', '#e8e0c8', '#3a8a4a'];
for (let i = 0; i < Math.min(24, Math.ceil(n / 5)); i++) {
const col = i % 6, row = Math.floor(i / 6), cx = x + col * 14 - 35, cy = y - row * 3;
rect(ctx, cx, cy, 12, 4, '#1a1410'); rect(ctx, cx, cy - 1, 12, 3, colors[i % colors.length]);
}
}
// Dove siedono: Vannino a sinistra, Pierino a destra, Andrea in basso.
const SPOTS = { vannino: { x: 40, y: 70 }, pierino: { x: 420, y: 70 } };
export function drawPoker(ctx, c) {
ctx.save(); ctx.imageSmoothingEnabled = false;
drawTable(ctx, 'IL POKERINO DEL BARRETTINO · 10 LUGLIO 1997');
const show = c.step === 'show' || c.step === 'result';
for (const who of ['vannino', 'pierino']) {
const { x, y } = SPOTS[who], out = !c.in?.[who];
text(ctx, `${NAMES[who]} · ${c.chips[who]}`, x + 90, y - 22, 7, c.current === who ? '#ffe28a' : '#fff0c8', 'center');
c.cards[who].forEach((card, i) => drawFrench(ctx, card, x + i * 36, y - 10, 42, 62, !(show && c.in[who])));
if (out && c.step !== 'result') { ctx.fillStyle = 'rgba(20,16,12,0.55)'; ctx.fillRect(x - 2, y - 12, 190, 66); text(ctx, 'lascia', x + 90, y + 22, 7, '#d9ccba', 'center'); }
if (show && c.in[who] && c.cards[who].length) text(ctx, CATEGORIES[evaluate(c.cards[who])[0]], x + 90, y + 62, 6, c.winners?.includes(who) ? '#b9e5b3' : '#d9ccba', 'center');
if (c.changed?.[who] !== undefined && c.step !== 'show') text(ctx, c.changed[who] ? `cambia ${c.changed[who]}` : 'servito', x + 90, y + 62, 6, '#9fd0ff', 'center');
}
// Il piatto in mezzo.
chips(ctx, c.pot ?? 0, 320, 178);
text(ctx, `Piatto ${c.pot ?? 0}`, 320, 192, 7, '#ffe09b', 'center');
// Le carte di Andrea: quelle scelte per il cambio si alzano.
c.cards.andrea.forEach((card, i) => {
const up = c.selected?.has(i) ? -14 : 0;
drawFrench(ctx, card, HAND.x + i * HAND.gap, HAND.y + up, HAND.w, HAND.h);
if (up) text(ctx, 'cambia', HAND.x + i * HAND.gap + HAND.w / 2, HAND.y + up - 7, 5, '#ffe28a', 'center');
});
text(ctx, `Andrea · ${c.chips.andrea}`, 320, 210, 7, myTurn(c) || canDraw(c) ? '#ffe28a' : '#fff0c8', 'center');
if (c.cards.andrea.length) text(ctx, CATEGORIES[evaluate(c.cards.andrea)[0]], 590, 300, 6, '#d9ccba', 'center');
text(ctx, `Mano ${Math.min(c.hand, MANI)}/${MANI}`, 40, 300, 6, '#d9ccba');
if (c.line && c.step !== 'result') { ctx.fillStyle = '#17160fd0'; ctx.fillRect(120, 146, 400, 16); text(ctx, c.line, 320, 154, 7, c.step === 'show' ? '#b9e5b3' : '#9fd0ff', 'center'); }
if (c.step === 'result') {
const r = c.result, title = r.winner === 'player' ? 'HAI VINTO IL POKERINO!' : r.winner === 'draw' ? 'PARI' : `HA VINTO ${NAMES[r.top[0]].toUpperCase()}`;
panel(ctx, 150, 110, 340, 84);
text(ctx, title, 320, 132, 11, r.winner === 'player' ? '#b9e5b3' : '#efd49e', 'center');
text(ctx, SEATS.map(s => `${NAMES[s]} ${r.chips[s]}`).join(' · '), 320, 156, 7, '#efd49e', 'center');
text(ctx, 'Torna al Barrettino per uscire.', 320, 176, 7, '#d9ccba', 'center');
}
ctx.restore();
}
+239
View File
@@ -0,0 +1,239 @@
// Il pokerino del Barrettino, luglio 1997 (idea di Andrea, 29 ottobre): poker a cinque carte coperte col cambio, col
// mazzo corto da 32 (dal 7 all'asso, come nei bar), Andrea contro Vannino e Pierino Parigi. Si gioca a fiches.
// Col mazzo corto il colore è più raro del full e lo batte; la scala più bassa è A-7-8-9-10.
// Una mano: il piatto (ognuno mette l'invito), cinque carte, un giro di puntate, il cambio, un altro giro, chi resta
// mostra. Puntate fisse (PUNTO) e al massimo RILANCI rilanci a giro. Dopo MANI mani vince chi ha più fiches.
export const RANKS = ['7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
export const SUITS = ['cuori', 'quadri', 'fiori', 'picche'];
export const SYMBOL = { cuori: '♥', quadri: '♦', fiori: '♣', picche: '♠' };
export const NAMES = { andrea: 'Andrea', vannino: 'Vannino', pierino: 'Pierino' };
export const SEATS = ['andrea', 'vannino', 'pierino'];
export const START = 100, INVITO = 2, PUNTO = 5, RILANCI = 2, MANI = 5, CPU_TIME = 0.9;
export const CATEGORIES = ['carta alta', 'coppia', 'doppia coppia', 'tris', 'scala', 'full', 'colore', 'poker', 'scala colore'];
// I livelli: quanto ci pensano gli altri (0 = a caso, 1 = col conto giusto) e quanto bluffano.
export const LEVELS = {
bona: { skill: 0.35, bluff: 0.04 },
seria: { skill: 0.7, bluff: 0.08 },
male: { skill: 1, bluff: 0.14 },
};
const value = card => RANKS.indexOf(card.rank) + 7;
export const cardName = card => `${card.rank}${SYMBOL[card.suit]}`;
export const red = card => card.suit === 'cuori' || card.suit === 'quadri';
function random(c) { c.seed = (Math.imul(c.seed, 1664525) + 1013904223) >>> 0; return c.seed / 4294967296; }
function deck(c) {
const cards = SUITS.flatMap(suit => RANKS.map(rank => ({ rank, suit })));
for (let i = cards.length - 1; i > 0; i--) { const j = Math.floor(random(c) * (i + 1)); [cards[i], cards[j]] = [cards[j], cards[i]]; }
return cards;
}
// Il punto di una mano: [categoria, poi i valori per gli spareggi], da confrontare in ordine.
export function evaluate(hand) {
const values = hand.map(value).sort((a, b) => b - a);
const counts = new Map();
for (const v of values) counts.set(v, (counts.get(v) ?? 0) + 1);
const groups = [...counts].sort((a, b) => b[1] - a[1] || b[0] - a[0]);
const flush = hand.every(card => card.suit === hand[0].suit);
const distinct = [...counts.keys()].sort((a, b) => b - a);
let straight = null;
if (distinct.length === 5) {
if (distinct[0] - distinct[4] === 4) straight = distinct[0];
else if (distinct.join() === '14,10,9,8,7') straight = 10; // A-7-8-9-10: l'asso fa da 6
}
const shape = groups.map(g => g[1]).join('');
const tie = groups.map(g => g[0]);
if (straight && flush) return [8, straight];
if (shape === '41') return [7, ...tie];
if (flush) return [6, ...values];
if (shape === '32') return [5, ...tie];
if (straight) return [4, straight];
if (shape === '311') return [3, ...tie];
if (shape === '221') return [2, ...tie];
if (shape === '2111') return [1, ...tie];
return [0, ...values];
}
export const compare = (a, b) => { for (let i = 0; i < Math.max(a.length, b.length); i++) if ((a[i] ?? 0) !== (b[i] ?? 0)) return (a[i] ?? 0) - (b[i] ?? 0); return 0; };
export const handName = hand => CATEGORIES[evaluate(hand)[0]];
export function newPoker(seed = Date.now() >>> 0, def = {}) {
const level = LEVELS[def.level] ? def.level : 'seria';
const c = { step: 'deal', t: 0, seed, level, skill: LEVELS[level], chips: Object.fromEntries(SEATS.map(s => [s, START])),
hand: 0, dealer: 2, events: [], line: null, selected: new Set(), history: [], result: null };
newHand(c);
return c;
}
const alive = c => SEATS.filter(s => c.chips[s] >= INVITO);
const active = c => SEATS.filter(s => c.in[s]);
// Una mano nuova: l'invito, il mazzo mischiato, cinque carte a testa; parla per primo chi sta dopo il mazziere.
function newHand(c) {
c.hand++;
c.dealer = (c.dealer + 1) % SEATS.length;
c.deck = deck(c);
c.in = Object.fromEntries(SEATS.map(s => [s, c.chips[s] >= INVITO]));
c.pot = 0;
for (const s of active(c)) { c.chips[s] -= INVITO; c.pot += INVITO; }
c.cards = Object.fromEntries(SEATS.map(s => [s, c.in[s] ? c.deck.splice(0, 5) : []]));
c.changed = {}; c.selected = new Set(); c.winners = null; c.line = null;
c.events.push({ kind: 'card-shuffle', power: 1 });
startBetting(c, 'bet1');
}
function startBetting(c, step) {
c.step = step; c.t = 0;
c.bets = Object.fromEntries(SEATS.map(s => [s, 0]));
c.acted = Object.fromEntries(SEATS.map(s => [s, false]));
c.raises = 0; c.toBet = 0;
c.current = firstAfter(c, c.dealer);
skipIfDone(c);
}
function firstAfter(c, i) {
for (let k = 1; k <= SEATS.length; k++) { const s = SEATS[(i + k) % SEATS.length]; if (c.in[s]) return s; }
return null;
}
// Un giro di puntate finisce quando tutti quelli rimasti hanno parlato e messo quanto il più alto.
const roundOver = c => active(c).every(s => c.acted[s] && (c.bets[s] === c.toBet || c.chips[s] === 0));
function skipIfDone(c) {
if (active(c).length === 1) return showdown(c);
if (roundOver(c)) return endBetting(c);
while (c.current && (!c.in[c.current] || c.chips[c.current] === 0 && c.acted[c.current])) c.current = firstAfter(c, SEATS.indexOf(c.current));
}
function endBetting(c) {
if (c.step === 'bet1') { c.step = 'draw'; c.t = 0; c.current = firstAfter(c, c.dealer); return; }
showdown(c);
}
export const toCall = (c, who = 'andrea') => Math.max(0, c.toBet - c.bets[who]);
export const myTurn = c => (c.step === 'bet1' || c.step === 'bet2') && c.current === 'andrea' && c.in.andrea;
export const canRaise = (c, who = 'andrea') => c.raises < RILANCI && c.chips[who] > toCall(c, who);
export const canDraw = c => c.step === 'draw' && c.current === 'andrea';
// Le mosse: «lascio» (si esce dalla mano), «vedo» (o «parola» se non c'è niente da mettere), «punto»/«rilancio».
function put(c, who, amount) {
const n = Math.min(amount, c.chips[who]);
c.chips[who] -= n; c.bets[who] += n; c.pot += n;
if (n) c.events.push({ kind: 'card-tap', power: 0.6 });
}
export function act(c, who, move) {
if (c.current !== who || !(c.step === 'bet1' || c.step === 'bet2')) return false;
const due = toCall(c, who);
if (move === 'lascio' && due === 0) move = 'vedo'; // lasciare gratis non ha senso: si dice parola
if (move === 'rilancio' && !canRaise(c, who)) move = 'vedo';
if (move === 'lascio') { c.in[who] = false; c.line = `${NAMES[who]}: lascio.`; }
else if (move === 'vedo') { put(c, who, due); c.line = `${NAMES[who]}: ${due ? `vedo (${due})` : 'parola'}.`; }
else { put(c, who, due + PUNTO); c.toBet = c.bets[who]; c.raises++; for (const s of SEATS) if (s !== who) c.acted[s] = false; c.line = `${NAMES[who]}: ${due ? 'rilancio' : 'apro'} di ${PUNTO}.`; }
c.acted[who] = true;
c.history.push({ hand: c.hand, step: c.step, who, move });
c.current = firstAfter(c, SEATS.indexOf(who));
c.t = 0;
skipIfDone(c);
return true;
}
// Il cambio: si scartano le carte scelte (al massimo 4) e se ne pescano altrettante.
export function draw(c, who, indexes) {
if (c.step !== 'draw' || c.current !== who) return false;
const keep = c.cards[who].filter((_, i) => !indexes.includes(i));
c.cards[who] = [...keep, ...c.deck.splice(0, 5 - keep.length)];
c.changed[who] = 5 - keep.length;
c.line = `${NAMES[who]}: ${c.changed[who] ? `cambio ${c.changed[who]}` : 'servito'}.`;
if (c.changed[who]) c.events.push({ kind: 'card-slide', power: 0.6 });
if (who === 'andrea') c.selected = new Set();
c.current = firstAfter(c, SEATS.indexOf(who));
c.t = 0;
// Il cambio è finito quando tutti quelli rimasti hanno cambiato.
if (active(c).every(s => c.changed[s] !== undefined)) startBetting(c, 'bet2');
return true;
}
function showdown(c) {
const left = active(c);
const best = left.map(s => ({ s, score: evaluate(c.cards[s]) })).sort((a, b) => compare(b.score, a.score));
const winners = left.length === 1 ? left : best.filter(b => compare(b.score, best[0].score) === 0).map(b => b.s);
const share = Math.floor(c.pot / winners.length);
for (const w of winners) c.chips[w] += share;
c.chips[winners[0]] += c.pot - share * winners.length;
c.winners = winners;
c.line = left.length === 1 ? `Tutti lasciano: il piatto (${c.pot}) va a ${NAMES[winners[0]]}.`
: `${winners.map(w => NAMES[w]).join(' e ')} ${winners.length > 1 ? 'dividono' : 'vince'} con ${handName(c.cards[winners[0]])}: ${c.pot} fiches.`;
c.step = 'show'; c.t = 0; c.current = null;
c.events.push({ kind: 'card-shuffle', power: 0.5 });
}
// Dopo la mostra un tocco (o «Prossima mano») va avanti; finite le mani, o senza più fiches, il risultato.
export function advance(c) {
if (c.step !== 'show') return false;
if (c.hand >= MANI || c.chips.andrea < INVITO || alive(c).length < 2) return finish(c);
newHand(c);
return true;
}
function finish(c) {
const max = Math.max(...SEATS.map(s => c.chips[s])), top = SEATS.filter(s => c.chips[s] === max);
c.step = 'result';
c.result = { winner: top.includes('andrea') ? (top.length > 1 ? 'draw' : 'player') : 'cpu', chips: { ...c.chips }, top };
return true;
}
// --- Gli altri due: quanto vale la mano (0..1), cosa cambiare, come puntare.
export function strength(hand) {
const [cat, ...rest] = evaluate(hand);
return Math.min(1, cat / 8 + ((rest[0] ?? 7) - 7) / 7 / 9);
}
export function cpuDiscards(c, who) {
const hand = c.cards[who], values = hand.map(value), [cat] = evaluate(hand);
if (cat >= 4 && cat !== 7) return []; // scala, full, colore, scala colore: servito
const counts = new Map(); values.forEach(v => counts.set(v, (counts.get(v) ?? 0) + 1));
if (cat >= 1) return hand.map((_, i) => i).filter(i => counts.get(values[i]) === 1 && !(cat === 7)); // tiene le coppie, i tris, il poker
const bySuit = SUITS.map(s => hand.map((card, i) => card.suit === s ? i : -1).filter(i => i >= 0)).find(l => l.length === 4);
if (bySuit && random(c) < c.skill.skill) return hand.map((_, i) => i).filter(i => !bySuit.includes(i)); // quattro a colore
// Altrimenti tiene le due carte più alte (con poca testa, una a caso).
const order = hand.map((_, i) => i).sort((a, b) => values[b] - values[a]);
return order.slice(random(c) < c.skill.skill ? 2 : 1);
}
export function cpuMove(c, who) {
const s = strength(c.cards[who]), due = toCall(c, who), k = c.skill, noise = (random(c) - 0.5) * (1 - k.skill) * 0.5;
const value = s + noise, bluff = random(c) < k.bluff;
if (value > 0.42 || bluff) return canRaise(c, who) && (value > 0.55 || bluff) ? 'rilancio' : 'vedo';
if (due === 0) return 'vedo';
return value > 0.13 + due / 60 ? 'vedo' : 'lascio';
}
export function update(c, dt) {
if (!Number.isFinite(dt) || dt <= 0) return;
c.t += dt;
if (!c.current || c.current === 'andrea' || c.t < CPU_TIME) return;
if (c.step === 'bet1' || c.step === 'bet2') act(c, c.current, cpuMove(c, c.current));
else if (c.step === 'draw') draw(c, c.current, cpuDiscards(c, c.current));
}
// --- I comandi di Andrea: toccare una carta la segna per il cambio; i pulsanti fanno il resto.
export function play(c, index) {
if (canDraw(c)) {
if (c.selected.has(index)) c.selected.delete(index);
else if (c.selected.size < 4) c.selected.add(index);
return true;
}
return false;
}
export const change = c => canDraw(c) && draw(c, 'andrea', [...c.selected]);
export function moves(c) {
if (canDraw(c)) return [{ id: 'cambia', label: c.selected.size ? `Cambia ${c.selected.size}` : 'Servito' }];
if (!myTurn(c)) return [];
const due = toCall(c);
return [
{ id: 'lascio', label: 'Lascio', disabled: due === 0 },
{ id: 'vedo', label: due ? `Vedo ${due}` : 'Parola' },
{ id: 'rilancio', label: due ? `Rilancio ${PUNTO}` : `Apro ${PUNTO}`, disabled: !canRaise(c) },
];
}
export function choose(c, id) {
if (id === 'cambia') return change(c);
return myTurn(c) && act(c, 'andrea', id);
}
export function status(c) {
const chips = SEATS.map(s => `${NAMES[s]} ${c.chips[s]}`).join(', ');
if (c.step === 'result') return `Fine del pokerino. ${chips}. ${c.result.winner === 'player' ? 'Hai vinto tu!' : c.result.winner === 'draw' ? 'Pari.' : `Ha vinto ${NAMES[c.result.top[0]]}.`}`;
const where = `Mano ${c.hand} di ${MANI}, piatto ${c.pot}. ${chips}.`;
if (c.step === 'show') return `${where} ${c.line} Tocca per la prossima mano.`;
if (canDraw(c)) return `${where} Cambio: tocca le carte da cambiare (al massimo 4), poi Cambia.`;
if (myTurn(c)) return `${where} ${c.line ?? ''} Tocca a te: ${toCall(c) ? `per vedere servono ${toCall(c)}` : 'parola o apri'}.`.replace(' ', ' ');
return `${where} ${c.line ?? ''} ${c.current ? `Parla ${NAMES[c.current]}…` : ''}`.trim();
}
+8
View File
@@ -11,6 +11,8 @@ import { drawPesca } from './pesca-render.js';
import { drawGara } from './gara-render.js';
import { drawMicro } from './micro-render.js';
import { drawCerbottana } from './cerbottana-render.js';
import { drawPoker } from './poker-render.js';
import { drawViaggio } from './viaggio.js';
import { drawBriscola } from './briscola-render.js';
import { drawTressette } from './tressette-render.js';
import { drawScopa } from './scopa-render.js';
@@ -678,6 +680,11 @@ function drawCloseup(ctx, game, time) {
if (def.type === 'briscola') { drawBriscola(ctx, c, def); return; }
if (def.type === 'tressette') { drawTressette(ctx, c, def); return; }
if (def.type === 'scopa') { drawScopa(ctx, c, def); return; }
if (def.type === 'viaggio') {
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
drawViaggio(ctx, c, def, backgrounds.get(def.picture));
return;
}
if (def.type === 'torch') {
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
drawTorch(ctx, c, def, backgrounds.get(def.picture));
@@ -688,6 +695,7 @@ function drawCloseup(ctx, game, time) {
drawBocce(ctx, c, def, backgrounds.get(def.picture), time);
return;
}
if (def.type === 'poker') { drawPoker(ctx, c); return; }
if (def.type === 'cerbottana') {
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
drawCerbottana(ctx, c, def, backgrounds.get(def.picture));
+51
View File
@@ -0,0 +1,51 @@
// Il disegno del viaggio nel tempo (story.closeups, type 'viaggio'; i tempi stanno in game.js, TRIP): il cruscotto,
// la data che si compone nel display con le cifre a segmenti che brillano, poi tremori, lampi e il bianco.
import { TRIP } from './game.js';
// Le cifre a sette segmenti: quali segmenti sono accesi (a b c d e f g).
const SEG = { 0: 'abcdef', 1: 'bc', 2: 'abged', 3: 'abgcd', 4: 'fgbc', 5: 'afgcd', 6: 'afgedc', 7: 'abc', 8: 'abcdefg', 9: 'abcdfg', '/': '', ':': '', ' ': '' };
function digit(ctx, ch, x, y, w, h, color) {
const t = Math.max(2, Math.round(w / 6)), on = SEG[ch] ?? '';
const bars = { a: [x + t, y, w - 2 * t, t], b: [x + w - t, y + t, t, h / 2 - t], c: [x + w - t, y + h / 2, t, h / 2 - t],
d: [x + t, y + h - t, w - 2 * t, t], e: [x, y + h / 2, t, h / 2 - t], f: [x, y + t, t, h / 2 - t], g: [x + t, y + h / 2 - t / 2, w - 2 * t, t] };
for (const [k, r] of Object.entries(bars)) { ctx.fillStyle = on.includes(k) ? color : 'rgba(255,90,40,0.08)'; ctx.fillRect(...r.map(Math.round)); }
if (ch === ':') { ctx.fillStyle = color; ctx.fillRect(x + w / 2 - t / 2, y + h * 0.3, t, t); ctx.fillRect(x + w / 2 - t / 2, y + h * 0.65, t, t); }
if (ch === '/') { ctx.fillStyle = color; for (let i = 0; i < 6; i++) ctx.fillRect(x + w * 0.7 - i * w * 0.08, y + h * 0.15 + i * h * 0.13, t, t); }
}
export function drawViaggio(ctx, c, def, bg) {
const t = c.t, T = TRIP, [dx, dy, dw, dh] = def.display;
// Tremori: crescono dalla carica al bianco.
const shake = t > T.charge ? Math.min(1, (t - T.charge) / (T.flash - T.charge)) * 5 : 0;
ctx.save();
ctx.translate(Math.round((Math.sin(t * 61) + Math.sin(t * 37)) * shake), Math.round(Math.cos(t * 53) * shake));
if (bg) ctx.drawImage(bg, 0, 0, 640, 360); else { ctx.fillStyle = '#111'; ctx.fillRect(0, 0, 640, 360); }
// La data, un tasto alla volta, in due righe: giorno/mese/anno e ora.
const lines = def.date.split(' '), shown = c.typed ?? 0;
let n = 0;
const glow = t > T.charge ? 0.75 + 0.25 * Math.sin(t * 30) : 1;
ctx.shadowColor = '#ff5a28'; ctx.shadowBlur = 8;
lines.forEach((line, row) => {
const ch = 14, cw = 13, gap = 4, total = line.length * (cw + gap) - gap;
const x0 = dx + (dw - total) / 2, y0 = dy + 8 + row * (ch * 2 + 2);
[...line].forEach((d, i) => {
const lit = n++ < shown;
digit(ctx, lit ? d : ' ', x0 + i * (cw + gap), y0, cw, ch * 2 - 6, `rgba(255,${Math.round(110 * glow)},40,${lit ? 1 : 0})`);
});
n++; // lo spazio fra le due righe
});
ctx.shadowBlur = 0;
ctx.restore();
// I lampi: archi azzurri che attraversano il parabrezza, a scatti.
if (t > T.charge + 0.6) {
const k = Math.floor(t * 14);
if (k % 3 !== 0) {
ctx.strokeStyle = 'rgba(160,220,255,0.9)'; ctx.lineWidth = 2; ctx.beginPath();
let x = 40 + (k * 97) % 560, y = 0; ctx.moveTo(x, y);
for (let i = 0; i < 7; i++) { x += ((k * (i + 3) * 37) % 50) - 25; y += 18; ctx.lineTo(x, y); }
ctx.stroke();
ctx.fillStyle = 'rgba(180,230,255,0.12)'; ctx.fillRect(0, 0, 640, 360);
}
}
// Il bianco, poi resta bianco fino alla fine.
if (t > T.flash) { ctx.fillStyle = `rgba(255,255,255,${Math.min(1, (t - T.flash) / 0.5)})`; ctx.fillRect(0, 0, 640, 360); }
}
+25 -3
View File
@@ -13,6 +13,8 @@ import * as biliardo from './engine/biliardo.js';
import * as rinquarto from './engine/rinquarto.js';
import * as micro from './engine/micro.js';
import * as cerbottana from './engine/cerbottana.js';
import * as poker from './engine/poker.js';
import { cardAt as pokerAt } from './engine/poker-render.js';
import * as basket from './engine/basket.js';
import * as pesca from './engine/pesca.js';
import * as gara from './engine/gara.js';
@@ -604,6 +606,7 @@ const MINI = {
bocce: { ...bocce, nudge: (c, dir) => bocce.aim(c, c.aim.u, c.aim.w + dir * 0.3), back: 'Torna al pallaio', label: 'Bocce: tocca il campo o il dettaglio per mirare. Regola la forza e premi Lancia.' },
biliardo: { ...biliardo, back: 'Torna al bar', label: 'Biliardo: clic o tocco fissa la mira. Frecce per affinarla, Maiusc per passi più piccoli. Regola la forza e premi Tira.' },
rinquarto: { ...rinquarto, back: 'Torna da Trappizzio Berti', label: 'I rinquarti di Trappizzio Berti: clic o tocco fissa la mira, frecce o pulsanti Mira per girare la stecca (Maiusc per i passi fini), Tira per tirare. Dopo l’esito, Prossima.' },
poker: { ...poker, back: 'Torna al Barrettino', label: 'Il pokerino: tocca le carte da cambiare, poi Cambia; nei giri di puntate Lascio, Vedo o Parola, Apro o Rilancio. Dopo la mostra un tocco va avanti.' },
cerbottana: { ...cerbottana, back: 'Torna in piazza', label: 'La cerbottana: muovi il mouse o tocca il muro per mirare, tieni premuto per prendere fiato e lascia per sparare; oppure regola il fiato e premi Spara. Frecce per spostare il mirino.' },
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.' },
@@ -648,7 +651,12 @@ $('bocce-throw').addEventListener('pointerdown', e => {
for (const ev of ['pointerup', 'pointercancel', 'lostpointercapture']) $('bocce-throw').addEventListener(ev, () => { const c = miniState(); if (holdGas(c)) gara.release(c, 'gas-button'); });
$('bocce-throw').addEventListener('contextmenu', e => { if (holdGas(miniState())) e.preventDefault(); });
// Le carte da gioco: dove si tocca la carta sul tavolo, gioco per gioco.
const CARD_AT = { briscola: cardAt, tressette: tressetteAt, scopa: scopaAt };
const CARD_AT = { briscola: cardAt, tressette: tressetteAt, scopa: scopaAt, poker: pokerAt };
// Il pokerino: tre pulsanti fissi (su iPhone un pulsante rifatto a ogni fotogramma perde il tocco), la scritta cambia.
for (let i = 0; i < 3; i++) $('poker-move-' + i).addEventListener('click', e => {
const c = game?.closeup; if (!c || story.closeups[c.id].type !== 'poker' || game.speech) return;
if (c.step === 'show') poker.advance(c); else poker.choose(c, e.currentTarget.dataset.move);
});
for (let i = 0; i < 3; i++) {
const button = $('scopa-card-' + i);
button.addEventListener('click', () => game?.closeupPlay(i));
@@ -946,7 +954,7 @@ function updateUi() {
updateChoices();
updateSpeech();
const mg = miniState() && mini(), def = mg && story.closeups[c.id];
const cards = ['briscola', 'tressette', 'scopa'].includes(def?.type);
const cards = ['briscola', 'tressette', 'scopa', 'poker'].includes(def?.type);
// La presentazione dei piloti del GP a tutto schermo: i comandi arrivano col semaforo (Andrea, 29 ottobre).
const presenta = !!mg && story.closeups[c.id].type === 'gara' && c.step === 'intro';
$('app').classList.toggle('presenta', presenta);
@@ -956,6 +964,20 @@ function updateUi() {
setProp($('briscola-controls'), 'inert', !running);
setProp($('scopa-controls'), 'hidden', def?.type !== 'scopa');
setProp($('scopa-controls'), 'inert', !running);
setProp($('poker-controls'), 'hidden', def?.type !== 'poker');
setProp($('poker-controls'), 'inert', !running);
if (def?.type === 'poker') {
setProp($('panel'), 'hidden', true); setProp($('choices'), 'hidden', true);
setText($('poker-status'), poker.status(c));
const moves = c.step === 'show' ? [{ id: 'avanti', label: 'Prossima mano' }] : poker.moves(c);
for (let i = 0; i < 3; i++) {
const b = $('poker-move-' + i), m = moves[i];
setProp(b, 'hidden', !m);
if (!m) continue;
setText(b, m.label); b.dataset.move = m.id;
setProp(b, 'disabled', !!m.disabled || !!game.speech);
}
}
if (def?.type === 'scopa') {
setProp($('panel'), 'hidden', true); setProp($('choices'), 'hidden', true);
const line = scopa.status(c, def.names);
@@ -1055,7 +1077,7 @@ function updateUi() {
setProp($('gara-help'), 'hidden', def.type !== 'gara');
if (mg.aimFree?.(c) ?? !mg.canThrow(c)) c.aimLocked = false; // a ogni nuovo tiro la mira torna libera
}
if (mg) for (const event of c.events.splice(0)) ambience.play(event.kind, { pan: 0, strength: event.power });
if (mg || c?.events) for (const event of c.events.splice(0)) ambience.play(event.kind, { pan: 0, strength: event.power });
// Il pulsante della mappa c'è solo in partita, fuori dai dialoghi, e quando c'è almeno un altro posto dove andare.
setProp($('map-button'), 'hidden', !running || !!game.dialogue || !!game.closeup || game.mapPlaces().length < 2);
setProp($('pause-button'), 'hidden', !running);
+115 -1
View File
@@ -233,6 +233,24 @@ export const story = {
quit: [{ say: 'Questo me lo tengo per la prossima.' }],
draw: [{ say: 'Visto? Geometrie celestiali. Si riprova quando vuoi.', who: 'bertovo', pose: 'explain' }],
},
// Il viaggio nel tempo dalla macchina del tempo in Via Liguri Magelli: la data sul cruscotto (Codex), poi il Barrettino
// del 10 luglio 1997 (Andrea, 29 ottobre).
viaggio: {
type: 'viaggio',
picture: 'assets/closeups/cruscotto-tempo.png',
display: [220, 158, 212, 62],
date: '10/07/1997 18:30',
complete: [{ go: 'barrettino_1997', at: [320, 330], face: 'up' }],
},
// Il pokerino del Barrettino con Vannino e Pierino (Andrea, 29 ottobre). [DA ANDREA: le battute]
poker: {
type: 'poker',
names: { player: 'Andrea', cpu: 'Vannino e Pierino' },
win: [{ say: 'M\'hai pelato, ragazzo. Nel 1997 un ci si vedeva da queste parti, con quella faccia.', who: 'vannino', pose: 'shrug' }, { set: 'poker_vinto' }],
lose: [{ say: 'Le fiches restano al banco. E tu torna quando hai imparato a bluffare.', who: 'pierino_parigi', pose: 'laugh' }],
quit: [{ say: 'Già finito? E il caffè chi lo paga?', who: 'vannino', pose: 'talk' }],
draw: [{ say: 'Pari. Si rifà domani sera, alla stess\'ora.', who: 'pierino_parigi', pose: 'explain' }],
},
// La cerbottana con lo stucco verde di Pierin di Frilli, contro il muro della piazza (Andrea, 29 ottobre).
// [DA ANDREA: le battute sono provvisorie]
cerbottana: {
@@ -323,6 +341,11 @@ export const story = {
tozzino: { name: 'Il Tozzino', color: '#c8f5b0', sprite: 'assets/characters/tozzino.json' },
// Il Chiaru (Matteo Chiarusi, consenso dato ad Andrea il 29 ottobre), alla pista col gesso di Cardetole: sprite di
// Codex dalla foto di Andrea.
// Al Barrettino del 1997 (Andrea, 29 ottobre; senza foto, dalla descrizione di Andrea): Vannino, alto, magro, coi
// capelli corti e la fossetta nel mento; Pierino Parigi (consenso del figlio Marco, 29 ottobre), brizzolato, con gli
// occhiali e la barba rada. Sprite di Codex.
vannino: { name: 'Vannino', color: '#ffe6a8', sprite: 'assets/characters/vannino.json' },
pierino_parigi: { name: 'Pierino', color: '#c8e0ff', sprite: 'assets/characters/pierino_parigi.json' },
// Lo zio Mario di Andrea (sprite di Codex dalla foto di Andrea, 29 ottobre), alla Ghiacciomobile nel muro.
zio_mario: { name: 'Lo zio Mario', color: '#cfe3ff', sprite: 'assets/characters/zio_mario.json' },
chiaru: { name: 'Il Chiaru', color: '#ffd6a0', sprite: 'assets/characters/chiaru.json' },
@@ -2749,7 +2772,9 @@ export const story = {
speakers: { arcu: [374, 176], zio_mario: [440, 196] },
people: [
// L'Arcu sotto la tenda del Trappola: due minuti fa era a Campomigliaio (Andrea, 28 ottobre).
{ who: 'arcu', at: [374, 248], facing: -1, size: 0.75, idle: 'stand', talk: ['talk', 'explain'], blink: 'blink', every: 4.2 },
// La macchina del tempo, dopo i pokerini dell'Arcu.
{ who: 'macchina', when: 'macchina_trappola', at: [200, 262], size: 1.1, idle: 'stand', talk: [] },
{ who: 'arcu', at: [374, 248], facing: -1, size: 1.05, idle: 'stand', talk: ['talk', 'explain'], blink: 'blink', every: 4.2 },
// Lo zio Mario davanti alla Ghiacciomobile nel muro, che si gratta la testa (sprite di Codex dalla sua foto,
// 29 ottobre). Solo finché la macchina è lì. [DA ANDREA: le sue battute]
{ who: 'zio_mario', when: '!indizio_regolo', at: [440, 330], facing: 1, idle: 'think', talk: ['talk', 'explain', 'point'] },
@@ -2795,6 +2820,14 @@ export const story = {
use: [{ say: 'Per tirarla fuori di lì ci vuole altro che le mie braccia.' }],
},
},
{
id: 'macchina_tempo', name: 'macchina del tempo', box: [150, 214, 104, 48], at: [212, 280], face: 'up', default: 'use', when: 'macchina_trappola',
actions: {
look: [{ say: 'La macchina del tempo, parcheggiata davanti al Trappola. Sul cruscotto la data è già pronta.' }],
use: [{ say: 'Salgo. Imposto: 10 luglio 1997, ore 18 e 30. Il pokerino da Vannino.' }, { closeup: 'viaggio' }],
open: [{ say: 'Salgo. Imposto: 10 luglio 1997, ore 18 e 30. Il pokerino da Vannino.' }, { closeup: 'viaggio' }],
},
},
{
// La prima battuta è di Andrea (dal circolino, 26 ottobre).
id: 'betino', name: 'il Betino dell\'Arcu', box: [232, 172, 40, 24], at: [250, 206], face: 'up', default: 'look',
@@ -3322,6 +3355,54 @@ export const story = {
},
],
},
// Il Barrettino il 10 luglio 1997 alle 18:30 (Andrea, 29 ottobre; l'interno è una proposta di Codex, [DA ANDREA]):
// Vannino e Pierino Parigi con la tavola apparecchiata da poker. Si torna nel 2007 dalla porta.
barrettino_1997: {
name: 'Il Barrettino, 10 luglio 1997',
// Ci si arriva solo con la macchina del tempo: niente mappa, niente strade (la porta riporta nel 2007).
viaggio: true,
background: 'assets/scenes/barrettino-1997.png',
indoor: true,
walk: [[0, 360], [0, 300], [110, 282], [540, 282], [640, 300], [640, 360]],
depth: [[282, 2.1], [360, 2.6]],
entry: [320, 330],
enter: [{ if: '!visto_1997', then: [{ set: 'visto_1997' }, { say: 'Il Barrettino. Nel 1997. Si sente ancora l\'odore del caffè e delle sigarette.' }] }],
speakers: { vannino: [160, 120], pierino_parigi: [470, 120] },
people: [
{ who: 'vannino', at: [160, 300], facing: 1, size: 1, idle: 'stand', talk: ['talk', 'explain'], blink: 'blink', every: 4.1 },
{ who: 'pierino_parigi', at: [470, 300], facing: -1, size: 1, idle: 'cards', talk: ['talk', 'explain'], blink: 'blink', every: 3.6 },
],
hotspots: [
{
id: 'tavola', name: 'la tavola da poker', box: [236, 186, 150, 70], at: [320, 300], face: 'up', default: 'talk',
actions: {
look: [{ say: 'Il panno verde, le fiches in pila, il mazzo e il posacenere pieno. Una sedia è vuota: è per me.' }],
talk: [{ dialogue: 'pokerino' }],
use: [{ dialogue: 'pokerino' }],
},
},
{
id: 'vannino', name: 'Vannino', box: [130, 160, 60, 140], at: [220, 320], face: 'left', default: 'talk',
actions: { look: [{ say: 'Vannino, alto e secco, col canovaccio sulla spalla. E quella fossetta nel mento.' }], talk: [{ dialogue: 'pokerino' }] },
},
{
id: 'pierino', name: 'Pierino', box: [440, 160, 60, 140], at: [410, 320], face: 'right', default: 'talk',
actions: { look: [{ say: 'Pierino Parigi, con gli occhiali sul naso, che guarda le carte come se lo tradissero.' }], talk: [{ dialogue: 'pokerino' }] },
},
{
id: 'televisore', name: 'televisore', box: [440, 20, 60, 50], at: [480, 300], face: 'up',
actions: { look: [{ say: 'Il televisore a tubo. È il 1997: niente smartphone, solo il televideo.' }] },
},
{
id: 'porta', name: 'la porta: si torna nel 2007', box: [70, 30, 70, 230], at: [120, 300], face: 'left', default: 'walk',
actions: {
look: [{ say: 'Fuori c\'è la sera del 1997. E parcheggiata lì, la macchina del tempo.' }],
walk: [{ say: 'Si torna nel 2007. Ciao Vannino, ciao Pierino.' }, { go: 'via_liguri_magelli', at: [212, 280], face: 'down' }],
open: [{ say: 'Si torna nel 2007. Ciao Vannino, ciao Pierino.' }, { go: 'via_liguri_magelli', at: [212, 280], face: 'down' }],
},
},
],
},
// L'ingresso della scuola, giù per la strada dalla salita di Villa Adami: dal cancello alle porte a vetri.
scuola: {
name: 'La scuola',
@@ -3685,6 +3766,16 @@ export const story = {
{ say: 'Col Betino? Io un perdo mai: un vo mai forte abbastanza da cadere.', who: 'arcu', pose: 'explain' },
{ say: 'Come la si fa?', who: 'arcu', pose: 'talk' },
] },
// I pokerini da Vannino: l'Arcu fa comparire la macchina del tempo (Andrea, 29 ottobre). [DA ANDREA: le battute dell'Arcu]
{ text: 'Bei tempi quando qui si faceva i pokerini...', then: [
{ if: 'macchina_trappola', then: [{ say: 'La macchina è sempre lì. La data è già impostata.', who: 'arcu', pose: 'explain' }], else: [
{ say: 'I pokerini da Vannino, col Pierino che bluffava anche a briscola.', who: 'arcu', pose: 'laugh' },
{ say: 'Ci vuoi tornare? Aspetta.', who: 'arcu', pose: 'explain' },
{ set: 'macchina_trappola' },
{ say: 'Ma quella è... la macchina del tempo!' },
{ say: 'Il plutonio l\'ha trovato Alvarino. Basta salire e mettere la data.', who: 'arcu', pose: 'talk' },
] },
] },
{ text: 'Ci si vede dopo, Arcu.', next: 'end' },
],
},
@@ -3760,6 +3851,29 @@ export const story = {
},
},
},
// Al Barrettino del 1997: il pokerino con Vannino e Pierino. [DA ANDREA: le battute]
pokerino: {
nodes: {
start: {
options: [
{ text: 'C\'è posto per uno?', next: 'livello', then: [
{ say: 'La sedia è lì. Invito due, punto cinque. Come la vuoi?', who: 'vannino', pose: 'explain' },
] },
{ text: 'Vannino, un caffè?', then: [{ say: 'Subito. Ma prima una mano, sennò che ci fai qui?', who: 'vannino', pose: 'talk' }] },
{ text: 'Pierino, come va la mano?', then: [{ say: 'Bene. O male. Con me un si sa mai, ed è questo il bello.', who: 'pierino_parigi', pose: 'laugh' }] },
{ text: 'Dopo.', next: 'end' },
],
},
livello: {
options: [
{ text: 'Alla bona.', next: 'end', then: [{ say: 'Alla bona: si gioca per il caffè.', who: 'pierino_parigi', pose: 'talk' }, { closeup: 'poker', level: 'bona' }] },
{ text: 'Seria.', next: 'end', then: [{ say: 'Seria. Carte, Vannino.', who: 'pierino_parigi', pose: 'cards' }, { closeup: 'poker', level: 'seria' }] },
{ text: 'A fassi male.', next: 'end', then: [{ say: 'A fassi male? Allora io bluffo.', who: 'pierino_parigi', pose: 'laugh' }, { closeup: 'poker', level: 'male' }] },
{ text: 'No, aspetta.', next: 'start' },
],
},
},
},
// Il Chiaru al parcheggio di Cardetole, accanto alla pista col gesso (Andrea, 29 ottobre). [DA ANDREA: battute provvisorie]
chiaru: {
nodes: {