Le bocce al pallaio contro Mariano: si tiene premuto per la forza, tre bocce a testa, vince il più vicino al pallino; se vinci, «Hai vinto, ti ci porto!» a Brento Sanico (sfondo e Mariano di Codex dalle foto di Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ks2K99uqCZjAxvy7Rdj1R
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
// Le bocce al pallaio (primo piano `type: 'bocce'`): una mano da tre bocce a testa contro il campione.
|
||||
// Tieni premuto e la forza va su e giù; lasci e la boccia parte. Vince la mano chi ha la boccia più vicina al pallino.
|
||||
// La pista è un piano: `u` lungo la pista (0 dove si tira, 100 la sponda di fondo), `w` di traverso (-10…10).
|
||||
// Niente fisica vera: attrito costante, urti semplici (chi colpisce cede quasi tutta la spinta), sponde che respingono.
|
||||
|
||||
export const LANE = 100, HALF = 10, FRICTION = 40, BALL = 1.25, JACK = 0.6, BALLS = 3;
|
||||
export const SWING = 1.6; // secondi per un giro della forza (0 → 1 → 0)
|
||||
const REST = 0.6; // pausa a bocce ferme prima del tiro seguente
|
||||
|
||||
// Numeri «a caso» ripetibili: lo stato del generatore sta nel primo piano, così le prove sono sempre uguali.
|
||||
function random(c) {
|
||||
c.seed = (c.seed * 1664525 + 1013904223) % 4294967296;
|
||||
return c.seed / 4294967296;
|
||||
}
|
||||
const gauss = c => (random(c) + random(c) + random(c) - 1.5) * 1.15;
|
||||
|
||||
export function newBocce(seed = Date.now() % 4294967296) {
|
||||
const c = { step: 'aim', t: 0, age: 0, seed, balls: [], turn: 0, power: 0, holding: false, holdTime: 0 };
|
||||
c.jack = { u: 38 + random(c) * 22, w: (random(c) - 0.5) * 5, vu: 0, vw: 0, r: JACK, jack: true };
|
||||
return c;
|
||||
}
|
||||
|
||||
// Di chi è il tiro: Andrea ai tiri pari, il campione ai dispari.
|
||||
export const thrower = c => (c.turn % 2 ? 'cpu' : 'player');
|
||||
export const left = (c, who) => BALLS - c.balls.filter(b => b.who === who).length;
|
||||
const moving = c => [c.jack, ...c.balls].some(b => Math.hypot(b.vu, b.vw) > 0.01);
|
||||
const dist = (a, b) => Math.hypot(a.u - b.u, a.w - b.w);
|
||||
|
||||
// Una boccia lanciata verso un punto: parte da dove si tira con la velocità che basta ad arrivarci.
|
||||
function launch(c, who, u, w) {
|
||||
const d = Math.hypot(u, w), v = Math.sqrt(2 * FRICTION * d);
|
||||
c.balls.push({ who, u: 0, w: 0, vu: v * u / d, vw: v * w / d, r: BALL });
|
||||
c.step = 'rolling';
|
||||
c.t = 0;
|
||||
}
|
||||
|
||||
// Andrea: la forza decide quanto lontano; la direzione punta al pallino, con un po' di mano che trema.
|
||||
export function press(c) {
|
||||
if (c.step !== 'aim' || thrower(c) !== 'player' || c.holding) return false;
|
||||
c.holding = true;
|
||||
c.holdTime = 0;
|
||||
c.power = 0;
|
||||
return true;
|
||||
}
|
||||
export function release(c) {
|
||||
if (!c.holding) return false;
|
||||
c.holding = false;
|
||||
launch(c, 'player', 8 + c.power * (LANE - 4), c.jack.w + gauss(c) * 0.9);
|
||||
return true;
|
||||
}
|
||||
export const powerAt = holdTime => (1 - Math.cos((2 * Math.PI * holdTime) / SWING)) / 2;
|
||||
|
||||
function step(c, dt) {
|
||||
const all = [c.jack, ...c.balls];
|
||||
for (const b of all) {
|
||||
const v = Math.hypot(b.vu, b.vw);
|
||||
if (v <= 0.01) { b.vu = b.vw = 0; continue; }
|
||||
const slow = Math.max(0, v - FRICTION * dt) / v;
|
||||
b.vu *= slow; b.vw *= slow;
|
||||
b.u += b.vu * dt; b.w += b.vw * dt;
|
||||
// Le sponde: di lato e in fondo respingono, piano.
|
||||
if (Math.abs(b.w) > HALF - b.r) { b.w = Math.sign(b.w) * (HALF - b.r); b.vw *= -0.4; }
|
||||
if (b.u > LANE - b.r) { b.u = LANE - b.r; b.vu *= -0.3; }
|
||||
}
|
||||
// Gli urti: chi va più forte spinge l'altro lungo la congiungente e si tiene poco.
|
||||
for (let i = 0; i < all.length; i++) for (let j = i + 1; j < all.length; j++) {
|
||||
const a = all[i], b = all[j], d = dist(a, b), min = a.r + b.r;
|
||||
if (d >= min || d === 0) continue;
|
||||
const [hit, other] = Math.hypot(a.vu, a.vw) >= Math.hypot(b.vu, b.vw) ? [a, b] : [b, a];
|
||||
const nu = (other.u - hit.u) / d, nw = (other.w - hit.w) / d, push = hit.vu * nu + hit.vw * nw;
|
||||
if (push <= 0) continue;
|
||||
const k = other.jack ? 1.1 : 0.85;
|
||||
other.vu += nu * push * k; other.vw += nw * push * k;
|
||||
hit.vu -= nu * push * 0.85; hit.vw -= nw * push * 0.85;
|
||||
// Si staccano subito, così non restano incollate.
|
||||
const back = (min - d) / 2;
|
||||
hit.u -= nu * back; hit.w -= nw * back; other.u += nu * back; other.w += nw * back;
|
||||
}
|
||||
}
|
||||
|
||||
// Il punto: chi ha la boccia più vicina al pallino vince, e segna una boccia per ognuna più vicina della migliore
|
||||
// dell'altro.
|
||||
export function score(c) {
|
||||
const by = who => c.balls.filter(b => b.who === who).map(b => dist(b, c.jack)).sort((a, b) => a - b);
|
||||
const p = by('player'), q = by('cpu');
|
||||
if (!p.length || !q.length) return { winner: null, points: 0 };
|
||||
const winner = p[0] <= q[0] ? 'player' : 'cpu', [mine, theirs] = winner === 'player' ? [p, q] : [q, p];
|
||||
return { winner, points: mine.filter(d => d < theirs[0]).length };
|
||||
}
|
||||
|
||||
// L'età del primo piano (`age`) la conta il motore; qui solo il tempo del passo in corso.
|
||||
export function update(c, dt) {
|
||||
c.t += dt;
|
||||
if (c.holding) { c.holdTime += dt; c.power = powerAt(c.holdTime); }
|
||||
if (c.step === 'rolling') {
|
||||
for (let s = 0; s < 4; s++) step(c, dt / 4);
|
||||
if (!moving(c) && c.t > 0.3) { c.step = 'settle'; c.t = 0; }
|
||||
} else if (c.step === 'settle' && c.t >= REST) {
|
||||
c.turn++;
|
||||
c.t = 0;
|
||||
c.step = c.turn >= BALLS * 2 ? 'result' : 'aim';
|
||||
if (c.step === 'result') c.result = score(c);
|
||||
} else if (c.step === 'aim' && thrower(c) === 'cpu' && c.t >= 1.1) {
|
||||
// Il campione mira al pallino e sbaglia di poco, a volte di più.
|
||||
launch(c, 'cpu', Math.min(LANE - 2, Math.max(10, c.jack.u + gauss(c) * 7)), c.jack.w + gauss(c) * 1.4);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Il disegno: lo sfondo della pista e, sopra, bocce e pallino in prospettiva (misurata sullo sfondo di Codex).
|
||||
const NEAR = { y: 360, x: 355, half: 339 }, FAR = { y: 187, x: 298, half: 37.5 };
|
||||
const S_FAR = FAR.half / NEAR.half, Q = (1 / S_FAR - 1) / LANE, HORIZON = (FAR.y - NEAR.y * S_FAR) / (1 - S_FAR);
|
||||
export function project(u, w) {
|
||||
const s = 1 / (1 + Q * u), k = (s - S_FAR) / (1 - S_FAR);
|
||||
const cx = FAR.x + (NEAR.x - FAR.x) * k;
|
||||
return { x: cx + (w / HALF) * NEAR.half * s, y: HORIZON + (NEAR.y - HORIZON) * s, s };
|
||||
}
|
||||
|
||||
const COLORS = { player: ['#1d3f8f', '#3f6fd1', '#a9c4ff'], cpu: ['#7a1f1a', '#c2392d', '#ffb0a3'], jack: ['#a8862a', '#f2d75c', '#fff6c4'] };
|
||||
// Il disegno ha la sua misura (le bocce vere su una pista di 4 metri), più piccola di quella degli urti; lontano si
|
||||
// ingrandisce un po' (fino a 2,6 volte in fondo), sennò in prospettiva esatta resterebbero puntini.
|
||||
const LOOK = { player: 0.42, cpu: 0.42, jack: 0.2 };
|
||||
function ball(ctx, b, kind) {
|
||||
const { x, y, s } = project(b.u, b.w), r = Math.max(2, LOOK[kind] * (NEAR.half / HALF) * s * (1 + 1.6 * (1 - s)));
|
||||
const [dark, mid, light] = COLORS[kind];
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.28)';
|
||||
ctx.beginPath(); ctx.ellipse(x + r * 0.25, y + r * 0.15, r, r * 0.45, 0, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.fillStyle = dark; ctx.beginPath(); ctx.arc(x, y - r, r, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.fillStyle = mid; ctx.beginPath(); ctx.arc(x - r * 0.15, y - r * 1.12, r * 0.72, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.fillStyle = light; ctx.fillRect(Math.round(x - r * 0.45), Math.round(y - r * 1.55), Math.max(1, Math.round(r * 0.35)), Math.max(1, Math.round(r * 0.3)));
|
||||
}
|
||||
|
||||
const FONT = '"Press Start 2P", monospace';
|
||||
function label(ctx, text, x, y, size = 7, color = '#ffd27a', align = 'center') {
|
||||
ctx.font = `${size}px ${FONT}`;
|
||||
ctx.textAlign = align;
|
||||
ctx.textBaseline = 'middle';
|
||||
const w = ctx.measureText(text).width, x0 = align === 'center' ? x - w / 2 : align === 'right' ? x - w : x;
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.62)';
|
||||
ctx.fillRect(Math.round(x0 - 6), Math.round(y - size), Math.round(w + 12), size * 2);
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillText(text, Math.round(x), Math.round(y));
|
||||
}
|
||||
|
||||
export function drawBocce(ctx, c, def, bg, time) {
|
||||
ctx.save();
|
||||
if (bg) ctx.drawImage(bg, 0, 0, 640, 360); else { ctx.fillStyle = '#c9b48a'; ctx.fillRect(0, 0, 640, 360); }
|
||||
const pieces = [{ b: c.jack, kind: 'jack' }, ...c.balls.map(b => ({ b, kind: b.who }))].sort((a, b) => b.b.u - a.b.u);
|
||||
for (const p of pieces) ball(ctx, p.b, p.kind);
|
||||
// Il punteggio in basso a sinistra (in alto ci sono Pausa e il volume): le bocce che restano a ciascuno.
|
||||
const dots = (who, y) => label(ctx, `${def.names[who]} ${'o'.repeat(left(c, who))}`, 16, y, 7, who === 'player' ? '#a9c4ff' : '#ffb0a3', 'left');
|
||||
dots('player', 326);
|
||||
dots('cpu', 344);
|
||||
if (c.step === 'aim' && thrower(c) === 'player') {
|
||||
// La forza: una barra che sale e scende finché si tiene premuto.
|
||||
const x = 586, y = 320, h = 120;
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(x - 3, y - h - 3, 20, h + 6);
|
||||
ctx.fillStyle = '#3a2d42'; ctx.fillRect(x, y - h, 14, h);
|
||||
const p = c.holding ? c.power : 0, grad = ['#7fe08a', '#ffd27a', '#ff7a5a'];
|
||||
ctx.fillStyle = grad[Math.min(2, Math.floor(p * 3))]; ctx.fillRect(x, Math.round(y - h * p), 14, Math.round(h * p));
|
||||
label(ctx, 'FORZA', x + 7, y + 14, 6);
|
||||
if (!c.holding && c.age > 0.4) {
|
||||
ctx.globalAlpha = 0.65 + 0.35 * Math.sin(time / 300);
|
||||
label(ctx, 'Tieni premuto per caricare, lascia per tirare', 320, 62);
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
} else if (c.step === 'aim') label(ctx, `Tira ${def.names.cpu}...`, 320, 62);
|
||||
if (c.step === 'result' && c.result) {
|
||||
const win = c.result.winner === 'player';
|
||||
label(ctx, win ? `Punto per ${def.names.player}!` : `Punto per ${def.names.cpu}!`, 320, 150, 12, win ? '#7fe08a' : '#ffb0a3');
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
+23
-4
@@ -1,6 +1,7 @@
|
||||
// Il cuore dell'avventura, senza DOM: stato, comandi, coda delle azioni, dialoghi.
|
||||
// La storia (scene, oggetti, dialoghi) arriva tutta dai dati: qui non c'è nessun enigma scritto a mano.
|
||||
import { findPath, dist, inBox, boxDistance, nearestInside } from './geometry.js';
|
||||
import { newBocce, update as bocceUpdate, press as boccePress, release as bocceRelease } from './bocce.js';
|
||||
|
||||
export const VERBS = ['walk', 'look', 'take', 'use', 'talk', 'open', 'give', 'drop'];
|
||||
export const VERB_LABELS = { walk: 'Vai a', look: 'Guarda', take: 'Prendi', use: 'Usa', talk: 'Parla con', open: 'Apri', give: 'Dai', drop: 'Butta' };
|
||||
@@ -229,7 +230,7 @@ export class Game {
|
||||
closeupTap() {
|
||||
const c = this.closeup;
|
||||
if (!c || this.speech) return false;
|
||||
if (this.story.closeups[c.id].type === 'torch') return false;
|
||||
if (this.story.closeups[c.id].type) return false; // torcia e bocce non si sfogliano
|
||||
if (c.step === 'cover') Object.assign(c, { step: 'title', t: 0 });
|
||||
else if (c.step === 'title') Object.assign(c, { step: 'turn', t: 0 });
|
||||
else return false;
|
||||
@@ -247,11 +248,20 @@ export class Game {
|
||||
if (c.step === 'done') this.queue.unshift(...def.complete);
|
||||
return true;
|
||||
}
|
||||
// Le bocce: a mano finita, la battuta di chi ha vinto; uscendo prima, quella di chi lascia.
|
||||
if (def?.type === 'bocce') {
|
||||
this.queue.unshift(...(c.step === 'result' ? def[c.result?.winner === 'player' ? 'win' : 'lose'] : def.quit ?? []));
|
||||
return true;
|
||||
}
|
||||
// Chi è arrivato all'ultima pagina, chiudendo, la commenta (`closing`).
|
||||
if (c.step === 'end') this.queue.unshift(...(this.story.closeups?.[c.id]?.closing ?? []));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Le bocce: si tiene premuto per caricare la forza e si lascia per tirare (mouse, dito o spazio).
|
||||
closeupPress() { const c = this.closeup; return !!c && this.story.closeups[c.id].type === 'bocce' && !this.speech && boccePress(c); }
|
||||
closeupRelease() { const c = this.closeup; return !!c && this.story.closeups[c.id].type === 'bocce' && bocceRelease(c); }
|
||||
|
||||
// Solo luce, nessun clic sugli oggetti dietro al primo piano. Il progresso è transitorio:
|
||||
// uscire o ricaricare durante la ricerca permette di ripeterla, senza anticipare il finale.
|
||||
closeupAim([x, y]) {
|
||||
@@ -378,6 +388,12 @@ 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 === 'bocce') {
|
||||
bocceUpdate(c, dt);
|
||||
// Il risultato resta a schermo un momento, poi si torna al pallaio per i commenti.
|
||||
if (c.step === 'result' && c.t >= 2.4) this.closeupClose();
|
||||
return;
|
||||
}
|
||||
c.t += dt;
|
||||
if (c.step === 'turn' && c.t >= PAGE_TURN) Object.assign(c, { step: 'end', t: 0 });
|
||||
if (c.step === 'end' && !c.lines && c.t >= CLOSEUP_PAUSE) c.lines = [...(this.story.closeups?.[c.id]?.comment ?? [])];
|
||||
@@ -409,9 +425,12 @@ export class Game {
|
||||
else if (e.node) this.dialogue.node = e.node;
|
||||
else if (e.endDialogue) this.dialogue = null;
|
||||
else if (e.end) s.ended = true;
|
||||
else if (e.closeup) this.closeup = this.story.closeups[e.closeup].type === 'torch'
|
||||
? { id: e.closeup, step: 'search', t: 0, age: 0, found: 0, dwell: 0, beam: [320, 180] }
|
||||
: { id: e.closeup, step: 'cover', t: 0, age: 0 };
|
||||
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 === 'bocce' ? { id: e.closeup, ...newBocce(Math.floor(Math.random() * 4294967296)) }
|
||||
: { id: e.closeup, step: 'cover', t: 0, age: 0 };
|
||||
}
|
||||
else throw new Error('Effetto sconosciuto: ' + JSON.stringify(e));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { check, PAGE_TURN } from './game.js';
|
||||
import { ambientEffects, ambientHash } from './ambient.js';
|
||||
import { drawTorch } from './torch.js';
|
||||
import { drawBocce } from './bocce.js';
|
||||
import { versioned } from '../version.js';
|
||||
export const WIDTH = 640, HEIGHT = 360;
|
||||
|
||||
@@ -669,6 +670,11 @@ function drawCloseup(ctx, game, time) {
|
||||
drawTorch(ctx, c, def, backgrounds.get(def.picture));
|
||||
return;
|
||||
}
|
||||
if (def.type === 'bocce') {
|
||||
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
|
||||
drawBocce(ctx, c, def, backgrounds.get(def.picture), time);
|
||||
return;
|
||||
}
|
||||
ctx.save();
|
||||
ctx.fillStyle = `rgba(8, 5, 12, ${Math.min(0.78, c.age / 0.35 * 0.78)})`;
|
||||
ctx.fillRect(0, 0, WIDTH, HEIGHT);
|
||||
|
||||
+8
-2
@@ -397,6 +397,8 @@ canvas.addEventListener('pointerdown', e => {
|
||||
// Nel primo piano un tocco sfoglia (o salta la battuta): gli oggetti della scena dietro non si toccano.
|
||||
if (game.closeup) {
|
||||
press = { closeup: true };
|
||||
// Alle bocce si tiene premuto per caricare la forza (la battuta in corso, un tocco la salta).
|
||||
if (!game.skip()) game.closeupPress();
|
||||
game.closeupAim(scenePoint(e));
|
||||
canvas.setPointerCapture(e.pointerId);
|
||||
return;
|
||||
@@ -406,7 +408,7 @@ canvas.addEventListener('pointerdown', e => {
|
||||
});
|
||||
canvas.addEventListener('pointerup', e => {
|
||||
if (!press) return;
|
||||
if (press.closeup) { press = null; if (running && !game.skip()) game.closeupTap(); return; }
|
||||
if (press.closeup) { press = null; if (running && !game.closeupRelease() && !game.skip()) game.closeupTap(); return; }
|
||||
clearTimeout(press.timer);
|
||||
// Il secondo clic vicino al primo, entro poco, è un doppio clic: stesso comando, al doppio della velocità.
|
||||
if (!press.long) {
|
||||
@@ -529,6 +531,7 @@ window.addEventListener('keydown', e => {
|
||||
return;
|
||||
}
|
||||
if (e.key === 'Escape') game.closeupClose();
|
||||
else if ([' ', 'Enter'].includes(e.key) && story.closeups[game.closeup.id].type === 'bocce') { e.preventDefault(); if (!e.repeat && !game.skip()) game.closeupPress(); }
|
||||
else if ([' ', 'Enter', 'ArrowRight'].includes(e.key)) { e.preventDefault(); if (!game.skip()) game.closeupTap(); }
|
||||
else if (e.key === 'm' || e.key === 'M') toggleMute();
|
||||
return;
|
||||
@@ -546,6 +549,9 @@ window.addEventListener('keydown', e => {
|
||||
if (game.dialogue && n >= 1 && n <= 9) game.choose(n - 1);
|
||||
});
|
||||
|
||||
// Alle bocce, lasciando spazio o Invio parte il tiro.
|
||||
window.addEventListener('keyup', e => { if (running && [' ', 'Enter'].includes(e.key)) game?.closeupRelease(); });
|
||||
|
||||
// --- Il primo piano: si chiude col pulsante; la pagina che gira fruscia (rumore filtrato, niente file audio).
|
||||
$('closeup-close').addEventListener('click', () => game?.closeupClose());
|
||||
let turning = null, reveal = null;
|
||||
@@ -590,7 +596,7 @@ function updateUi() {
|
||||
$('spots-button').hidden = !running || !!game.closeup;
|
||||
$('notebook-button').hidden = !running || !story.notebook || !!game.closeup;
|
||||
$('closeup-close').hidden = !running || !game.closeup;
|
||||
$('closeup-close').textContent = torch ? c.step === 'done' ? 'Continua' : 'Torna fuori' : 'Chiudi';
|
||||
$('closeup-close').textContent = torch ? c.step === 'done' ? 'Continua' : 'Torna fuori' : c && story.closeups[c.id].type === 'bocce' ? 'Lascia stare' : 'Chiudi';
|
||||
$('panel').inert = !!c;
|
||||
canvas.classList.toggle('torch', !!torch);
|
||||
canvas.setAttribute('aria-label', torch ? 'Fessura: sposta la luce con mouse, dito o frecce. Invio cerca il prossimo dettaglio; Esc esce.' : "La scena dell'avventura");
|
||||
|
||||
+48
-2
@@ -107,6 +107,22 @@ export const story = {
|
||||
// Primi piani interattivi: libro dell'oratorio e ricerca con la torcia nella fessura.
|
||||
// Le immagini (Codex, assets/closeups/) hanno l'etichetta e le pagine vuote: titolo e «FINE» li scrive il codice.
|
||||
closeups: {
|
||||
// Le bocce al pallaio contro Mariano, il campione (idea di Andrea, 25 ottobre): solo per gloria.
|
||||
bocce: {
|
||||
type: 'bocce',
|
||||
picture: 'assets/closeups/pallaio.png',
|
||||
names: { player: 'Andrea', cpu: 'Mariano' },
|
||||
win: [
|
||||
{ say: 'Aspetta, ci vuole il metro...', who: 'mariano', pose: 'measure' },
|
||||
{ say: 'Hai vinto, ti ci porto!', who: 'mariano', pose: 'laugh' },
|
||||
{ set: 'bocce_vinte' },
|
||||
],
|
||||
lose: [
|
||||
{ say: 'Bocciato! Al pallaio comando io.', who: 'mariano', pose: 'cheer' },
|
||||
{ say: 'Quando vuoi la rivincita, sono qui.', who: 'mariano' },
|
||||
],
|
||||
quit: [{ say: 'Già stanco? Il pallaio ti aspetta.', who: 'mariano', pose: 'laugh' }],
|
||||
},
|
||||
fessura: {
|
||||
type: 'torch',
|
||||
picture: 'assets/closeups/palio-2007.jpg',
|
||||
@@ -155,6 +171,7 @@ export const story = {
|
||||
arcu: { name: 'L\'Arcu', color: '#7fb2ff', sprite: 'assets/characters/arcu.json' },
|
||||
tozzino: { name: 'Il Tozzino', color: '#c8f5b0', sprite: 'assets/characters/tozzino.json' },
|
||||
barondeduke: { name: 'Il BaronDeDuke', color: '#e0c090', sprite: 'assets/characters/barondeduke.json' },
|
||||
mariano: { name: 'Mariano', color: '#ffb0a3', sprite: 'assets/characters/mariano.json' },
|
||||
},
|
||||
items: {
|
||||
squama: {
|
||||
@@ -1424,7 +1441,9 @@ export const story = {
|
||||
name: 'Il tennis della chiesa',
|
||||
map: [66, 51],
|
||||
background: 'assets/scenes/tennis-chiesa.png',
|
||||
speakers: { tosca: [292, 196] },
|
||||
speakers: { tosca: [292, 196], mariano: [500, 234] },
|
||||
// Mariano, il campione del pallaio (sprite di Codex dalla foto di Andrea, 25 ottobre).
|
||||
people: [{ who: 'mariano', at: [500, 290], facing: -1, idle: 'stand', talk: ['talk', 'explain'], blink: 'blink', every: 4.4 }],
|
||||
walk: [[0, 360], [0, 300], [200, 290], [420, 268], [640, 258], [640, 360]],
|
||||
depth: [[258, 0.7], [360, 1.15]],
|
||||
entry: [320, 350],
|
||||
@@ -1433,7 +1452,7 @@ export const story = {
|
||||
id: 'pallaio', name: 'il pallaio', box: [300, 248, 340, 40], at: [400, 300], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'Il pallaio, dove si gioca a bocce. Qui un punto si discute più di un rigore.' }],
|
||||
use: [{ say: 'Senza bocce, e senza un vecchietto che misura col metro, non è una partita.' }],
|
||||
use: [{ say: 'Una partita? Chiedo a Mariano, il campione.' }, { dialogue: 'mariano' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1464,6 +1483,13 @@ export const story = {
|
||||
{
|
||||
id: 'al_cancello', name: 'dal cancello verde, verso il posticino della Carza', box: [470, 140, 120, 130], at: [515, 280], exit: { scene: 'carza', at: [440, 290], face: 'left' },
|
||||
},
|
||||
{
|
||||
id: 'mariano', name: 'Mariano', box: [482, 238, 36, 52], at: [455, 300], face: 'right', default: 'talk',
|
||||
actions: {
|
||||
look: [{ say: 'Mariano, il campione del pallaio. Gli occhiali in testa, il sorriso di chi ha già vinto.' }],
|
||||
talk: [{ dialogue: 'mariano' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Fra la Fortezza e i cipressi: la stradina fra le vigne, la casa del maestro Bacci, e lassù il Trebbio.
|
||||
@@ -2095,6 +2121,26 @@ export const story = {
|
||||
},
|
||||
},
|
||||
},
|
||||
// Mariano al pallaio: una mano a bocce, solo per gloria; se Andrea vince, la promessa di Brento Sanico.
|
||||
mariano: {
|
||||
nodes: {
|
||||
start: {
|
||||
options: [
|
||||
{
|
||||
text: 'Mariano! Facciamo una partita a bocce?', when: '!bocce_vinte', next: 'end',
|
||||
then: [
|
||||
{ say: 'Con me? Al pallaio sono il campione.', who: 'mariano', pose: 'explain' },
|
||||
{ say: 'Facciamo così: se mi batti, ti porto a Brento Sanico.', who: 'mariano' },
|
||||
{ closeup: 'bocce' },
|
||||
],
|
||||
},
|
||||
{ text: 'La rivincita?', when: 'bocce_vinte', next: 'end', then: [{ say: 'Il campione non scappa. Tira!', who: 'mariano', pose: 'throw' }, { closeup: 'bocce' }] },
|
||||
{ text: 'Allora, Brento Sanico?', when: 'bocce_vinte', then: [{ say: 'Promesso: ti ci porto. Ma prima ritrova il palio, che senza festa non si parte.', who: 'mariano', pose: 'explain' }] },
|
||||
{ text: 'Ciao Mariano.', next: 'end' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
// Lo "psicologo": il castello ascolta e risponde con un indizio su quello che manca.
|
||||
castello: {
|
||||
nodes: {
|
||||
|
||||
Reference in New Issue
Block a user