Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bec7f067bc | ||
|
|
e50a3e80e9 | ||
|
|
afb0b931c5 | ||
|
|
fb1568259b | ||
|
|
c1049bf69d | ||
|
|
b05fe5b302 | ||
|
|
505eda01a3 | ||
|
|
d329cbd6ef | ||
|
|
1e4dcaaf3b | ||
|
|
4c5ec099d0 | ||
|
|
b2ec499590 | ||
|
|
75b532cda0 | ||
|
|
68686efe03 | ||
|
|
208e1d9535 | ||
|
|
f2a0f22f4a |
@@ -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: 244 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 5.1 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: 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",
|
||||
@@ -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
|
||||
@@ -1984,8 +2112,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 +2163,50 @@
|
||||
"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
|
||||
@@ -2315,6 +2487,26 @@
|
||||
"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,6 +204,13 @@ 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.
|
||||
- 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). 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
|
||||
@@ -218,6 +225,17 @@ I prompt fissi (stile, scene, foglio sprite) sono in `prompts/STILE.md`, escluso
|
||||
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.
|
||||
|
||||
|
||||
@@ -219,6 +219,17 @@
|
||||
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.22",
|
||||
"version": "0.1.28",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -4,6 +4,7 @@ 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';
|
||||
@@ -16,6 +17,7 @@ export const MINIGAMES = {
|
||||
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' : '…';
|
||||
@@ -6,6 +6,7 @@ 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';
|
||||
@@ -683,6 +684,11 @@ 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'));
|
||||
|
||||
@@ -125,6 +125,16 @@ export class Soundscape {
|
||||
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.
|
||||
|
||||
@@ -11,6 +11,7 @@ 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';
|
||||
@@ -501,7 +502,7 @@ canvas.addEventListener('pointerdown', e => {
|
||||
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 (['bocce', 'basket'].includes(story.closeups[game.closeup.id].type) && 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();
|
||||
@@ -551,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', 'basket'].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));
|
||||
});
|
||||
@@ -565,6 +566,7 @@ const MINI = {
|
||||
bocce: { ...bocce, nudge: (c, dir) => bocce.aim(c, c.aim.u, c.aim.w + dir * 0.3), back: 'Torna al pallaio', label: 'Bocce: tocca il campo o il dettaglio per mirare. Regola la forza e premi Lancia.' },
|
||||
biliardo: { ...biliardo, back: 'Torna al bar', label: 'Biliardo: clic o tocco fissa la mira. Frecce per affinarla, Maiusc per passi più piccoli. Regola la forza e premi Tira.' },
|
||||
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];
|
||||
@@ -906,18 +908,20 @@ function updateUi() {
|
||||
}
|
||||
}
|
||||
if (mg && !cards) {
|
||||
$('bocce-controls').setAttribute('aria-label', { biliardo: 'Comandi del biliardo', basket: 'Comandi del basket' }[def.type] ?? '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';
|
||||
$('basket-help').hidden = def.type !== 'basket';
|
||||
if (!mg.canThrow(c)) c.aimLocked = false; // a ogni nuovo tiro la mira torna libera
|
||||
$('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.
|
||||
|
||||
@@ -80,6 +80,22 @@ export const ambience = {
|
||||
butterfly([150, 170, 150, 60], 5000),
|
||||
{ type: 'lamps', phase: 'sera', points: [[616, 118]], glow: 12 },
|
||||
],
|
||||
// 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,
|
||||
|
||||
@@ -166,6 +166,24 @@ export const story = {
|
||||
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',
|
||||
@@ -233,6 +251,7 @@ export const story = {
|
||||
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' },
|
||||
@@ -496,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' },
|
||||
@@ -529,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' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1182,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' },
|
||||
},
|
||||
@@ -1279,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' },
|
||||
@@ -1307,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' },
|
||||
},
|
||||
@@ -1314,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' },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1343,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],
|
||||
@@ -1366,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',
|
||||
@@ -2102,7 +2324,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' },
|
||||
@@ -2275,6 +2497,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' },
|
||||
},
|
||||
@@ -2303,9 +2528,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' },
|
||||
},
|
||||
@@ -2314,7 +2536,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],
|
||||
@@ -2337,7 +2560,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' },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -2365,6 +2588,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' },
|
||||
},
|
||||
@@ -2484,7 +2708,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' },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -2695,6 +2919,28 @@ 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: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// La versione del gioco: la cambia scripts/bump.sh, insieme a package.json.
|
||||
export const VERSION = '0.1.22';
|
||||
export const VERSION = '0.1.28';
|
||||
|
||||
// Immagini, musiche e voci con ?v=<versione>: a ogni versione l'indirizzo cambia e il browser le riscarica,
|
||||
// mentre fra una versione e l'altra le tiene in cache a lungo (nginx.conf).
|
||||
|
||||
@@ -53,16 +53,16 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h
|
||||
.bocce-adjust button { min-height: 44px; padding: 8px 12px; border: 1px solid #897b57; border-radius: 4px; background: #38453c; color: #f2e1bf; cursor: pointer; font-size: 11px; }
|
||||
#bocce-throw { background: #ead09a; color: #202b25; min-width: 100px; }
|
||||
.bocce-adjust button:disabled { opacity: .4; cursor: default; }
|
||||
#bocce-help, #biliardo-help, #basket-help { margin-top: 9px; color: #c5c6b6; font-size: 10px; }
|
||||
#bocce-help summary, #biliardo-help summary, #basket-help summary { cursor: pointer; padding: 6px 0; }
|
||||
#bocce-help p, #biliardo-help p, #basket-help p { max-width: 76em; line-height: 1.7; }
|
||||
#bocce-help, #biliardo-help, #basket-help, #pesca-help { margin-top: 9px; color: #c5c6b6; font-size: 10px; }
|
||||
#bocce-help summary, #biliardo-help summary, #basket-help summary, #pesca-help summary { cursor: pointer; padding: 6px 0; }
|
||||
#bocce-help p, #biliardo-help p, #basket-help p, #pesca-help p { max-width: 76em; line-height: 1.7; }
|
||||
@media (max-width: 600px), (orientation: landscape) and (max-height: 520px) {
|
||||
#bocce-controls { padding: 8px; font-size: 10px; overflow-y: auto; min-height: 0; }
|
||||
.bocce-adjust { flex-wrap: wrap; gap: 6px; }
|
||||
.bocce-adjust label { order: -1; flex-basis: 100%; max-width: none; }
|
||||
.bocce-adjust button { flex: 1; font-size: 9px; padding: 6px; }
|
||||
#bocce-throw { flex-basis: 100%; }
|
||||
#bocce-help, #biliardo-help, #basket-help { font-size: 9px; }
|
||||
#bocce-help, #biliardo-help, #basket-help, #pesca-help { font-size: 9px; }
|
||||
}
|
||||
.effects-control { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; font-size: clamp(9px, 1.2vw, 13px); }
|
||||
#effects-volume { width: 130px; accent-color: var(--accent); }
|
||||
|
||||
@@ -483,6 +483,38 @@ try {
|
||||
await k.close();
|
||||
}
|
||||
|
||||
// La pesca al lago dell'Anatre: coi ragazzi in Fortezza «S'ha andà a fallo?!?», poi il lancio tenendo premuto e l'attesa.
|
||||
{
|
||||
const k = await open({ width: 1280, height: 860 });
|
||||
await k.click('#splash');
|
||||
await k.click('#new-game');
|
||||
await k.click('#rione [data-rione="campomigliaio"]');
|
||||
await k.click('#skip-intro');
|
||||
await settle(k);
|
||||
await k.evaluate(() => game.go('fortezza', [240, 334]));
|
||||
await settle(k);
|
||||
await clickScene(k, await hotspotCenter(k, 'pescatori'));
|
||||
await k.waitForFunction(() => game.dialogue && !game.busy, null, { timeout: 40000 });
|
||||
await k.locator('#choices li', { hasText: 'S’ha andà a fallo?!?' }).or(k.locator('#choices li', { hasText: "S'ha andà a fallo?!?" })).first().click();
|
||||
await k.waitForFunction(() => game.options().some(o => o.text === 'Seria.'), null, { timeout: 30000 });
|
||||
await k.locator('#choices li', { hasText: 'Seria.' }).click();
|
||||
await k.waitForFunction(() => game.closeup?.id === 'pesca' && game.closeup.step === 'aim' && !game.speech, null, { timeout: 30000 });
|
||||
const r = await k.locator('#scene').boundingBox();
|
||||
await k.mouse.move(r.x + r.width * 0.7, r.y + r.height * 0.7);
|
||||
assert.ok(await k.evaluate(() => game.closeup.power > 0.5), 'toccando l\'acqua al largo la forza sale');
|
||||
await k.mouse.down();
|
||||
await k.waitForTimeout(500);
|
||||
assert.ok(await k.evaluate(() => game.closeup.holding && game.closeup.power > 0.2), 'tenendo premuto si carica il lancio');
|
||||
await k.mouse.up();
|
||||
await k.waitForFunction(() => game.closeup.step === 'wait', null, { timeout: 5000 });
|
||||
await k.waitForTimeout(600);
|
||||
await k.screenshot({ path: `${OUT}/03g-pesca-attesa.png` });
|
||||
assert.equal(await k.textContent('#bocce-throw'), 'Ferra');
|
||||
await k.keyboard.press('Escape');
|
||||
await k.waitForFunction(() => game.speech?.text.startsWith('Lascio la canna'), null, { timeout: 5000 });
|
||||
await k.close();
|
||||
}
|
||||
|
||||
// Il biliardo del Circolo 4 Maggio: la sala, poi la partita; un tocco sul tavolo mira, tenendo premuto la forza sale.
|
||||
{
|
||||
const b = await open({ width: 1280, height: 860 });
|
||||
|
||||
@@ -15,7 +15,7 @@ test('clic intelligente: parla con le persone, guarda le cose, attraversa le usc
|
||||
const { g, c, at } = setup();
|
||||
assert.equal(c.sentence('vecchietti'), 'Parla con vecchietti');
|
||||
assert.equal(c.sentence('manifesto'), 'Guarda manifesto del Palio');
|
||||
assert.equal(c.sentence('verso_piazzetta'), 'Vai a Piazzetta Cambray Digny, verso Via de\' Medici');
|
||||
assert.equal(c.sentence('verso_piazzetta'), 'Vai a Via dell\'Antica Posta, verso Piazzetta Cambray Digny');
|
||||
assert.equal(c.clickScene(at('vecchietti')), 'act');
|
||||
settle(g);
|
||||
assert.equal(g.dialogue?.id, 'vecchietti');
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { newPesca, press, release, cancel, update, cast, strike, shoot, setPower, aimScreen, nudge, status, canThrow, canCast, canAdjust,
|
||||
distanceFor, biting, CASTS, LEVELS, FISHES, NEAR, FAR, BANK, SHORE, WATER, FLY, REST } from '../src/engine/pesca.js';
|
||||
import { story } from '../src/story/story.js';
|
||||
import { Game } from '../src/engine/game.js';
|
||||
import { settle } from './helpers.mjs';
|
||||
|
||||
// Come il gioco: a ogni fotogramma gli eventi sonori si consumano (e qui si ricordano).
|
||||
const heard = new Set();
|
||||
const run = (c, seconds, dt = 1 / 60) => { for (let t = 0; t < seconds; t += dt) { update(c, dt); for (const e of c.events.splice(0)) heard.add(e.kind); } };
|
||||
// Lancia e aspetta l'abboccata vera (non i tocchi), poi ferra: la mano ferma dei test.
|
||||
function hook(c) {
|
||||
assert.ok(cast(c)); run(c, FLY + 0.05);
|
||||
assert.equal(c.step, 'wait');
|
||||
for (let i = 0; i < 60 * 15 && !c.float.bitten; i++) update(c, 1 / 60);
|
||||
assert.ok(c.float.bitten, 'prima o poi abbocca');
|
||||
assert.ok(strike(c)); assert.equal(c.step, 'fight');
|
||||
}
|
||||
// Recupera con giudizio: tiene premuto finché la lenza non è quasi al limite, poi molla un attimo.
|
||||
function land(c) {
|
||||
for (let i = 0; i < 60 * 90 && c.step === 'fight'; i++) {
|
||||
if (c.tension > 0.8 && c.reeling) release(c);
|
||||
else if (c.tension < 0.55 && !c.reeling) press(c);
|
||||
update(c, 1 / 60); for (const e of c.events.splice(0)) heard.add(e.kind);
|
||||
}
|
||||
assert.notEqual(c.step, 'fight', 'il recupero finisce');
|
||||
}
|
||||
|
||||
test('la forza sale finché si tiene premuto; un tocco breve non spreca il lancio; dove si tocca l\'acqua si lancia', () => {
|
||||
const c = newPesca(1);
|
||||
assert.ok(press(c)); update(c, 0.05); assert.ok(release(c));
|
||||
assert.equal(c.casts, 0); assert.equal(c.power, 0.5);
|
||||
assert.ok(aimScreen(c, [FAR, 250])); assert.equal(c.power, 1);
|
||||
assert.ok(aimScreen(c, [NEAR, 250])); assert.equal(c.power, 0);
|
||||
assert.ok(!aimScreen(c, [BANK - 20, 250]), 'sulla riva non si lancia');
|
||||
assert.ok(!aimScreen(c, [400, 60]), 'il cielo non è acqua');
|
||||
assert.ok(nudge(c, 1)); assert.ok(Math.abs(c.power - 0.05) < 1e-9);
|
||||
setPower(c, 0.5);
|
||||
assert.ok(press(c)); update(c, 0.7); assert.ok(release(c));
|
||||
assert.equal(c.casts, 1); assert.equal(c.step, 'cast');
|
||||
assert.ok(Math.abs(c.float.x - distanceFor(c.power)) < 1e-9);
|
||||
assert.ok(c.events.some(e => e.kind === 'pesca-lancio'));
|
||||
assert.ok(!setPower(c, 0.2), 'a lenza in acqua la forza non si tocca');
|
||||
});
|
||||
|
||||
test('il galleggiante: i tocchi finti abbassano appena, l\'abboccata lo porta sotto; ferrare a vuoto perde il lancio', () => {
|
||||
const c = newPesca(2); cast(c); run(c, FLY + 0.05);
|
||||
const f = c.float;
|
||||
assert.ok(f.nibbles.length >= 1 && f.nibbles.every(n => n.at < f.biteAt), 'i tocchi vengono prima');
|
||||
assert.ok(strike(c)); assert.equal(c.step, 'settle'); assert.equal(c.how, 'empty');
|
||||
assert.match(c.last.text, /a vuoto|spaventato/);
|
||||
run(c, REST + 0.1); assert.equal(c.step, 'aim'); assert.equal(c.casts, 1);
|
||||
const d = newPesca(3); cast(d); run(d, FLY + 0.05);
|
||||
const g = d.float;
|
||||
for (let i = 0; i < 60 * 15 && !(g.t >= g.nibbles[0].at && g.t < g.nibbles[0].at + g.nibbles[0].for); i++) update(d, 1 / 60);
|
||||
assert.ok(g.dip > 0 && g.dip < 5, `un tocco finto: ${g.dip}`);
|
||||
for (let i = 0; i < 60 * 15 && !g.bitten; i++) update(d, 1 / 60);
|
||||
assert.equal(g.dip, 7, 'abboccata: sotto');
|
||||
assert.ok(d.events.some(e => e.kind === 'pesca-abbocca'));
|
||||
});
|
||||
|
||||
test('non ferrata in tempo, l\'abboccata passa; alla terza il pesce se ne va', () => {
|
||||
const c = newPesca(4, { level: 'male' }); cast(c); run(c, FLY + 0.05);
|
||||
for (let i = 0; i < 60 * 15 && !c.float.bitten; i++) update(c, 1 / 60);
|
||||
// Il segnale e la finestra coincidono: finché dice «Ferra!» si ferra; dopo, lo sta sputando.
|
||||
assert.ok(biting(c) && c.float.dip === 7 && /Ferra!/.test(status(c)));
|
||||
run(c, c.skill.window + 0.05);
|
||||
assert.ok(!biting(c) && c.float.bitten && c.float.dip === 3 && /sputando/.test(status(c)));
|
||||
const late = structuredClone(c); strike(late);
|
||||
assert.equal(late.last.kind, 'late'); assert.match(late.last.text, /tardi/);
|
||||
run(c, 0.5);
|
||||
assert.ok(!c.float.bitten && c.float.missed === 1, 'persa la prima');
|
||||
assert.equal(c.step, 'wait');
|
||||
assert.match(status(c), /2 abboccate ancora/);
|
||||
run(c, 60);
|
||||
assert.equal(c.how, 'empty', 'dopo tre, o dopo quaranta secondi, si ricupera a vuoto');
|
||||
assert.equal(c.last.kind, 'gone'); assert.equal(c.step, 'aim');
|
||||
});
|
||||
|
||||
test('ferrato in tempo si combatte: tenendo premuto la lenza tira e il pesce si avvicina, mollando si allontana; troppo tesa si spezza', () => {
|
||||
const c = newPesca(5, { level: 'bona' }); setPower(c, 0.5); hook(c);
|
||||
assert.ok(c.fish && c.fish.dist === 1);
|
||||
const x0 = c.float.x;
|
||||
press(c); run(c, 1.5);
|
||||
assert.ok(c.float.x < x0 && c.float.x > SHORE, `il galleggiante viene verso riva senza salti: ${x0} → ${c.float.x}`);
|
||||
assert.ok(c.fish.dist < 1 && c.tension > 0.3, 'recuperando si avvicina');
|
||||
const d0 = c.fish.dist;
|
||||
release(c); run(c, 1.5);
|
||||
assert.ok(c.fish.dist > d0 - 0.001, 'mollando non si avvicina');
|
||||
land(c);
|
||||
assert.equal(c.how, 'landed', 'con giudizio si tira a riva');
|
||||
assert.equal(c.caught.length, 1); assert.ok(c.weight > 0);
|
||||
assert.ok(heard.has('pesca-preso'));
|
||||
// A fassi male, una carpa al largo tenuta sempre tesa spezza la lenza.
|
||||
const d = newPesca(6, { level: 'male' }); setPower(d, 1); hook(d);
|
||||
d.fish = { ...FISHES.at(-1), dist: 1, energy: 1, burst: 0, reel: 0.11 };
|
||||
press(d); run(d, 8);
|
||||
assert.equal(d.how, 'broken', `invece: ${d.step} ${d.how}`);
|
||||
assert.ok(heard.has('pesca-rottura'));
|
||||
});
|
||||
|
||||
test('un gesto solo: premendo all\'abboccata si ferra e si recupera; il pannello alterna recupera e molla; il sesto lancio si può finire', () => {
|
||||
const c = newPesca(21, { level: 'bona' }); setPower(c, 0.3); cast(c); run(c, FLY + 0.05);
|
||||
for (let i = 0; i < 60 * 15 && !c.float.bitten; i++) update(c, 1 / 60);
|
||||
assert.ok(press(c)); assert.equal(c.step, 'fight'); assert.ok(c.reeling, 'ferrato e già in recupero');
|
||||
assert.ok(release(c)); assert.ok(!c.reeling);
|
||||
assert.ok(shoot(c)); assert.ok(c.reeling, 'dal pannello: recupera'); assert.ok(shoot(c)); assert.ok(!c.reeling, 'dal pannello: molla');
|
||||
const d = newPesca(22); d.casts = CASTS - 1;
|
||||
assert.ok(canCast(d)); cast(d); run(d, FLY + 0.05);
|
||||
assert.ok(!canCast(d) && canThrow(d), 'al sesto lancio si può ancora ferrare');
|
||||
assert.ok(!canAdjust(d), 'ma non regolare la distanza');
|
||||
for (let i = 0; i < 60 * 15 && !d.float.bitten; i++) update(d, 1 / 60);
|
||||
assert.ok(press(d)); assert.equal(d.step, 'fight'); assert.ok(canThrow(d), 'e recuperare');
|
||||
d.fish = { ...FISHES[1], dist: 1, energy: 1, burst: 0, reel: 0.2 }; land(d);
|
||||
assert.equal(d.how, 'landed');
|
||||
assert.ok(d.result, 'il risultato è deciso appena il pesce è a riva, prima della posa');
|
||||
run(d, REST + 0.1); assert.equal(d.step, 'result');
|
||||
});
|
||||
|
||||
test('la lenza si spezza davvero: tenendo sempre premuto, a seria un cavedano o più grosso la rompe, una lasca no', () => {
|
||||
const fight = (kind, level) => { const c = newPesca(23, { level }); setPower(c, 0.5); hook(c); c.fish = { ...FISHES.find(f => f.kind === kind), dist: 1, energy: 1, burst: 0, reel: 0.15 }; press(c); run(c, 20); return c.how; };
|
||||
assert.equal(fight('lasca', 'seria'), 'landed');
|
||||
assert.equal(fight('cavedano', 'seria'), 'broken');
|
||||
assert.equal(fight('carpa', 'bona'), 'broken');
|
||||
assert.equal(fight('cavedano', 'bona'), 'landed');
|
||||
// Mollata e ripresa, la stessa carpa viene a riva.
|
||||
const c = newPesca(24, { level: 'male' }); setPower(c, 1); hook(c); c.fish = { ...FISHES.at(-1), dist: 1, energy: 1, burst: 0, reel: 0.11 }; land(c);
|
||||
assert.equal(c.how, 'landed');
|
||||
});
|
||||
|
||||
test('al largo i pesci sono più grossi; lo stivale solo sotto riva e non conta per la gloria', () => {
|
||||
const near = new Set(), far = new Set();
|
||||
for (let seed = 1; seed < 40; seed++) {
|
||||
for (const [power, set] of [[0, near], [1, far]]) {
|
||||
const c = newPesca(seed); setPower(c, power); hook(c); set.add(c.fish.kind);
|
||||
}
|
||||
}
|
||||
assert.ok(far.has('carpa') || far.has('tinca'), `al largo: ${[...far]}`);
|
||||
assert.ok(!near.has('carpa'), 'sotto riva niente carpe');
|
||||
assert.ok(!far.has('stivale'), 'lo stivale sta sotto riva');
|
||||
const c = newPesca(7, { level: 'bona' }); c.caught = [{ ...FISHES[0] }, { ...FISHES[1] }]; c.weight = 1;
|
||||
assert.match(status(c), /Pesci 1 /, 'lo stivale non è un pesce');
|
||||
const d = newPesca(25); setPower(d, 0); hook(d); d.fish = { ...FISHES[0], dist: 1, energy: 1, burst: 0, reel: 0.3 }; land(d);
|
||||
assert.equal(d.weight, 0, 'e non pesa'); assert.match(d.last.text, /stivale/);
|
||||
});
|
||||
|
||||
test('sei lanci, poi il risultato: la gloria dipende dal livello', () => {
|
||||
for (const [level, { goal }] of Object.entries(LEVELS)) {
|
||||
const c = newPesca(8, { level }), fishes = [];
|
||||
for (let i = 0; i < CASTS; i++) {
|
||||
assert.ok(canThrow(c));
|
||||
// Finché serve si pesca per davvero (con una lasca docile sotto riva), poi si ricupera a vuoto.
|
||||
if (c.caught.length < goal) { setPower(c, 0); hook(c); c.fish = { ...FISHES[1], dist: 1, energy: 1, burst: 0, reel: 0.2 }; land(c); assert.equal(c.how, 'landed'); }
|
||||
else { cast(c); run(c, FLY + 0.05); strike(c); }
|
||||
run(c, REST + 0.1);
|
||||
}
|
||||
assert.equal(c.step, 'result'); assert.ok(!canThrow(c)); assert.ok(!cast(c));
|
||||
assert.equal(c.result.fish, goal); assert.equal(c.result.winner, 'player', level);
|
||||
assert.match(status(c), /pesci su 6 lanci/);
|
||||
}
|
||||
const miss = newPesca(9, { level: 'bona' });
|
||||
for (let i = 0; i < CASTS; i++) { cast(miss); run(miss, FLY + 0.05); strike(miss); run(miss, REST + 0.1); }
|
||||
assert.equal(miss.result.winner, 'cpu');
|
||||
});
|
||||
|
||||
test('nella storia: coi ragazzi in Fortezza «S\'ha andà a fallo?!?», loro accettano, si sceglie e si pesca; la gloria lascia il flag', () => {
|
||||
const g = new Game(story);
|
||||
g.go('fortezza', [240, 334]); settle(g);
|
||||
g.act('talk', 'pescatori'); const said = settle(g).map(s => s.text);
|
||||
assert.ok(said.some(t => t.includes('mia mamma aveva ragione')), 'la prima volta le canne');
|
||||
assert.ok(g.dialogue, 'poi si parla');
|
||||
g.choose(g.options().findIndex(o => o.text === 'S\'ha andà a fallo?!?'));
|
||||
const yes = settle(g).map(s => `${s.who ?? 'andrea'}: ${s.text}`);
|
||||
assert.ok(yes.includes('pescatori: Gnamo! La canna ti si dà noi.'));
|
||||
g.choose(g.options().findIndex(o => o.text === 'Seria.')); settle(g);
|
||||
assert.equal(g.closeup?.id, 'pesca'); assert.equal(g.closeup.level, 'seria');
|
||||
g.closeup.result = { winner: 'player', fish: 3, weight: 2.1, goal: 3, caught: [] }; g.closeup.step = 'result';
|
||||
g.closeupClose(); const after = settle(g).map(s => s.text);
|
||||
assert.ok(g.state.flags.pesca_vinta);
|
||||
assert.ok(after.some(t => t.includes('Stasera si frigge')));
|
||||
// La seconda volta si va dritti al dialogo.
|
||||
g.act('talk', 'pescatori'); settle(g);
|
||||
assert.ok(g.dialogue);
|
||||
});
|
||||
@@ -5,6 +5,8 @@
|
||||
"padel": "pàdel",
|
||||
"l'A. Bettini": "l'Abbettini",
|
||||
"da i' Trappola, ai' Barrettino": "dai Trappola, ai Barrettino",
|
||||
"S'ha andà a fallo": "Si va a farlo",
|
||||
"Ghiacciomobile": "Ghiacciomòbile"
|
||||
"S'ha andà a fallo": "saandà a fallo",
|
||||
"Ghiacciomobile": "Ghiacciomòbile",
|
||||
"Not today": "Nòt tudèi",
|
||||
"Commodore": "Commòdòr"
|
||||
}
|
||||
|
||||