Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15ae2b8221 | ||
|
|
f895b7979d | ||
|
|
bec7f067bc | ||
|
|
e50a3e80e9 | ||
|
|
afb0b931c5 | ||
|
|
fb1568259b | ||
|
|
c1049bf69d | ||
|
|
b05fe5b302 | ||
|
|
505eda01a3 | ||
|
|
d329cbd6ef | ||
|
|
1e4dcaaf3b | ||
|
|
4c5ec099d0 | ||
|
|
b2ec499590 | ||
|
|
75b532cda0 | ||
|
|
68686efe03 | ||
|
|
208e1d9535 | ||
|
|
f2a0f22f4a | ||
|
|
dea124933e | ||
|
|
51a46fe5a0 | ||
|
|
f6fb8922af | ||
|
|
bd7fbc3003 | ||
|
|
8b957c7cf7 | ||
|
|
f1de62f01e |
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"image": "andrea-basket.png",
|
||||
"height": 120,
|
||||
"frames": {
|
||||
"hold": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 51,
|
||||
"h": 120,
|
||||
"ax": 19.1,
|
||||
"ay": 120
|
||||
},
|
||||
"load": {
|
||||
"x": 53,
|
||||
"y": 0,
|
||||
"w": 62,
|
||||
"h": 106,
|
||||
"ax": 32.9,
|
||||
"ay": 106
|
||||
},
|
||||
"shoot": {
|
||||
"x": 117,
|
||||
"y": 0,
|
||||
"w": 69,
|
||||
"h": 124,
|
||||
"ax": 30.0,
|
||||
"ay": 124
|
||||
},
|
||||
"cheer": {
|
||||
"x": 188,
|
||||
"y": 0,
|
||||
"w": 59,
|
||||
"h": 118,
|
||||
"ax": 26.5,
|
||||
"ay": 118
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"image": "andrea-pesca.png",
|
||||
"height": 120,
|
||||
"frames": {
|
||||
"hold": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 101,
|
||||
"h": 120,
|
||||
"ax": 31.2,
|
||||
"ay": 120
|
||||
},
|
||||
"cast": {
|
||||
"x": 103,
|
||||
"y": 0,
|
||||
"w": 145,
|
||||
"h": 89,
|
||||
"ax": 56.1,
|
||||
"ay": 89
|
||||
},
|
||||
"reel": {
|
||||
"x": 250,
|
||||
"y": 0,
|
||||
"w": 102,
|
||||
"h": 107,
|
||||
"ax": 32.5,
|
||||
"ay": 107
|
||||
},
|
||||
"show": {
|
||||
"x": 354,
|
||||
"y": 0,
|
||||
"w": 129,
|
||||
"h": 113,
|
||||
"ax": 87.9,
|
||||
"ay": 113
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 276 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 456 KiB |
|
After Width: | Height: | Size: 280 KiB |
|
After Width: | Height: | Size: 233 KiB |
@@ -211,9 +211,9 @@
|
||||
"file": "andrea-22b8efe94b.mp3",
|
||||
"sec": 1.75
|
||||
},
|
||||
"pescatori|Si preparano le canne: domattina si va a pescare al lago dell'Anatre, dietro Cardetole!": {
|
||||
"file": "pescatori-486103d189.mp3",
|
||||
"sec": 3.74
|
||||
"pescatori|Si preparano le canne: si va a pescare al lago dell'Anatre, dietro Cardetole!": {
|
||||
"file": "pescatori-e7edfae41f.mp3",
|
||||
"sec": 3.51
|
||||
},
|
||||
"pescatori|Guarda questa: la lenza la regge anche una carpa di dieci chili.": {
|
||||
"file": "pescatori-afda722e0c.mp3",
|
||||
@@ -543,9 +543,9 @@
|
||||
"file": "andrea-5e37f70197.mp3",
|
||||
"sec": 5.35
|
||||
},
|
||||
"andrea|Il campo da basket del Circolo, lassù oltre la rete. Prima o poi due tiri ce li faccio.": {
|
||||
"file": "andrea-8cd2b5a36d.mp3",
|
||||
"sec": 6.49
|
||||
"andrea|Il campo da basket del Circolo, lassù oltre la rete. Due tiri ce li faccio sempre volentieri.": {
|
||||
"file": "andrea-3c4ee692ad.mp3",
|
||||
"sec": 7.55
|
||||
},
|
||||
"andrea|La fermata della corriera. Da qui si va dappertutto. Se passa.": {
|
||||
"file": "andrea-ccd735c926.mp3",
|
||||
@@ -707,6 +707,42 @@
|
||||
"file": "andrea-60f39f0dd7.mp3",
|
||||
"sec": 6.48
|
||||
},
|
||||
"andrea|L'edicola. Qui compravo le cassette per il Commodore 64: trenta giochi a mille lire, diceva la copertina.": {
|
||||
"file": "andrea-a8283c5640.mp3",
|
||||
"sec": 8.27
|
||||
},
|
||||
"andrea|Si infilava la cassetta, si scriveva LOAD, si premeva play... e si aspettava dieci minuti col registratore che cigolava.": {
|
||||
"file": "andrea-753071634e.mp3",
|
||||
"sec": 8.67
|
||||
},
|
||||
"andrea|Poi, quando andava bene, partiva il gioco. Quando andava male: LOAD ERROR. E si riavvolgeva tutto. Bei tempi.": {
|
||||
"file": "andrea-c99539e242.mp3",
|
||||
"sec": 10.4
|
||||
},
|
||||
"andrea|Chiusa. E comunque le cassette non le fanno più. Neanche i Commodore.": {
|
||||
"file": "andrea-4fb258d12c.mp3",
|
||||
"sec": 5.79
|
||||
},
|
||||
"andrea|Vorrei un Topolino e trenta giochi a mille lire. ...Niente, parlo con una serranda.": {
|
||||
"file": "andrea-0ad7d35251.mp3",
|
||||
"sec": 5.97
|
||||
},
|
||||
"andrea|La banca. Il bancomat funziona solo il mercoledì delle settimane pari, negli anni bisestili, quando Venere si allinea a Plutone.": {
|
||||
"file": "andrea-01433de83e.mp3",
|
||||
"sec": 9.25
|
||||
},
|
||||
"andrea|Oggi? Not today.": {
|
||||
"file": "andrea-a698d91684.mp3",
|
||||
"sec": 2.87
|
||||
},
|
||||
"andrea|Chiusa. Come il bancomat, del resto.": {
|
||||
"file": "andrea-f508d2ed4c.mp3",
|
||||
"sec": 3.69
|
||||
},
|
||||
"andrea|Carta dentro, carta fuori. «Servizio momentaneamente non disponibile». Momentaneamente dal 1998.": {
|
||||
"file": "andrea-d482267e5d.mp3",
|
||||
"sec": 9.3
|
||||
},
|
||||
"andrea|Cistola. Il talento di via del Pignone.": {
|
||||
"file": "andrea-2b47355c2c.mp3",
|
||||
"sec": 3.81
|
||||
@@ -739,6 +775,98 @@
|
||||
"file": "andrea-7fd73fbb77.mp3",
|
||||
"sec": 2.66
|
||||
},
|
||||
"andrea|Le colonnine con le catene: le macchine di qua non passano.": {
|
||||
"file": "andrea-7967c3d476.mp3",
|
||||
"sec": 4.05
|
||||
},
|
||||
"andrea|Io sì. Scavalcare le catene è uno sport che pratico da quando avevo sei anni.": {
|
||||
"file": "andrea-db2e00019a.mp3",
|
||||
"sec": 6.2
|
||||
},
|
||||
"andrea|Una tenda a righe bianche e rosse. Fa tanto spiaggia: peccato che il mare sia di là dall'Appennino.": {
|
||||
"file": "andrea-b8038d49dc.mp3",
|
||||
"sec": 6.68
|
||||
},
|
||||
"andrea|Il muro di sasso con l'edera e la ringhiera in cima. Lassù c'è il verde della villa.": {
|
||||
"file": "andrea-1ec3557612.mp3",
|
||||
"sec": 5.8
|
||||
},
|
||||
"andrea|Via dell'Antica Posta. Chissà se la posta antica arrivava prima di quella moderna.": {
|
||||
"file": "andrea-c0f51c8219.mp3",
|
||||
"sec": 6.36
|
||||
},
|
||||
"andrea|La torre dell'orologio, vista dal retro. Anche da qui segna un'ora tutta sua.": {
|
||||
"file": "andrea-a196260e7c.mp3",
|
||||
"sec": 5.34
|
||||
},
|
||||
"andrea|La porta del Comune. Dal retro, ma sempre Comune.": {
|
||||
"file": "andrea-66cab2e717.mp3",
|
||||
"sec": 4.23
|
||||
},
|
||||
"andrea|Chiusa. Anche dalla porta di dietro, senza un modulo non si entra.": {
|
||||
"file": "andrea-835648f0b6.mp3",
|
||||
"sec": 4.54
|
||||
},
|
||||
"andrea|Una scultura moderna, lunga e appuntita. Secondo me è un'antenna per parlare coi marziani.": {
|
||||
"file": "andrea-d7c8824e13.mp3",
|
||||
"sec": 7.13
|
||||
},
|
||||
"andrea|Questa sembra un sasso. Ma sta su un piedistallo, quindi è arte.": {
|
||||
"file": "andrea-d8a1173809.mp3",
|
||||
"sec": 4.91
|
||||
},
|
||||
"andrea|Pesa più di me. E poi l'arte non si porta via in tasca.": {
|
||||
"file": "andrea-51f23d7ac6.mp3",
|
||||
"sec": 4.15
|
||||
},
|
||||
"andrea|Un vasone di fiori. Qualcuno se ne prende cura, al Comune.": {
|
||||
"file": "andrea-1fa2d33c2f.mp3",
|
||||
"sec": 4.49
|
||||
},
|
||||
"andrea|Due gatti che dormono sull'erba. Il Palio, per loro, può anche aspettare.": {
|
||||
"file": "andrea-1c8aae44f9.mp3",
|
||||
"sec": 6.01
|
||||
},
|
||||
"andrea|Micio, micio... Niente. Per meno di una scatoletta non aprono neanche un occhio.": {
|
||||
"file": "andrea-7ea38df174.mp3",
|
||||
"sec": 6.41
|
||||
},
|
||||
"andrea|Svegliare un gatto che dorme al sole? Non sono così coraggioso.": {
|
||||
"file": "andrea-17ccb76f0c.mp3",
|
||||
"sec": 4.55
|
||||
},
|
||||
"andrea|Due panchine all'ombra del muro. Un posto per chi non ha un palio da ritrovare.": {
|
||||
"file": "andrea-8136bec81c.mp3",
|
||||
"sec": 4.95
|
||||
},
|
||||
"andrea|Mi siederei volentieri, ma il Palio non si ritrova da solo.": {
|
||||
"file": "andrea-735b8b0920.mp3",
|
||||
"sec": 3.38
|
||||
},
|
||||
"andrea|Un ponticello sopra la strada, da una casa all'altra. Per andare dal vicino senza fare le scale.": {
|
||||
"file": "andrea-04381e498b.mp3",
|
||||
"sec": 7.67
|
||||
},
|
||||
"andrea|Il muro di Villa Schifanoia, alto alto, con l'edera che ormai lo tiene su lei.": {
|
||||
"file": "andrea-4deca437c8.mp3",
|
||||
"sec": 5.28
|
||||
},
|
||||
"andrea|Una saracinesca color ruggine. Dietro, scommetto, una macchina e trent'anni di roba da buttare.": {
|
||||
"file": "andrea-b72a76810b.mp3",
|
||||
"sec": 7.04
|
||||
},
|
||||
"andrea|Chiusa. E se la apro io, mi tocca anche fare ordine.": {
|
||||
"file": "andrea-3b7d5860dc.mp3",
|
||||
"sec": 4.36
|
||||
},
|
||||
"andrea|Panni stesi al sole. Se piove, qui lo sanno tutti prima del telegiornale.": {
|
||||
"file": "andrea-1c6bb20507.mp3",
|
||||
"sec": 5.41
|
||||
},
|
||||
"andrea|Rubare i panni stesi? Mi conoscono tutti, mi ritroverebbero prima del Palio.": {
|
||||
"file": "andrea-5b0b04fef8.mp3",
|
||||
"sec": 6.3
|
||||
},
|
||||
"andrea|L'area feste. D'estate ci montano la pista da ballo, le tavolate e lo stand della pasta.": {
|
||||
"file": "andrea-a942b2723e.mp3",
|
||||
"sec": 6.77
|
||||
@@ -1207,6 +1335,22 @@
|
||||
"file": "andrea-3e2f9b4424.mp3",
|
||||
"sec": 6.42
|
||||
},
|
||||
"andrea|La pozza. Anche il quindici d'agosto è sempre bella piena.": {
|
||||
"file": "andrea-8c0ccd6cd7.mp3",
|
||||
"sec": 5.09
|
||||
},
|
||||
"andrea|Un giorno ci casco dentro e ci affogo: il primo annegato in mezzo al bosco.": {
|
||||
"file": "andrea-d7a9731b0f.mp3",
|
||||
"sec": 4.59
|
||||
},
|
||||
"andrea|Un bagno nella pozza? No, grazie: non so quanto è fonda, e non lo voglio sapere.": {
|
||||
"file": "andrea-97a7504f87.mp3",
|
||||
"sec": 6.15
|
||||
},
|
||||
"andrea|Una rete vecchia, tenuta su coi pali di legno e con la speranza.": {
|
||||
"file": "andrea-bbd7781659.mp3",
|
||||
"sec": 4.13
|
||||
},
|
||||
"andrea|Giù a destra, oltre le vigne, c'è la casa del maestro Bacci. Mezzo paese ha imparato a leggere e scrivere da lui.": {
|
||||
"file": "andrea-060513a152.mp3",
|
||||
"sec": 8.65
|
||||
@@ -1984,8 +2128,8 @@
|
||||
"sec": 1.24
|
||||
},
|
||||
"andrea|S'ha andà a fallo!?!?": {
|
||||
"file": "andrea-b25aeefc97.mp3",
|
||||
"sec": 1.16
|
||||
"file": "andrea-bb61f7641e.mp3",
|
||||
"sec": 2.17
|
||||
},
|
||||
"andrea|Ci si becca da i' Trappola, ai' Barrettino!": {
|
||||
"file": "andrea-fca6f59a70.mp3",
|
||||
@@ -2035,6 +2179,74 @@
|
||||
"file": "arcu-406cd02197.mp3",
|
||||
"sec": 4.46
|
||||
},
|
||||
"andrea|S'ha andà a fallo?!?": {
|
||||
"file": "andrea-c80714aae7.mp3",
|
||||
"sec": 1.08
|
||||
},
|
||||
"andrea|Quanti ne prendete, di solito?": {
|
||||
"file": "andrea-1d758ed60c.mp3",
|
||||
"sec": 1.84
|
||||
},
|
||||
"andrea|Buona pesca, ragazzi.": {
|
||||
"file": "andrea-cb3510ac67.mp3",
|
||||
"sec": 1.85
|
||||
},
|
||||
"pescatori|Gnamo! La canna ti si dà noi.": {
|
||||
"file": "pescatori-32054cfa54.mp3",
|
||||
"sec": 2.97
|
||||
},
|
||||
"pescatori|Come peschi?": {
|
||||
"file": "pescatori-520fe85721.mp3",
|
||||
"sec": 0.56
|
||||
},
|
||||
"pescatori|Dipende da chi racconta. Il Nanni dice dieci, il cestino dice due.": {
|
||||
"file": "pescatori-901d65f3bc.mp3",
|
||||
"sec": 4.09
|
||||
},
|
||||
"pescatori|Se vedi il palio, salutacelo!": {
|
||||
"file": "pescatori-52125dc78f.mp3",
|
||||
"sec": 1.74
|
||||
},
|
||||
"andrea|Alla bona, tanto per bagnare la lenza.": {
|
||||
"file": "andrea-b74e348e25.mp3",
|
||||
"sec": 2.55
|
||||
},
|
||||
"pescatori|Alla bona. Due pesci e si va a casa contenti.": {
|
||||
"file": "pescatori-8af3768e38.mp3",
|
||||
"sec": 3.26
|
||||
},
|
||||
"pescatori|Seria: tre pesci, e lo stivale non conta.": {
|
||||
"file": "pescatori-e417a89a3a.mp3",
|
||||
"sec": 2.82
|
||||
},
|
||||
"pescatori|A fassi male? Quattro pesci, e le carpe tirano come trattori.": {
|
||||
"file": "pescatori-befa87eee8.mp3",
|
||||
"sec": 4.17
|
||||
},
|
||||
"andrea|Due tiri alla bona, tanto per scaldarmi.": {
|
||||
"file": "andrea-d564365fc2.mp3",
|
||||
"sec": 2.53
|
||||
},
|
||||
"andrea|Due tiri seri.": {
|
||||
"file": "andrea-ec8f0db709.mp3",
|
||||
"sec": 1.32
|
||||
},
|
||||
"andrea|Lascia stare, ho il palio da trovare.": {
|
||||
"file": "andrea-f5519518f2.mp3",
|
||||
"sec": 1.99
|
||||
},
|
||||
"andrea|Tre su dieci e sono contento.": {
|
||||
"file": "andrea-5e3315a040.mp3",
|
||||
"sec": 2.33
|
||||
},
|
||||
"andrea|Cinque su dieci. Come ai tempi del Circolino.": {
|
||||
"file": "andrea-1bc731bcdf.mp3",
|
||||
"sec": 3.59
|
||||
},
|
||||
"andrea|Sette su dieci, senza guardare la parabola. Vediamo chi sono.": {
|
||||
"file": "andrea-fa062d00bd.mp3",
|
||||
"sec": 4.95
|
||||
},
|
||||
"andrea|Mariano! Facciamo una partita a bocce?": {
|
||||
"file": "andrea-83b1337b4d.mp3",
|
||||
"sec": 3.31
|
||||
@@ -2279,6 +2491,38 @@
|
||||
"file": "mariano-39e27158b7.mp3",
|
||||
"sec": 2.83
|
||||
},
|
||||
"andrea|Che mano! Jordan, fatti da parte.": {
|
||||
"file": "andrea-3a52789f04.mp3",
|
||||
"sec": 2.98
|
||||
},
|
||||
"andrea|Il ferro oggi era stretto. Riprovo dopo.": {
|
||||
"file": "andrea-2e9d8549c8.mp3",
|
||||
"sec": 3.36
|
||||
},
|
||||
"andrea|Lascio il pallone lì. Tanto lo sanno tutti che è mio.": {
|
||||
"file": "andrea-ee42022092.mp3",
|
||||
"sec": 3.98
|
||||
},
|
||||
"pescatori|Oh, hai visto? Mica male, per una canna di bambù.": {
|
||||
"file": "pescatori-2583bf891f.mp3",
|
||||
"sec": 3.79
|
||||
},
|
||||
"andrea|Stasera si frigge. Ma prima il palio.": {
|
||||
"file": "andrea-b00b469ef8.mp3",
|
||||
"sec": 3.48
|
||||
},
|
||||
"pescatori|I pesci c'erano. Eri te che non c'eri.": {
|
||||
"file": "pescatori-53d5588588.mp3",
|
||||
"sec": 2.41
|
||||
},
|
||||
"andrea|Si rifà. Dopo il palio.": {
|
||||
"file": "andrea-5ebf35bc74.mp3",
|
||||
"sec": 2.37
|
||||
},
|
||||
"andrea|Lascio la canna ai ragazzi. Il lago non scappa, il palio sì.": {
|
||||
"file": "andrea-72bf22a3f9.mp3",
|
||||
"sec": 4.49
|
||||
},
|
||||
"ing_tozzi|Venti. Hai vinto te, stasera il caffè lo pago io.": {
|
||||
"file": "ing_tozzi-51f855478a.mp3",
|
||||
"sec": 4.42
|
||||
|
||||
@@ -105,18 +105,43 @@ si segna qui con la data e il punto di `src/story/story.js` dove sta; le persone
|
||||
- *Regia alla fessura*: col meccanismo del primo piano (`closeup`) il buio, la torcia, un istante di silenzio,
|
||||
poi gli occhi. Due immagini di Codex.
|
||||
- *Segreti facoltativi del paese* con pagine illustrate nel taccuino: funzione nuova, dopo la storia principale.
|
||||
- **Altri minigiochi come le bocce** (25 ottobre 2026): ognuno è un primo piano (`closeup` con il suo `type`, come
|
||||
`bocce`), con comandi per dito, mouse e tastiera, i suoi test e una battuta per chi vince e per chi perde.
|
||||
Il posto naturale sono le gare del Palio del secondo capitolo:
|
||||
- *Passamano dei mattoni* (da fare per primo): un gioco di ritmo, i mattoni arrivano lungo la fila e si passano al
|
||||
momento giusto; sbagliando il tempo ne cade uno. Facile col dito, e chiude il cerchio col finale (i mattoni
|
||||
portati in Fortezza).
|
||||
- **Altri minigiochi** (25 ottobre 2026, rivisti il 28 ottobre con le proposte di Claude che ad Andrea piacciono):
|
||||
ognuno è un primo piano (`closeup` con il suo `type`), con comandi per dito, mouse e tastiera, i suoi test, una
|
||||
battuta per chi vince e per chi perde, e a fine lavoro la revisione critica di Codex se Andrea la concede.
|
||||
Già fatti: briscola, tressette e scopa all'oratorio; le bocce al pallaio; il biliardo coi birilli al Circolo
|
||||
4 Maggio; «Fai due tiri» al campo da basket del Circolo (27 ottobre). I prossimi due, se Andrea è d'accordo:
|
||||
il calcio balilla (riusa la fisica del biliardo, fa subito bar) e il tiro alla fune (è il Palio, e vale come
|
||||
enigma del rione).
|
||||
Le gare del Palio (il secondo capitolo: passamano, tiro alla fune, carrette, lavandaie, caccia al tesoro):
|
||||
- *Passamano dei mattoni*: un gioco di ritmo, i mattoni arrivano lungo la fila e si passano al momento giusto,
|
||||
un tasto per ogni passaggio; sbagliando il tempo ne cade uno. Facile col dito, e chiude il cerchio col finale
|
||||
(i mattoni portati in Fortezza).
|
||||
- *Tiro alla fune a Cardetole*: si batte a ritmo (spazio o tocco) seguendo una barra che oscilla, lo sforzo
|
||||
della squadra; chi perde il tempo scivola. Semplice da fare, molto da palio; contro il bullo del rione, come in
|
||||
`docs/PROGETTO.md`.
|
||||
- *Le carrette*: corsa in discesa vista di lato, a destra e sinistra fra buche e balle di paglia. La più
|
||||
spettacolare, la più lunga da disegnare.
|
||||
- *Bandierine (lavanderine)*: correre a prendere la bandiera prima dell'avversario, di riflessi.
|
||||
spettacolare, la più lunga da disegnare: il «gioco grosso», con Mitrugno (consenso).
|
||||
- *Le lavandaie* (Andrea, 28 ottobre; prima era segnato male come «bandierine»): un vascone pieno d'acqua con
|
||||
dentro gli stracci, pezzi di stoffa dei quattro colori dei rioni mescolati; ogni squadra deve ripescare i suoi.
|
||||
Come minigioco: vista dall'alto sul vascone, gli stracci galleggiano e girano, si tocca il proprio colore per
|
||||
tirarlo fuori, un errore (il colore di un altro) fa perdere tempo; contro il tempo o contro le altre squadre.
|
||||
- *Caccia al tesoro*: un giro di indizi fra le scene che ci sono già, senza un minigioco nuovo.
|
||||
Fuori dal Palio:
|
||||
- *Briscola al circolino* con l'Arcu e il Barone (la gag del «Male male si divide»): una mano corta.
|
||||
- *Rigori all'A. Bettini*: mira e forza come alle bocce, contro un portiere. Andrea ha ritrovato la foto di squadra
|
||||
sul campo in terra (porta, rete, la torre della Pieve dietro, anni '90): lo sfondo del minigioco lo fa Codex da quella,
|
||||
senza le persone (per i volti serve il consenso). Da salvare in `assets/raw/foto/campo-bettini-squadra.jpg`.
|
||||
Al bar e al circolo:
|
||||
- *Calcio balilla al River*: due stecche sole (difesa e attacco), vista dall'alto come il biliardo, che ha già
|
||||
la fisica delle sponde. Il gioco da bar più sampierino che c'è; il River «coi flipper e le figurine» lo cita già.
|
||||
- *Flipper del River*: più lavoro, ma la fisica delle biglie c'è già.
|
||||
- *Freccette al Circolo*: la mira vaga e si ferma al clic, tre freccette, si chiude il 301. Poco codice.
|
||||
- *Morra*: niente fisica, tutto dialogo e tempismo; molto toscana, le grida dei numeri in voce XTTS.
|
||||
Sport:
|
||||
- *Rigori all'A. Bettini*: mira e forza come alle bocce, contro un portiere che si butta. Andrea ha ritrovato la
|
||||
foto di squadra sul campo in terra (porta, rete, la torre della Pieve dietro, anni '90): lo sfondo lo fa Codex
|
||||
da quella, senza le persone (per i volti serve il consenso). Da salvare in `assets/raw/foto/campo-bettini-squadra.jpg`.
|
||||
- *Tennis o padel al campo secondario*: la macchina del tempo compare già quando Andrea dice di voler giocare a
|
||||
padel; un «muro» a tempismo basterebbe.
|
||||
- *Ping-pong al pallaio*: il tavolo verde c'è già nello sfondo.
|
||||
Giochi da bambini del 2007 (nostalgia):
|
||||
- *Le figurine al River*: un «batti-muro», o lo scambio «ce l'ho, ce l'ho, mi manca», più dialogo che gioco.
|
||||
- *La pesca al lago dell'Anatre*: coi ragazzi delle canne in Fortezza (27 ottobre) la scusa c'è già. Tempismo:
|
||||
la lenza vibra, si tira al momento giusto. Serve la scena del lago, dietro Cardetole.
|
||||
- *Briscola al circolino* con l'Arcu e il Barone (la gag del «Male male si divide»): una mano corta; la briscola
|
||||
c'è già all'oratorio, qui conta la gag.
|
||||
|
||||
@@ -165,7 +165,7 @@ tipi riconoscibili che persone identificabili, salvo accordi.
|
||||
|
||||
Obiettivo: entrare nella Fortezza e trovare il palio. Serve l'aiuto dei tre rioni oltre a quello di Andrea; ogni
|
||||
rione vuole una sfida del **Palietto** (i giochi dei bambini del Palio: passaggio dei mattoni, tiro alla fune, corsa
|
||||
coi carretti, gioco dell'elastico), qui trasformate in enigmi comici.
|
||||
coi carretti, gioco dell'elastico, le lavandaie: gli stracci dei quattro colori nel vascone, ognuno ripesca i suoi), qui trasformate in enigmi comici.
|
||||
|
||||
Le gare che Andrea ricorda davvero:
|
||||
|
||||
|
||||
@@ -204,13 +204,41 @@ I prompt fissi (stile, scene, foglio sprite) sono in `prompts/STILE.md`, escluso
|
||||
Via del Pignone (sfondo di Codex da `assets/raw/foto/via-del-pignone.png`): sul muro di destra il
|
||||
murale di Cistola (Davide Poggi), «il talento di via del Pignone», alla maniera dei murali di Maradona a Napoli
|
||||
(volto da `assets/raw/riferimenti/cistola.png`). Consenso di Davide confermato da Andrea il 2026-10-27.
|
||||
- Via dell'Antica Posta (foto di Andrea, 28 ottobre, sfondi di Codex): dalla Piazza, a sinistra, sale verso Piazzetta
|
||||
Cambray Digny, che non è più collegata direttamente alla Piazza. A sinistra la Piazzetta del Comune (il retro del
|
||||
municipio con la torre e le sculture); in cima il muro di Villa Schifanoia, il ponticello, le panchine e due gatti.
|
||||
Dalla Piazzetta, a destra accanto al palazzo, si scende per la scalinata in un cortile coi garage che porta ai
|
||||
giardini del Comune (lato sinistro della scena del Comune); la scalinata si prende anche dalla curva a sinistra in
|
||||
cima a Via dell'Antica Posta. In Piazzetta tutto il lato sinistro porta in Via de' Medici. Da confermare con Andrea:
|
||||
il nome vero della scalinata. Battute provvisorie.
|
||||
- Il sentiero della Fortezza (foto di Andrea, 28 ottobre, sfondo di Codex): fra la Fortezza (a diritto) e la casa del
|
||||
maestro Bacci (alle spalle), che non sono più collegate direttamente. La pozza c'è anche il 15 d'agosto (Andrea).
|
||||
Il nome del sentiero è provvisorio.
|
||||
- Via Pietro Cipriani (fra Piazza e campeggio): Cinema Taiuti aperto con la locandina del Rattattù 2007 (stile da
|
||||
`assets/raw/riferimenti/rattattu-locandina.png`, scaletta illeggibile), campo da basket del Circolo: **da fare il
|
||||
gioco del basket** (idea di Andrea, 27 ottobre).
|
||||
`assets/raw/riferimenti/rattattu-locandina.png`, scaletta illeggibile). Al campo da basket del Circolo, «Fai due tiri»
|
||||
(`src/engine/basket.js`, vista di lato, sfondo e sprite di Andrea col pallone da Codex): da solo, dieci palloni, a ogni
|
||||
canestro si arretra fino alla linea da tre; tre livelli (quanti canestri per la gloria, quanta parabola mostra la
|
||||
guida). Battute provvisorie. Idea di Andrea del 27 ottobre. Revisione critica di Codex (27 ottobre) applicata per
|
||||
i punti gravi (angolo iniziale 64° con fascia di forza buona da ogni postazione, palo e tabellone da entrambi i lati,
|
||||
canestro che conta subito, pose senza doppio pallone, guida fino al primo urto, obiettivo sempre in vista). Restano
|
||||
le sue idee: «Riprova» dal risultato e record per livello; Andrea che arretra camminando; battute che dicono perché
|
||||
è uscita (corto, lungo, ferro); l'angolo anche nei comandi HTML; testi del canvas piccoli sul telefono;
|
||||
`basket_vinto` che lasci traccia nel paese; pannelli più compatti.
|
||||
- Fuori paese, dal campo secondario (a destra): le Mozzète → il ponticino → Coldaia → la falegnameria → Gabbiano;
|
||||
da Via Cipriani, indietro, Via Massorondinaio → campeggio. Sfondi da Street View, battute provvisorie.
|
||||
- In Fortezza quattro ragazzi inventati si fanno le canne da pesca col bambù per il lago dell'Anatre (sprite di Codex,
|
||||
`assets/raw/pescatori-sheet.png`, ritagliato 2x2 a mano: work, talk, laugh, proud); battuta finale di Andrea.
|
||||
Parlandoci: «S'ha andà a fallo?!?» (Andrea, 28 ottobre) e si va a pescare: minigioco `src/engine/pesca.js`
|
||||
(primo piano del lago, sfondo e Andrea con la canna di Codex; lancio tenendo premuto, tocchi finti e abboccata,
|
||||
ferrata a tempo, recupero con la lenza che si può spezzare; sei lanci; pesci più grossi al largo, lo stivale sotto
|
||||
riva; tre livelli). Battute provvisorie. Il lago come scena da visitare non c'è: serve una foto di Andrea.
|
||||
Revisione di Codex (28 ottobre) applicata nei punti gravi (galleggiante che saltava, sesto lancio bloccato dal
|
||||
pannello, invito a ferrare fuori tempo, lenza che non si spezzava mai, mira che si sbloccava) e in parecchi utili
|
||||
(un gesto solo per ferrare e recuperare, pulsante Recupera/Molla, recupero breve per i pesci piccoli, attese ripetute
|
||||
dimezzate, tocchi finti variabili, lenza dalla punta vera della canna, stivale con la sua battuta e senza peso,
|
||||
risultato deciso all'ultimo pesce). Restano le sue idee: «Porta a casa la pescata» a gloria raggiunta; premio per
|
||||
peso o varietà; i ragazzi che reagiscono (stivale, carpa, lenza rotta); paesaggio sonoro del lago (ora quello della
|
||||
Fortezza); abboccata e tensione anche nei comandi HTML e un richiamo grande sul telefono; Esc col fuoco sui pulsanti.
|
||||
- Andrea rifatto dalla sua foto del 2007 (`assets/raw/andrea-sheet-prova-2.png`; il vecchio foglio resta in `andrea-sheet.png`).
|
||||
- Strada del campeggio (poi Bosco ai Frati) e strada della scuola: per ora battute, scene da fare con le foto di Andrea.
|
||||
|
||||
|
||||
@@ -209,6 +209,27 @@
|
||||
<p>Tre bocce a testa. Tira chi non ha il punto; segna ogni boccia più vicina della migliore avversaria.
|
||||
Vince chi fa più punti nella mano.</p>
|
||||
</details>
|
||||
<details id="basket-help" hidden><summary>Come si gioca</summary>
|
||||
<p>Muovi il mouse sul campo per scegliere l’angolo del tiro: la freccia parte dalle mani di Andrea. Un clic (o un
|
||||
tocco) fissa la mira, così puoi raggiungere i comandi senza cambiarla; un altro clic la sposta. Tieni premuto sul
|
||||
campo (o Spazio) e la forza riparte da zero e sale: lascia per tirare. Oppure regola la forza e premi Tira.
|
||||
Frecce ← →: angolo più teso o più alto; Maiusc + ← →: a mezzo grado; ↑ ↓: forza. Esc esce.</p>
|
||||
<p>Dieci palloni. Si comincia da sotto; a ogni canestro si arretra di una postazione: tiro libero, media distanza,
|
||||
da tre e da lontanissimo, dove un canestro vale tre punti invece di due. Il ferro e il tabellone rimandano la
|
||||
palla: entra anche sporca, ma non da sotto. La gloria: 3 canestri alla bona, 5 seria, 7 a fassi male. Alla bona la
|
||||
guida mostra la parabola fino al primo urto, Seria solo l’inizio, A fassi male niente.</p>
|
||||
</details>
|
||||
<details id="pesca-help" hidden><summary>Come si gioca</summary>
|
||||
<p>Un tocco sull’acqua sceglie dove lanciare (o regola la forza: più lontano, pesci più grossi, ma attese più
|
||||
lunghe). Tieni premuto sulla scena (o Spazio): la forza riparte da zero e sale, lascia per lanciare; oppure premi
|
||||
Lancia. Poi si aspetta: il galleggiante ha dei tocchi finti (si abbassa appena) e un’abboccata vera (va sotto del
|
||||
tutto): premi in quel momento per ferrare, e <b>resta premuto</b>: stai già recuperando. Premendo a vuoto si
|
||||
ricupera la lenza vuota e il lancio è perso; se non ferri in tempo il pesce lo sputa, e riprova fino a tre volte.</p>
|
||||
<p>Nel recupero guarda la lenza e la barra in basso: quando il pesce strattona (la lenza trema) la barra sale verso
|
||||
il rosso, lascia andare un attimo, poi riprendi. I pesci piccoli vengono a riva in fretta, le carpe no. Dal pannello
|
||||
il pulsante alterna Recupera e Molla. Sei lanci. La gloria: 2 pesci alla bona, 3 seria, 4 a fassi male (lo stivale
|
||||
non conta, e non pesa). Frecce ← →: distanza del lancio; Esc esce.</p>
|
||||
</details>
|
||||
<details id="biliardo-help" hidden><summary>Come si gioca</summary>
|
||||
<button class="tutorial-open" type="button" data-tutorial="biliardo" hidden>Guarda il video</button>
|
||||
<p>Clic o tocco sul tavolo fissa la mira: puoi spostarti sui comandi senza cambiarla.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "leggenda-del-regolo",
|
||||
"version": "0.1.20",
|
||||
"version": "0.1.29",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import { FLOOR, RIM, BALL, BALLS, SPOTS, canThrow, left, spot, releasePoint, guide as guidePoints, status } from './basket.js';
|
||||
import { text, panel } from './bocce-render.js';
|
||||
|
||||
const ANDREA = { scale: 0.66 };
|
||||
// Quale posa: carica mentre tiene premuto; finché la palla vola Andrea resta a braccia alzate (l'unica posa senza
|
||||
// pallone), esulta quando entra, e a palla ferma ce l'ha di nuovo in mano.
|
||||
export function andreaPose(c) {
|
||||
if (c.holding) return 'load';
|
||||
if (c.step === 'flight') return c.ball.scored ? 'cheer' : 'shoot';
|
||||
if (c.step === 'settle' && c.ball?.scored) return 'cheer';
|
||||
return 'hold';
|
||||
}
|
||||
function andrea(ctx, c, sheet) {
|
||||
const s = spot(c), x = s.x, y = FLOOR;
|
||||
ctx.fillStyle = 'rgba(8,10,9,0.5)'; ctx.beginPath(); ctx.ellipse(x + 4, y, 16, 3, 0, 0, Math.PI * 2); ctx.fill();
|
||||
if (!sheet) return;
|
||||
const pose = andreaPose(c), frame = sheet.atlas.frames[pose] ?? sheet.atlas.frames.hold, k = ANDREA.scale;
|
||||
// Nel salto i piedi si staccano da terra per un attimo.
|
||||
const lift = pose === 'shoot' ? Math.round(Math.sin(Math.min(1, c.ball.time / 0.45) * Math.PI) * 10) : 0;
|
||||
ctx.drawImage(sheet.img, frame.x, frame.y, frame.w, frame.h,
|
||||
Math.round(x - frame.ax * k), Math.round(y - frame.ay * k) - lift, Math.round(frame.w * k), Math.round(frame.h * k));
|
||||
}
|
||||
function ball(ctx, b, r = BALL) {
|
||||
// L'ombra sul campo si stringe quando la palla è alta.
|
||||
const h = Math.max(0, FLOOR - b.y - r), k = Math.max(0.35, 1 - h / 260);
|
||||
ctx.fillStyle = `rgba(8,10,9,${0.45 * k})`; ctx.beginPath(); ctx.ellipse(b.x, FLOOR - 1, r * 1.2 * k, r * 0.4 * k, 0, 0, Math.PI * 2); ctx.fill();
|
||||
const shade = ctx.createRadialGradient(b.x - r * 0.35, b.y - r * 0.35, 0, b.x, b.y, r * 1.05);
|
||||
shade.addColorStop(0, '#ffb067'); shade.addColorStop(0.5, '#e8702a'); shade.addColorStop(1, '#8a3512');
|
||||
ctx.fillStyle = shade; ctx.beginPath(); ctx.arc(b.x, b.y, r, 0, Math.PI * 2); ctx.fill();
|
||||
// Le cuciture girano con la palla.
|
||||
ctx.save(); ctx.beginPath(); ctx.arc(b.x, b.y, r - 0.3, 0, Math.PI * 2); ctx.clip();
|
||||
ctx.strokeStyle = 'rgba(60,20,8,0.75)'; ctx.lineWidth = 1;
|
||||
const a = b.spin ?? 0;
|
||||
ctx.beginPath(); ctx.ellipse(b.x, b.y, r, Math.abs(Math.cos(a)) * r, 0, 0, Math.PI * 2); ctx.stroke();
|
||||
ctx.beginPath(); ctx.ellipse(b.x, b.y, Math.abs(Math.sin(a)) * r, r, 0, 0, Math.PI * 2); ctx.stroke();
|
||||
ctx.restore();
|
||||
ctx.fillStyle = '#ffd9b0'; ctx.fillRect(Math.round(b.x - r * 0.4), Math.round(b.y - r * 0.45), 1, 1);
|
||||
}
|
||||
function guide(ctx, c) {
|
||||
const r = releasePoint(c);
|
||||
// La freccia della mira c'è sempre, con la punta e un bordo scuro per staccarsi da rete e alberi; la parabola per
|
||||
// quanto concede il livello, fino al primo urto.
|
||||
const a = c.angle * Math.PI / 180, len = 22 + c.power * 18, tip = { x: r.x + Math.cos(a) * len, y: r.y - Math.sin(a) * len };
|
||||
const arrow = () => {
|
||||
ctx.beginPath(); ctx.moveTo(r.x, r.y); ctx.lineTo(tip.x, tip.y);
|
||||
for (const s of [-1, 1]) { ctx.moveTo(tip.x, tip.y); ctx.lineTo(tip.x - Math.cos(a - s * 0.5) * 6, tip.y + Math.sin(a - s * 0.5) * 6); }
|
||||
ctx.stroke();
|
||||
};
|
||||
ctx.lineWidth = 3; ctx.strokeStyle = 'rgba(20,16,10,0.8)'; arrow();
|
||||
ctx.lineWidth = 1; ctx.strokeStyle = '#fff6d2'; arrow();
|
||||
if (c.skill.preview > 0) {
|
||||
const pts = guidePoints(c, c.skill.preview);
|
||||
for (const [width, color] of [[3, 'rgba(20,16,10,0.6)'], [1, 'rgba(143,199,255,0.95)']]) {
|
||||
ctx.lineWidth = width; ctx.strokeStyle = color; ctx.setLineDash([2, 4]);
|
||||
ctx.beginPath();
|
||||
for (const [i, p] of pts.entries()) { if (i) ctx.lineTo(p.x, p.y); else ctx.moveTo(p.x, p.y); }
|
||||
ctx.stroke(); ctx.setLineDash([]);
|
||||
}
|
||||
}
|
||||
ctx.fillStyle = 'rgba(20,16,10,0.6)'; ctx.fillRect(r.x - 34, r.y - 16, 30, 11);
|
||||
text(ctx, `${c.angle.toFixed(c.angle % 1 ? 1 : 0)}°`, r.x - 6, r.y - 10, 6, '#fff4c9', 'right');
|
||||
}
|
||||
function swish(ctx, c) {
|
||||
// Un lampo di righe attorno al ferro quando la palla è appena entrata.
|
||||
const b = c.ball; if (c.step !== 'flight' || !b?.scored || b.time - b.scoredAt > 0.3) return;
|
||||
const cx = (RIM.x1 + RIM.x2) / 2;
|
||||
ctx.strokeStyle = 'rgba(255,240,170,0.8)';
|
||||
for (let i = 0; i < 6; i++) { const t = i / 6 * Math.PI * 2; ctx.beginPath(); ctx.moveTo(cx + Math.cos(t) * 20, RIM.y + Math.sin(t) * 8); ctx.lineTo(cx + Math.cos(t) * 30, RIM.y + Math.sin(t) * 13); ctx.stroke(); }
|
||||
}
|
||||
|
||||
export function drawBasket(ctx, c, def, bg, sheet) {
|
||||
ctx.save();
|
||||
if (bg) ctx.drawImage(bg, 0, 0, 640, 360);
|
||||
else { ctx.fillStyle = '#6d7a5a'; ctx.fillRect(0, 0, 640, 360); }
|
||||
// Le postazioni sul campo: un segno chiaro dove si tira ora, uno spento dove si è già stati.
|
||||
for (const [i, s] of SPOTS.entries()) {
|
||||
ctx.fillStyle = i === c.spot ? 'rgba(255,244,201,0.9)' : i < c.spot ? 'rgba(255,244,201,0.35)' : 'rgba(255,244,201,0.15)';
|
||||
ctx.fillRect(s.x - 1, FLOOR + 2, 3, 3);
|
||||
}
|
||||
andrea(ctx, c, sheet);
|
||||
for (const [i, t] of c.trails.entries()) {
|
||||
ctx.fillStyle = `rgba(255,190,120,${0.3 * (1 - t.age / 0.5)})`;
|
||||
ctx.fillRect(Math.round(t.x) - 1, Math.round(t.y) - 1, i % 2 ? 2 : 3, i % 2 ? 2 : 3);
|
||||
}
|
||||
if (canThrow(c) || c.holding) guide(ctx, c);
|
||||
if (c.ball && c.step === 'flight') ball(ctx, c.ball); // ferma, la palla è già tornata in mano ad Andrea
|
||||
swish(ctx, c);
|
||||
panel(ctx, 182, 50, 440, 24);
|
||||
text(ctx, status(c, def.names), 402, 63, 6, '#fff0cb', 'center');
|
||||
// I palloni rimasti e il conto.
|
||||
panel(ctx, 18, 86, 196, 58);
|
||||
text(ctx, 'PALLONI', 26, 98, 6, '#eed5a0');
|
||||
for (let n = 0; n < BALLS; n++) {
|
||||
const x = 30 + n * 14, y = 113;
|
||||
if (n < left(c)) ball(ctx, { x, y, spin: 0 }, 4);
|
||||
else { ctx.strokeStyle = '#766e5c'; ctx.strokeRect(x - 3, y - 3, 6, 6); }
|
||||
}
|
||||
text(ctx, `${c.baskets} su ${c.skill.goal} per la gloria · ${c.points} punti`, 26, 131, 6, c.baskets >= c.skill.goal ? '#b9e5b3' : '#d7ccb4');
|
||||
// La forza in basso a sinistra: a destra c'è il pulsante per uscire.
|
||||
if (canThrow(c) || c.holding) {
|
||||
panel(ctx, 18, 306, 176, 37);
|
||||
text(ctx, `FORZA ${Math.round(c.power * 100)}%`, 30, 318, 7);
|
||||
ctx.fillStyle = '#3e3e36'; ctx.fillRect(30, 331, 150, 5);
|
||||
ctx.fillStyle = '#b9d392'; ctx.fillRect(30, 331, Math.round(c.power * 150), 5);
|
||||
}
|
||||
if (c.step === 'result') {
|
||||
panel(ctx, 206, 91, 397, 70);
|
||||
text(ctx, c.result.winner === 'player' ? 'CHE MANO!' : 'DUE TIRI FATTI', 404, 108, 9, '#efd49e', 'center');
|
||||
text(ctx, `${c.result.baskets} canestri su ${BALLS}, ${c.result.points} punti`, 404, 132, 9, c.result.winner === 'player' ? '#b9e5b3' : '#f1d9be', 'center');
|
||||
text(ctx, c.result.winner === 'player' ? `Obiettivo ${c.result.goal}: fatto.` : `Per la gloria ne servivano ${c.result.goal}.`, 404, 150, 6, '#d6c7af', 'center');
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// «Fai due tiri»: Andrea da solo al campo del Circolo 4 Maggio (idea di Andrea, 27 ottobre). Vista di lato, nelle
|
||||
// coordinate dello schermo (640x360): x verso il canestro, y verso il basso. Un pixel è circa 2,3 cm: il ferro, a
|
||||
// 3,05 m, sta 133 px sopra il campo. Dieci palloni; a ogni canestro si arretra di una postazione, fino a quella da
|
||||
// tre. Niente avversario: il risultato è quanti canestri su dieci, e i punti (due, tre da dietro la linea).
|
||||
export const FLOOR = 296, RIM = { x1: 482, x2: 518, y: 163, r: 2 }, BOARD = { x: 519, top: 118, bottom: 178, depth: 8 }, NET = 190, POLE = { x: 562, top: 112 };
|
||||
export const BALL = 8, G = 560, V_MIN = 140, V_MAX = 590, SWING = 1.4, BALLS = 10;
|
||||
export const FIXED_STEP = 1 / 240, REST = 0.55, FLIGHT_MAX = 7;
|
||||
export const RELEASE = { dx: 18, dy: -72 }; // da dove parte la palla rispetto ai piedi di Andrea: sopra la testa, nel salto
|
||||
export const ANGLE = { min: 25, max: 78 };
|
||||
export const SPOTS = [
|
||||
{ x: 400, name: 'da sotto', points: 2 },
|
||||
{ x: 320, name: 'dal tiro libero', points: 2 },
|
||||
{ x: 240, name: 'da media distanza', points: 2 },
|
||||
{ x: 150, name: 'da tre', points: 3 },
|
||||
{ x: 90, name: 'da lontanissimo', points: 3 },
|
||||
];
|
||||
// I livelli: quanti canestri su dieci valgono la gloria, e per quanto tempo di volo la guida mostra la parabola
|
||||
// (alla bona fino al primo urto, che è quasi tutta).
|
||||
export const LEVELS = {
|
||||
bona: { goal: 3, preview: 3 },
|
||||
seria: { goal: 5, preview: 0.4 },
|
||||
male: { goal: 7, preview: 0 },
|
||||
};
|
||||
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
||||
const rad = deg => deg * Math.PI / 180;
|
||||
|
||||
export function newBasket(seed = Date.now() >>> 0, def = {}) {
|
||||
const level = LEVELS[def.level] ? def.level : 'seria';
|
||||
return { step: 'aim', t: 0, age: 0, seed, level, skill: LEVELS[level], current: 'player', spot: 0, thrown: 0, baskets: 0, points: 0,
|
||||
angle: 64, power: 0.5, holding: false, holdTime: 0, accumulator: 0, ball: null, events: [], trails: [], history: [] };
|
||||
}
|
||||
export const canThrow = c => c.step === 'aim' && c.thrown < BALLS;
|
||||
export const left = c => Math.max(0, BALLS - c.thrown);
|
||||
export const spot = c => SPOTS[c.spot];
|
||||
export const powerAt = time => clamp(time / SWING, 0, 1);
|
||||
export const speedFor = power => V_MIN + (V_MAX - V_MIN) * clamp(power, 0, 1);
|
||||
export function releasePoint(c) { const s = spot(c); return { x: s.x + RELEASE.dx, y: FLOOR + RELEASE.dy }; }
|
||||
export function velocity(c) { const v = speedFor(c.power), a = rad(c.angle); return { vx: v * Math.cos(a), vy: -v * Math.sin(a) }; }
|
||||
|
||||
export function setPower(c, value) {
|
||||
if (!canThrow(c) || c.holding || !Number.isFinite(value)) return false;
|
||||
c.power = clamp(value, 0, 1); return true;
|
||||
}
|
||||
export function setAngle(c, deg) {
|
||||
if (!canThrow(c) || c.holding || !Number.isFinite(deg)) return false;
|
||||
c.angle = clamp(deg, ANGLE.min, ANGLE.max); return true;
|
||||
}
|
||||
export const nudge = (c, dir, fine = false) => setAngle(c, c.angle + dir * (fine ? 0.5 : 2));
|
||||
// La mira segue il punto toccato: l'angolo fra la mano di Andrea e il dito. Dietro di lui non si tira.
|
||||
export function aimScreen(c, [x, y]) {
|
||||
if (!Number.isFinite(x) || !Number.isFinite(y)) return false;
|
||||
const r = releasePoint(c);
|
||||
if (x <= r.x + 4) return false;
|
||||
return setAngle(c, Math.atan2(r.y - y, x - r.x) * 180 / Math.PI);
|
||||
}
|
||||
// La parabola libera (senza urti) per `seconds` di volo: la guida la mostra per il tempo che il livello concede.
|
||||
export function arc(c, seconds, every = 0.04) {
|
||||
const r = releasePoint(c), v = velocity(c), out = [];
|
||||
for (let t = 0; t <= seconds; t += every) out.push({ x: r.x + v.vx * t, y: r.y + v.vy * t + 0.5 * G * t * t });
|
||||
return out;
|
||||
}
|
||||
// La guida: la parabola libera finché non tocca qualcosa (ferro, tabellone, palo, campo), al massimo `seconds`.
|
||||
export function guide(c, seconds) {
|
||||
const out = [];
|
||||
for (const p of arc(c, seconds)) {
|
||||
const touches = p.y + BALL > FLOOR || [RIM.x1, RIM.x2].some(x => Math.hypot(p.x - x, p.y - RIM.y) < BALL + RIM.r)
|
||||
|| (p.x + BALL > BOARD.x && p.y + BALL > BOARD.top && p.y - BALL < BOARD.bottom) || (p.x + BALL > POLE.x && p.y + BALL > POLE.top);
|
||||
out.push(p);
|
||||
if (touches) break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
export function shoot(c) {
|
||||
if (!canThrow(c) || c.holding) return false;
|
||||
const r = releasePoint(c), v = velocity(c);
|
||||
c.ball = { x: r.x, y: r.y, vx: v.vx, vy: v.vy, time: 0, bounces: 0, rim: false, board: false, under: false, scored: false, scoredAt: null, dead: false, spin: 0 };
|
||||
c.step = 'flight'; c.t = c.accumulator = 0; c.thrown++; c.trails = [];
|
||||
c.events.push({ kind: 'basket-throw', power: c.power });
|
||||
return true;
|
||||
}
|
||||
export function press(c) {
|
||||
if (!canThrow(c) || c.holding) return false;
|
||||
c.previousPower = c.power; c.holding = true; c.holdTime = c.power = 0; return true;
|
||||
}
|
||||
export function cancel(c) {
|
||||
if (!c.holding) return false;
|
||||
c.holding = false; c.power = c.previousPower; c.holdTime = 0; return true;
|
||||
}
|
||||
export function release(c) {
|
||||
if (!c.holding) return false;
|
||||
if (c.holdTime < 0.14) return cancel(c); // un tocco breve serve a mirare, non spreca un pallone
|
||||
c.holding = false; return shoot(c);
|
||||
}
|
||||
|
||||
function hit(c, kind, strength) { if (c.events.length < 12) c.events.push({ kind, power: clamp(strength, 0, 1) }); }
|
||||
// Il ferro visto di lato sono due punti: il davanti e il dietro dell'anello. La palla ci rimbalza come su un tondino.
|
||||
function rimEnd(c, b, px, py) {
|
||||
const dx = b.x - px, dy = b.y - py, d = Math.hypot(dx, dy), r = BALL + RIM.r;
|
||||
if (d >= r || d < 1e-9) return;
|
||||
const nx = dx / d, ny = dy / d, closing = b.vx * nx + b.vy * ny;
|
||||
b.x = px + nx * r; b.y = py + ny * r;
|
||||
if (closing < 0) {
|
||||
const tx = -ny, ty = nx, vt = (b.vx * tx + b.vy * ty) * 0.92, vn = -closing * 0.58;
|
||||
b.vx = vn * nx + vt * tx; b.vy = vn * ny + vt * ty;
|
||||
b.rim = true;
|
||||
if (-closing > 25) hit(c, 'basket-rim', -closing / 400); // i microurti di una palla che si posa non tintinnano
|
||||
}
|
||||
}
|
||||
function physics(c, dt) {
|
||||
const b = c.ball, before = b.y;
|
||||
b.vy += G * dt; b.x += b.vx * dt; b.y += b.vy * dt; b.time += dt; b.spin += b.vx * dt / BALL;
|
||||
// Il tabellone: una lastra verticale che guarda Andrea, ma rimanda anche una palla che torna da dietro.
|
||||
// Il palo dietro ferma quel che passa oltre, dalla sua cima in giù: sopra, la palla vola via e ricade.
|
||||
if (b.y + BALL > BOARD.top && b.y - BALL < BOARD.bottom) {
|
||||
if (b.vx > 0 && b.x + BALL > BOARD.x && b.x - BALL < BOARD.x + BOARD.depth) { b.x = BOARD.x - BALL; b.vx = -b.vx * 0.6; b.board = true; hit(c, 'basket-board', Math.abs(b.vx) / 300); }
|
||||
else if (b.vx < 0 && b.x - BALL < BOARD.x + BOARD.depth && b.x + BALL > BOARD.x) { b.x = BOARD.x + BOARD.depth + BALL; b.vx = -b.vx * 0.6; b.board = true; hit(c, 'basket-board', Math.abs(b.vx) / 300); }
|
||||
}
|
||||
if (b.x + BALL > POLE.x && b.y + BALL > POLE.top) { b.x = POLE.x - BALL; if (b.vx > 0) { b.vx = -b.vx * 0.5; b.board = true; hit(c, 'basket-board', 0.3); } }
|
||||
rimEnd(c, b, RIM.x1, RIM.y); rimEnd(c, b, RIM.x2, RIM.y);
|
||||
// Il canestro: la palla attraversa l'anello scendendo, fra il davanti e il dietro, e non da sotto.
|
||||
const inside = b.x > RIM.x1 + BALL * 0.6 && b.x < RIM.x2 - BALL * 0.6;
|
||||
if (inside && before <= RIM.y && b.y > RIM.y && b.vy > 0 && !b.scored && !b.under) {
|
||||
b.scored = true; b.scoredAt = b.time; b.vx *= 0.35; b.vy *= 0.5;
|
||||
hit(c, b.rim || b.board ? 'basket-in' : 'basket-swish', 0.7);
|
||||
// Il canestro conta subito: il punteggio e, all'ultimo pallone, il risultato non aspettano la palla ferma.
|
||||
c.baskets++; c.points += spot(c).points;
|
||||
if (c.thrown >= BALLS) finish(c);
|
||||
} else if (inside && before >= RIM.y && b.y < RIM.y && b.vy < 0) b.under = true;
|
||||
if (b.scored && b.y < NET + BALL) { b.vx *= 1 - 2.5 * dt; b.vy *= 1 - 1.2 * dt; }
|
||||
if (b.y + BALL > FLOOR) {
|
||||
b.y = FLOOR - BALL;
|
||||
if (b.vy > 40) { b.vy = -b.vy * 0.55; b.vx *= 0.85; b.bounces++; hit(c, 'basket-bounce', b.vy / -400); }
|
||||
else { b.vy = 0; b.vx *= 1 - 3 * dt; }
|
||||
}
|
||||
const still = Math.hypot(b.vx, b.vy) < 10;
|
||||
// Entrata, la palla è finita: non si aspetta che rotoli. Una palla posata sul ferro, nemmeno.
|
||||
if (b.x < -20 || b.time > FLIGHT_MAX || b.bounces > 6 || (b.y + BALL >= FLOOR - 0.5 && Math.abs(b.vx) < 6 && b.vy === 0)
|
||||
|| (b.scored && b.y > NET + 30) || (still && b.time > 1.5)) b.dead = true;
|
||||
}
|
||||
function finish(c) {
|
||||
c.result = { winner: c.baskets >= c.skill.goal ? 'player' : 'cpu', baskets: c.baskets, points: c.points, goal: c.skill.goal };
|
||||
}
|
||||
function nextBall(c) {
|
||||
const b = c.ball;
|
||||
c.history.push({ spot: c.spot, scored: b.scored, rim: b.rim, board: b.board });
|
||||
if (b.scored) c.spot = Math.min(c.spot + 1, SPOTS.length - 1);
|
||||
if (c.thrown >= BALLS) { if (!c.result) finish(c); c.step = 'result'; } else c.step = 'aim';
|
||||
c.t = 0;
|
||||
}
|
||||
export function update(c, dt) {
|
||||
if (!Number.isFinite(dt) || dt <= 0) return;
|
||||
dt = Math.min(dt, 0.25); c.t += dt;
|
||||
if (c.holding) { c.holdTime += dt; c.power = powerAt(c.holdTime); }
|
||||
if (c.step === 'flight') {
|
||||
c.accumulator += dt;
|
||||
while (c.accumulator >= FIXED_STEP && !c.ball.dead) { physics(c, FIXED_STEP); c.accumulator -= FIXED_STEP; }
|
||||
const b = c.ball;
|
||||
if (!b.dead && Math.hypot(b.vx, b.vy) > 60) { c.trails.push({ x: b.x, y: b.y, age: 0 }); if (c.trails.length > 14) c.trails.shift(); }
|
||||
if (b.dead) { c.step = 'settle'; c.t = 0; }
|
||||
} else if (c.step === 'settle' && c.t >= REST) nextBall(c);
|
||||
for (const t of c.trails) t.age += dt;
|
||||
c.trails = c.trails.filter(t => t.age < 0.5);
|
||||
}
|
||||
export function status(c, names = { player: 'Andrea' }) {
|
||||
const tally = `Canestri ${c.baskets} (ne servono ${c.skill.goal}), punti ${c.points}.`;
|
||||
if (c.step === 'result') return `${c.result.baskets} canestri su ${BALLS}, ${c.result.points} punti. ${c.result.winner === 'player' ? `Obiettivo ${c.result.goal}: fatto!` : `Ne servivano ${c.result.goal}.`}`;
|
||||
if (c.step === 'flight') return c.ball.scored ? `Dentro! ${tally}` : 'Palla in aria…';
|
||||
if (c.step === 'settle') return c.ball.scored ? `Canestro! ${tally}` : `Fuori. ${tally}`;
|
||||
return `${names.player}, pallone ${c.thrown + 1} di ${BALLS}, ${spot(c).name}. ${tally}`;
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
import { findPath, dist, inBox, boxDistance, nearestInside } from './geometry.js';
|
||||
import * as bocce from './bocce.js';
|
||||
import * as biliardo from './biliardo.js';
|
||||
import * as basket from './basket.js';
|
||||
import * as pesca from './pesca.js';
|
||||
import * as briscola from './briscola.js';
|
||||
import * as tressette from './tressette.js';
|
||||
import * as scopa from './scopa.js';
|
||||
@@ -14,6 +16,8 @@ export const MINIGAMES = {
|
||||
scopa: { create: (seed, level, deck) => scopa.newScopa(seed, { level, deck }), update: scopa.update, play: scopa.play },
|
||||
bocce: { create: (seed, level) => bocce.newBocce(seed, { level }), update: bocce.update, press: bocce.press, release: bocce.release, cancel: bocce.cancel, aim: bocce.aimScreen },
|
||||
biliardo: { create: (seed, level) => biliardo.newBiliardo(seed, { level }), update: biliardo.update, press: biliardo.press, release: biliardo.release, cancel: biliardo.cancel, aim: biliardo.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 },
|
||||
};
|
||||
|
||||
export const VERBS = ['walk', 'look', 'take', 'use', 'talk', 'open', 'give', 'drop'];
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { BANK, WATER, CASTS, left, fishCount, distanceFor, status } from './pesca.js';
|
||||
import { text, panel } from './bocce-render.js';
|
||||
import { ambientHash as hash } from './ambient.js';
|
||||
|
||||
const ANDREA = { x: 92, y: 258, scale: 0.68 }, BOYS = { x: 44, y: 262, scale: 0.5 };
|
||||
// La punta della canna in ogni posa, rispetto ai piedi di Andrea (misurata sul foglio di Codex): la lenza parte da lì.
|
||||
const TIPS = { hold: [60, -81], cast: [80, -57], reel: [43, -66], show: [60, -81] };
|
||||
const ROD_TIP = { x: ANDREA.x + TIPS.hold[0], y: ANDREA.y + TIPS.hold[1] };
|
||||
export function andreaPose(c) {
|
||||
if (c.step === 'cast') return 'cast';
|
||||
if (c.step === 'fight') return 'reel';
|
||||
if (c.step === 'settle' && c.how === 'landed') return c.fish?.gag ? 'hold' : 'show';
|
||||
return 'hold';
|
||||
}
|
||||
function sprite(ctx, sheet, pose, { x, y, scale }) {
|
||||
if (!sheet) return;
|
||||
const frame = sheet.atlas.frames[pose] ?? Object.values(sheet.atlas.frames)[0];
|
||||
ctx.drawImage(sheet.img, frame.x, frame.y, frame.w, frame.h,
|
||||
Math.round(x - frame.ax * scale), Math.round(y - frame.ay * scale), Math.round(frame.w * scale), Math.round(frame.h * scale));
|
||||
}
|
||||
// Dove sta la punta della canna: segue la posa, così la lenza parte sempre dalla cima.
|
||||
function rodTip(c) {
|
||||
const [dx, dy] = TIPS[andreaPose(c)] ?? TIPS.hold;
|
||||
return { x: ANDREA.x + dx, y: ANDREA.y + dy };
|
||||
}
|
||||
function floatAt(c) {
|
||||
const f = c.float;
|
||||
if (!f) return null;
|
||||
if (c.step === 'cast') {
|
||||
// La parabola del lancio dalla punta della canna all'acqua.
|
||||
const tip = rodTip(c), k = f.fly, x = tip.x + (f.x - tip.x) * k, y = tip.y + (WATER - tip.y) * k - Math.sin(k * Math.PI) * 60;
|
||||
return { x, y };
|
||||
}
|
||||
return { x: f.x, y: WATER + f.dip + Math.sin(c.t * 2.2 + f.x) * 0.8 };
|
||||
}
|
||||
function fishShadow(ctx, c, p) {
|
||||
const fish = c.fish; if (!fish || c.step !== 'fight') return;
|
||||
const w = 10 + fish.weight * 9, h = 4 + fish.weight * 2.5, x = p.x + 8 + Math.sin(c.t * 3) * 6, y = WATER + 22 + Math.sin(c.t * 1.7) * 5;
|
||||
ctx.fillStyle = 'rgba(20,40,38,0.55)';
|
||||
ctx.beginPath(); ctx.ellipse(x, y, w / 2, h / 2, 0, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.beginPath(); ctx.moveTo(x + w / 2, y); ctx.lineTo(x + w / 2 + 5, y - 4); ctx.lineTo(x + w / 2 + 5, y + 4); ctx.fill();
|
||||
}
|
||||
function fishIcon(ctx, f, x, y) {
|
||||
if (f.gag) { ctx.fillStyle = '#4a3a2a'; ctx.fillRect(x - 3, y - 5, 4, 8); ctx.fillRect(x - 3, y + 1, 8, 3); return; }
|
||||
const w = 6 + f.weight * 3;
|
||||
ctx.fillStyle = f.kind === 'carpa' ? '#c9a24a' : f.kind === 'tinca' ? '#5a7a3a' : f.kind === 'persico' ? '#d9b14a' : '#9fb3b8';
|
||||
ctx.beginPath(); ctx.ellipse(x, y, w / 2, 2.5 + f.weight, 0, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.beginPath(); ctx.moveTo(x - w / 2, y); ctx.lineTo(x - w / 2 - 3, y - 3); ctx.lineTo(x - w / 2 - 3, y + 3); ctx.fill();
|
||||
ctx.fillStyle = '#1a1a1a'; ctx.fillRect(Math.round(x + w / 2 - 3), Math.round(y - 1), 1, 1);
|
||||
}
|
||||
|
||||
export function drawPesca(ctx, c, def, bg, sheet, boys) {
|
||||
ctx.save();
|
||||
if (bg) ctx.drawImage(bg, 0, 0, 640, 360);
|
||||
else { ctx.fillStyle = '#5c7f7a'; ctx.fillRect(0, 0, 640, 360); ctx.fillStyle = '#7a9a4a'; ctx.fillRect(0, 240, BANK, 120); }
|
||||
// I ragazzi sulla riva, dietro Andrea, intenti alle loro canne.
|
||||
sprite(ctx, boys, 'work', BOYS);
|
||||
sprite(ctx, sheet, andreaPose(c), ANDREA);
|
||||
// I cerchi nell'acqua: al tuffo del galleggiante e quando il pesce abbocca.
|
||||
for (const r of c.ripples) {
|
||||
ctx.strokeStyle = `rgba(220,240,235,${0.6 * (1 - r.age / 1.6)})`; ctx.lineWidth = 1;
|
||||
for (const k of [0, 0.4]) { const a = r.age - k; if (a < 0) continue; ctx.beginPath(); ctx.ellipse(r.x, WATER, 4 + a * 18, 1.5 + a * 5, 0, 0, Math.PI * 2); ctx.stroke(); }
|
||||
}
|
||||
// A lancio finito (pesce in mano, lenza rotta o vuota) niente galleggiante: la lenza è fuori dall'acqua.
|
||||
const p = ['cast', 'wait', 'fight'].includes(c.step) ? floatAt(c) : null;
|
||||
if (p) {
|
||||
// La lenza, con un po' di pancia; tesa e tremante quando il pesce tira.
|
||||
const tip = rodTip(c), sag = c.step === 'fight' ? 2 - c.tension * 2 : c.step === 'wait' ? 10 : 4;
|
||||
// Trema già allo strattone, prima che la lenza si tenda: il tempo di mollare.
|
||||
const shake = c.step === 'fight' && (c.tension > 0.75 || c.danger > 0.55) ? Math.sin(c.t * 60) * 1.2 : 0;
|
||||
ctx.strokeStyle = c.step === 'fight' && c.tension > 0.85 ? '#ffb0a0' : 'rgba(240,240,230,0.85)'; ctx.lineWidth = 1;
|
||||
ctx.beginPath(); ctx.moveTo(tip.x, tip.y); ctx.quadraticCurveTo((tip.x + p.x) / 2, Math.max(tip.y, p.y) + sag + shake, p.x, p.y); ctx.stroke();
|
||||
fishShadow(ctx, c, p);
|
||||
// Il galleggiante: rosso sopra, bianco sotto; sott'acqua si vede appena.
|
||||
const under = Math.max(0, p.y - WATER);
|
||||
ctx.fillStyle = '#e2e2d8'; ctx.fillRect(Math.round(p.x) - 1, Math.round(p.y) - 1, 3, 4);
|
||||
ctx.fillStyle = '#d8402a'; ctx.fillRect(Math.round(p.x) - 1, Math.round(p.y) - 6, 3, 5);
|
||||
if (under > 0) { ctx.fillStyle = 'rgba(40,70,80,0.55)'; ctx.fillRect(Math.round(p.x) - 2, WATER, 5, Math.round(under) + 1); }
|
||||
}
|
||||
// La mira: dove cadrà il galleggiante, e il filo tratteggiato della parabola.
|
||||
if (c.step === 'aim' && c.casts < CASTS) {
|
||||
const x = distanceFor(c.power);
|
||||
ctx.strokeStyle = 'rgba(255,246,210,0.9)'; ctx.setLineDash([2, 4]);
|
||||
ctx.beginPath(); ctx.moveTo(ROD_TIP.x, ROD_TIP.y); ctx.quadraticCurveTo((ROD_TIP.x + x) / 2, ROD_TIP.y - 70, x, WATER); ctx.stroke(); ctx.setLineDash([]);
|
||||
ctx.beginPath(); ctx.ellipse(x, WATER, 7, 2.5, 0, 0, Math.PI * 2); ctx.stroke();
|
||||
ctx.beginPath(); ctx.moveTo(x, WATER - 8); ctx.lineTo(x, WATER + 8); ctx.stroke();
|
||||
}
|
||||
panel(ctx, 182, 50, 440, 24);
|
||||
text(ctx, status(c, def.names), 402, 63, 6, '#fff0cb', 'center');
|
||||
// I lanci rimasti, il cestino dei pesci.
|
||||
panel(ctx, 18, 86, 196, 58);
|
||||
text(ctx, 'LANCI', 26, 98, 6, '#eed5a0');
|
||||
for (let n = 0; n < CASTS; n++) {
|
||||
const x = 70 + n * 12, y = 98;
|
||||
if (n < left(c)) { ctx.fillStyle = '#d8402a'; ctx.fillRect(x - 1, y - 4, 3, 4); ctx.fillStyle = '#e2e2d8'; ctx.fillRect(x - 1, y, 3, 3); }
|
||||
else { ctx.strokeStyle = '#766e5c'; ctx.strokeRect(x - 2, y - 3, 5, 6); }
|
||||
}
|
||||
text(ctx, 'CESTINO', 26, 116, 6, '#eed5a0');
|
||||
c.caught.forEach((f, i) => fishIcon(ctx, f, 80 + i * 22, 116));
|
||||
const fish = fishCount(c);
|
||||
text(ctx, `${fish} su ${c.skill.goal} per la gloria · ${c.weight} kg`, 26, 133, 6, fish >= c.skill.goal ? '#b9e5b3' : '#d7ccb4');
|
||||
// In basso a sinistra: la forza del lancio, o la tensione della lenza.
|
||||
if (c.step === 'aim' || c.holding) {
|
||||
panel(ctx, 18, 306, 176, 37);
|
||||
text(ctx, `FORZA ${Math.round(c.power * 100)}%`, 30, 318, 7);
|
||||
ctx.fillStyle = '#3e3e36'; ctx.fillRect(30, 331, 150, 5);
|
||||
ctx.fillStyle = '#b9d392'; ctx.fillRect(30, 331, Math.round(c.power * 150), 5);
|
||||
} else if (c.step === 'fight') {
|
||||
panel(ctx, 18, 306, 176, 37);
|
||||
text(ctx, `LENZA ${Math.round(c.tension * 100)}%`, 30, 318, 7, c.tension > 0.85 ? '#ffb0a0' : '#f4e3c2');
|
||||
text(ctx, `riva ${Math.round((1 - Math.min(1, c.fish.dist)) * 100)}%`, 182, 318, 6, '#d7ccb4', 'right');
|
||||
ctx.fillStyle = '#3e3e36'; ctx.fillRect(30, 331, 150, 5);
|
||||
ctx.fillStyle = 'rgba(224,87,74,0.35)'; ctx.fillRect(30 + 127, 331, 23, 5); // la zona rossa: qui si molla
|
||||
ctx.fillStyle = c.tension > 0.85 ? '#e0574a' : c.tension > 0.65 ? '#e0b24a' : '#b9d392'; ctx.fillRect(30, 331, Math.round(c.tension * 150), 5);
|
||||
ctx.fillStyle = '#fff'; ctx.fillRect(30 + 127, 329, 1, 9);
|
||||
}
|
||||
if (c.step === 'result') {
|
||||
panel(ctx, 206, 91, 397, 78);
|
||||
text(ctx, c.result.winner === 'player' ? 'BELLA PESCATA!' : 'ACQUA CHETA', 404, 108, 9, '#efd49e', 'center');
|
||||
text(ctx, `${c.result.fish} pesci su ${CASTS} lanci, ${c.result.weight} kg`, 404, 130, 9, c.result.winner === 'player' ? '#b9e5b3' : '#f1d9be', 'center');
|
||||
text(ctx, c.result.winner === 'player' ? `Obiettivo ${c.result.goal}: fatto.` : `Per la gloria ne servivano ${c.result.goal}.`, 404, 148, 6, '#d6c7af', 'center');
|
||||
c.result.caught.forEach((f, i) => fishIcon(ctx, f, 404 - (c.result.caught.length - 1) * 11 + i * 22, 160));
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// La pesca al lago dell'Anatre coi ragazzi delle canne (idea di Andrea, 28 ottobre). Vista di lato, coordinate dello
|
||||
// schermo (640x360): Andrea sulla riva a sinistra, l'acqua a destra. Si tiene premuto per lanciare (più forza, più
|
||||
// lontano: e al largo i pesci sono più grossi); il galleggiante balla, ai tocchi finti non si abbocca, quando va sotto
|
||||
// davvero si ferra premendo in tempo e, senza lasciare, si recupera: quando il pesce strattona la lenza si tende, e
|
||||
// bisogna mollare un attimo. Sei lanci; la gloria dipende da quanti pesci si tirano a riva (LEVELS).
|
||||
// Rivista con la revisione di Codex del 28 ottobre (galleggiante che saltava, sesto lancio bloccato dal pannello,
|
||||
// invito a ferrare fuori tempo, lenza che non si spezzava mai, mira che si sbloccava da sola).
|
||||
export const BANK = 150, WATER = 250, NEAR = 190, FAR = 600, SHORE = NEAR - 20;
|
||||
export const CASTS = 6, SWING = 1.4, FLY = 0.8, REST = 1.4, WAIT_MAX = 45;
|
||||
export const LEVELS = {
|
||||
bona: { goal: 2, window: 1.0, pull: 0.5 },
|
||||
seria: { goal: 3, window: 0.62, pull: 0.78 },
|
||||
male: { goal: 4, window: 0.38, pull: 1.0 },
|
||||
};
|
||||
// I pesci del lago, dal più piccolo al più grosso: al largo si pescano i grossi. Lo stivale è la beffa della riva.
|
||||
export const FISHES = [
|
||||
{ kind: 'stivale', name: 'uno stivale', weight: 0.9, strength: 0.25, stamina: 0.3, far: [0, 0.25], gag: true },
|
||||
{ kind: 'lasca', name: 'una lasca', weight: 0.15, strength: 0.3, stamina: 0.5, far: [0, 0.55] },
|
||||
{ kind: 'persico', name: 'un persico sole', weight: 0.3, strength: 0.45, stamina: 0.7, far: [0.1, 0.7] },
|
||||
{ kind: 'cavedano', name: 'un cavedano', weight: 0.8, strength: 0.6, stamina: 0.9, far: [0.3, 0.9] },
|
||||
{ kind: 'tinca', name: 'una tinca', weight: 1.4, strength: 0.75, stamina: 1.1, far: [0.45, 1] },
|
||||
{ kind: 'carpa', name: 'una carpa', weight: 3.2, strength: 1, stamina: 1.4, far: [0.7, 1] },
|
||||
];
|
||||
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; }
|
||||
|
||||
export function newPesca(seed = Date.now() >>> 0, def = {}) {
|
||||
const level = LEVELS[def.level] ? def.level : 'seria';
|
||||
return { step: 'aim', t: 0, age: 0, seed, level, skill: LEVELS[level], current: 'player', casts: 0, caught: [], weight: 0,
|
||||
power: 0.5, holding: false, holdTime: 0, reeling: false, float: null, fish: null, tension: 0, pull: 0, events: [], ripples: [], last: null };
|
||||
}
|
||||
export const left = c => Math.max(0, CASTS - c.casts);
|
||||
export const fishCount = c => c.caught.filter(f => !f.gag).length;
|
||||
// Tre domande diverse: si può lanciare, si può agire (ferrare, recuperare), si può regolare la distanza.
|
||||
export const canCast = c => c.step === 'aim' && !c.holding && c.casts < CASTS;
|
||||
export const canThrow = c => canCast(c) || ((c.step === 'wait' || c.step === 'fight') && !c.holding);
|
||||
export const canAdjust = c => c.step === 'aim' && !c.holding && c.casts < CASTS;
|
||||
export const aimFree = c => c.step !== 'aim';
|
||||
export const powerAt = time => clamp(time / SWING, 0, 1);
|
||||
export const distanceFor = power => NEAR + (FAR - NEAR) * clamp(power, 0, 1);
|
||||
export function setPower(c, value) {
|
||||
if (!canAdjust(c) || !Number.isFinite(value)) return false;
|
||||
c.power = clamp(value, 0, 1); return true;
|
||||
}
|
||||
// Un tocco sull'acqua (non sul cielo, non sulla riva) sceglie dove lanciare: la forza che ci vuole per arrivarci.
|
||||
export function aimScreen(c, [x, y]) {
|
||||
if (!canAdjust(c) || !Number.isFinite(x) || !Number.isFinite(y) || x < BANK + 10 || y < WATER - 40) return false;
|
||||
return setPower(c, (x - NEAR) / (FAR - NEAR));
|
||||
}
|
||||
export const nudge = (c, dir, fine = false) => setPower(c, c.power + dir * (fine ? 0.01 : 0.05));
|
||||
|
||||
function event(c, kind, strength = 0.5) { if (c.events.length < 12) c.events.push({ kind, power: clamp(strength, 0, 1) }); }
|
||||
// Quando arriva il pesce: da uno a tre tocchi finti, a ritmo diverso, poi quello vero. Al largo si aspetta un po' di
|
||||
// più; dopo un'abboccata mancata l'attesa è la metà.
|
||||
function schedule(c) {
|
||||
const f = c.float, far = (f.x - NEAR) / (FAR - NEAR);
|
||||
f.biteAt = f.t + (2.5 + random(c) * 4 + far * 2) * (f.missed ? 0.5 : 1);
|
||||
const n = 1 + Math.floor(random(c) * 3);
|
||||
f.nibbles = Array.from({ length: n }, (_, i) => ({ at: f.biteAt - (n - i) * (0.6 + random(c) * 0.9), for: 0.18 + random(c) * 0.2 }));
|
||||
f.bitten = false;
|
||||
}
|
||||
function pickFish(c, far) {
|
||||
const pool = FISHES.filter(f => far >= f.far[0] && far <= f.far[1] && (!f.gag || random(c) < 0.35));
|
||||
const f = pool[Math.floor(random(c) * pool.length)] ?? FISHES[1];
|
||||
const size = 0.8 + random(c) * 0.5; // non tutti i cavedani sono uguali
|
||||
return { ...f, weight: Math.round(f.weight * size * 100) / 100, stamina: f.stamina * size, dist: 1, energy: 1, burst: random(c) * 6,
|
||||
reel: 0.22 / (0.7 + f.stamina * size) }; // i piccoli vengono a riva in fretta, le carpe no
|
||||
}
|
||||
export function cast(c) {
|
||||
if (!canCast(c)) return false;
|
||||
c.float = { x: distanceFor(c.power), y: WATER, t: 0, total: 0, fly: 0, dip: 0, missed: 0, from: null };
|
||||
c.step = 'cast'; c.t = 0; c.casts++; c.fish = null; c.tension = 0; c.pull = 0; c.reeling = false; c.last = null;
|
||||
event(c, 'pesca-lancio', c.power);
|
||||
return true;
|
||||
}
|
||||
// Il tasto dei comandi: in attesa ferra; nel recupero alterna recupera e molla (col dito si tiene premuto da sé).
|
||||
export function shoot(c) {
|
||||
if (c.step === 'aim') return cast(c);
|
||||
if (c.step === 'wait') return strike(c);
|
||||
if (c.step === 'fight') { c.reeling = !c.reeling; return true; }
|
||||
return false;
|
||||
}
|
||||
// Un gesto solo: premendo si ferra e, se il pesce c'è, si comincia subito a recuperare; lasciando si molla.
|
||||
export function press(c) {
|
||||
if (c.step === 'aim') { if (!canCast(c)) return false; c.previousPower = c.power; c.holding = true; c.holdTime = c.power = 0; return true; }
|
||||
if (c.step === 'wait') { strike(c); if (c.step === 'fight') c.reeling = true; return true; }
|
||||
if (c.step === 'fight') { c.reeling = true; return true; }
|
||||
return false;
|
||||
}
|
||||
export function cancel(c) {
|
||||
if (c.holding) { c.holding = false; c.power = c.previousPower; c.holdTime = 0; return true; }
|
||||
if (c.reeling) { c.reeling = false; return true; }
|
||||
return false;
|
||||
}
|
||||
export function release(c) {
|
||||
if (c.holding) {
|
||||
if (c.holdTime < 0.14) return cancel(c); // un tocco breve non spreca il lancio
|
||||
c.holding = false; return cast(c);
|
||||
}
|
||||
if (c.reeling) { c.reeling = false; return true; }
|
||||
return false;
|
||||
}
|
||||
export const biting = c => c.step === 'wait' && c.float?.bitten && c.float.t - c.float.biteAt <= c.skill.window;
|
||||
// Ferrare: dentro la finestra il pesce è preso; prima, o dopo, si recupera la lenza vuota (lancio sprecato).
|
||||
export function strike(c) {
|
||||
const f = c.float;
|
||||
if (c.step !== 'wait' || !f) return false;
|
||||
if (biting(c)) {
|
||||
c.fish = pickFish(c, (f.x - NEAR) / (FAR - NEAR));
|
||||
f.from = f.x;
|
||||
c.step = 'fight'; c.t = 0; c.tension = 0.3; c.reeling = false; c.danger = 0;
|
||||
event(c, 'pesca-ferrata', 0.8);
|
||||
} else {
|
||||
const late = f.bitten && f.t > f.biteAt;
|
||||
const touched = f.nibbles.some(n => f.t >= n.at);
|
||||
c.last = { kind: late ? 'late' : touched ? 'nibble' : 'early',
|
||||
text: late ? 'Troppo tardi: l\'ha già sputato.' : touched ? 'Era solo un tocco: a tirare così l\'ho spaventato.' : 'Ho tirato a vuoto: non aveva ancora abboccato.' };
|
||||
event(c, 'pesca-vuoto', 0.4);
|
||||
end(c, 'empty');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function end(c, how) {
|
||||
if (how === 'landed') {
|
||||
c.caught.push(c.fish);
|
||||
if (!c.fish.gag) c.weight = Math.round((c.weight + c.fish.weight) * 100) / 100;
|
||||
event(c, 'pesca-preso', 0.9);
|
||||
}
|
||||
if (how === 'broken') event(c, 'pesca-rottura', 1);
|
||||
if (how === 'free') event(c, 'pesca-vuoto', 0.6);
|
||||
c.step = 'settle'; c.t = 0; c.how = how; c.reeling = false; c.pull = 0;
|
||||
// L'ultimo lancio decide subito il risultato: uscire durante la posa non è abbandonare.
|
||||
if (c.casts >= CASTS) finish(c);
|
||||
}
|
||||
function finish(c) {
|
||||
c.result = { winner: fishCount(c) >= c.skill.goal ? 'player' : 'cpu', fish: fishCount(c), weight: c.weight, goal: c.skill.goal, caught: c.caught };
|
||||
}
|
||||
export function update(c, dt) {
|
||||
if (!Number.isFinite(dt) || dt <= 0) return;
|
||||
dt = Math.min(dt, 0.25); c.t += dt;
|
||||
if (c.holding) { c.holdTime += dt; c.power = powerAt(c.holdTime); }
|
||||
const f = c.float;
|
||||
if (c.step === 'cast') {
|
||||
f.fly = Math.min(1, c.t / FLY);
|
||||
if (c.t >= FLY) { c.step = 'wait'; f.t = 0; schedule(c); c.ripples.push({ x: f.x, age: 0 }); event(c, 'pesca-tuffo', 0.4); }
|
||||
} else if (c.step === 'wait') {
|
||||
f.t += dt; f.total += dt;
|
||||
const nibbling = f.nibbles.some(n => f.t >= n.at && f.t < n.at + n.for);
|
||||
if (!f.bitten && f.t >= f.biteAt) { f.bitten = true; event(c, 'pesca-abbocca', 0.6); c.ripples.push({ x: f.x, age: 0 }); }
|
||||
const since = f.bitten ? f.t - f.biteAt : -1;
|
||||
if (f.bitten && since > c.skill.window + 0.4) {
|
||||
// Non ferrato in tempo: il pesce se ne va, si riprova, ma non all'infinito.
|
||||
f.missed++; f.t = 0; schedule(c);
|
||||
if (f.missed >= 3) { c.last = { kind: 'gone', text: 'Tre abboccate e niente: qui non abbocca più nessuno.' }; end(c, 'empty'); }
|
||||
} else if (nibbling) { if (!f.nibbleSound) { event(c, 'pesca-tocco', 0.3); f.nibbleSound = true; } }
|
||||
else f.nibbleSound = false;
|
||||
// Sotto finché si può ferrare; poi riaffiora mentre il pesce lo sputa; i tocchi lo abbassano appena.
|
||||
f.dip = biting(c) ? 7 : f.bitten && since <= c.skill.window + 0.4 ? 3 : nibbling ? 2.5 : 0;
|
||||
if (f.total > WAIT_MAX) { c.last = { kind: 'gone', text: 'Niente. Qui i pesci dormono.' }; end(c, 'empty'); }
|
||||
} else if (c.step === 'fight') {
|
||||
const fish = c.fish, k = c.skill.pull;
|
||||
fish.burst += dt;
|
||||
// Gli strattoni: brevi, a picco; la lenza tesa li sente poco dopo, e c'è il tempo di mollare.
|
||||
const burst = 0.35 + 0.95 * Math.max(0, Math.sin(fish.burst * 2.1)) ** 3, peak = fish.strength * k * (0.3 + 0.7 * fish.energy);
|
||||
c.pull = peak * burst;
|
||||
c.danger = burst / 1.3; // quanto è forte lo strattone rispetto al massimo di questo pesce: la lenza trema prima
|
||||
const target = c.reeling ? 0.42 + c.pull * 1.1 : c.pull * 0.3;
|
||||
c.tension += (target - c.tension) * Math.min(1, dt * 6.5);
|
||||
if (c.reeling) {
|
||||
fish.dist -= dt * fish.reel * (1 - c.pull * 0.4); fish.energy = Math.max(0, fish.energy - dt * 0.09 / fish.stamina);
|
||||
// Il cigolio della lenza tesa, non a ogni fotogramma.
|
||||
c.reelSound = (c.reelSound ?? 0) - dt;
|
||||
if (c.tension > 0.6 && c.reelSound <= 0) { event(c, 'pesca-mulinello', c.tension); c.reelSound = 0.14; }
|
||||
} else { fish.dist += dt * (0.02 + 0.05 * c.pull); fish.energy = Math.max(0, fish.energy - dt * 0.03 / fish.stamina); }
|
||||
f.x = SHORE + fish.dist * (f.from - SHORE);
|
||||
f.dip = 5 + Math.sin(c.t * 7) * 2;
|
||||
if (c.tension >= 1) { c.last = { kind: 'broken', text: 'Crac! La lenza non ha retto.' }; end(c, 'broken'); }
|
||||
else if (fish.dist >= 1.35) { c.last = { kind: 'free', text: 'Ha preso troppo filo: si è slamato.' }; end(c, 'free'); }
|
||||
else if (fish.dist <= 0) {
|
||||
c.last = { kind: 'landed', text: fish.gag ? 'Uno stivale. Numero quarantatré. Di chi sarà?' : `${fish.name[0].toUpperCase()}${fish.name.slice(1)}, ${fish.weight} kg!` };
|
||||
end(c, 'landed');
|
||||
}
|
||||
} else if (c.step === 'settle' && c.t >= REST) {
|
||||
c.float = null; c.fish = null; c.tension = 0;
|
||||
if (c.casts >= CASTS) c.step = 'result'; else { c.step = 'aim'; c.t = 0; }
|
||||
}
|
||||
for (const r of c.ripples) r.age += dt;
|
||||
c.ripples = c.ripples.filter(r => r.age < 1.6);
|
||||
}
|
||||
export function status(c, names = { player: 'Andrea' }) {
|
||||
const tally = `Pesci ${fishCount(c)} (ne servono ${c.skill.goal}), ${c.weight} kg.`;
|
||||
if (c.step === 'result') return `${c.result.fish} pesci su ${CASTS} lanci, ${c.result.weight} kg. ${c.result.winner === 'player' ? `Obiettivo ${c.result.goal}: fatto!` : `Ne servivano ${c.result.goal}.`}`;
|
||||
if (c.step === 'cast') return 'Lancio…';
|
||||
if (c.step === 'wait') return biting(c) ? 'ABBOCCA! Ferra!' : c.float.bitten ? 'Lo sta sputando…' : c.float.dip > 0 ? 'Un tocco… aspetta.' : c.float.missed ? `Riprova: ${3 - c.float.missed} abboccate ancora.` : 'Il galleggiante è fermo. Si aspetta.';
|
||||
if (c.step === 'fight') return c.tension > 0.85 ? 'La lenza tira troppo: MOLLA!' : c.danger > 0.55 ? 'Strattona…' : c.reeling ? 'Recupera…' : 'Tieni premuto per recuperare.';
|
||||
if (c.step === 'settle') return c.last?.text ?? '';
|
||||
return `${names.player}, lancio ${c.casts + 1} di ${CASTS}. ${tally}`;
|
||||
}
|
||||
export const button = c => c.step === 'result' ? 'Finita' : c.step === 'wait' ? 'Ferra' : c.step === 'fight' ? (c.reeling ? 'Molla' : 'Recupera') : c.step === 'aim' ? 'Lancia' : '…';
|
||||
@@ -5,6 +5,8 @@ import { ambientEffects, ambientHash } from './ambient.js';
|
||||
import { drawTorch } from './torch.js';
|
||||
import { drawBocce } from './bocce-render.js';
|
||||
import { drawBiliardo } from './biliardo-render.js';
|
||||
import { drawBasket } from './basket-render.js';
|
||||
import { drawPesca } from './pesca-render.js';
|
||||
import { drawBriscola } from './briscola-render.js';
|
||||
import { drawTressette } from './tressette-render.js';
|
||||
import { drawScopa } from './scopa-render.js';
|
||||
@@ -682,6 +684,16 @@ function drawCloseup(ctx, game, time) {
|
||||
drawBocce(ctx, c, def, backgrounds.get(def.picture), time);
|
||||
return;
|
||||
}
|
||||
if (def.type === 'pesca') {
|
||||
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
|
||||
drawPesca(ctx, c, def, backgrounds.get(def.picture), sprites.get('andrea_pesca'), sprites.get('pescatori'));
|
||||
return;
|
||||
}
|
||||
if (def.type === 'basket') {
|
||||
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
|
||||
drawBasket(ctx, c, def, backgrounds.get(def.picture), sprites.get('andrea_basket'));
|
||||
return;
|
||||
}
|
||||
if (def.type === 'biliardo') {
|
||||
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
|
||||
drawBiliardo(ctx, c, def, backgrounds.get(def.picture), sprites.get('ing_tozzi'));
|
||||
|
||||
@@ -118,6 +118,23 @@ export class Soundscape {
|
||||
else if (kind === 'biliardo-hit') { this.tone(2600, 1500, 0.045, 0.03 + strength * 0.08, 0, pan); this.noiseSound(0.04, 3200, 0.03, { pan }); }
|
||||
else if (kind === 'biliardo-rail') this.noiseSound(0.12, 520, 0.03 + strength * 0.06, { pan });
|
||||
else if (kind === 'biliardo-pin') { this.tone(3200, 2200, 0.03, 0.04 + strength * 0.05, 0, pan); this.noiseSound(0.08, 1800, 0.035, { pan }); }
|
||||
// Il basket: il tiro, la palla che batte per terra, il ferro, il tabellone, la retina.
|
||||
else if (kind === 'basket-throw') this.noiseSound(0.08, 1200, 0.05, { pan });
|
||||
else if (kind === 'basket-bounce') { this.tone(190, 95, 0.11, 0.05 + strength * 0.12, 0, pan); this.noiseSound(0.06, 500, 0.05, { pan }); }
|
||||
else if (kind === 'basket-rim') { this.tone(1500, 1100, 0.14, 0.04 + strength * 0.1, 0, pan); this.tone(2300, 1900, 0.08, 0.03, 0, pan); }
|
||||
else if (kind === 'basket-board') { this.tone(280, 170, 0.09, 0.05 + strength * 0.08, 0, pan); this.noiseSound(0.07, 900, 0.07, { pan }); }
|
||||
else if (kind === 'basket-swish') this.noiseSound(0.28, 3200, 0.1, { paper: true, pan });
|
||||
else if (kind === 'basket-in') { this.tone(1300, 1000, 0.08, 0.04, 0, pan); this.noiseSound(0.22, 2800, 0.08, { paper: true, pan }); }
|
||||
// La pesca: il lancio, il tuffo del galleggiante, il tocco, l'abboccata, la ferrata, la lenza che tira, lo strappo, il pesce a riva.
|
||||
else if (kind === 'pesca-lancio') this.noiseSound(0.22, 1800, 0.05 + strength * 0.06, { pan });
|
||||
else if (kind === 'pesca-tuffo') { this.tone(420, 180, 0.07, 0.07, 0, pan); this.noiseSound(0.12, 900, 0.06, { pan }); }
|
||||
else if (kind === 'pesca-tocco') this.tone(900, 700, 0.04, 0.03, 0, pan);
|
||||
else if (kind === 'pesca-abbocca') { this.tone(520, 260, 0.1, 0.08, 0, pan); this.noiseSound(0.1, 1100, 0.06, { pan }); }
|
||||
else if (kind === 'pesca-ferrata') this.noiseSound(0.1, 2600, 0.08, { pan });
|
||||
else if (kind === 'pesca-mulinello') this.tone(1800 + strength * 900, 1700 + strength * 900, 0.05, 0.015 + strength * 0.02, 0, pan);
|
||||
else if (kind === 'pesca-rottura') { this.tone(2400, 300, 0.12, 0.1, 0, pan); this.noiseSound(0.08, 3000, 0.06, { pan }); }
|
||||
else if (kind === 'pesca-vuoto') this.noiseSound(0.14, 1400, 0.05, { pan });
|
||||
else if (kind === 'pesca-preso') { this.noiseSound(0.25, 1000, 0.1, { pan }); this.tone(660, 990, 0.18, 0.05, 0.1, pan); }
|
||||
else if (kind === 'wood') this.noiseSound(0.3, 750, 0.13, { pan });
|
||||
else if (kind === 'tavolo-lontano') {
|
||||
// Su un altro tavolo: il colpo di stecca, poi la biglia che ne prende un'altra, piano, da lontano.
|
||||
|
||||
@@ -10,6 +10,8 @@ import { Soundscape } from './engine/soundscape.js';
|
||||
import { soundscapeFor } from './story/soundscapes.js';
|
||||
import * as bocce from './engine/bocce.js';
|
||||
import * as biliardo from './engine/biliardo.js';
|
||||
import * as basket from './engine/basket.js';
|
||||
import * as pesca from './engine/pesca.js';
|
||||
import * as briscola from './engine/briscola.js';
|
||||
import { cardAt } from './engine/briscola-render.js';
|
||||
import * as tressette from './engine/tressette.js';
|
||||
@@ -496,11 +498,11 @@ canvas.addEventListener('pointerdown', e => {
|
||||
return;
|
||||
}
|
||||
const aimed = game.closeupAim(scenePoint(e));
|
||||
if (story.closeups[game.closeup.id].type === 'biliardo') {
|
||||
if (!aimed && !game.speech) return;
|
||||
if (['biliardo', 'basket'].includes(story.closeups[game.closeup.id].type)) {
|
||||
if (!aimed && !game.speech) return; // dove non si può mirare (dietro Andrea) non si carica nemmeno
|
||||
if (aimed) game.closeup.aimLocked = true; // il clic fissa la mira
|
||||
}
|
||||
if (story.closeups[game.closeup.id].type === 'bocce' && aimed) game.closeup.aimLocked = true;
|
||||
if (['bocce', 'basket', 'pesca'].includes(story.closeups[game.closeup.id].type) && aimed) game.closeup.aimLocked = true;
|
||||
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();
|
||||
@@ -550,7 +552,7 @@ canvas.addEventListener('pointermove', e => {
|
||||
// Al biliardo e alle bocce la mira segue il mouse finché un clic (o una regolazione fine) non la fissa: dopo,
|
||||
// attraversare il campo per raggiungere la forza, «Tira» o «Lancia» non cambia il colpo. Al tiro dopo torna libera.
|
||||
// (Bocce: scoperto girando il video «Come si gioca», la mira finiva a due passi andando verso il cursore.)
|
||||
const aiming = game.closeup && ['biliardo', 'bocce'].includes(story.closeups[game.closeup.id].type);
|
||||
const aiming = game.closeup && ['biliardo', 'bocce', 'basket', 'pesca'].includes(story.closeups[game.closeup.id].type);
|
||||
if (aiming) { if (e.pointerType === 'mouse' && !press && !game.closeup.aimLocked) game.closeupAim(scenePoint(e)); }
|
||||
else if (e.pointerType === 'mouse' || press?.closeup) game.closeupAim(scenePoint(e));
|
||||
});
|
||||
@@ -563,6 +565,8 @@ const MINI = {
|
||||
briscola: { ...briscola, back: 'Torna all’oratorio', label: 'Briscola: tocca una carta per giocarla. Frecce o 1, 2, 3 scelgono; Invio gioca. Esc esce.' },
|
||||
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.' },
|
||||
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.' },
|
||||
pesca: { ...pesca, back: 'Torna in Fortezza', label: 'La pesca: tieni premuto per lanciare, premi quando il galleggiante va sotto, tieni premuto per recuperare.' },
|
||||
};
|
||||
const miniState = () => running && game?.closeup && MINI[story.closeups[game.closeup.id].type] ? game.closeup : null;
|
||||
const mini = () => MINI[story.closeups[game.closeup.id].type];
|
||||
@@ -904,17 +908,20 @@ function updateUi() {
|
||||
}
|
||||
}
|
||||
if (mg && !cards) {
|
||||
$('bocce-controls').setAttribute('aria-label', def.type === 'biliardo' ? 'Comandi del biliardo' : 'Comandi delle bocce');
|
||||
$('bocce-controls').setAttribute('aria-label', { biliardo: 'Comandi del biliardo', basket: 'Comandi del basket', pesca: 'Comandi della pesca' }[def.type] ?? 'Comandi delle bocce');
|
||||
$('panel').hidden = $('choices').hidden = true;
|
||||
const line = mg.status(c, def.names);
|
||||
if ($('bocce-status').textContent !== line) $('bocce-status').textContent = line;
|
||||
$('bocce-power').value = Math.round(c.power * 100);
|
||||
$('bocce-power-value').value = `${Math.round(c.power * 100)}%`;
|
||||
for (const id of ['bocce-power', 'bocce-left', 'bocce-right', 'bocce-throw']) $(id).disabled = !mg.canThrow(c) || c.holding || !!game.speech;
|
||||
$('bocce-throw').textContent = c.step === 'result' ? 'Finita' : c.step === 'rolling' ? 'In movimento…' : c.current === 'cpu' ? `Tira ${def.names.cpu}…` : def.type === 'bocce' ? 'Lancia' : 'Tira';
|
||||
for (const id of ['bocce-power', 'bocce-left', 'bocce-right']) $(id).disabled = !(mg.canAdjust?.(c) ?? mg.canThrow(c)) || c.holding || !!game.speech;
|
||||
$('bocce-throw').disabled = !mg.canThrow(c) || c.holding || !!game.speech;
|
||||
$('bocce-throw').textContent = mg.button?.(c) ?? (c.step === 'result' ? 'Finita' : c.step === 'rolling' ? 'In movimento…' : c.current === 'cpu' ? `Tira ${def.names.cpu}…` : def.type === 'bocce' ? 'Lancia' : 'Tira');
|
||||
$('bocce-help').hidden = def.type !== 'bocce';
|
||||
$('biliardo-help').hidden = def.type !== 'biliardo';
|
||||
if (!mg.canThrow(c)) c.aimLocked = false; // a ogni nuovo tiro la mira torna libera
|
||||
$('basket-help').hidden = def.type !== 'basket';
|
||||
$('pesca-help').hidden = def.type !== 'pesca';
|
||||
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 });
|
||||
// Il pulsante della mappa c'è solo in partita, fuori dai dialoghi, e quando c'è almeno un altro posto dove andare.
|
||||
|
||||
@@ -68,4 +68,82 @@ export const ambience = {
|
||||
viale_cipressi: [{ type: 'cloudshadow', phase: day, area: [95, 225, 435, 135], offset: 11000,
|
||||
clip: [[300, 225], [337, 225], [530, 360], [95, 360]] }],
|
||||
trebbio: [shade([278, 277, 112, 23], 6)],
|
||||
// Le scene del 27 ottobre (Andrea: «applicale tutte»).
|
||||
via_pignone: [
|
||||
{ type: 'clouds', area: [100, 0, 440, 110], count: 3, big: true },
|
||||
{ type: 'birds', phase: day, area: [200, 20, 300, 80], count: 3, every: 23000, duration: 5000, offset: 6000 },
|
||||
shade([330, 282, 180, 50], 10),
|
||||
{ type: 'lamps', phase: 'sera', points: [[250, 74]], glow: 10 },
|
||||
],
|
||||
piazzetta: [
|
||||
shade([30, 290, 300, 60], 14),
|
||||
butterfly([150, 170, 150, 60], 5000),
|
||||
{ type: 'lamps', phase: 'sera', points: [[616, 118]], glow: 12 },
|
||||
],
|
||||
sentiero_fortezza: [
|
||||
{ type: 'cloudshadow', phase: day, area: [0, 180, 640, 180], offset: 5000, clip: rect([0, 180, 640, 180]) },
|
||||
butterfly([380, 200, 160, 70], 8000),
|
||||
],
|
||||
// Via dell'Antica Posta e dintorni (28 ottobre).
|
||||
via_antica_posta: [
|
||||
{ type: 'clouds', area: [180, 0, 300, 90], count: 2 },
|
||||
shade([0, 300, 640, 60], 10),
|
||||
{ type: 'lamps', phase: 'sera', points: [[62, 120], [366, 142]], glow: 10 },
|
||||
],
|
||||
piazzetta_comune: [
|
||||
{ type: 'clouds', area: [60, 0, 520, 110], count: 3, big: true },
|
||||
butterfly([510, 250, 110, 50], 4000),
|
||||
{ type: 'lamps', phase: 'sera', points: [[436, 170]], glow: 10 },
|
||||
],
|
||||
antica_posta_alta: [
|
||||
shade([0, 290, 360, 70], 12),
|
||||
butterfly([480, 170, 120, 60], 6000),
|
||||
{ type: 'lamps', phase: 'sera', points: [[184, 74]], glow: 10 },
|
||||
],
|
||||
provinciale_pignone: [
|
||||
shade([80, 270, 220, 70], 14), shade([470, 290, 170, 60], 10),
|
||||
{ type: 'cloudshadow', phase: day, area: [120, 230, 420, 130], offset: 8000,
|
||||
clip: [[290, 232], [370, 232], [640, 330], [640, 360], [0, 360], [0, 330]] },
|
||||
bird([410, 258, 70, 16], 9000),
|
||||
],
|
||||
gabbiano: [
|
||||
{ type: 'clouds', area: [140, 0, 500, 140], count: 3, big: true },
|
||||
{ type: 'birds', phase: day, area: [200, 40, 400, 100], count: 4, every: 21000, duration: 5500, offset: 3000 },
|
||||
{ type: 'cloudshadow', phase: day, area: [140, 160, 500, 200], offset: 7000, clip: rect([140, 160, 500, 200]) },
|
||||
{ type: 'sway', rects: [[430, 128, 70, 60]], amount: 2 },
|
||||
butterfly([120, 250, 200, 60], 3000),
|
||||
],
|
||||
ponticino_mozzete: [
|
||||
shade([0, 230, 640, 110], 22),
|
||||
{ type: 'insect', kind: 'dragonfly', phase: day, area: [80, 230, 140, 50], every: 37000, duration: 8500, offset: 7000 },
|
||||
],
|
||||
via_cipriani: [
|
||||
{ type: 'clouds', area: [0, 0, 420, 150], count: 3, big: true },
|
||||
shade([300, 300, 160, 50], 10),
|
||||
// Di sera le lampade del cinema e il cartellone del Rattattù illuminato.
|
||||
{ type: 'lamps', phase: 'sera', points: [[545, 214], [620, 168]], glow: 12 },
|
||||
{ type: 'lamps', phase: 'sera', points: [[490, 200]], glow: 34 },
|
||||
],
|
||||
via_massorondinaio: [
|
||||
{ type: 'clouds', area: [0, 0, 640, 100], count: 3, big: true },
|
||||
{ type: 'birds', phase: day, area: [100, 30, 400, 100], count: 3, every: 25000, duration: 5000, offset: 11000 },
|
||||
{ type: 'sway', rects: [[200, 128, 90, 72]], amount: 2 },
|
||||
],
|
||||
coldaia: [
|
||||
{ type: 'clouds', area: [200, 0, 440, 120], count: 2 },
|
||||
{ type: 'cloudshadow', phase: day, area: [0, 150, 640, 210], offset: 15000, clip: rect([0, 160, 640, 200]) },
|
||||
{ type: 'birds', phase: day, area: [250, 20, 350, 90], count: 3, every: 27000, duration: 5500, offset: 2000 },
|
||||
butterfly([60, 280, 200, 50], 12000),
|
||||
],
|
||||
falegnameria_coldaia: [
|
||||
{ type: 'clouds', area: [0, 0, 640, 110], count: 3, big: true },
|
||||
{ type: 'dust', phase: day, area: [330, 150, 260, 70], count: 10 },
|
||||
bird([250, 230, 90, 18], 8000, 'blackbird'),
|
||||
],
|
||||
mozzete: [
|
||||
{ type: 'clouds', area: [100, 0, 540, 110], count: 3, big: true },
|
||||
shade([0, 215, 230, 60], 12),
|
||||
{ type: 'cloudshadow', phase: day, area: [60, 220, 520, 140], offset: 23000,
|
||||
clip: [[0, 215], [400, 195], [640, 260], [640, 360], [0, 360]] },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ export const soundscapes = {
|
||||
};
|
||||
|
||||
const groups = {
|
||||
bosco: ['bosco_ai_frati', 'viale_cipressi', 'trebbio', 'salita_fortezza', 'casa_maestro_bacci', 'campo_giannini', 'area_camper', 'giardini_poste'],
|
||||
bosco: ['bosco_ai_frati', 'sentiero_fortezza', 'viale_cipressi', 'trebbio', 'salita_fortezza', 'casa_maestro_bacci', 'campo_giannini', 'area_camper', 'giardini_poste'],
|
||||
acqua: ['ponte_sieve', 'campomigliaio_ponte', 'verso_luna'],
|
||||
pieve: ['pieve', 'pieve_interno', 'oratorio'],
|
||||
circolo: ['campomigliaio_circolo', 'tennis_chiesa', 'area_feste_festa'],
|
||||
|
||||
@@ -150,6 +150,40 @@ export const story = {
|
||||
quit: [{ say: 'Già stanco? Il pallaio ti aspetta.', who: 'mariano', pose: 'laugh' }],
|
||||
draw: [{ say: 'Quando vuoi la rivincita, sono qui.', who: 'mariano' }],
|
||||
},
|
||||
// «Fai due tiri» al campo da basket del Circolo 4 Maggio (idea di Andrea, 27 ottobre): da solo, dieci palloni,
|
||||
// si arretra a ogni canestro. La gloria dipende dal livello (basket.LEVELS). [DA ANDREA: le sue battute]
|
||||
basket: {
|
||||
type: 'basket',
|
||||
picture: 'assets/closeups/basket-campo.png',
|
||||
names: { player: 'Andrea', cpu: 'il canestro' },
|
||||
win: [
|
||||
{ say: 'Che mano! Jordan, fatti da parte.' },
|
||||
{ set: 'basket_vinto' },
|
||||
],
|
||||
lose: [
|
||||
{ say: 'Il ferro oggi era stretto. Riprovo dopo.' },
|
||||
],
|
||||
quit: [{ say: 'Lascio il pallone lì. Tanto lo sanno tutti che è mio.' }],
|
||||
draw: [],
|
||||
},
|
||||
// La pesca al lago dell'Anatre coi ragazzi delle canne (idea di Andrea, 28 ottobre): sei lanci, da solo con la
|
||||
// canna di bambù; la gloria dipende dal livello (pesca.LEVELS). [DA ANDREA: le sue battute]
|
||||
pesca: {
|
||||
type: 'pesca',
|
||||
picture: 'assets/closeups/lago-anatre.png',
|
||||
names: { player: 'Andrea', cpu: 'il lago' },
|
||||
win: [
|
||||
{ say: 'Oh, hai visto? Mica male, per una canna di bambù.', who: 'pescatori', pose: 'laugh' },
|
||||
{ say: 'Stasera si frigge. Ma prima il palio.' },
|
||||
{ set: 'pesca_vinta' },
|
||||
],
|
||||
lose: [
|
||||
{ say: 'I pesci c\'erano. Eri te che non c\'eri.', who: 'pescatori', pose: 'talk' },
|
||||
{ say: 'Si rifà. Dopo il palio.' },
|
||||
],
|
||||
quit: [{ say: 'Lascio la canna ai ragazzi. Il lago non scappa, il palio sì.' }],
|
||||
draw: [],
|
||||
},
|
||||
// Il biliardo del Circolo 4 Maggio contro l'Ing. Tozzi: cinque birilli, un colpo a testa, si va a 20. [DA ANDREA: le sue battute]
|
||||
biliardo: {
|
||||
type: 'biliardo',
|
||||
@@ -215,6 +249,9 @@ export const story = {
|
||||
// [DA ANDREA: chi è] Figura inventata, nessuna persona reale.
|
||||
giocatore_carte: { name: 'Il giocatore di carte', color: '#edc476', sprite: 'assets/characters/giocatore-carte.json' },
|
||||
andrea: { name: 'Andrea', color: '#ffffff', sprite: 'assets/characters/andrea.json' },
|
||||
// Andrea col pallone, per «Fai due tiri» (foglio di Codex dal suo sprite): non parla, lo disegna basket-render.
|
||||
andrea_basket: { name: 'Andrea', sprite: 'assets/characters/andrea-basket.json' },
|
||||
andrea_pesca: { name: 'Andrea', sprite: 'assets/characters/andrea-pesca.json' },
|
||||
// Quattro ragazzi inventati (sprite di Codex, idea di Andrea del 27 ottobre): nessuna persona reale.
|
||||
pescatori: { name: 'I ragazzi delle canne', color: '#b8f08a', sprite: 'assets/characters/pescatori.json' },
|
||||
vecchietta: { name: 'Vecchietta', color: '#ffd27a', sprite: 'assets/characters/vecchietta.json' },
|
||||
@@ -478,11 +515,12 @@ export const story = {
|
||||
take: [{ give: 'squama' }, { set: 'squama_presa' }, { say: 'Una squama! E bella grossa.' }],
|
||||
},
|
||||
},
|
||||
// Si compare in basso: la foto di Via Cipriani è presa dall'altro verso (Andrea, 28 ottobre, va bene così).
|
||||
{
|
||||
id: 'verso_campeggio', name: 'Via Cipriani, verso il cinema e il campeggio', box: [330, 190, 34, 48], at: [345, 234], exit: { scene: 'via_cipriani', at: [300, 345], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'verso_piazzetta', name: 'Piazzetta Cambray Digny, verso Via de\' Medici', box: [0, 196, 146, 54], at: [92, 250], exit: { scene: 'piazzetta', at: [510, 274], face: 'down' },
|
||||
id: 'verso_piazzetta', name: 'Via dell\'Antica Posta, verso Piazzetta Cambray Digny', box: [0, 196, 146, 54], at: [92, 250], exit: { scene: 'via_antica_posta', at: [320, 345], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'verso_calimara', name: 'Via Calimara, verso la farmacia', box: [180, 334, 380, 26], at: [360, 354], exit: { scene: 'via_calimara', at: [365, 212], face: 'down' },
|
||||
@@ -511,12 +549,13 @@ export const story = {
|
||||
id: 'pescatori', name: 'ragazzi con le canne', box: [92, 236, 120, 86], at: [240, 334], face: 'left', default: 'talk',
|
||||
actions: {
|
||||
look: [{ say: 'Quattro ragazzi che affilano canne di bambù col coltellino e ci legano le lenze.' }],
|
||||
talk: [
|
||||
talk: [{ if: '!pescatori_parlato', then: [
|
||||
{ set: 'pescatori_parlato' },
|
||||
{ say: 'Oh, che fate?' },
|
||||
{ say: 'Si preparano le canne: domattina si va a pescare al lago dell\'Anatre, dietro Cardetole!', who: 'pescatori', pose: 'talk' },
|
||||
{ say: 'Si preparano le canne: si va a pescare al lago dell\'Anatre, dietro Cardetole!', who: 'pescatori', pose: 'talk' },
|
||||
{ say: 'Guarda questa: la lenza la regge anche una carpa di dieci chili.', who: 'pescatori', pose: 'proud' },
|
||||
{ say: '(Vedi, mia mamma aveva ragione: in Fortezza si viene a farsi le canne.)' },
|
||||
],
|
||||
] }, { dialogue: 'pescatori' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -587,7 +626,7 @@ export const story = {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'sentiero_trebbio', name: 'sentiero per il Trebbio', box: [0, 200, 90, 160], at: [20, 330], exit: { scene: 'casa_maestro_bacci', at: [120, 350], face: 'up' },
|
||||
id: 'sentiero_trebbio', name: 'sentiero per il Trebbio', box: [0, 200, 90, 160], at: [20, 330], exit: { scene: 'sentiero_fortezza', at: [325, 190], face: 'down' },
|
||||
},
|
||||
{
|
||||
id: 'verso_villa_adami', name: 'giù per il Poggio, verso Villa Adami', box: [180, 326, 340, 34], at: [330, 352], exit: { scene: 'salita_fortezza', at: [150, 318], face: 'up' },
|
||||
@@ -897,9 +936,11 @@ export const story = {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'campo_basket', name: 'campo da basket del Circolo', box: [250, 110, 170, 80], at: [340, 316], face: 'up',
|
||||
id: 'campo_basket', name: 'campo da basket del Circolo', box: [250, 110, 170, 80], at: [340, 316], face: 'up', default: 'use',
|
||||
actions: {
|
||||
look: [{ say: 'Il campo da basket del Circolo, lassù oltre la rete. Prima o poi due tiri ce li faccio.' }],
|
||||
look: [{ say: 'Il campo da basket del Circolo, lassù oltre la rete. Due tiri ce li faccio sempre volentieri.' }],
|
||||
use: [{ dialogue: 'basket' }],
|
||||
walk: [{ dialogue: 'basket' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1162,6 +1203,9 @@ export const story = {
|
||||
open: [{ say: 'All\'asilo? Ho già dato, grazie.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'alla_scalinata', name: 'a sinistra, la scalinata della Piazzetta', box: [0, 120, 150, 212], at: [40, 300], face: 'left', exit: { scene: 'scalinata', at: [300, 350], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'al_giardino', name: 'verso il giardino di Via Calimara', box: [596, 290, 44, 70], at: [600, 340], exit: { scene: 'via_calimara', at: [160, 290], face: 'right' },
|
||||
},
|
||||
@@ -1259,13 +1303,13 @@ export const story = {
|
||||
id: 'al_ponte', name: 'a sinistra, verso il campo sportivo', box: [0, 216, 60, 100], at: [12, 318], exit: { scene: 'ponte_sieve', at: [300, 350], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'alla_pieve', name: 'dietro la farmacia, verso la Pieve', box: [470, 180, 120, 60], at: [560, 240], exit: { scene: 'provinciale_pignone', at: [300, 345], face: 'up' },
|
||||
id: 'alla_pieve', name: 'dietro la farmacia, verso la Pieve', box: [420, 170, 118, 70], at: [480, 242], exit: { scene: 'provinciale_pignone', at: [300, 345], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'area_feste', name: 'qui sotto, verso l\'area feste', box: [0, 318, 300, 42], at: [120, 352], exit: { scene: 'area_feste', at: [44, 350], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'al_comune', name: 'verso il Comune', box: [592, 140, 48, 96], at: [622, 244], exit: { scene: 'comune', at: [320, 350], face: 'up' },
|
||||
id: 'al_comune', name: 'verso il Comune', box: [540, 40, 100, 198], at: [622, 244], exit: { scene: 'comune', at: [320, 350], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'alla_calimara', name: 'Via Calimara, verso la Piazza', box: [520, 260, 120, 100], at: [600, 330], exit: { scene: 'via_calimara', at: [300, 350], face: 'up' },
|
||||
@@ -1287,6 +1331,31 @@ export const story = {
|
||||
look: [{ say: 'Cartelli per la Pieve, per la stazione, per Firenze. Per il palio, nessuno.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
// L'edicola, col chiosco chiuso: qui Andrea comprava le cassette per il Commodore 64 (Andrea, 28 ottobre).
|
||||
id: 'edicola', name: 'edicola', box: [20, 160, 92, 100], at: [120, 278], face: 'left',
|
||||
actions: {
|
||||
look: [
|
||||
{ say: 'L\'edicola. Qui compravo le cassette per il Commodore 64: trenta giochi a mille lire, diceva la copertina.' },
|
||||
{ say: 'Si infilava la cassetta, si scriveva LOAD, si premeva play... e si aspettava dieci minuti col registratore che cigolava.' },
|
||||
{ say: 'Poi, quando andava bene, partiva il gioco. Quando andava male: LOAD ERROR. E si riavvolgeva tutto. Bei tempi.' },
|
||||
],
|
||||
open: [{ say: 'Chiusa. E comunque le cassette non le fanno più. Neanche i Commodore.' }],
|
||||
use: [{ say: 'Vorrei un Topolino e trenta giochi a mille lire. ...Niente, parlo con una serranda.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
// La banca all'angolo di Via del Pignone: il bancomat non funziona quasi mai (Andrea, 28 ottobre).
|
||||
id: 'banca', name: 'banca', box: [368, 166, 80, 50], at: [400, 236], face: 'up',
|
||||
actions: {
|
||||
look: [
|
||||
{ say: 'La banca. Il bancomat funziona solo il mercoledì delle settimane pari, negli anni bisestili, quando Venere si allinea a Plutone.' },
|
||||
{ say: 'Oggi? Not today.' },
|
||||
],
|
||||
open: [{ say: 'Chiusa. Come il bancomat, del resto.' }],
|
||||
use: [{ say: 'Carta dentro, carta fuori. «Servizio momentaneamente non disponibile». Momentaneamente dal 1998.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'alla_pieve', name: 'in fondo, verso la Pieve', box: [236, 150, 64, 64], at: [268, 216], exit: { scene: 'pieve', at: [24, 312], face: 'right' },
|
||||
},
|
||||
@@ -1294,7 +1363,7 @@ export const story = {
|
||||
id: 'via_pignone', name: 'a destra, Via del Pignone', box: [440, 196, 100, 56], at: [530, 245], face: 'right', exit: { scene: 'via_pignone', at: [300, 345], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'alla_rotonda', name: 'indietro, verso la rotonda della farmacia', box: [0, 334, 640, 26], at: [300, 354], exit: { scene: 'via_provinciale', at: [560, 240], face: 'left' },
|
||||
id: 'alla_rotonda', name: 'indietro, verso la rotonda della farmacia', box: [0, 334, 640, 26], at: [300, 354], exit: { scene: 'via_provinciale', at: [480, 246], face: 'down' },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1323,7 +1392,8 @@ export const story = {
|
||||
},
|
||||
],
|
||||
},
|
||||
// Piazzetta Cambray Digny, in cima a Via del Pignone, sotto Villa Schifanoia: a sinistra Via de' Medici, a diritto la Piazza.
|
||||
// Piazzetta Cambray Digny, in cima a Via del Pignone, sotto Villa Schifanoia: a sinistra Via de' Medici, a diritto
|
||||
// Via dell'Antica Posta che scende in Piazza, a destra del palazzo la scalinata che scende al Comune (Andrea, 28 ottobre).
|
||||
piazzetta: {
|
||||
name: 'Piazzetta Cambray Digny',
|
||||
map: [40, 38],
|
||||
@@ -1346,16 +1416,188 @@ export const story = {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'alla_piazza', name: 'a diritto, verso la Piazza', box: [470, 226, 70, 48], at: [510, 274], exit: { scene: 'piazza', at: [92, 250], face: 'right' },
|
||||
id: 'alla_piazza', name: 'a diritto, Via dell\'Antica Posta verso la Piazza', box: [470, 226, 70, 48], at: [510, 274], exit: { scene: 'antica_posta_alta', at: [235, 230], face: 'down' },
|
||||
},
|
||||
{
|
||||
id: 'via_de_medici', name: 'a sinistra, Via de\' Medici', box: [0, 280, 40, 80], at: [20, 320], face: 'left', exit: { scene: 'via_de_medici', at: [300, 348], face: 'up' },
|
||||
id: 'via_de_medici', name: 'a sinistra, Via de\' Medici', box: [0, 100, 60, 260], at: [20, 320], face: 'left', exit: { scene: 'via_de_medici', at: [300, 348], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'giu_scalinata', name: 'a destra, giù per la scalinata verso il Comune', box: [541, 226, 40, 60], at: [560, 285], exit: { scene: 'scalinata', at: [560, 325], face: 'down' },
|
||||
},
|
||||
{
|
||||
id: 'giu_pignone', name: 'giù per Via del Pignone', box: [570, 300, 70, 60], at: [610, 345], exit: { scene: 'via_pignone', at: [343, 212], face: 'down' },
|
||||
},
|
||||
],
|
||||
},
|
||||
// Via dell'Antica Posta (foto di Andrea, 28 ottobre): dalla Piazza, a sinistra, sale verso Piazzetta Cambray Digny.
|
||||
// La Piazza è alle spalle; a sinistra, coi tre scalini, la Piazzetta del Comune; in fondo si sale.
|
||||
via_antica_posta: {
|
||||
name: 'Via dell\'Antica Posta',
|
||||
map: [35, 41],
|
||||
background: 'assets/scenes/via-antica-posta.png',
|
||||
walk: [[0, 360], [0, 300], [120, 290], [220, 252], [300, 240], [372, 226], [396, 226], [470, 252], [630, 300], [640, 360]],
|
||||
depth: [[226, 0.35], [360, 1.15]],
|
||||
entry: [320, 345],
|
||||
hotspots: [
|
||||
{
|
||||
id: 'colonnine', name: 'colonnine con le catene', box: [225, 225, 95, 68], at: [290, 300], face: 'left',
|
||||
actions: {
|
||||
look: [{ say: 'Le colonnine con le catene: le macchine di qua non passano.' }, { say: 'Io sì. Scavalcare le catene è uno sport che pratico da quando avevo sei anni.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'tenda', name: 'tenda a righe', box: [508, 182, 30, 28], at: [500, 268], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Una tenda a righe bianche e rosse. Fa tanto spiaggia: peccato che il mare sia di là dall\'Appennino.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'muro_edera', name: 'muro con l\'edera', box: [546, 140, 94, 150], at: [570, 296], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Il muro di sasso con l\'edera e la ringhiera in cima. Lassù c\'è il verde della villa.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'alla_piazzetta_comune', name: 'a sinistra, il Comune', box: [0, 90, 200, 204], at: [130, 292], face: 'left', exit: { scene: 'piazzetta_comune', at: [260, 350], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'su_antica_posta', name: 'su, in fondo a Via dell\'Antica Posta', box: [350, 200, 70, 30], at: [384, 230], exit: { scene: 'antica_posta_alta', at: [240, 350], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'alla_piazza', name: 'indietro, la Piazza', box: [0, 334, 640, 26], at: [320, 354], exit: { scene: 'piazza', at: [92, 250], face: 'right' },
|
||||
},
|
||||
],
|
||||
enter: [{ if: '!antica_posta_vista', then: [{ set: 'antica_posta_vista' }, { say: 'Via dell\'Antica Posta. Chissà se la posta antica arrivava prima di quella moderna.' }] }],
|
||||
},
|
||||
// La Piazzetta del Comune, dietro il municipio: la torre dell'orologio, le sculture sui pilastrini di mattoni.
|
||||
piazzetta_comune: {
|
||||
name: 'Piazzetta del Comune',
|
||||
map: [32, 39],
|
||||
background: 'assets/scenes/piazzetta-comune.png',
|
||||
walk: [[0, 360], [0, 318], [130, 292], [470, 288], [540, 300], [520, 360]],
|
||||
depth: [[288, 0.55], [360, 1.15]],
|
||||
entry: [260, 350],
|
||||
hotspots: [
|
||||
{
|
||||
id: 'torre', name: 'torre dell\'orologio', box: [264, 16, 58, 104], at: [290, 294], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'La torre dell\'orologio, vista dal retro. Anche da qui segna un\'ora tutta sua.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'porta_comune', name: 'porta del Comune', box: [286, 238, 26, 50], at: [298, 292], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'La porta del Comune. Dal retro, ma sempre Comune.' }],
|
||||
open: [{ say: 'Chiusa. Anche dalla porta di dietro, senza un modulo non si entra.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'scultura', name: 'scultura alta', box: [264, 248, 22, 68], at: [250, 320], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Una scultura moderna, lunga e appuntita. Secondo me è un\'antenna per parlare coi marziani.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'sasso', name: 'scultura a sasso', box: [92, 276, 42, 54], at: [150, 330], face: 'left',
|
||||
actions: {
|
||||
look: [{ say: 'Questa sembra un sasso. Ma sta su un piedistallo, quindi è arte.' }],
|
||||
take: [{ say: 'Pesa più di me. E poi l\'arte non si porta via in tasca.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'vaso_fiori', name: 'vaso di fiori', box: [520, 264, 106, 82], at: [505, 330], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Un vasone di fiori. Qualcuno se ne prende cura, al Comune.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'alla_via', name: 'indietro, Via dell\'Antica Posta', box: [0, 336, 520, 24], at: [260, 354], exit: { scene: 'via_antica_posta', at: [140, 294], face: 'right' },
|
||||
},
|
||||
],
|
||||
},
|
||||
// In cima a Via dell'Antica Posta, prima della Piazzetta: il muro della villa con l'edera, il ponticello sopra la
|
||||
// strada, il giardinetto con le panchine e due gatti al sole. A sinistra, sotto il ponticello, la scalinata (Andrea).
|
||||
antica_posta_alta: {
|
||||
name: 'In cima a Via dell\'Antica Posta',
|
||||
map: [38, 39],
|
||||
background: 'assets/scenes/via-antica-posta-alta.png',
|
||||
walk: [[0, 360], [0, 322], [190, 226], [283, 222], [470, 360]],
|
||||
depth: [[222, 0.35], [360, 1.15]],
|
||||
entry: [240, 350],
|
||||
hotspots: [
|
||||
{
|
||||
id: 'gatti', name: 'gatti', box: [490, 236, 100, 28], at: [420, 330], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Due gatti che dormono sull\'erba. Il Palio, per loro, può anche aspettare.' }],
|
||||
talk: [{ say: 'Micio, micio... Niente. Per meno di una scatoletta non aprono neanche un occhio.' }],
|
||||
take: [{ say: 'Svegliare un gatto che dorme al sole? Non sono così coraggioso.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'panchine', name: 'panchine', box: [358, 212, 130, 44], at: [375, 300], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Due panchine all\'ombra del muro. Un posto per chi non ha un palio da ritrovare.' }],
|
||||
use: [{ say: 'Mi siederei volentieri, ma il Palio non si ritrova da solo.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ponticello', name: 'ponticello', box: [187, 34, 92, 38], at: [235, 240], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'Un ponticello sopra la strada, da una casa all\'altra. Per andare dal vicino senza fare le scale.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'muro_villa', name: 'muro della villa', box: [383, 0, 257, 205], at: [330, 280], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Il muro di Villa Schifanoia, alto alto, con l\'edera che ormai lo tiene su lei.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'su_piazzetta', name: 'su, Piazzetta Cambray Digny', box: [250, 196, 80, 30], at: [280, 226], exit: { scene: 'piazzetta', at: [510, 276], face: 'down' },
|
||||
},
|
||||
{
|
||||
id: 'alla_scalinata', name: 'a sinistra, giù per la scalinata verso il Comune', box: [150, 190, 60, 36], at: [195, 228], exit: { scene: 'scalinata', at: [470, 312], face: 'down' },
|
||||
},
|
||||
{
|
||||
id: 'giu_antica_posta', name: 'giù, Via dell\'Antica Posta', box: [0, 334, 470, 26], at: [240, 354], exit: { scene: 'via_antica_posta', at: [384, 234], face: 'down' },
|
||||
},
|
||||
],
|
||||
},
|
||||
// La scalinata (foto di Andrea, 28 ottobre): un cortile coi garage; su per le scale la Piazzetta Cambray Digny,
|
||||
// alle spalle i giardini e il retro del Comune.
|
||||
scalinata: {
|
||||
name: 'La scalinata della Piazzetta',
|
||||
map: [30, 42],
|
||||
background: 'assets/scenes/scalinata-piazzetta.png',
|
||||
walk: [[0, 360], [0, 290], [100, 282], [380, 278], [400, 300], [620, 330], [640, 360]],
|
||||
depth: [[278, 0.55], [360, 1.15]],
|
||||
entry: [300, 350],
|
||||
hotspots: [
|
||||
{
|
||||
id: 'saracinesca', name: 'saracinesca', box: [268, 196, 62, 79], at: [300, 290], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'Una saracinesca color ruggine. Dietro, scommetto, una macchina e trent\'anni di roba da buttare.' }],
|
||||
open: [{ say: 'Chiusa. E se la apro io, mi tocca anche fare ordine.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'panni', name: 'panni stesi', box: [110, 108, 34, 30], at: [130, 290], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'Panni stesi al sole. Se piove, qui lo sanno tutti prima del telegiornale.' }],
|
||||
take: [{ say: 'Rubare i panni stesi? Mi conoscono tutti, mi ritroverebbero prima del Palio.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'su_antica_posta', name: 'su per le scale, poi a sinistra verso Via dell\'Antica Posta', box: [440, 196, 90, 70], at: [470, 312], face: 'up', exit: { scene: 'antica_posta_alta', at: [195, 228], face: 'right' },
|
||||
},
|
||||
{
|
||||
id: 'su_piazzetta', name: 'su per le scale, poi a destra verso Piazzetta Cambray Digny', box: [530, 196, 80, 70], at: [560, 325], face: 'up', exit: { scene: 'piazzetta', at: [560, 285], face: 'left' },
|
||||
},
|
||||
{
|
||||
id: 'al_comune', name: 'indietro, i giardini del Comune', box: [0, 334, 640, 26], at: [300, 354], exit: { scene: 'comune', at: [40, 300], face: 'right' },
|
||||
},
|
||||
],
|
||||
},
|
||||
// L'area feste (il parco Antonio Berti): dalla rotonda del River, a sinistra.
|
||||
area_feste: {
|
||||
name: 'Area feste',
|
||||
@@ -1986,6 +2228,37 @@ export const story = {
|
||||
},
|
||||
],
|
||||
},
|
||||
// Il sentiero nel bosco fra la casa del maestro Bacci (alle spalle) e la Fortezza (a diritto): foto di Andrea,
|
||||
// 28 ottobre. La pozza c'è anche a Ferragosto (Andrea).
|
||||
sentiero_fortezza: {
|
||||
name: 'Il sentiero della Fortezza',
|
||||
map: [43, 9],
|
||||
background: 'assets/scenes/sentiero-fortezza.png',
|
||||
walk: [[0, 360], [0, 321], [208, 267], [292, 196], [317, 181], [342, 181], [375, 217], [500, 292], [640, 325], [640, 360]],
|
||||
depth: [[181, 0.3], [360, 1.15]],
|
||||
entry: [320, 350],
|
||||
hotspots: [
|
||||
{
|
||||
id: 'pozza', name: 'pozza', box: [271, 212, 96, 54], at: [250, 285], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'La pozza. Anche il quindici d\'agosto è sempre bella piena.' }, { say: 'Un giorno ci casco dentro e ci affogo: il primo annegato in mezzo al bosco.' }],
|
||||
use: [{ say: 'Un bagno nella pozza? No, grazie: non so quanto è fonda, e non lo voglio sapere.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'rete', name: 'rete', box: [0, 140, 230, 150], at: [120, 300], face: 'left',
|
||||
actions: {
|
||||
look: [{ say: 'Una rete vecchia, tenuta su coi pali di legno e con la speranza.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'alla_fortezza', name: 'a diritto, verso la Fortezza', box: [295, 160, 70, 36], at: [328, 186], exit: { scene: 'fortezza', at: [20, 330], face: 'right' },
|
||||
},
|
||||
{
|
||||
id: 'alla_casa_bacci', name: 'indietro, verso la casa del maestro Bacci', box: [0, 334, 640, 26], at: [320, 354], exit: { scene: 'casa_maestro_bacci', at: [120, 350], face: 'up' },
|
||||
},
|
||||
],
|
||||
},
|
||||
// Fra la Fortezza e i cipressi: la stradina fra le vigne, la casa del maestro Bacci, e lassù il Trebbio.
|
||||
casa_maestro_bacci: {
|
||||
name: 'La casa del maestro Bacci',
|
||||
@@ -2018,7 +2291,7 @@ export const story = {
|
||||
id: 'ai_cipressi', name: 'avanti, verso i cipressi e il Trebbio', box: [120, 150, 56, 30], at: [138, 226], exit: { scene: 'viale_cipressi', at: [300, 350], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'alla_fortezza', name: 'indietro, verso la Fortezza', box: [0, 330, 240, 30], at: [120, 354], exit: { scene: 'fortezza', at: [20, 330], face: 'right' },
|
||||
id: 'alla_fortezza', name: 'indietro, verso la Fortezza', box: [0, 330, 240, 30], at: [120, 354], exit: { scene: 'sentiero_fortezza', at: [320, 350], face: 'up' },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -2082,7 +2355,7 @@ export const story = {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'verso_piazza', name: 'Via de\' Medici, verso la Piazza', box: [20, 215, 50, 40], at: [45, 250], exit: { scene: 'via_de_medici', at: [300, 216], face: 'left' },
|
||||
id: 'verso_piazza', name: 'Via de\' Medici, verso la Piazza', box: [20, 215, 50, 40], at: [45, 250], exit: { scene: 'via_de_medici', at: [300, 216], face: 'down' },
|
||||
},
|
||||
{
|
||||
id: 'verso_pieve', name: 'discesa verso la Pieve', box: [560, 236, 60, 38], at: [600, 266], exit: { scene: 'pieve', at: [600, 350], face: 'left' },
|
||||
@@ -2255,6 +2528,9 @@ export const story = {
|
||||
{
|
||||
id: 'all_area_camper', name: 'a sinistra, verso l\'area camper', box: [320, 160, 110, 42], at: [442, 202], exit: { scene: 'area_camper', at: [600, 330], face: 'left' },
|
||||
},
|
||||
{
|
||||
id: 'al_giannini', name: 'in fondo a sinistra, verso il campo del Giannini', box: [380, 165, 58, 30], at: [440, 200], exit: { scene: 'campo_giannini', at: [480, 218], face: 'left' },
|
||||
},
|
||||
{
|
||||
id: 'alla_stazione', name: 'a destra, verso la stazione', box: [486, 160, 90, 40], at: [474, 202], exit: { scene: 'stazione', at: [20, 300], face: 'right' },
|
||||
},
|
||||
@@ -2283,9 +2559,6 @@ export const story = {
|
||||
look: [{ say: 'Le villette rosa coi balconi bianchi. Da qui a casa mia è un attimo.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'al_giannini', name: 'il campo del Giannini', box: [140, 160, 260, 70], at: [250, 238], exit: { scene: 'campo_giannini', at: [320, 350], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'a_cardetole', name: 'verso casa, a Cardetole', box: [0, 230, 80, 130], at: [20, 300], exit: { scene: 'cardetole', at: [300, 350], face: 'up' },
|
||||
},
|
||||
@@ -2294,7 +2567,8 @@ export const story = {
|
||||
},
|
||||
],
|
||||
},
|
||||
// Il prato del parco dei camper visto dall'altra parte: per i ragazzi, «il campo del Giannini».
|
||||
// Il prato del parco dei camper visto dall'altra parte: per i ragazzi, «il campo del Giannini». Ci si arriva dal
|
||||
// ponticino della Carza; niente uscita verso l'area camper, perché è già questa (Andrea, 28 ottobre).
|
||||
campo_giannini: {
|
||||
name: 'Il campo del Giannini',
|
||||
map: [82, 96],
|
||||
@@ -2317,7 +2591,7 @@ export const story = {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'al_parco', name: 'indietro, verso l\'area camper', box: [0, 330, 640, 30], at: [320, 354], exit: { scene: 'area_camper', at: [250, 238], face: 'down' },
|
||||
id: 'al_ponticino', name: 'verso il ponticino della Carza', box: [440, 150, 90, 60], at: [480, 216], exit: { scene: 'ponticino', at: [444, 204], face: 'right' },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -2345,6 +2619,7 @@ export const story = {
|
||||
use: [{ say: 'Arrampicarmi? Alla mia età, e davanti a tutto il condominio?' }],
|
||||
},
|
||||
},
|
||||
// Si compare a destra dell'area camper: inquadrature invertite rispetto alle foto (Andrea, 28 ottobre, va bene così).
|
||||
{
|
||||
id: 'alla_pieve', name: 'su verso la Pieve', box: [0, 330, 640, 30], at: [300, 354], exit: { scene: 'area_camper', at: [600, 330], face: 'left' },
|
||||
},
|
||||
@@ -2464,7 +2739,7 @@ export const story = {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'verso_fortezza', name: 'viale dei cipressi, verso la Fortezza', box: [0, 250, 110, 110], at: [92, 345], exit: { scene: 'viale_cipressi', at: [300, 256], face: 'left' },
|
||||
id: 'verso_fortezza', name: 'viale dei cipressi, verso la Fortezza', box: [0, 250, 110, 110], at: [92, 345], exit: { scene: 'viale_cipressi', at: [300, 256], face: 'down' },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -2675,6 +2950,41 @@ export const story = {
|
||||
},
|
||||
},
|
||||
// Mariano al pallaio: una mano a bocce, solo per gloria; se Andrea vince, la promessa di Brento Sanico.
|
||||
// I ragazzi delle canne: «S'ha andà a fallo?!?» (Andrea, 28 ottobre) e si va a pescare.
|
||||
pescatori: {
|
||||
nodes: {
|
||||
start: {
|
||||
options: [
|
||||
{ text: 'S\'ha andà a fallo?!?', next: 'livello', then: [
|
||||
{ say: 'Gnamo! La canna ti si dà noi.', who: 'pescatori', pose: 'laugh' },
|
||||
{ say: 'Come peschi?', who: 'pescatori', pose: 'talk' },
|
||||
] },
|
||||
{ text: 'Quanti ne prendete, di solito?', then: [{ say: 'Dipende da chi racconta. Il Nanni dice dieci, il cestino dice due.', who: 'pescatori', pose: 'laugh' }] },
|
||||
{ text: 'Buona pesca, ragazzi.', next: 'end', then: [{ say: 'Se vedi il palio, salutacelo!', who: 'pescatori', pose: 'talk' }] },
|
||||
],
|
||||
},
|
||||
livello: {
|
||||
options: [
|
||||
{ text: 'Alla bona, tanto per bagnare la lenza.', next: 'end', then: [{ say: 'Alla bona. Due pesci e si va a casa contenti.', who: 'pescatori' }, { closeup: 'pesca', level: 'bona' }] },
|
||||
{ text: 'Seria.', next: 'end', then: [{ say: 'Seria: tre pesci, e lo stivale non conta.', who: 'pescatori', pose: 'proud' }, { closeup: 'pesca', level: 'seria' }] },
|
||||
{ text: 'A fassi male.', next: 'end', then: [{ say: 'A fassi male? Quattro pesci, e le carpe tirano come trattori.', who: 'pescatori', pose: 'laugh' }, { closeup: 'pesca', level: 'male' }] },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
// Da solo al campo del Circolo: Andrea decide quanto farsi male.
|
||||
basket: {
|
||||
nodes: {
|
||||
start: {
|
||||
options: [
|
||||
{ text: 'Due tiri alla bona, tanto per scaldarmi.', next: 'end', then: [{ say: 'Tre su dieci e sono contento.' }, { closeup: 'basket', level: 'bona' }] },
|
||||
{ text: 'Due tiri seri.', next: 'end', then: [{ say: 'Cinque su dieci. Come ai tempi del Circolino.' }, { closeup: 'basket', level: 'seria' }] },
|
||||
{ text: 'A fassi male.', next: 'end', then: [{ say: 'Sette su dieci, senza guardare la parabola. Vediamo chi sono.' }, { closeup: 'basket', level: 'male' }] },
|
||||
{ text: 'Lascia stare, ho il palio da trovare.', next: 'end' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
mariano: {
|
||||
nodes: {
|
||||
start: {
|
||||
|
||||