La macchina del tempo: dall'Arcu («Bei tempi quando qui si faceva i pokerini…») al Barrettino del 10 luglio 1997 col viaggio sul cruscotto, Vannino e Pierino Parigi, e il pokerino a cinque carte col cambio; l'Arcu in Via Liguri Magelli più grande (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
This commit is contained in:
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"image": "pierino_parigi.png",
|
||||||
|
"height": 120,
|
||||||
|
"frames": {
|
||||||
|
"explain": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 61,
|
||||||
|
"h": 120,
|
||||||
|
"ax": 29.3,
|
||||||
|
"ay": 120
|
||||||
|
},
|
||||||
|
"talk": {
|
||||||
|
"x": 63,
|
||||||
|
"y": 0,
|
||||||
|
"w": 69,
|
||||||
|
"h": 120,
|
||||||
|
"ax": 26.6,
|
||||||
|
"ay": 120
|
||||||
|
},
|
||||||
|
"blink": {
|
||||||
|
"x": 134,
|
||||||
|
"y": 0,
|
||||||
|
"w": 44,
|
||||||
|
"h": 120,
|
||||||
|
"ax": 25.0,
|
||||||
|
"ay": 120
|
||||||
|
},
|
||||||
|
"stand": {
|
||||||
|
"x": 180,
|
||||||
|
"y": 0,
|
||||||
|
"w": 44,
|
||||||
|
"h": 120,
|
||||||
|
"ax": 24.9,
|
||||||
|
"ay": 120
|
||||||
|
},
|
||||||
|
"cheer": {
|
||||||
|
"x": 226,
|
||||||
|
"y": 0,
|
||||||
|
"w": 66,
|
||||||
|
"h": 128,
|
||||||
|
"ax": 34.6,
|
||||||
|
"ay": 128
|
||||||
|
},
|
||||||
|
"shrug": {
|
||||||
|
"x": 294,
|
||||||
|
"y": 0,
|
||||||
|
"w": 89,
|
||||||
|
"h": 120,
|
||||||
|
"ax": 43.8,
|
||||||
|
"ay": 120
|
||||||
|
},
|
||||||
|
"cards": {
|
||||||
|
"x": 385,
|
||||||
|
"y": 0,
|
||||||
|
"w": 50,
|
||||||
|
"h": 120,
|
||||||
|
"ax": 26.2,
|
||||||
|
"ay": 120
|
||||||
|
},
|
||||||
|
"laugh": {
|
||||||
|
"x": 437,
|
||||||
|
"y": 0,
|
||||||
|
"w": 54,
|
||||||
|
"h": 123,
|
||||||
|
"ax": 24.4,
|
||||||
|
"ay": 123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"image": "vannino.png",
|
||||||
|
"height": 120,
|
||||||
|
"frames": {
|
||||||
|
"explain": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 56,
|
||||||
|
"h": 120,
|
||||||
|
"ax": 28.6,
|
||||||
|
"ay": 120
|
||||||
|
},
|
||||||
|
"talk": {
|
||||||
|
"x": 58,
|
||||||
|
"y": 0,
|
||||||
|
"w": 64,
|
||||||
|
"h": 120,
|
||||||
|
"ax": 25.6,
|
||||||
|
"ay": 120
|
||||||
|
},
|
||||||
|
"blink": {
|
||||||
|
"x": 124,
|
||||||
|
"y": 0,
|
||||||
|
"w": 45,
|
||||||
|
"h": 120,
|
||||||
|
"ax": 25.0,
|
||||||
|
"ay": 120
|
||||||
|
},
|
||||||
|
"stand": {
|
||||||
|
"x": 171,
|
||||||
|
"y": 0,
|
||||||
|
"w": 46,
|
||||||
|
"h": 120,
|
||||||
|
"ax": 25.3,
|
||||||
|
"ay": 120
|
||||||
|
},
|
||||||
|
"cheer": {
|
||||||
|
"x": 219,
|
||||||
|
"y": 0,
|
||||||
|
"w": 52,
|
||||||
|
"h": 120,
|
||||||
|
"ax": 26.8,
|
||||||
|
"ay": 120
|
||||||
|
},
|
||||||
|
"shrug": {
|
||||||
|
"x": 273,
|
||||||
|
"y": 0,
|
||||||
|
"w": 80,
|
||||||
|
"h": 120,
|
||||||
|
"ax": 40.7,
|
||||||
|
"ay": 120
|
||||||
|
},
|
||||||
|
"cards": {
|
||||||
|
"x": 355,
|
||||||
|
"y": 0,
|
||||||
|
"w": 45,
|
||||||
|
"h": 121,
|
||||||
|
"ax": 22.7,
|
||||||
|
"ay": 121
|
||||||
|
},
|
||||||
|
"laugh": {
|
||||||
|
"x": 402,
|
||||||
|
"y": 0,
|
||||||
|
"w": 46,
|
||||||
|
"h": 121,
|
||||||
|
"ax": 22.7,
|
||||||
|
"ay": 121
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 233 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 190 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+120
-4
@@ -1631,6 +1631,14 @@
|
|||||||
"file": "andrea-a193a43f3e.mp3",
|
"file": "andrea-a193a43f3e.mp3",
|
||||||
"sec": 3.78
|
"sec": 3.78
|
||||||
},
|
},
|
||||||
|
"andrea|La macchina del tempo, parcheggiata davanti al Trappola. Sul cruscotto la data è già pronta.": {
|
||||||
|
"file": "andrea-5e16aca755.mp3",
|
||||||
|
"sec": 6.33
|
||||||
|
},
|
||||||
|
"andrea|Salgo. Imposto: 10 luglio 1997, ore 18 e 30. Il pokerino da Vannino.": {
|
||||||
|
"file": "andrea-c77b0c4b4c.mp3",
|
||||||
|
"sec": 9.24
|
||||||
|
},
|
||||||
"andrea|Nessuno ha mai capito davvero il tuo immenso valore.": {
|
"andrea|Nessuno ha mai capito davvero il tuo immenso valore.": {
|
||||||
"file": "andrea-3e85200509.mp3",
|
"file": "andrea-3e85200509.mp3",
|
||||||
"sec": 2.93
|
"sec": 2.93
|
||||||
@@ -1879,6 +1887,34 @@
|
|||||||
"file": "andrea-d32e1a2a87.mp3",
|
"file": "andrea-d32e1a2a87.mp3",
|
||||||
"sec": 3.45
|
"sec": 3.45
|
||||||
},
|
},
|
||||||
|
"andrea|Il Barrettino. Nel 1997. Si sente ancora l'odore del caffè e delle sigarette.": {
|
||||||
|
"file": "andrea-e9ea220852.mp3",
|
||||||
|
"sec": 7.94
|
||||||
|
},
|
||||||
|
"andrea|Il panno verde, le fiches in pila, il mazzo e il posacenere pieno. Una sedia è vuota: è per me.": {
|
||||||
|
"file": "andrea-8c283ea588.mp3",
|
||||||
|
"sec": 8.13
|
||||||
|
},
|
||||||
|
"andrea|Vannino, alto e secco, col canovaccio sulla spalla. E quella fossetta nel mento.": {
|
||||||
|
"file": "andrea-c342d764ea.mp3",
|
||||||
|
"sec": 6.77
|
||||||
|
},
|
||||||
|
"andrea|Pierino Parigi, con gli occhiali sul naso, che guarda le carte come se lo tradissero.": {
|
||||||
|
"file": "andrea-a1771b44e8.mp3",
|
||||||
|
"sec": 5.22
|
||||||
|
},
|
||||||
|
"andrea|Il televisore a tubo. È il 1997: niente smartphone, solo il televideo.": {
|
||||||
|
"file": "andrea-64b8a38c86.mp3",
|
||||||
|
"sec": 7.87
|
||||||
|
},
|
||||||
|
"andrea|Fuori c'è la sera del 1997. E parcheggiata lì, la macchina del tempo.": {
|
||||||
|
"file": "andrea-1efd47285d.mp3",
|
||||||
|
"sec": 5.94
|
||||||
|
},
|
||||||
|
"andrea|Si torna nel 2007. Ciao Vannino, ciao Pierino.": {
|
||||||
|
"file": "andrea-baa95acb36.mp3",
|
||||||
|
"sec": 4.41
|
||||||
|
},
|
||||||
"andrea|Un pino a ombrello enorme. Alla ricreazione, tutti all'ombra lì sotto. Sembra ieri.": {
|
"andrea|Un pino a ombrello enorme. Alla ricreazione, tutti all'ombra lì sotto. Sembra ieri.": {
|
||||||
"file": "andrea-0a073729fd.mp3",
|
"file": "andrea-0a073729fd.mp3",
|
||||||
"sec": 7.3
|
"sec": 7.3
|
||||||
@@ -2511,6 +2547,10 @@
|
|||||||
"file": "andrea-a960f89e7a.mp3",
|
"file": "andrea-a960f89e7a.mp3",
|
||||||
"sec": 3.45
|
"sec": 3.45
|
||||||
},
|
},
|
||||||
|
"andrea|Bei tempi quando qui si faceva i pokerini...": {
|
||||||
|
"file": "andrea-2e7cda51c4.mp3",
|
||||||
|
"sec": 2.55
|
||||||
|
},
|
||||||
"andrea|Ci si vede dopo, Arcu.": {
|
"andrea|Ci si vede dopo, Arcu.": {
|
||||||
"file": "andrea-191021ad7c.mp3",
|
"file": "andrea-191021ad7c.mp3",
|
||||||
"sec": 1.68
|
"sec": 1.68
|
||||||
@@ -2539,6 +2579,26 @@
|
|||||||
"file": "arcu-fcaee733df.mp3",
|
"file": "arcu-fcaee733df.mp3",
|
||||||
"sec": 0.92
|
"sec": 0.92
|
||||||
},
|
},
|
||||||
|
"arcu|La macchina è sempre lì. La data è già impostata.": {
|
||||||
|
"file": "arcu-2ddbc3341d.mp3",
|
||||||
|
"sec": 3.69
|
||||||
|
},
|
||||||
|
"arcu|I pokerini da Vannino, col Pierino che bluffava anche a briscola.": {
|
||||||
|
"file": "arcu-6f0e1c4f8a.mp3",
|
||||||
|
"sec": 3.85
|
||||||
|
},
|
||||||
|
"arcu|Ci vuoi tornare? Aspetta.": {
|
||||||
|
"file": "arcu-00f5e0a28f.mp3",
|
||||||
|
"sec": 3.73
|
||||||
|
},
|
||||||
|
"andrea|Ma quella è... la macchina del tempo!": {
|
||||||
|
"file": "andrea-a748d9ad25.mp3",
|
||||||
|
"sec": 3.13
|
||||||
|
},
|
||||||
|
"arcu|Il plutonio l'ha trovato Alvarino. Basta salire e mettere la data.": {
|
||||||
|
"file": "arcu-1611cdb5fb.mp3",
|
||||||
|
"sec": 4.88
|
||||||
|
},
|
||||||
"andrea|Alla bona, un giro di paese.": {
|
"andrea|Alla bona, un giro di paese.": {
|
||||||
"file": "andrea-b35a4d8cc0.mp3",
|
"file": "andrea-b35a4d8cc0.mp3",
|
||||||
"sec": 2.15
|
"sec": 2.15
|
||||||
@@ -2659,6 +2719,50 @@
|
|||||||
"file": "andrea-78ad61a67a.mp3",
|
"file": "andrea-78ad61a67a.mp3",
|
||||||
"sec": 1.22
|
"sec": 1.22
|
||||||
},
|
},
|
||||||
|
"andrea|C'è posto per uno?": {
|
||||||
|
"file": "andrea-f7481e5519.mp3",
|
||||||
|
"sec": 1.81
|
||||||
|
},
|
||||||
|
"andrea|Vannino, un caffè?": {
|
||||||
|
"file": "andrea-23e4ec2f09.mp3",
|
||||||
|
"sec": 1.86
|
||||||
|
},
|
||||||
|
"andrea|Pierino, come va la mano?": {
|
||||||
|
"file": "andrea-34d248b998.mp3",
|
||||||
|
"sec": 1.59
|
||||||
|
},
|
||||||
|
"andrea|Dopo.": {
|
||||||
|
"file": "andrea-b410f40be6.mp3",
|
||||||
|
"sec": 1.73
|
||||||
|
},
|
||||||
|
"vannino|La sedia è lì. Invito due, punto cinque. Come la vuoi?": {
|
||||||
|
"file": "vannino-225c8700a1.mp3",
|
||||||
|
"sec": 5.37
|
||||||
|
},
|
||||||
|
"vannino|Subito. Ma prima una mano, sennò che ci fai qui?": {
|
||||||
|
"file": "vannino-cd3ce69939.mp3",
|
||||||
|
"sec": 3.21
|
||||||
|
},
|
||||||
|
"pierino_parigi|Bene. O male. Con me un si sa mai, ed è questo il bello.": {
|
||||||
|
"file": "pierino_parigi-46d59a6a61.mp3",
|
||||||
|
"sec": 5.7
|
||||||
|
},
|
||||||
|
"andrea|No, aspetta.": {
|
||||||
|
"file": "andrea-04890c1d8e.mp3",
|
||||||
|
"sec": 1.39
|
||||||
|
},
|
||||||
|
"pierino_parigi|Alla bona: si gioca per il caffè.": {
|
||||||
|
"file": "pierino_parigi-9b57763042.mp3",
|
||||||
|
"sec": 2.33
|
||||||
|
},
|
||||||
|
"pierino_parigi|Seria. Carte, Vannino.": {
|
||||||
|
"file": "pierino_parigi-259f48a09a.mp3",
|
||||||
|
"sec": 2.55
|
||||||
|
},
|
||||||
|
"pierino_parigi|A fassi male? Allora io bluffo.": {
|
||||||
|
"file": "pierino_parigi-d98fcb71d3.mp3",
|
||||||
|
"sec": 2.76
|
||||||
|
},
|
||||||
"andrea|Si fa una gara alle macchinine?!?!": {
|
"andrea|Si fa una gara alle macchinine?!?!": {
|
||||||
"file": "andrea-17216997e2.mp3",
|
"file": "andrea-17216997e2.mp3",
|
||||||
"sec": 2.74
|
"sec": 2.74
|
||||||
@@ -2707,10 +2811,6 @@
|
|||||||
"file": "andrea-fc5a08bbd6.mp3",
|
"file": "andrea-fc5a08bbd6.mp3",
|
||||||
"sec": 0.73
|
"sec": 0.73
|
||||||
},
|
},
|
||||||
"andrea|No, aspetta.": {
|
|
||||||
"file": "andrea-04890c1d8e.mp3",
|
|
||||||
"sec": 1.39
|
|
||||||
},
|
|
||||||
"chiaru|Alla bona: ti faccio vedere dove si ferma. E io vado piano.": {
|
"chiaru|Alla bona: ti faccio vedere dove si ferma. E io vado piano.": {
|
||||||
"file": "chiaru-d31cc223f6.mp3",
|
"file": "chiaru-d31cc223f6.mp3",
|
||||||
"sec": 5.08
|
"sec": 5.08
|
||||||
@@ -3075,6 +3175,22 @@
|
|||||||
"file": "bertovo-ca941bee91.mp3",
|
"file": "bertovo-ca941bee91.mp3",
|
||||||
"sec": 5.41
|
"sec": 5.41
|
||||||
},
|
},
|
||||||
|
"vannino|M'hai pelato, ragazzo. Nel 1997 un ci si vedeva da queste parti, con quella faccia.": {
|
||||||
|
"file": "vannino-e68fb40eba.mp3",
|
||||||
|
"sec": 7.85
|
||||||
|
},
|
||||||
|
"pierino_parigi|Le fiches restano al banco. E tu torna quando hai imparato a bluffare.": {
|
||||||
|
"file": "pierino_parigi-2c2a083725.mp3",
|
||||||
|
"sec": 5.21
|
||||||
|
},
|
||||||
|
"vannino|Già finito? E il caffè chi lo paga?": {
|
||||||
|
"file": "vannino-3307746bb8.mp3",
|
||||||
|
"sec": 3.98
|
||||||
|
},
|
||||||
|
"pierino_parigi|Pari. Si rifà domani sera, alla stess'ora.": {
|
||||||
|
"file": "pierino_parigi-75a2b84953.mp3",
|
||||||
|
"sec": 3.8
|
||||||
|
},
|
||||||
"andrea|Tutti sul muro, verdi in fila. Lo stucco di Pierino un tradisce mai.": {
|
"andrea|Tutti sul muro, verdi in fila. Lo stucco di Pierino un tradisce mai.": {
|
||||||
"file": "andrea-585cd65f93.mp3",
|
"file": "andrea-585cd65f93.mp3",
|
||||||
"sec": 5.2
|
"sec": 5.2
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -35,6 +35,8 @@ ne è un'opera derivata. Le foto scattate da Andrea non hanno problemi; quelle p
|
|||||||
| `characters/zio_mario.png` | Codex | `foto/zio-mario.png` | Andrea (29 ottobre) | sì (famiglia di Andrea) |
|
| `characters/zio_mario.png` | Codex | `foto/zio-mario.png` | Andrea (29 ottobre) | sì (famiglia di Andrea) |
|
||||||
| `terrazza-cardetole.png` | Codex (dalla foto, poi il terrazzo davanti) | `cardetole-finestra.jpg` | Andrea (29 ottobre) | **da decidere**: come `cardetole.png`, dice dove abita Andrea |
|
| `terrazza-cardetole.png` | Codex (dalla foto, poi il terrazzo davanti) | `cardetole-finestra.jpg` | Andrea (29 ottobre) | **da decidere**: come `cardetole.png`, dice dove abita Andrea |
|
||||||
| `closeups/muro-cerbottana.png` | Codex, solo testo (stile dalla piazza) | nessuna | — | sì |
|
| `closeups/muro-cerbottana.png` | Codex, solo testo (stile dalla piazza) | nessuna | — | sì |
|
||||||
|
| `barrettino-1997.png`, `closeups/cruscotto-tempo.png` | Codex, solo testo | nessuna | — | sì |
|
||||||
|
| `characters/vannino.png`, `characters/pierino_parigi.png` | Codex, dalla descrizione di Andrea (niente foto) | nessuna | — | sì (Pierino: consenso del figlio Marco) |
|
||||||
| `cardetole.png` | Codex | `cardetole.jpg` | Andrea (EXIF) | **da decidere**: è la casa vera di Andrea, pubblicarla dice dove abita |
|
| `cardetole.png` | Codex | `cardetole.jpg` | Andrea (EXIF) | **da decidere**: è la casa vera di Andrea, pubblicarla dice dove abita |
|
||||||
| `pieve-interno.png` | Codex | `pieve-interno.png` | Mariano Mirannalti (mandata ad Andrea su WhatsApp) | sì, se Mariano è d'accordo; citarlo nei crediti |
|
| `pieve-interno.png` | Codex | `pieve-interno.png` | Mariano Mirannalti (mandata ad Andrea su WhatsApp) | sì, se Mariano è d'accordo; citarlo nei crediti |
|
||||||
| `panorama-intro.png` | Codex | `altre/panorama-intro.png` | da internet (screenshot con l'icona di Google Lens) | **no**, finché non si sostituisce |
|
| `panorama-intro.png` | Codex | `altre/panorama-intro.png` | da internet (screenshot con l'icona di Google Lens) | **no**, finché non si sostituisce |
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ Online su 50037.manzolo.it tutto fino agli effetti ambientali di Codex. Aperto,
|
|||||||
|
|
||||||
## Dove siamo arrivati
|
## Dove siamo arrivati
|
||||||
|
|
||||||
|
- **La macchina del tempo e il pokerino del 1997 (29 ottobre)**: dall'Arcu sotto la tenda del Trappola, «Bei tempi
|
||||||
|
quando qui si faceva i pokerini…»: compare la macchina del tempo in Via Liguri Magelli. Dentro, il cruscotto (Codex)
|
||||||
|
compone 10/07/1997 18:30, poi tremori, lampi e il bianco (primo piano `viaggio`, `src/engine/viaggio.js`, tempi in
|
||||||
|
`TRIP` di `game.js`): si arriva al Barrettino del 1997 (`barrettino_1997`, scena `viaggio: true`: niente mappa né
|
||||||
|
strade, la porta riporta nel 2007), con Vannino (dalla descrizione di Andrea) e Pierino Parigi (consenso del figlio
|
||||||
|
Marco, 29 ottobre), sprite di Codex. Il pokerino (`poker.js`, `poker-render.js`): cinque carte col cambio, mazzo
|
||||||
|
corto dal 7 all'asso (colore sopra il full, A-7-8-9-10 scala), invito 2, punto 5, cinque mani. [DA ANDREA] l'interno
|
||||||
|
del Barrettino (è una proposta di Codex) e le battute.
|
||||||
- **Nuovi posti dalle foto di Andrea (29 ottobre, sfondi di Codex)**: Via della Compagnia (fra Via de' Medici, la
|
- **Nuovi posti dalle foto di Andrea (29 ottobre, sfondi di Codex)**: Via della Compagnia (fra Via de' Medici, la
|
||||||
Pieve e Villa Adami), Via Cafaggio in tre tratti fino alla rotonda, la Provinciale sotto i platani, la passeggiata
|
Pieve e Villa Adami), Via Cafaggio in tre tratti fino alla rotonda, la Provinciale sotto i platani, la passeggiata
|
||||||
sulla Carza (cascatella, orti, dietro le poste, sotto il ponte delle poste, gli orti dello zio Mario), la strada fra
|
sulla Carza (cascatella, orti, dietro le poste, sotto il ponte delle poste, gli orti dello zio Mario), la strada fra
|
||||||
|
|||||||
+16
@@ -171,6 +171,22 @@
|
|||||||
Tastiera: B, S, V; frecce e 1–0 scelgono, Invio gioca, Esc lascia il tavolo.</p>
|
Tastiera: B, S, V; frecce e 1–0 scelgono, Invio gioca, Esc lascia il tavolo.</p>
|
||||||
</details>
|
</details>
|
||||||
</section>
|
</section>
|
||||||
|
<section id="poker-controls" hidden aria-label="Comandi del pokerino">
|
||||||
|
<p id="poker-status" role="status"></p>
|
||||||
|
<div class="card-buttons">
|
||||||
|
<button id="poker-move-0" type="button" hidden></button>
|
||||||
|
<button id="poker-move-1" type="button" hidden></button>
|
||||||
|
<button id="poker-move-2" type="button" hidden></button>
|
||||||
|
</div>
|
||||||
|
<details><summary>Come si gioca</summary>
|
||||||
|
<p>Il pokerino del Barrettino: cinque carte coperte col cambio, mazzo corto dal 7 all'asso. Ognuno mette l'invito
|
||||||
|
(2 fiches) nel piatto. Primo giro di puntate: Parola se non c'è niente da mettere, Apro per puntare 5, Vedo per
|
||||||
|
mettere quanto gli altri, Rilancio per alzare di 5, Lascio per uscire dalla mano. Poi il cambio: tocca le carte da
|
||||||
|
cambiare (al massimo 4) e premi Cambia (Servito se le tieni tutte). Secondo giro di puntate, poi chi resta mostra.</p>
|
||||||
|
<p>Punti, dal più basso: carta alta, coppia, doppia coppia, tris, scala, full, colore, poker, scala colore. Col mazzo
|
||||||
|
corto il colore batte il full, e A-7-8-9-10 è la scala più bassa. Cinque mani: vince chi ha più fiches. Esc esce.</p>
|
||||||
|
</details>
|
||||||
|
</section>
|
||||||
<section id="briscola-controls" hidden aria-label="Comandi della briscola">
|
<section id="briscola-controls" hidden aria-label="Comandi della briscola">
|
||||||
<p id="briscola-status" role="status"></p>
|
<p id="briscola-status" role="status"></p>
|
||||||
<div class="card-hand" aria-label="La mano di Andrea">
|
<div class="card-hand" aria-label="La mano di Andrea">
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import * as scopa from './scopa.js';
|
|||||||
import * as gara from './gara.js';
|
import * as gara from './gara.js';
|
||||||
import * as micro from './micro.js';
|
import * as micro from './micro.js';
|
||||||
import * as cerbottana from './cerbottana.js';
|
import * as cerbottana from './cerbottana.js';
|
||||||
|
import * as poker from './poker.js';
|
||||||
|
|
||||||
// I minigiochi in primo piano: comandi opzionali per il tiro o per la scelta di una carta.
|
// I minigiochi in primo piano: comandi opzionali per il tiro o per la scelta di una carta.
|
||||||
export const MINIGAMES = {
|
export const MINIGAMES = {
|
||||||
@@ -23,6 +24,7 @@ export const MINIGAMES = {
|
|||||||
rinquarto: { create: (seed, level) => rinquarto.newRinquarto(seed, { level }), update: rinquarto.update, press: rinquarto.press, release: rinquarto.release, cancel: rinquarto.cancel, aim: rinquarto.aimScreen },
|
rinquarto: { create: (seed, level) => rinquarto.newRinquarto(seed, { level }), update: rinquarto.update, press: rinquarto.press, release: rinquarto.release, cancel: rinquarto.cancel, aim: rinquarto.aimScreen },
|
||||||
basket: { create: (seed, level) => basket.newBasket(seed, { level }), update: basket.update, press: basket.press, release: basket.release, cancel: basket.cancel, aim: basket.aimScreen },
|
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 },
|
pesca: { create: (seed, level) => pesca.newPesca(seed, { level }), update: pesca.update, press: pesca.press, release: pesca.release, cancel: pesca.cancel, aim: pesca.aimScreen },
|
||||||
|
poker: { create: (seed, level) => poker.newPoker(seed, { level }), update: poker.update, play: poker.play },
|
||||||
cerbottana: { create: (seed, level) => cerbottana.newCerbottana(seed, { level }), update: cerbottana.update, press: cerbottana.press, release: cerbottana.release, cancel: cerbottana.cancel, aim: cerbottana.aimScreen },
|
cerbottana: { create: (seed, level) => cerbottana.newCerbottana(seed, { level }), update: cerbottana.update, press: cerbottana.press, release: cerbottana.release, cancel: cerbottana.cancel, aim: cerbottana.aimScreen },
|
||||||
micro: { create: (seed, level) => micro.newMicro(seed, { level }), update: micro.update, press: micro.press, release: micro.release, cancel: micro.cancel, aim: micro.aimScreen },
|
micro: { create: (seed, level) => micro.newMicro(seed, { level }), update: micro.update, press: micro.press, release: micro.release, cancel: micro.cancel, aim: micro.aimScreen },
|
||||||
gara: { create: (seed, level, deck, def) => gara.newGara(seed, { level, riders: def.riders }), update: gara.update, press: gara.press, release: gara.release, cancel: gara.cancel, aim: gara.aimScreen },
|
gara: { create: (seed, level, deck, def) => gara.newGara(seed, { level, riders: def.riders }), update: gara.update, press: gara.press, release: gara.release, cancel: gara.cancel, aim: gara.aimScreen },
|
||||||
@@ -51,6 +53,9 @@ const WALK = ['walk_1', 'walk_2', 'walk_3', 'walk_4'], STEP = 15, REACH = 0.45;
|
|||||||
// Il primo piano di un libro (story.closeups, effetto `closeup`): copertina → frontespizio → una pagina girata →
|
// Il primo piano di un libro (story.closeups, effetto `closeup`): copertina → frontespizio → una pagina girata →
|
||||||
// l'ultima pagina, poi dopo una pausa il commento. Tempi in secondi.
|
// l'ultima pagina, poi dopo una pausa il commento. Tempi in secondi.
|
||||||
export const PAGE_TURN = 0.7, CLOSEUP_PAUSE = 1.4;
|
export const PAGE_TURN = 0.7, CLOSEUP_PAUSE = 1.4;
|
||||||
|
// I tempi del viaggio nel tempo, in secondi: quando comincia a comparire la data, quanto dura ogni tasto, quando si
|
||||||
|
// carica (tremori e lampi), quando arriva il bianco, quando si chiude.
|
||||||
|
export const TRIP = { type: 0.8, key: 0.16, charge: 3.6, flash: 5.4, end: 6.6 };
|
||||||
|
|
||||||
// Condizioni: 'flag', '!flag', 'has:oggetto', '!has:oggetto', oppure un elenco (tutte vere).
|
// Condizioni: 'flag', '!flag', 'has:oggetto', '!has:oggetto', oppure un elenco (tutte vere).
|
||||||
export function check(cond, state) {
|
export function check(cond, state) {
|
||||||
@@ -306,6 +311,8 @@ export class Game {
|
|||||||
if (c.step === 'done') this.queue.unshift(...def.complete);
|
if (c.step === 'done') this.queue.unshift(...def.complete);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// Il viaggio si chiude da solo alla fine (o uscendo prima): si arriva comunque.
|
||||||
|
if (def?.type === 'viaggio') { this.queue.unshift(...def.complete); return true; }
|
||||||
// I minigiochi: a partita finita (risultato deciso, anche se il conteggio si sta ancora mostrando) la battuta
|
// I minigiochi: a partita finita (risultato deciso, anche se il conteggio si sta ancora mostrando) la battuta
|
||||||
// di chi ha vinto; uscendo prima, quella di chi lascia.
|
// di chi ha vinto; uscendo prima, quella di chi lascia.
|
||||||
if (MINIGAMES[def?.type]) {
|
if (MINIGAMES[def?.type]) {
|
||||||
@@ -336,6 +343,18 @@ export class Game {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Il viaggio nel tempo (story.closeups, type 'viaggio'): la data si compone sul cruscotto una cifra alla volta, poi
|
||||||
|
// tremori, lampi e il bianco; alla fine si chiude da solo e parte `complete` (di solito il `go` nella scena d'arrivo).
|
||||||
|
updateTrip(dt) {
|
||||||
|
const c = this.closeup, def = this.story.closeups[c.id], T = TRIP;
|
||||||
|
c.t += dt;
|
||||||
|
const typed = Math.max(0, Math.min(def.date.length, Math.floor((c.t - T.type) / T.key) + 1));
|
||||||
|
for (; c.typed < typed; c.typed++) if (def.date[c.typed] !== ' ') c.events.push({ kind: 'card-tap', power: 0.7 });
|
||||||
|
if (!c.charge && c.t >= T.charge) { c.charge = true; c.events.push({ kind: 'gara-turbo', power: 1 }); }
|
||||||
|
if (!c.boom && c.t >= T.flash) { c.boom = true; c.events.push({ kind: 'gara-buca', power: 1 }); }
|
||||||
|
if (c.t >= T.end) this.closeupClose();
|
||||||
|
}
|
||||||
|
|
||||||
updateTorch(dt) {
|
updateTorch(dt) {
|
||||||
const c = this.closeup, def = this.story.closeups[c.id];
|
const c = this.closeup, def = this.story.closeups[c.id];
|
||||||
c.t += dt;
|
c.t += dt;
|
||||||
@@ -453,6 +472,7 @@ export class Game {
|
|||||||
const c = this.closeup;
|
const c = this.closeup;
|
||||||
if (c) {
|
if (c) {
|
||||||
if (this.story.closeups[c.id].type === 'torch') { this.updateTorch(dt); return; }
|
if (this.story.closeups[c.id].type === 'torch') { this.updateTorch(dt); return; }
|
||||||
|
if (this.story.closeups[c.id].type === 'viaggio') { this.updateTrip(dt); return; }
|
||||||
const m = this.minigame();
|
const m = this.minigame();
|
||||||
if (m) {
|
if (m) {
|
||||||
m.update(c, dt);
|
m.update(c, dt);
|
||||||
@@ -494,6 +514,7 @@ export class Game {
|
|||||||
else if (e.closeup) {
|
else if (e.closeup) {
|
||||||
const type = this.story.closeups[e.closeup].type;
|
const type = this.story.closeups[e.closeup].type;
|
||||||
this.closeup = type === 'torch' ? { id: e.closeup, step: 'search', t: 0, age: 0, found: 0, dwell: 0, beam: [320, 180] }
|
this.closeup = type === 'torch' ? { id: e.closeup, step: 'search', t: 0, age: 0, found: 0, dwell: 0, beam: [320, 180] }
|
||||||
|
: type === 'viaggio' ? { id: e.closeup, t: 0, age: 0, events: [], typed: 0 }
|
||||||
// `level`: «Alla bona», «Seria» o «A fassi male», scelto nel dialogo con l'avversario.
|
// `level`: «Alla bona», «Seria» o «A fassi male», scelto nel dialogo con l'avversario.
|
||||||
// `decks`: il mazzo da usare quando il suo flag è acceso (briscola: le napoletane al posto delle toscane).
|
// `decks`: il mazzo da usare quando il suo flag è acceso (briscola: le napoletane al posto delle toscane).
|
||||||
: MINIGAMES[type] ? { id: e.closeup, ...MINIGAMES[type].create(Math.floor(Math.random() * 4294967296), e.level,
|
: MINIGAMES[type] ? { id: e.closeup, ...MINIGAMES[type].create(Math.floor(Math.random() * 4294967296), e.level,
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { SEATS, NAMES, MANI, canDraw, myTurn, evaluate, CATEGORIES, red } from './poker.js';
|
||||||
|
import { drawTable, drawCard } from './briscola-render.js';
|
||||||
|
import { text, panel } from './bocce-render.js';
|
||||||
|
|
||||||
|
// Le carte di Andrea, in basso: coordinate condivise da disegno e tocco (bersagli larghi anche sul telefono).
|
||||||
|
export const HAND = { x: 150, y: 222, gap: 70, w: 62, h: 90 };
|
||||||
|
export function cardAt(c, [x, y]) {
|
||||||
|
if (!canDraw(c)) return -1;
|
||||||
|
return c.cards.andrea.findIndex((_, i) => x >= HAND.x + i * HAND.gap && x < HAND.x + i * HAND.gap + HAND.w && y >= HAND.y - 14 && y < HAND.y + HAND.h);
|
||||||
|
}
|
||||||
|
const ink = '#2a1f1a', paper = '#fff4dc';
|
||||||
|
function rect(ctx, x, y, w, h, color) { ctx.fillStyle = color; ctx.fillRect(Math.round(x), Math.round(y), w, h); }
|
||||||
|
// I semi francesi a pixel (il carattere del gioco non ha ♥♦♣♠): cuore, quadro, fiore, picca, larghi 2·s.
|
||||||
|
function pip(ctx, suit, x, y, s) {
|
||||||
|
const color = suit === 'cuori' || suit === 'quadri' ? '#c0392b' : ink;
|
||||||
|
ctx.fillStyle = color; ctx.beginPath();
|
||||||
|
if (suit === 'quadri') { ctx.moveTo(x, y - s); ctx.lineTo(x + s * 0.75, y); ctx.lineTo(x, y + s); ctx.lineTo(x - s * 0.75, y); }
|
||||||
|
else if (suit === 'cuori') {
|
||||||
|
ctx.moveTo(x, y + s); ctx.lineTo(x - s, y - s * 0.1); ctx.arc(x - s * 0.5, y - s * 0.35, s * 0.52, Math.PI * 0.85, Math.PI * 1.95);
|
||||||
|
ctx.arc(x + s * 0.5, y - s * 0.35, s * 0.52, Math.PI * 1.05, Math.PI * 0.15); ctx.lineTo(x, y + s);
|
||||||
|
} else if (suit === 'picche') {
|
||||||
|
ctx.moveTo(x, y - s); ctx.lineTo(x + s, y + s * 0.15); ctx.arc(x + s * 0.5, y + s * 0.3, s * 0.5, 0, Math.PI * 0.9);
|
||||||
|
ctx.lineTo(x, y + s * 0.2); ctx.arc(x - s * 0.5, y + s * 0.3, s * 0.5, Math.PI * 0.1, Math.PI); ctx.closePath();
|
||||||
|
ctx.fill(); ctx.beginPath(); ctx.moveTo(x, y + s * 0.3); ctx.lineTo(x + s * 0.35, y + s); ctx.lineTo(x - s * 0.35, y + s);
|
||||||
|
} else {
|
||||||
|
for (const [dx, dy] of [[0, -0.45], [-0.48, 0.15], [0.48, 0.15]]) { ctx.moveTo(x + dx * s + s * 0.42, y + dy * s); ctx.arc(x + dx * s, y + dy * s, s * 0.42, 0, Math.PI * 2); }
|
||||||
|
ctx.fill(); ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + s * 0.35, y + s); ctx.lineTo(x - s * 0.35, y + s);
|
||||||
|
}
|
||||||
|
ctx.closePath(); ctx.fill();
|
||||||
|
}
|
||||||
|
// Una carta francese: angolo col valore e il seme, il seme grande in mezzo (le figure con una cornice).
|
||||||
|
export function drawFrench(ctx, card, x, y, w, h, back = false) {
|
||||||
|
if (back) { drawCard(ctx, null, x, y, w, h, true); return; }
|
||||||
|
rect(ctx, x + 2, y + 3, w, h, '#15231d'); rect(ctx, x, y, w, h, ink); rect(ctx, x + 1, y + 1, w - 2, h - 2, paper);
|
||||||
|
const color = red(card) ? '#c0392b' : ink, k = w / 62;
|
||||||
|
text(ctx, card.rank, x + 5 * k, y + 9 * k, Math.round(9 * k), color);
|
||||||
|
pip(ctx, card.suit, x + 9 * k, y + 22 * k, 5 * k);
|
||||||
|
if (['J', 'Q', 'K'].includes(card.rank)) {
|
||||||
|
rect(ctx, x + 14 * k, y + 28 * k, 34 * k, 50 * k, red(card) ? '#f3d6c4' : '#d8dfe6');
|
||||||
|
ctx.strokeStyle = color; ctx.lineWidth = 1; ctx.strokeRect(Math.round(x + 14 * k) + 0.5, Math.round(y + 28 * k) + 0.5, Math.round(34 * k), Math.round(50 * k));
|
||||||
|
text(ctx, card.rank, x + 31 * k, y + 46 * k, Math.round(16 * k), color, 'center');
|
||||||
|
pip(ctx, card.suit, x + 31 * k, y + 66 * k, 7 * k);
|
||||||
|
} else pip(ctx, card.suit, x + 31 * k, y + 52 * k, 14 * k);
|
||||||
|
}
|
||||||
|
// Le fiches del piatto: pile colorate, una ogni cinque.
|
||||||
|
function chips(ctx, n, x, y) {
|
||||||
|
const colors = ['#c0392b', '#2f6fb0', '#e8e0c8', '#3a8a4a'];
|
||||||
|
for (let i = 0; i < Math.min(24, Math.ceil(n / 5)); i++) {
|
||||||
|
const col = i % 6, row = Math.floor(i / 6), cx = x + col * 14 - 35, cy = y - row * 3;
|
||||||
|
rect(ctx, cx, cy, 12, 4, '#1a1410'); rect(ctx, cx, cy - 1, 12, 3, colors[i % colors.length]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Dove siedono: Vannino a sinistra, Pierino a destra, Andrea in basso.
|
||||||
|
const SPOTS = { vannino: { x: 40, y: 70 }, pierino: { x: 420, y: 70 } };
|
||||||
|
|
||||||
|
export function drawPoker(ctx, c) {
|
||||||
|
ctx.save(); ctx.imageSmoothingEnabled = false;
|
||||||
|
drawTable(ctx, 'IL POKERINO DEL BARRETTINO · 10 LUGLIO 1997');
|
||||||
|
const show = c.step === 'show' || c.step === 'result';
|
||||||
|
for (const who of ['vannino', 'pierino']) {
|
||||||
|
const { x, y } = SPOTS[who], out = !c.in?.[who];
|
||||||
|
text(ctx, `${NAMES[who]} · ${c.chips[who]}`, x + 90, y - 22, 7, c.current === who ? '#ffe28a' : '#fff0c8', 'center');
|
||||||
|
c.cards[who].forEach((card, i) => drawFrench(ctx, card, x + i * 36, y - 10, 42, 62, !(show && c.in[who])));
|
||||||
|
if (out && c.step !== 'result') { ctx.fillStyle = 'rgba(20,16,12,0.55)'; ctx.fillRect(x - 2, y - 12, 190, 66); text(ctx, 'lascia', x + 90, y + 22, 7, '#d9ccba', 'center'); }
|
||||||
|
if (show && c.in[who] && c.cards[who].length) text(ctx, CATEGORIES[evaluate(c.cards[who])[0]], x + 90, y + 62, 6, c.winners?.includes(who) ? '#b9e5b3' : '#d9ccba', 'center');
|
||||||
|
if (c.changed?.[who] !== undefined && c.step !== 'show') text(ctx, c.changed[who] ? `cambia ${c.changed[who]}` : 'servito', x + 90, y + 62, 6, '#9fd0ff', 'center');
|
||||||
|
}
|
||||||
|
// Il piatto in mezzo.
|
||||||
|
chips(ctx, c.pot ?? 0, 320, 178);
|
||||||
|
text(ctx, `Piatto ${c.pot ?? 0}`, 320, 192, 7, '#ffe09b', 'center');
|
||||||
|
// Le carte di Andrea: quelle scelte per il cambio si alzano.
|
||||||
|
c.cards.andrea.forEach((card, i) => {
|
||||||
|
const up = c.selected?.has(i) ? -14 : 0;
|
||||||
|
drawFrench(ctx, card, HAND.x + i * HAND.gap, HAND.y + up, HAND.w, HAND.h);
|
||||||
|
if (up) text(ctx, 'cambia', HAND.x + i * HAND.gap + HAND.w / 2, HAND.y + up - 7, 5, '#ffe28a', 'center');
|
||||||
|
});
|
||||||
|
text(ctx, `Andrea · ${c.chips.andrea}`, 320, 210, 7, myTurn(c) || canDraw(c) ? '#ffe28a' : '#fff0c8', 'center');
|
||||||
|
if (c.cards.andrea.length) text(ctx, CATEGORIES[evaluate(c.cards.andrea)[0]], 590, 300, 6, '#d9ccba', 'center');
|
||||||
|
text(ctx, `Mano ${Math.min(c.hand, MANI)}/${MANI}`, 40, 300, 6, '#d9ccba');
|
||||||
|
if (c.line && c.step !== 'result') { ctx.fillStyle = '#17160fd0'; ctx.fillRect(120, 146, 400, 16); text(ctx, c.line, 320, 154, 7, c.step === 'show' ? '#b9e5b3' : '#9fd0ff', 'center'); }
|
||||||
|
if (c.step === 'result') {
|
||||||
|
const r = c.result, title = r.winner === 'player' ? 'HAI VINTO IL POKERINO!' : r.winner === 'draw' ? 'PARI' : `HA VINTO ${NAMES[r.top[0]].toUpperCase()}`;
|
||||||
|
panel(ctx, 150, 110, 340, 84);
|
||||||
|
text(ctx, title, 320, 132, 11, r.winner === 'player' ? '#b9e5b3' : '#efd49e', 'center');
|
||||||
|
text(ctx, SEATS.map(s => `${NAMES[s]} ${r.chips[s]}`).join(' · '), 320, 156, 7, '#efd49e', 'center');
|
||||||
|
text(ctx, 'Torna al Barrettino per uscire.', 320, 176, 7, '#d9ccba', 'center');
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
// Il pokerino del Barrettino, luglio 1997 (idea di Andrea, 29 ottobre): poker a cinque carte coperte col cambio, col
|
||||||
|
// mazzo corto da 32 (dal 7 all'asso, come nei bar), Andrea contro Vannino e Pierino Parigi. Si gioca a fiches.
|
||||||
|
// Col mazzo corto il colore è più raro del full e lo batte; la scala più bassa è A-7-8-9-10.
|
||||||
|
// Una mano: il piatto (ognuno mette l'invito), cinque carte, un giro di puntate, il cambio, un altro giro, chi resta
|
||||||
|
// mostra. Puntate fisse (PUNTO) e al massimo RILANCI rilanci a giro. Dopo MANI mani vince chi ha più fiches.
|
||||||
|
|
||||||
|
export const RANKS = ['7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
|
||||||
|
export const SUITS = ['cuori', 'quadri', 'fiori', 'picche'];
|
||||||
|
export const SYMBOL = { cuori: '♥', quadri: '♦', fiori: '♣', picche: '♠' };
|
||||||
|
export const NAMES = { andrea: 'Andrea', vannino: 'Vannino', pierino: 'Pierino' };
|
||||||
|
export const SEATS = ['andrea', 'vannino', 'pierino'];
|
||||||
|
export const START = 100, INVITO = 2, PUNTO = 5, RILANCI = 2, MANI = 5, CPU_TIME = 0.9;
|
||||||
|
export const CATEGORIES = ['carta alta', 'coppia', 'doppia coppia', 'tris', 'scala', 'full', 'colore', 'poker', 'scala colore'];
|
||||||
|
// I livelli: quanto ci pensano gli altri (0 = a caso, 1 = col conto giusto) e quanto bluffano.
|
||||||
|
export const LEVELS = {
|
||||||
|
bona: { skill: 0.35, bluff: 0.04 },
|
||||||
|
seria: { skill: 0.7, bluff: 0.08 },
|
||||||
|
male: { skill: 1, bluff: 0.14 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = card => RANKS.indexOf(card.rank) + 7;
|
||||||
|
export const cardName = card => `${card.rank}${SYMBOL[card.suit]}`;
|
||||||
|
export const red = card => card.suit === 'cuori' || card.suit === 'quadri';
|
||||||
|
function random(c) { c.seed = (Math.imul(c.seed, 1664525) + 1013904223) >>> 0; return c.seed / 4294967296; }
|
||||||
|
function deck(c) {
|
||||||
|
const cards = SUITS.flatMap(suit => RANKS.map(rank => ({ rank, suit })));
|
||||||
|
for (let i = cards.length - 1; i > 0; i--) { const j = Math.floor(random(c) * (i + 1)); [cards[i], cards[j]] = [cards[j], cards[i]]; }
|
||||||
|
return cards;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Il punto di una mano: [categoria, poi i valori per gli spareggi], da confrontare in ordine.
|
||||||
|
export function evaluate(hand) {
|
||||||
|
const values = hand.map(value).sort((a, b) => b - a);
|
||||||
|
const counts = new Map();
|
||||||
|
for (const v of values) counts.set(v, (counts.get(v) ?? 0) + 1);
|
||||||
|
const groups = [...counts].sort((a, b) => b[1] - a[1] || b[0] - a[0]);
|
||||||
|
const flush = hand.every(card => card.suit === hand[0].suit);
|
||||||
|
const distinct = [...counts.keys()].sort((a, b) => b - a);
|
||||||
|
let straight = null;
|
||||||
|
if (distinct.length === 5) {
|
||||||
|
if (distinct[0] - distinct[4] === 4) straight = distinct[0];
|
||||||
|
else if (distinct.join() === '14,10,9,8,7') straight = 10; // A-7-8-9-10: l'asso fa da 6
|
||||||
|
}
|
||||||
|
const shape = groups.map(g => g[1]).join('');
|
||||||
|
const tie = groups.map(g => g[0]);
|
||||||
|
if (straight && flush) return [8, straight];
|
||||||
|
if (shape === '41') return [7, ...tie];
|
||||||
|
if (flush) return [6, ...values];
|
||||||
|
if (shape === '32') return [5, ...tie];
|
||||||
|
if (straight) return [4, straight];
|
||||||
|
if (shape === '311') return [3, ...tie];
|
||||||
|
if (shape === '221') return [2, ...tie];
|
||||||
|
if (shape === '2111') return [1, ...tie];
|
||||||
|
return [0, ...values];
|
||||||
|
}
|
||||||
|
export const compare = (a, b) => { for (let i = 0; i < Math.max(a.length, b.length); i++) if ((a[i] ?? 0) !== (b[i] ?? 0)) return (a[i] ?? 0) - (b[i] ?? 0); return 0; };
|
||||||
|
export const handName = hand => CATEGORIES[evaluate(hand)[0]];
|
||||||
|
|
||||||
|
export function newPoker(seed = Date.now() >>> 0, def = {}) {
|
||||||
|
const level = LEVELS[def.level] ? def.level : 'seria';
|
||||||
|
const c = { step: 'deal', t: 0, seed, level, skill: LEVELS[level], chips: Object.fromEntries(SEATS.map(s => [s, START])),
|
||||||
|
hand: 0, dealer: 2, events: [], line: null, selected: new Set(), history: [], result: null };
|
||||||
|
newHand(c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
const alive = c => SEATS.filter(s => c.chips[s] >= INVITO);
|
||||||
|
const active = c => SEATS.filter(s => c.in[s]);
|
||||||
|
|
||||||
|
// Una mano nuova: l'invito, il mazzo mischiato, cinque carte a testa; parla per primo chi sta dopo il mazziere.
|
||||||
|
function newHand(c) {
|
||||||
|
c.hand++;
|
||||||
|
c.dealer = (c.dealer + 1) % SEATS.length;
|
||||||
|
c.deck = deck(c);
|
||||||
|
c.in = Object.fromEntries(SEATS.map(s => [s, c.chips[s] >= INVITO]));
|
||||||
|
c.pot = 0;
|
||||||
|
for (const s of active(c)) { c.chips[s] -= INVITO; c.pot += INVITO; }
|
||||||
|
c.cards = Object.fromEntries(SEATS.map(s => [s, c.in[s] ? c.deck.splice(0, 5) : []]));
|
||||||
|
c.changed = {}; c.selected = new Set(); c.winners = null; c.line = null;
|
||||||
|
c.events.push({ kind: 'card-shuffle', power: 1 });
|
||||||
|
startBetting(c, 'bet1');
|
||||||
|
}
|
||||||
|
function startBetting(c, step) {
|
||||||
|
c.step = step; c.t = 0;
|
||||||
|
c.bets = Object.fromEntries(SEATS.map(s => [s, 0]));
|
||||||
|
c.acted = Object.fromEntries(SEATS.map(s => [s, false]));
|
||||||
|
c.raises = 0; c.toBet = 0;
|
||||||
|
c.current = firstAfter(c, c.dealer);
|
||||||
|
skipIfDone(c);
|
||||||
|
}
|
||||||
|
function firstAfter(c, i) {
|
||||||
|
for (let k = 1; k <= SEATS.length; k++) { const s = SEATS[(i + k) % SEATS.length]; if (c.in[s]) return s; }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Un giro di puntate finisce quando tutti quelli rimasti hanno parlato e messo quanto il più alto.
|
||||||
|
const roundOver = c => active(c).every(s => c.acted[s] && (c.bets[s] === c.toBet || c.chips[s] === 0));
|
||||||
|
function skipIfDone(c) {
|
||||||
|
if (active(c).length === 1) return showdown(c);
|
||||||
|
if (roundOver(c)) return endBetting(c);
|
||||||
|
while (c.current && (!c.in[c.current] || c.chips[c.current] === 0 && c.acted[c.current])) c.current = firstAfter(c, SEATS.indexOf(c.current));
|
||||||
|
}
|
||||||
|
function endBetting(c) {
|
||||||
|
if (c.step === 'bet1') { c.step = 'draw'; c.t = 0; c.current = firstAfter(c, c.dealer); return; }
|
||||||
|
showdown(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toCall = (c, who = 'andrea') => Math.max(0, c.toBet - c.bets[who]);
|
||||||
|
export const myTurn = c => (c.step === 'bet1' || c.step === 'bet2') && c.current === 'andrea' && c.in.andrea;
|
||||||
|
export const canRaise = (c, who = 'andrea') => c.raises < RILANCI && c.chips[who] > toCall(c, who);
|
||||||
|
export const canDraw = c => c.step === 'draw' && c.current === 'andrea';
|
||||||
|
|
||||||
|
// Le mosse: «lascio» (si esce dalla mano), «vedo» (o «parola» se non c'è niente da mettere), «punto»/«rilancio».
|
||||||
|
function put(c, who, amount) {
|
||||||
|
const n = Math.min(amount, c.chips[who]);
|
||||||
|
c.chips[who] -= n; c.bets[who] += n; c.pot += n;
|
||||||
|
if (n) c.events.push({ kind: 'card-tap', power: 0.6 });
|
||||||
|
}
|
||||||
|
export function act(c, who, move) {
|
||||||
|
if (c.current !== who || !(c.step === 'bet1' || c.step === 'bet2')) return false;
|
||||||
|
const due = toCall(c, who);
|
||||||
|
if (move === 'lascio' && due === 0) move = 'vedo'; // lasciare gratis non ha senso: si dice parola
|
||||||
|
if (move === 'rilancio' && !canRaise(c, who)) move = 'vedo';
|
||||||
|
if (move === 'lascio') { c.in[who] = false; c.line = `${NAMES[who]}: lascio.`; }
|
||||||
|
else if (move === 'vedo') { put(c, who, due); c.line = `${NAMES[who]}: ${due ? `vedo (${due})` : 'parola'}.`; }
|
||||||
|
else { put(c, who, due + PUNTO); c.toBet = c.bets[who]; c.raises++; for (const s of SEATS) if (s !== who) c.acted[s] = false; c.line = `${NAMES[who]}: ${due ? 'rilancio' : 'apro'} di ${PUNTO}.`; }
|
||||||
|
c.acted[who] = true;
|
||||||
|
c.history.push({ hand: c.hand, step: c.step, who, move });
|
||||||
|
c.current = firstAfter(c, SEATS.indexOf(who));
|
||||||
|
c.t = 0;
|
||||||
|
skipIfDone(c);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Il cambio: si scartano le carte scelte (al massimo 4) e se ne pescano altrettante.
|
||||||
|
export function draw(c, who, indexes) {
|
||||||
|
if (c.step !== 'draw' || c.current !== who) return false;
|
||||||
|
const keep = c.cards[who].filter((_, i) => !indexes.includes(i));
|
||||||
|
c.cards[who] = [...keep, ...c.deck.splice(0, 5 - keep.length)];
|
||||||
|
c.changed[who] = 5 - keep.length;
|
||||||
|
c.line = `${NAMES[who]}: ${c.changed[who] ? `cambio ${c.changed[who]}` : 'servito'}.`;
|
||||||
|
if (c.changed[who]) c.events.push({ kind: 'card-slide', power: 0.6 });
|
||||||
|
if (who === 'andrea') c.selected = new Set();
|
||||||
|
c.current = firstAfter(c, SEATS.indexOf(who));
|
||||||
|
c.t = 0;
|
||||||
|
// Il cambio è finito quando tutti quelli rimasti hanno cambiato.
|
||||||
|
if (active(c).every(s => c.changed[s] !== undefined)) startBetting(c, 'bet2');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function showdown(c) {
|
||||||
|
const left = active(c);
|
||||||
|
const best = left.map(s => ({ s, score: evaluate(c.cards[s]) })).sort((a, b) => compare(b.score, a.score));
|
||||||
|
const winners = left.length === 1 ? left : best.filter(b => compare(b.score, best[0].score) === 0).map(b => b.s);
|
||||||
|
const share = Math.floor(c.pot / winners.length);
|
||||||
|
for (const w of winners) c.chips[w] += share;
|
||||||
|
c.chips[winners[0]] += c.pot - share * winners.length;
|
||||||
|
c.winners = winners;
|
||||||
|
c.line = left.length === 1 ? `Tutti lasciano: il piatto (${c.pot}) va a ${NAMES[winners[0]]}.`
|
||||||
|
: `${winners.map(w => NAMES[w]).join(' e ')} ${winners.length > 1 ? 'dividono' : 'vince'} con ${handName(c.cards[winners[0]])}: ${c.pot} fiches.`;
|
||||||
|
c.step = 'show'; c.t = 0; c.current = null;
|
||||||
|
c.events.push({ kind: 'card-shuffle', power: 0.5 });
|
||||||
|
}
|
||||||
|
// Dopo la mostra un tocco (o «Prossima mano») va avanti; finite le mani, o senza più fiches, il risultato.
|
||||||
|
export function advance(c) {
|
||||||
|
if (c.step !== 'show') return false;
|
||||||
|
if (c.hand >= MANI || c.chips.andrea < INVITO || alive(c).length < 2) return finish(c);
|
||||||
|
newHand(c);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function finish(c) {
|
||||||
|
const max = Math.max(...SEATS.map(s => c.chips[s])), top = SEATS.filter(s => c.chips[s] === max);
|
||||||
|
c.step = 'result';
|
||||||
|
c.result = { winner: top.includes('andrea') ? (top.length > 1 ? 'draw' : 'player') : 'cpu', chips: { ...c.chips }, top };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Gli altri due: quanto vale la mano (0..1), cosa cambiare, come puntare.
|
||||||
|
export function strength(hand) {
|
||||||
|
const [cat, ...rest] = evaluate(hand);
|
||||||
|
return Math.min(1, cat / 8 + ((rest[0] ?? 7) - 7) / 7 / 9);
|
||||||
|
}
|
||||||
|
export function cpuDiscards(c, who) {
|
||||||
|
const hand = c.cards[who], values = hand.map(value), [cat] = evaluate(hand);
|
||||||
|
if (cat >= 4 && cat !== 7) return []; // scala, full, colore, scala colore: servito
|
||||||
|
const counts = new Map(); values.forEach(v => counts.set(v, (counts.get(v) ?? 0) + 1));
|
||||||
|
if (cat >= 1) return hand.map((_, i) => i).filter(i => counts.get(values[i]) === 1 && !(cat === 7)); // tiene le coppie, i tris, il poker
|
||||||
|
const bySuit = SUITS.map(s => hand.map((card, i) => card.suit === s ? i : -1).filter(i => i >= 0)).find(l => l.length === 4);
|
||||||
|
if (bySuit && random(c) < c.skill.skill) return hand.map((_, i) => i).filter(i => !bySuit.includes(i)); // quattro a colore
|
||||||
|
// Altrimenti tiene le due carte più alte (con poca testa, una a caso).
|
||||||
|
const order = hand.map((_, i) => i).sort((a, b) => values[b] - values[a]);
|
||||||
|
return order.slice(random(c) < c.skill.skill ? 2 : 1);
|
||||||
|
}
|
||||||
|
export function cpuMove(c, who) {
|
||||||
|
const s = strength(c.cards[who]), due = toCall(c, who), k = c.skill, noise = (random(c) - 0.5) * (1 - k.skill) * 0.5;
|
||||||
|
const value = s + noise, bluff = random(c) < k.bluff;
|
||||||
|
if (value > 0.42 || bluff) return canRaise(c, who) && (value > 0.55 || bluff) ? 'rilancio' : 'vedo';
|
||||||
|
if (due === 0) return 'vedo';
|
||||||
|
return value > 0.13 + due / 60 ? 'vedo' : 'lascio';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update(c, dt) {
|
||||||
|
if (!Number.isFinite(dt) || dt <= 0) return;
|
||||||
|
c.t += dt;
|
||||||
|
if (!c.current || c.current === 'andrea' || c.t < CPU_TIME) return;
|
||||||
|
if (c.step === 'bet1' || c.step === 'bet2') act(c, c.current, cpuMove(c, c.current));
|
||||||
|
else if (c.step === 'draw') draw(c, c.current, cpuDiscards(c, c.current));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- I comandi di Andrea: toccare una carta la segna per il cambio; i pulsanti fanno il resto.
|
||||||
|
export function play(c, index) {
|
||||||
|
if (canDraw(c)) {
|
||||||
|
if (c.selected.has(index)) c.selected.delete(index);
|
||||||
|
else if (c.selected.size < 4) c.selected.add(index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
export const change = c => canDraw(c) && draw(c, 'andrea', [...c.selected]);
|
||||||
|
export function moves(c) {
|
||||||
|
if (canDraw(c)) return [{ id: 'cambia', label: c.selected.size ? `Cambia ${c.selected.size}` : 'Servito' }];
|
||||||
|
if (!myTurn(c)) return [];
|
||||||
|
const due = toCall(c);
|
||||||
|
return [
|
||||||
|
{ id: 'lascio', label: 'Lascio', disabled: due === 0 },
|
||||||
|
{ id: 'vedo', label: due ? `Vedo ${due}` : 'Parola' },
|
||||||
|
{ id: 'rilancio', label: due ? `Rilancio ${PUNTO}` : `Apro ${PUNTO}`, disabled: !canRaise(c) },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
export function choose(c, id) {
|
||||||
|
if (id === 'cambia') return change(c);
|
||||||
|
return myTurn(c) && act(c, 'andrea', id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function status(c) {
|
||||||
|
const chips = SEATS.map(s => `${NAMES[s]} ${c.chips[s]}`).join(', ');
|
||||||
|
if (c.step === 'result') return `Fine del pokerino. ${chips}. ${c.result.winner === 'player' ? 'Hai vinto tu!' : c.result.winner === 'draw' ? 'Pari.' : `Ha vinto ${NAMES[c.result.top[0]]}.`}`;
|
||||||
|
const where = `Mano ${c.hand} di ${MANI}, piatto ${c.pot}. ${chips}.`;
|
||||||
|
if (c.step === 'show') return `${where} ${c.line} Tocca per la prossima mano.`;
|
||||||
|
if (canDraw(c)) return `${where} Cambio: tocca le carte da cambiare (al massimo 4), poi Cambia.`;
|
||||||
|
if (myTurn(c)) return `${where} ${c.line ?? ''} Tocca a te: ${toCall(c) ? `per vedere servono ${toCall(c)}` : 'parola o apri'}.`.replace(' ', ' ');
|
||||||
|
return `${where} ${c.line ?? ''} ${c.current ? `Parla ${NAMES[c.current]}…` : ''}`.trim();
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ import { drawPesca } from './pesca-render.js';
|
|||||||
import { drawGara } from './gara-render.js';
|
import { drawGara } from './gara-render.js';
|
||||||
import { drawMicro } from './micro-render.js';
|
import { drawMicro } from './micro-render.js';
|
||||||
import { drawCerbottana } from './cerbottana-render.js';
|
import { drawCerbottana } from './cerbottana-render.js';
|
||||||
|
import { drawPoker } from './poker-render.js';
|
||||||
|
import { drawViaggio } from './viaggio.js';
|
||||||
import { drawBriscola } from './briscola-render.js';
|
import { drawBriscola } from './briscola-render.js';
|
||||||
import { drawTressette } from './tressette-render.js';
|
import { drawTressette } from './tressette-render.js';
|
||||||
import { drawScopa } from './scopa-render.js';
|
import { drawScopa } from './scopa-render.js';
|
||||||
@@ -678,6 +680,11 @@ function drawCloseup(ctx, game, time) {
|
|||||||
if (def.type === 'briscola') { drawBriscola(ctx, c, def); return; }
|
if (def.type === 'briscola') { drawBriscola(ctx, c, def); return; }
|
||||||
if (def.type === 'tressette') { drawTressette(ctx, c, def); return; }
|
if (def.type === 'tressette') { drawTressette(ctx, c, def); return; }
|
||||||
if (def.type === 'scopa') { drawScopa(ctx, c, def); return; }
|
if (def.type === 'scopa') { drawScopa(ctx, c, def); return; }
|
||||||
|
if (def.type === 'viaggio') {
|
||||||
|
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
|
||||||
|
drawViaggio(ctx, c, def, backgrounds.get(def.picture));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (def.type === 'torch') {
|
if (def.type === 'torch') {
|
||||||
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
|
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
|
||||||
drawTorch(ctx, c, def, backgrounds.get(def.picture));
|
drawTorch(ctx, c, def, backgrounds.get(def.picture));
|
||||||
@@ -688,6 +695,7 @@ function drawCloseup(ctx, game, time) {
|
|||||||
drawBocce(ctx, c, def, backgrounds.get(def.picture), time);
|
drawBocce(ctx, c, def, backgrounds.get(def.picture), time);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (def.type === 'poker') { drawPoker(ctx, c); return; }
|
||||||
if (def.type === 'cerbottana') {
|
if (def.type === 'cerbottana') {
|
||||||
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
|
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
|
||||||
drawCerbottana(ctx, c, def, backgrounds.get(def.picture));
|
drawCerbottana(ctx, c, def, backgrounds.get(def.picture));
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Il disegno del viaggio nel tempo (story.closeups, type 'viaggio'; i tempi stanno in game.js, TRIP): il cruscotto,
|
||||||
|
// la data che si compone nel display con le cifre a segmenti che brillano, poi tremori, lampi e il bianco.
|
||||||
|
import { TRIP } from './game.js';
|
||||||
|
|
||||||
|
// Le cifre a sette segmenti: quali segmenti sono accesi (a b c d e f g).
|
||||||
|
const SEG = { 0: 'abcdef', 1: 'bc', 2: 'abged', 3: 'abgcd', 4: 'fgbc', 5: 'afgcd', 6: 'afgedc', 7: 'abc', 8: 'abcdefg', 9: 'abcdfg', '/': '', ':': '', ' ': '' };
|
||||||
|
function digit(ctx, ch, x, y, w, h, color) {
|
||||||
|
const t = Math.max(2, Math.round(w / 6)), on = SEG[ch] ?? '';
|
||||||
|
const bars = { a: [x + t, y, w - 2 * t, t], b: [x + w - t, y + t, t, h / 2 - t], c: [x + w - t, y + h / 2, t, h / 2 - t],
|
||||||
|
d: [x + t, y + h - t, w - 2 * t, t], e: [x, y + h / 2, t, h / 2 - t], f: [x, y + t, t, h / 2 - t], g: [x + t, y + h / 2 - t / 2, w - 2 * t, t] };
|
||||||
|
for (const [k, r] of Object.entries(bars)) { ctx.fillStyle = on.includes(k) ? color : 'rgba(255,90,40,0.08)'; ctx.fillRect(...r.map(Math.round)); }
|
||||||
|
if (ch === ':') { ctx.fillStyle = color; ctx.fillRect(x + w / 2 - t / 2, y + h * 0.3, t, t); ctx.fillRect(x + w / 2 - t / 2, y + h * 0.65, t, t); }
|
||||||
|
if (ch === '/') { ctx.fillStyle = color; for (let i = 0; i < 6; i++) ctx.fillRect(x + w * 0.7 - i * w * 0.08, y + h * 0.15 + i * h * 0.13, t, t); }
|
||||||
|
}
|
||||||
|
export function drawViaggio(ctx, c, def, bg) {
|
||||||
|
const t = c.t, T = TRIP, [dx, dy, dw, dh] = def.display;
|
||||||
|
// Tremori: crescono dalla carica al bianco.
|
||||||
|
const shake = t > T.charge ? Math.min(1, (t - T.charge) / (T.flash - T.charge)) * 5 : 0;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(Math.round((Math.sin(t * 61) + Math.sin(t * 37)) * shake), Math.round(Math.cos(t * 53) * shake));
|
||||||
|
if (bg) ctx.drawImage(bg, 0, 0, 640, 360); else { ctx.fillStyle = '#111'; ctx.fillRect(0, 0, 640, 360); }
|
||||||
|
// La data, un tasto alla volta, in due righe: giorno/mese/anno e ora.
|
||||||
|
const lines = def.date.split(' '), shown = c.typed ?? 0;
|
||||||
|
let n = 0;
|
||||||
|
const glow = t > T.charge ? 0.75 + 0.25 * Math.sin(t * 30) : 1;
|
||||||
|
ctx.shadowColor = '#ff5a28'; ctx.shadowBlur = 8;
|
||||||
|
lines.forEach((line, row) => {
|
||||||
|
const ch = 14, cw = 13, gap = 4, total = line.length * (cw + gap) - gap;
|
||||||
|
const x0 = dx + (dw - total) / 2, y0 = dy + 8 + row * (ch * 2 + 2);
|
||||||
|
[...line].forEach((d, i) => {
|
||||||
|
const lit = n++ < shown;
|
||||||
|
digit(ctx, lit ? d : ' ', x0 + i * (cw + gap), y0, cw, ch * 2 - 6, `rgba(255,${Math.round(110 * glow)},40,${lit ? 1 : 0})`);
|
||||||
|
});
|
||||||
|
n++; // lo spazio fra le due righe
|
||||||
|
});
|
||||||
|
ctx.shadowBlur = 0;
|
||||||
|
ctx.restore();
|
||||||
|
// I lampi: archi azzurri che attraversano il parabrezza, a scatti.
|
||||||
|
if (t > T.charge + 0.6) {
|
||||||
|
const k = Math.floor(t * 14);
|
||||||
|
if (k % 3 !== 0) {
|
||||||
|
ctx.strokeStyle = 'rgba(160,220,255,0.9)'; ctx.lineWidth = 2; ctx.beginPath();
|
||||||
|
let x = 40 + (k * 97) % 560, y = 0; ctx.moveTo(x, y);
|
||||||
|
for (let i = 0; i < 7; i++) { x += ((k * (i + 3) * 37) % 50) - 25; y += 18; ctx.lineTo(x, y); }
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.fillStyle = 'rgba(180,230,255,0.12)'; ctx.fillRect(0, 0, 640, 360);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Il bianco, poi resta bianco fino alla fine.
|
||||||
|
if (t > T.flash) { ctx.fillStyle = `rgba(255,255,255,${Math.min(1, (t - T.flash) / 0.5)})`; ctx.fillRect(0, 0, 640, 360); }
|
||||||
|
}
|
||||||
+25
-3
@@ -13,6 +13,8 @@ import * as biliardo from './engine/biliardo.js';
|
|||||||
import * as rinquarto from './engine/rinquarto.js';
|
import * as rinquarto from './engine/rinquarto.js';
|
||||||
import * as micro from './engine/micro.js';
|
import * as micro from './engine/micro.js';
|
||||||
import * as cerbottana from './engine/cerbottana.js';
|
import * as cerbottana from './engine/cerbottana.js';
|
||||||
|
import * as poker from './engine/poker.js';
|
||||||
|
import { cardAt as pokerAt } from './engine/poker-render.js';
|
||||||
import * as basket from './engine/basket.js';
|
import * as basket from './engine/basket.js';
|
||||||
import * as pesca from './engine/pesca.js';
|
import * as pesca from './engine/pesca.js';
|
||||||
import * as gara from './engine/gara.js';
|
import * as gara from './engine/gara.js';
|
||||||
@@ -604,6 +606,7 @@ const MINI = {
|
|||||||
bocce: { ...bocce, nudge: (c, dir) => bocce.aim(c, c.aim.u, c.aim.w + dir * 0.3), back: 'Torna al pallaio', label: 'Bocce: tocca il campo o il dettaglio per mirare. Regola la forza e premi Lancia.' },
|
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.' },
|
biliardo: { ...biliardo, back: 'Torna al bar', label: 'Biliardo: clic o tocco fissa la mira. Frecce per affinarla, Maiusc per passi più piccoli. Regola la forza e premi Tira.' },
|
||||||
rinquarto: { ...rinquarto, back: 'Torna da Trappizzio Berti', label: 'I rinquarti di Trappizzio Berti: clic o tocco fissa la mira, frecce o pulsanti Mira per girare la stecca (Maiusc per i passi fini), Tira per tirare. Dopo l’esito, Prossima.' },
|
rinquarto: { ...rinquarto, back: 'Torna da Trappizzio Berti', label: 'I rinquarti di Trappizzio Berti: clic o tocco fissa la mira, frecce o pulsanti Mira per girare la stecca (Maiusc per i passi fini), Tira per tirare. Dopo l’esito, Prossima.' },
|
||||||
|
poker: { ...poker, back: 'Torna al Barrettino', label: 'Il pokerino: tocca le carte da cambiare, poi Cambia; nei giri di puntate Lascio, Vedo o Parola, Apro o Rilancio. Dopo la mostra un tocco va avanti.' },
|
||||||
cerbottana: { ...cerbottana, back: 'Torna in piazza', label: 'La cerbottana: muovi il mouse o tocca il muro per mirare, tieni premuto per prendere fiato e lascia per sparare; oppure regola il fiato e premi Spara. Frecce per spostare il mirino.' },
|
cerbottana: { ...cerbottana, back: 'Torna in piazza', label: 'La cerbottana: muovi il mouse o tocca il muro per mirare, tieni premuto per prendere fiato e lascia per sparare; oppure regola il fiato e premi Spara. Frecce per spostare il mirino.' },
|
||||||
micro: { ...micro, back: 'Torna dal Chiaru', label: 'La pista col gesso: muovi il mouse o tocca il parcheggio per mirare, tieni premuto per caricare la spinta e lascia; oppure regola la forza e premi Spingi. Frecce o pulsanti Mira per girare.' },
|
micro: { ...micro, back: 'Torna dal Chiaru', label: 'La pista col gesso: muovi il mouse o tocca il parcheggio per mirare, tieni premuto per caricare la spinta e lascia; oppure regola la forza e premi Spingi. Frecce o pulsanti Mira per girare.' },
|
||||||
basket: { ...basket, back: 'Torna in strada', label: 'Fai due tiri: muovi il mouse o tocca per mirare, tieni premuto per caricare la forza, lascia per tirare.' },
|
basket: { ...basket, back: 'Torna in strada', label: 'Fai due tiri: muovi il mouse o tocca per mirare, tieni premuto per caricare la forza, lascia per tirare.' },
|
||||||
@@ -648,7 +651,12 @@ $('bocce-throw').addEventListener('pointerdown', e => {
|
|||||||
for (const ev of ['pointerup', 'pointercancel', 'lostpointercapture']) $('bocce-throw').addEventListener(ev, () => { const c = miniState(); if (holdGas(c)) gara.release(c, 'gas-button'); });
|
for (const ev of ['pointerup', 'pointercancel', 'lostpointercapture']) $('bocce-throw').addEventListener(ev, () => { const c = miniState(); if (holdGas(c)) gara.release(c, 'gas-button'); });
|
||||||
$('bocce-throw').addEventListener('contextmenu', e => { if (holdGas(miniState())) e.preventDefault(); });
|
$('bocce-throw').addEventListener('contextmenu', e => { if (holdGas(miniState())) e.preventDefault(); });
|
||||||
// Le carte da gioco: dove si tocca la carta sul tavolo, gioco per gioco.
|
// Le carte da gioco: dove si tocca la carta sul tavolo, gioco per gioco.
|
||||||
const CARD_AT = { briscola: cardAt, tressette: tressetteAt, scopa: scopaAt };
|
const CARD_AT = { briscola: cardAt, tressette: tressetteAt, scopa: scopaAt, poker: pokerAt };
|
||||||
|
// Il pokerino: tre pulsanti fissi (su iPhone un pulsante rifatto a ogni fotogramma perde il tocco), la scritta cambia.
|
||||||
|
for (let i = 0; i < 3; i++) $('poker-move-' + i).addEventListener('click', e => {
|
||||||
|
const c = game?.closeup; if (!c || story.closeups[c.id].type !== 'poker' || game.speech) return;
|
||||||
|
if (c.step === 'show') poker.advance(c); else poker.choose(c, e.currentTarget.dataset.move);
|
||||||
|
});
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
const button = $('scopa-card-' + i);
|
const button = $('scopa-card-' + i);
|
||||||
button.addEventListener('click', () => game?.closeupPlay(i));
|
button.addEventListener('click', () => game?.closeupPlay(i));
|
||||||
@@ -946,7 +954,7 @@ function updateUi() {
|
|||||||
updateChoices();
|
updateChoices();
|
||||||
updateSpeech();
|
updateSpeech();
|
||||||
const mg = miniState() && mini(), def = mg && story.closeups[c.id];
|
const mg = miniState() && mini(), def = mg && story.closeups[c.id];
|
||||||
const cards = ['briscola', 'tressette', 'scopa'].includes(def?.type);
|
const cards = ['briscola', 'tressette', 'scopa', 'poker'].includes(def?.type);
|
||||||
// La presentazione dei piloti del GP a tutto schermo: i comandi arrivano col semaforo (Andrea, 29 ottobre).
|
// La presentazione dei piloti del GP a tutto schermo: i comandi arrivano col semaforo (Andrea, 29 ottobre).
|
||||||
const presenta = !!mg && story.closeups[c.id].type === 'gara' && c.step === 'intro';
|
const presenta = !!mg && story.closeups[c.id].type === 'gara' && c.step === 'intro';
|
||||||
$('app').classList.toggle('presenta', presenta);
|
$('app').classList.toggle('presenta', presenta);
|
||||||
@@ -956,6 +964,20 @@ function updateUi() {
|
|||||||
setProp($('briscola-controls'), 'inert', !running);
|
setProp($('briscola-controls'), 'inert', !running);
|
||||||
setProp($('scopa-controls'), 'hidden', def?.type !== 'scopa');
|
setProp($('scopa-controls'), 'hidden', def?.type !== 'scopa');
|
||||||
setProp($('scopa-controls'), 'inert', !running);
|
setProp($('scopa-controls'), 'inert', !running);
|
||||||
|
setProp($('poker-controls'), 'hidden', def?.type !== 'poker');
|
||||||
|
setProp($('poker-controls'), 'inert', !running);
|
||||||
|
if (def?.type === 'poker') {
|
||||||
|
setProp($('panel'), 'hidden', true); setProp($('choices'), 'hidden', true);
|
||||||
|
setText($('poker-status'), poker.status(c));
|
||||||
|
const moves = c.step === 'show' ? [{ id: 'avanti', label: 'Prossima mano' }] : poker.moves(c);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const b = $('poker-move-' + i), m = moves[i];
|
||||||
|
setProp(b, 'hidden', !m);
|
||||||
|
if (!m) continue;
|
||||||
|
setText(b, m.label); b.dataset.move = m.id;
|
||||||
|
setProp(b, 'disabled', !!m.disabled || !!game.speech);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (def?.type === 'scopa') {
|
if (def?.type === 'scopa') {
|
||||||
setProp($('panel'), 'hidden', true); setProp($('choices'), 'hidden', true);
|
setProp($('panel'), 'hidden', true); setProp($('choices'), 'hidden', true);
|
||||||
const line = scopa.status(c, def.names);
|
const line = scopa.status(c, def.names);
|
||||||
@@ -1055,7 +1077,7 @@ function updateUi() {
|
|||||||
setProp($('gara-help'), 'hidden', def.type !== 'gara');
|
setProp($('gara-help'), 'hidden', def.type !== 'gara');
|
||||||
if (mg.aimFree?.(c) ?? !mg.canThrow(c)) c.aimLocked = false; // a ogni nuovo tiro la mira torna libera
|
if (mg.aimFree?.(c) ?? !mg.canThrow(c)) c.aimLocked = false; // a ogni nuovo tiro la mira torna libera
|
||||||
}
|
}
|
||||||
if (mg) for (const event of c.events.splice(0)) ambience.play(event.kind, { pan: 0, strength: event.power });
|
if (mg || c?.events) for (const event of c.events.splice(0)) ambience.play(event.kind, { pan: 0, strength: event.power });
|
||||||
// Il pulsante della mappa c'è solo in partita, fuori dai dialoghi, e quando c'è almeno un altro posto dove andare.
|
// Il pulsante della mappa c'è solo in partita, fuori dai dialoghi, e quando c'è almeno un altro posto dove andare.
|
||||||
setProp($('map-button'), 'hidden', !running || !!game.dialogue || !!game.closeup || game.mapPlaces().length < 2);
|
setProp($('map-button'), 'hidden', !running || !!game.dialogue || !!game.closeup || game.mapPlaces().length < 2);
|
||||||
setProp($('pause-button'), 'hidden', !running);
|
setProp($('pause-button'), 'hidden', !running);
|
||||||
|
|||||||
+115
-1
@@ -233,6 +233,24 @@ export const story = {
|
|||||||
quit: [{ say: 'Questo me lo tengo per la prossima.' }],
|
quit: [{ say: 'Questo me lo tengo per la prossima.' }],
|
||||||
draw: [{ say: 'Visto? Geometrie celestiali. Si riprova quando vuoi.', who: 'bertovo', pose: 'explain' }],
|
draw: [{ say: 'Visto? Geometrie celestiali. Si riprova quando vuoi.', who: 'bertovo', pose: 'explain' }],
|
||||||
},
|
},
|
||||||
|
// Il viaggio nel tempo dalla macchina del tempo in Via Liguri Magelli: la data sul cruscotto (Codex), poi il Barrettino
|
||||||
|
// del 10 luglio 1997 (Andrea, 29 ottobre).
|
||||||
|
viaggio: {
|
||||||
|
type: 'viaggio',
|
||||||
|
picture: 'assets/closeups/cruscotto-tempo.png',
|
||||||
|
display: [220, 158, 212, 62],
|
||||||
|
date: '10/07/1997 18:30',
|
||||||
|
complete: [{ go: 'barrettino_1997', at: [320, 330], face: 'up' }],
|
||||||
|
},
|
||||||
|
// Il pokerino del Barrettino con Vannino e Pierino (Andrea, 29 ottobre). [DA ANDREA: le battute]
|
||||||
|
poker: {
|
||||||
|
type: 'poker',
|
||||||
|
names: { player: 'Andrea', cpu: 'Vannino e Pierino' },
|
||||||
|
win: [{ say: 'M\'hai pelato, ragazzo. Nel 1997 un ci si vedeva da queste parti, con quella faccia.', who: 'vannino', pose: 'shrug' }, { set: 'poker_vinto' }],
|
||||||
|
lose: [{ say: 'Le fiches restano al banco. E tu torna quando hai imparato a bluffare.', who: 'pierino_parigi', pose: 'laugh' }],
|
||||||
|
quit: [{ say: 'Già finito? E il caffè chi lo paga?', who: 'vannino', pose: 'talk' }],
|
||||||
|
draw: [{ say: 'Pari. Si rifà domani sera, alla stess\'ora.', who: 'pierino_parigi', pose: 'explain' }],
|
||||||
|
},
|
||||||
// La cerbottana con lo stucco verde di Pierin di Frilli, contro il muro della piazza (Andrea, 29 ottobre).
|
// La cerbottana con lo stucco verde di Pierin di Frilli, contro il muro della piazza (Andrea, 29 ottobre).
|
||||||
// [DA ANDREA: le battute sono provvisorie]
|
// [DA ANDREA: le battute sono provvisorie]
|
||||||
cerbottana: {
|
cerbottana: {
|
||||||
@@ -323,6 +341,11 @@ export const story = {
|
|||||||
tozzino: { name: 'Il Tozzino', color: '#c8f5b0', sprite: 'assets/characters/tozzino.json' },
|
tozzino: { name: 'Il Tozzino', color: '#c8f5b0', sprite: 'assets/characters/tozzino.json' },
|
||||||
// Il Chiaru (Matteo Chiarusi, consenso dato ad Andrea il 29 ottobre), alla pista col gesso di Cardetole: sprite di
|
// Il Chiaru (Matteo Chiarusi, consenso dato ad Andrea il 29 ottobre), alla pista col gesso di Cardetole: sprite di
|
||||||
// Codex dalla foto di Andrea.
|
// Codex dalla foto di Andrea.
|
||||||
|
// Al Barrettino del 1997 (Andrea, 29 ottobre; senza foto, dalla descrizione di Andrea): Vannino, alto, magro, coi
|
||||||
|
// capelli corti e la fossetta nel mento; Pierino Parigi (consenso del figlio Marco, 29 ottobre), brizzolato, con gli
|
||||||
|
// occhiali e la barba rada. Sprite di Codex.
|
||||||
|
vannino: { name: 'Vannino', color: '#ffe6a8', sprite: 'assets/characters/vannino.json' },
|
||||||
|
pierino_parigi: { name: 'Pierino', color: '#c8e0ff', sprite: 'assets/characters/pierino_parigi.json' },
|
||||||
// Lo zio Mario di Andrea (sprite di Codex dalla foto di Andrea, 29 ottobre), alla Ghiacciomobile nel muro.
|
// Lo zio Mario di Andrea (sprite di Codex dalla foto di Andrea, 29 ottobre), alla Ghiacciomobile nel muro.
|
||||||
zio_mario: { name: 'Lo zio Mario', color: '#cfe3ff', sprite: 'assets/characters/zio_mario.json' },
|
zio_mario: { name: 'Lo zio Mario', color: '#cfe3ff', sprite: 'assets/characters/zio_mario.json' },
|
||||||
chiaru: { name: 'Il Chiaru', color: '#ffd6a0', sprite: 'assets/characters/chiaru.json' },
|
chiaru: { name: 'Il Chiaru', color: '#ffd6a0', sprite: 'assets/characters/chiaru.json' },
|
||||||
@@ -2749,7 +2772,9 @@ export const story = {
|
|||||||
speakers: { arcu: [374, 176], zio_mario: [440, 196] },
|
speakers: { arcu: [374, 176], zio_mario: [440, 196] },
|
||||||
people: [
|
people: [
|
||||||
// L'Arcu sotto la tenda del Trappola: due minuti fa era a Campomigliaio (Andrea, 28 ottobre).
|
// L'Arcu sotto la tenda del Trappola: due minuti fa era a Campomigliaio (Andrea, 28 ottobre).
|
||||||
{ who: 'arcu', at: [374, 248], facing: -1, size: 0.75, idle: 'stand', talk: ['talk', 'explain'], blink: 'blink', every: 4.2 },
|
// La macchina del tempo, dopo i pokerini dell'Arcu.
|
||||||
|
{ who: 'macchina', when: 'macchina_trappola', at: [200, 262], size: 1.1, idle: 'stand', talk: [] },
|
||||||
|
{ who: 'arcu', at: [374, 248], facing: -1, size: 1.05, idle: 'stand', talk: ['talk', 'explain'], blink: 'blink', every: 4.2 },
|
||||||
// Lo zio Mario davanti alla Ghiacciomobile nel muro, che si gratta la testa (sprite di Codex dalla sua foto,
|
// Lo zio Mario davanti alla Ghiacciomobile nel muro, che si gratta la testa (sprite di Codex dalla sua foto,
|
||||||
// 29 ottobre). Solo finché la macchina è lì. [DA ANDREA: le sue battute]
|
// 29 ottobre). Solo finché la macchina è lì. [DA ANDREA: le sue battute]
|
||||||
{ who: 'zio_mario', when: '!indizio_regolo', at: [440, 330], facing: 1, idle: 'think', talk: ['talk', 'explain', 'point'] },
|
{ who: 'zio_mario', when: '!indizio_regolo', at: [440, 330], facing: 1, idle: 'think', talk: ['talk', 'explain', 'point'] },
|
||||||
@@ -2795,6 +2820,14 @@ export const story = {
|
|||||||
use: [{ say: 'Per tirarla fuori di lì ci vuole altro che le mie braccia.' }],
|
use: [{ say: 'Per tirarla fuori di lì ci vuole altro che le mie braccia.' }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'macchina_tempo', name: 'macchina del tempo', box: [150, 214, 104, 48], at: [212, 280], face: 'up', default: 'use', when: 'macchina_trappola',
|
||||||
|
actions: {
|
||||||
|
look: [{ say: 'La macchina del tempo, parcheggiata davanti al Trappola. Sul cruscotto la data è già pronta.' }],
|
||||||
|
use: [{ say: 'Salgo. Imposto: 10 luglio 1997, ore 18 e 30. Il pokerino da Vannino.' }, { closeup: 'viaggio' }],
|
||||||
|
open: [{ say: 'Salgo. Imposto: 10 luglio 1997, ore 18 e 30. Il pokerino da Vannino.' }, { closeup: 'viaggio' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// La prima battuta è di Andrea (dal circolino, 26 ottobre).
|
// La prima battuta è di Andrea (dal circolino, 26 ottobre).
|
||||||
id: 'betino', name: 'il Betino dell\'Arcu', box: [232, 172, 40, 24], at: [250, 206], face: 'up', default: 'look',
|
id: 'betino', name: 'il Betino dell\'Arcu', box: [232, 172, 40, 24], at: [250, 206], face: 'up', default: 'look',
|
||||||
@@ -3322,6 +3355,54 @@ export const story = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// Il Barrettino il 10 luglio 1997 alle 18:30 (Andrea, 29 ottobre; l'interno è una proposta di Codex, [DA ANDREA]):
|
||||||
|
// Vannino e Pierino Parigi con la tavola apparecchiata da poker. Si torna nel 2007 dalla porta.
|
||||||
|
barrettino_1997: {
|
||||||
|
name: 'Il Barrettino, 10 luglio 1997',
|
||||||
|
// Ci si arriva solo con la macchina del tempo: niente mappa, niente strade (la porta riporta nel 2007).
|
||||||
|
viaggio: true,
|
||||||
|
background: 'assets/scenes/barrettino-1997.png',
|
||||||
|
indoor: true,
|
||||||
|
walk: [[0, 360], [0, 300], [110, 282], [540, 282], [640, 300], [640, 360]],
|
||||||
|
depth: [[282, 2.1], [360, 2.6]],
|
||||||
|
entry: [320, 330],
|
||||||
|
enter: [{ if: '!visto_1997', then: [{ set: 'visto_1997' }, { say: 'Il Barrettino. Nel 1997. Si sente ancora l\'odore del caffè e delle sigarette.' }] }],
|
||||||
|
speakers: { vannino: [160, 120], pierino_parigi: [470, 120] },
|
||||||
|
people: [
|
||||||
|
{ who: 'vannino', at: [160, 300], facing: 1, size: 1, idle: 'stand', talk: ['talk', 'explain'], blink: 'blink', every: 4.1 },
|
||||||
|
{ who: 'pierino_parigi', at: [470, 300], facing: -1, size: 1, idle: 'cards', talk: ['talk', 'explain'], blink: 'blink', every: 3.6 },
|
||||||
|
],
|
||||||
|
hotspots: [
|
||||||
|
{
|
||||||
|
id: 'tavola', name: 'la tavola da poker', box: [236, 186, 150, 70], at: [320, 300], face: 'up', default: 'talk',
|
||||||
|
actions: {
|
||||||
|
look: [{ say: 'Il panno verde, le fiches in pila, il mazzo e il posacenere pieno. Una sedia è vuota: è per me.' }],
|
||||||
|
talk: [{ dialogue: 'pokerino' }],
|
||||||
|
use: [{ dialogue: 'pokerino' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'vannino', name: 'Vannino', box: [130, 160, 60, 140], at: [220, 320], face: 'left', default: 'talk',
|
||||||
|
actions: { look: [{ say: 'Vannino, alto e secco, col canovaccio sulla spalla. E quella fossetta nel mento.' }], talk: [{ dialogue: 'pokerino' }] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pierino', name: 'Pierino', box: [440, 160, 60, 140], at: [410, 320], face: 'right', default: 'talk',
|
||||||
|
actions: { look: [{ say: 'Pierino Parigi, con gli occhiali sul naso, che guarda le carte come se lo tradissero.' }], talk: [{ dialogue: 'pokerino' }] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'televisore', name: 'televisore', box: [440, 20, 60, 50], at: [480, 300], face: 'up',
|
||||||
|
actions: { look: [{ say: 'Il televisore a tubo. È il 1997: niente smartphone, solo il televideo.' }] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'porta', name: 'la porta: si torna nel 2007', box: [70, 30, 70, 230], at: [120, 300], face: 'left', default: 'walk',
|
||||||
|
actions: {
|
||||||
|
look: [{ say: 'Fuori c\'è la sera del 1997. E parcheggiata lì, la macchina del tempo.' }],
|
||||||
|
walk: [{ say: 'Si torna nel 2007. Ciao Vannino, ciao Pierino.' }, { go: 'via_liguri_magelli', at: [212, 280], face: 'down' }],
|
||||||
|
open: [{ say: 'Si torna nel 2007. Ciao Vannino, ciao Pierino.' }, { go: 'via_liguri_magelli', at: [212, 280], face: 'down' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
// L'ingresso della scuola, giù per la strada dalla salita di Villa Adami: dal cancello alle porte a vetri.
|
// L'ingresso della scuola, giù per la strada dalla salita di Villa Adami: dal cancello alle porte a vetri.
|
||||||
scuola: {
|
scuola: {
|
||||||
name: 'La scuola',
|
name: 'La scuola',
|
||||||
@@ -3685,6 +3766,16 @@ export const story = {
|
|||||||
{ say: 'Col Betino? Io un perdo mai: un vo mai forte abbastanza da cadere.', who: 'arcu', pose: 'explain' },
|
{ say: 'Col Betino? Io un perdo mai: un vo mai forte abbastanza da cadere.', who: 'arcu', pose: 'explain' },
|
||||||
{ say: 'Come la si fa?', who: 'arcu', pose: 'talk' },
|
{ say: 'Come la si fa?', who: 'arcu', pose: 'talk' },
|
||||||
] },
|
] },
|
||||||
|
// I pokerini da Vannino: l'Arcu fa comparire la macchina del tempo (Andrea, 29 ottobre). [DA ANDREA: le battute dell'Arcu]
|
||||||
|
{ text: 'Bei tempi quando qui si faceva i pokerini...', then: [
|
||||||
|
{ if: 'macchina_trappola', then: [{ say: 'La macchina è sempre lì. La data è già impostata.', who: 'arcu', pose: 'explain' }], else: [
|
||||||
|
{ say: 'I pokerini da Vannino, col Pierino che bluffava anche a briscola.', who: 'arcu', pose: 'laugh' },
|
||||||
|
{ say: 'Ci vuoi tornare? Aspetta.', who: 'arcu', pose: 'explain' },
|
||||||
|
{ set: 'macchina_trappola' },
|
||||||
|
{ say: 'Ma quella è... la macchina del tempo!' },
|
||||||
|
{ say: 'Il plutonio l\'ha trovato Alvarino. Basta salire e mettere la data.', who: 'arcu', pose: 'talk' },
|
||||||
|
] },
|
||||||
|
] },
|
||||||
{ text: 'Ci si vede dopo, Arcu.', next: 'end' },
|
{ text: 'Ci si vede dopo, Arcu.', next: 'end' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -3760,6 +3851,29 @@ export const story = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Al Barrettino del 1997: il pokerino con Vannino e Pierino. [DA ANDREA: le battute]
|
||||||
|
pokerino: {
|
||||||
|
nodes: {
|
||||||
|
start: {
|
||||||
|
options: [
|
||||||
|
{ text: 'C\'è posto per uno?', next: 'livello', then: [
|
||||||
|
{ say: 'La sedia è lì. Invito due, punto cinque. Come la vuoi?', who: 'vannino', pose: 'explain' },
|
||||||
|
] },
|
||||||
|
{ text: 'Vannino, un caffè?', then: [{ say: 'Subito. Ma prima una mano, sennò che ci fai qui?', who: 'vannino', pose: 'talk' }] },
|
||||||
|
{ text: 'Pierino, come va la mano?', then: [{ say: 'Bene. O male. Con me un si sa mai, ed è questo il bello.', who: 'pierino_parigi', pose: 'laugh' }] },
|
||||||
|
{ text: 'Dopo.', next: 'end' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
livello: {
|
||||||
|
options: [
|
||||||
|
{ text: 'Alla bona.', next: 'end', then: [{ say: 'Alla bona: si gioca per il caffè.', who: 'pierino_parigi', pose: 'talk' }, { closeup: 'poker', level: 'bona' }] },
|
||||||
|
{ text: 'Seria.', next: 'end', then: [{ say: 'Seria. Carte, Vannino.', who: 'pierino_parigi', pose: 'cards' }, { closeup: 'poker', level: 'seria' }] },
|
||||||
|
{ text: 'A fassi male.', next: 'end', then: [{ say: 'A fassi male? Allora io bluffo.', who: 'pierino_parigi', pose: 'laugh' }, { closeup: 'poker', level: 'male' }] },
|
||||||
|
{ text: 'No, aspetta.', next: 'start' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// Il Chiaru al parcheggio di Cardetole, accanto alla pista col gesso (Andrea, 29 ottobre). [DA ANDREA: battute provvisorie]
|
// Il Chiaru al parcheggio di Cardetole, accanto alla pista col gesso (Andrea, 29 ottobre). [DA ANDREA: battute provvisorie]
|
||||||
chiaru: {
|
chiaru: {
|
||||||
nodes: {
|
nodes: {
|
||||||
|
|||||||
@@ -37,14 +37,14 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h
|
|||||||
#scopa-controls p, #tressette-controls p { max-width: 76em; }
|
#scopa-controls p, #tressette-controls p { max-width: 76em; }
|
||||||
#scopa-next { width: 100%; min-height: 40px; margin-bottom: 8px; background: #38453c; color: #fff0cd; border: 2px solid #897b57; font-size: 9px; cursor: pointer; }
|
#scopa-next { width: 100%; min-height: 40px; margin-bottom: 8px; background: #38453c; color: #fff0cd; border: 2px solid #897b57; font-size: 9px; cursor: pointer; }
|
||||||
.card-hand + .card-buttons { margin-top: 8px; }
|
.card-hand + .card-buttons { margin-top: 8px; }
|
||||||
#briscola-controls { background: #242b28; padding: 10px 14px; border-top: 2px solid #897b57; font-size: 11px; line-height: 1.7; }
|
#poker-controls, #briscola-controls { background: #242b28; padding: 10px 14px; border-top: 2px solid #897b57; font-size: 11px; line-height: 1.7; }
|
||||||
#briscola-status { color: #f3dcac; min-height: 2em; margin: 0 0 10px; }
|
#briscola-status { color: #f3dcac; min-height: 2em; margin: 0 0 10px; }
|
||||||
.card-hand { display: flex; gap: 8px; }
|
.card-hand { display: flex; gap: 8px; }
|
||||||
.card-hand button { flex: 1; min-width: 0; min-height: 48px; padding: 8px; background: #38453c; color: #fff0cd; border: 2px solid #897b57; font-size: 10px; line-height: 1.7; cursor: pointer; }
|
.card-hand button { flex: 1; min-width: 0; min-height: 48px; padding: 8px; background: #38453c; color: #fff0cd; border: 2px solid #897b57; font-size: 10px; line-height: 1.7; cursor: pointer; }
|
||||||
.card-hand button[aria-pressed="true"] { border-color: #ffdb76; }
|
.card-hand button[aria-pressed="true"] { border-color: #ffdb76; }
|
||||||
.card-hand button:disabled { opacity: .5; cursor: default; }
|
.card-hand button:disabled { opacity: .5; cursor: default; }
|
||||||
#briscola-controls summary { cursor: pointer; padding: 10px 0; }
|
#poker-controls summary, #briscola-controls summary { cursor: pointer; padding: 10px 0; }
|
||||||
#briscola-controls p { max-width: 76em; }
|
#poker-controls p, #briscola-controls p { max-width: 76em; }
|
||||||
#bocce-controls { background: #242b28; padding: 10px 14px; border-top: 2px solid #897b57; font-size: 11px; }
|
#bocce-controls { background: #242b28; padding: 10px 14px; border-top: 2px solid #897b57; font-size: 11px; }
|
||||||
#bocce-status { color: #f3dcac; margin: 0 0 10px; line-height: 1.5; min-height: 1.5em; }
|
#bocce-status { color: #f3dcac; margin: 0 0 10px; line-height: 1.5; min-height: 1.5em; }
|
||||||
.bocce-adjust { display: flex; align-items: center; justify-content: center; gap: 12px; }
|
.bocce-adjust { display: flex; align-items: center; justify-content: center; gap: 12px; }
|
||||||
@@ -325,7 +325,7 @@ body.full #sound { position: fixed; z-index: 13; top: max(8px, env(safe-area-ins
|
|||||||
#app.presenta { justify-content: center; }
|
#app.presenta { justify-content: center; }
|
||||||
#app.presenta #stage.biliardo { width: min(calc((100dvh - 44px) * 16 / 9), 100vw); }
|
#app.presenta #stage.biliardo { width: min(calc((100dvh - 44px) * 16 / 9), 100vw); }
|
||||||
#stage.briscola { width: min(calc((100dvh - 90px) * 16 / 9), 68vw); }
|
#stage.briscola { width: min(calc((100dvh - 90px) * 16 / 9), 68vw); }
|
||||||
#briscola-controls, #tressette-controls, #scopa-controls { overflow-y: auto; min-height: 0; padding: 8px; font-size: 9px; }
|
#briscola-controls, #tressette-controls, #scopa-controls, #poker-controls { overflow-y: auto; min-height: 0; padding: 8px; font-size: 9px; }
|
||||||
/* Nella colonna stretta accanto al tavolo: carte e segni su due colonne, parole che vanno a capo. */
|
/* Nella colonna stretta accanto al tavolo: carte e segni su due colonne, parole che vanno a capo. */
|
||||||
.card-hand-many button, .card-buttons button { flex-basis: 40%; overflow-wrap: anywhere; }
|
.card-hand-many button, .card-buttons button { flex-basis: 40%; overflow-wrap: anywhere; }
|
||||||
.card-hand { flex-direction: column; gap: 4px; }
|
.card-hand { flex-direction: column; gap: 4px; }
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import * as P from '../src/engine/poker.js';
|
||||||
|
import { drawPoker, cardAt, HAND } from '../src/engine/poker-render.js';
|
||||||
|
import { Game, MINIGAMES, TRIP } from '../src/engine/game.js';
|
||||||
|
import { story } from '../src/story/story.js';
|
||||||
|
import { settle } from './helpers.mjs';
|
||||||
|
|
||||||
|
const card = s => ({ rank: s.slice(0, -1), suit: { c: 'cuori', q: 'quadri', f: 'fiori', p: 'picche' }[s.at(-1)] });
|
||||||
|
const hand = s => s.split(' ').map(card);
|
||||||
|
|
||||||
|
test('i punti del mazzo corto: il colore batte il full, A-7-8-9-10 è la scala più bassa', () => {
|
||||||
|
const order = ['Ac Kf 9p 8q 7c', '7c 7f Kp 9q 8c', '7c 7f 9p 9q Ac', 'Jc Jf Jp 9q 8c', 'Ac 7f 8p 9q 10c', '7c 8f 9p 10q Jc',
|
||||||
|
'Kc Kf Kp 9q 9c', 'Ac Kc 9c 8c 7c', 'Qc Qf Qp Qq 7c', '7c 8c 9c 10c Jc'];
|
||||||
|
const scores = order.map(h => P.evaluate(hand(h)));
|
||||||
|
for (let i = 1; i < scores.length; i++) assert.ok(P.compare(scores[i], scores[i - 1]) > 0, `${order[i]} batte ${order[i - 1]}`);
|
||||||
|
assert.equal(P.handName(hand('Ac Kc 9c 8c 7c')), 'colore');
|
||||||
|
assert.ok(P.compare(P.evaluate(hand('Ac Af 9p 8q 7c')), P.evaluate(hand('Ac Af 9p 8q Kc'))) < 0, 'a parità di coppia contano le carte spaiate');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('una partita intera: le fiches non si perdono, finisce sempre, e chi sa giocare vince più di chi vede sempre', () => {
|
||||||
|
const play = (seed, level, brain) => {
|
||||||
|
const c = P.newPoker(seed, { level });
|
||||||
|
for (let i = 0; i < 20000 && c.step !== 'result'; i++) {
|
||||||
|
if (P.myTurn(c)) P.choose(c, brain(c)); else if (P.canDraw(c)) { for (const k of P.cpuDiscards({ ...c, skill: P.LEVELS.male }, 'andrea')) P.play(c, k); P.choose(c, 'cambia'); }
|
||||||
|
else if (c.step === 'show') P.advance(c); else P.update(c, 1);
|
||||||
|
const sum = P.SEATS.reduce((n, s) => n + c.chips[s], 0) + (c.step === 'show' || c.step === 'result' ? 0 : c.pot);
|
||||||
|
assert.equal(sum, P.START * 3);
|
||||||
|
}
|
||||||
|
assert.equal(c.step, 'result');
|
||||||
|
return c.result.chips.andrea;
|
||||||
|
};
|
||||||
|
const smart = c => { const s = P.strength(c.cards.andrea); return s > 0.5 && P.canRaise(c) ? 'rilancio' : s > 0.15 || !P.toCall(c) ? 'vedo' : 'lascio'; };
|
||||||
|
let a = 0, b = 0;
|
||||||
|
for (let seed = 1; seed <= 60; seed++) { a += play(seed, 'bona', smart); b += play(seed, 'bona', () => 'vedo'); }
|
||||||
|
assert.ok(a > b, `con giudizio ${a}, vedendo sempre ${b}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('i comandi di Andrea: il cambio si sceglie toccando (al massimo 4), le puntate coi pulsanti', () => {
|
||||||
|
const c = P.newPoker(3);
|
||||||
|
while (!P.myTurn(c)) P.update(c, 1);
|
||||||
|
const moves = P.moves(c).map(m => m.id);
|
||||||
|
assert.deepEqual(moves, ['lascio', 'vedo', 'rilancio']);
|
||||||
|
assert.ok(P.choose(c, 'vedo'));
|
||||||
|
while (!P.canDraw(c) && c.step !== 'show') P.update(c, 1);
|
||||||
|
if (P.canDraw(c)) {
|
||||||
|
assert.equal(cardAt(c, [HAND.x + 5, HAND.y + 5]), 0);
|
||||||
|
for (const i of [0, 1, 2, 3, 4]) P.play(c, i);
|
||||||
|
assert.equal(c.selected.size, 4, 'al massimo quattro');
|
||||||
|
assert.ok(P.choose(c, 'cambia'));
|
||||||
|
assert.equal(c.changed.andrea, 4);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('il disegno del pokerino non cambia lo stato', () => {
|
||||||
|
const calls = [], ctx = new Proxy({}, { get: (o, k) => k in o ? o[k] : (...args) => calls.push([k, ...args]) });
|
||||||
|
const c = P.newPoker(9);
|
||||||
|
const before = JSON.stringify({ ...c, selected: [...c.selected] });
|
||||||
|
drawPoker(ctx, c);
|
||||||
|
assert.equal(JSON.stringify({ ...c, selected: [...c.selected] }), before);
|
||||||
|
assert.ok(calls.some(([op, t]) => op === 'fillText' && /POKERINO/.test(t)));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('la macchina del tempo: l\'Arcu la fa comparire, il viaggio porta al Barrettino del 1997 e la porta riporta nel 2007', () => {
|
||||||
|
assert.ok(MINIGAMES.poker);
|
||||||
|
const g = new Game(story); g.go('via_liguri_magelli'); settle(g);
|
||||||
|
assert.ok(!g.hotspot('macchina_tempo'));
|
||||||
|
g.act('talk', 'arcu'); settle(g);
|
||||||
|
g.choose(g.options().findIndex(o => o.text.startsWith('Bei tempi'))); settle(g);
|
||||||
|
assert.ok(g.state.flags.macchina_trappola && g.hotspot('macchina_tempo'));
|
||||||
|
g.choose(g.options().findIndex(o => o.text.startsWith('Ci si vede'))); settle(g);
|
||||||
|
g.act('use', 'macchina_tempo'); settle(g);
|
||||||
|
assert.equal(g.closeup?.id, 'viaggio');
|
||||||
|
for (let t = 0; t < TRIP.end + 0.5 && g.closeup; t += 0.05) g.update(0.05);
|
||||||
|
settle(g);
|
||||||
|
assert.equal(g.state.scene, 'barrettino_1997');
|
||||||
|
g.act('talk', 'tavola'); settle(g);
|
||||||
|
g.choose(g.options().findIndex(o => o.text.startsWith('C\'è posto'))); settle(g);
|
||||||
|
g.choose(g.options().findIndex(o => o.text === 'A fassi male.')); settle(g);
|
||||||
|
assert.equal(g.closeup?.id, 'poker'); assert.equal(g.closeup.level, 'male');
|
||||||
|
g.closeup.step = 'result'; g.closeup.result = { winner: 'player', chips: { andrea: 200, vannino: 60, pierino: 40 }, top: ['andrea'] };
|
||||||
|
g.closeupClose(); settle(g);
|
||||||
|
assert.ok(g.state.flags.poker_vinto);
|
||||||
|
g.act('walk', 'porta'); settle(g);
|
||||||
|
assert.equal(g.state.scene, 'via_liguri_magelli');
|
||||||
|
});
|
||||||
+10
-1
@@ -260,6 +260,8 @@ test('il libro dell\'oratorio: il primo piano si sfoglia, l\'ultima pagina è «
|
|||||||
|
|
||||||
test('la mappa: ogni scena ci sta, i posti compaiono solo visti, il viaggio rispetta i dialoghi', () => {
|
test('la mappa: ogni scena ci sta, i posti compaiono solo visti, il viaggio rispetta i dialoghi', () => {
|
||||||
for (const [id, sc] of Object.entries(story.scenes)) {
|
for (const [id, sc] of Object.entries(story.scenes)) {
|
||||||
|
// Le scene del viaggio nel tempo (`viaggio`) non sono sulla mappa: ci si va solo con la macchina.
|
||||||
|
if (sc.viaggio) { assert.ok(!sc.map, `${id}: nel passato la mappa non porta`); continue; }
|
||||||
assert.ok(Array.isArray(sc.map), `${id} non è sulla mappa`);
|
assert.ok(Array.isArray(sc.map), `${id} non è sulla mappa`);
|
||||||
assert.ok(sc.map.every(v => v >= 0 && v <= 100), `${id} fuori dalla pianta`);
|
assert.ok(sc.map.every(v => v >= 0 && v <= 100), `${id} fuori dalla pianta`);
|
||||||
}
|
}
|
||||||
@@ -341,7 +343,14 @@ test('le strade vanno e tornano: niente sensi unici, e ogni posto si raggiunge d
|
|||||||
for (const b of to) assert.ok(exits[b]?.has(a), `da ${a} si va a ${b}, ma da ${b} non si torna a ${a}`);
|
for (const b of to) assert.ok(exits[b]?.has(a), `da ${a} si va a ${b}, ma da ${b} non si torna a ${a}`);
|
||||||
const seen = new Set([story.start.scene]), queue = [story.start.scene];
|
const seen = new Set([story.start.scene]), queue = [story.start.scene];
|
||||||
while (queue.length) for (const b of exits[queue.shift()] ?? []) if (!seen.has(b)) { seen.add(b); queue.push(b); }
|
while (queue.length) for (const b of exits[queue.shift()] ?? []) if (!seen.has(b)) { seen.add(b); queue.push(b); }
|
||||||
for (const id of Object.keys(story.scenes)) assert.ok(seen.has(id), `${id} non si raggiunge`);
|
for (const [id, sc] of Object.entries(story.scenes)) {
|
||||||
|
if (!sc.viaggio) { assert.ok(seen.has(id), `${id} non si raggiunge`); continue; }
|
||||||
|
// Una scena del viaggio nel tempo: ci porta un `go` (la fine del viaggio), e un `go` riporta in una scena vera.
|
||||||
|
const text = JSON.stringify(story);
|
||||||
|
assert.ok(text.includes(`"go":"${id}"`), `${id}: nessun viaggio ci porta`);
|
||||||
|
const back = JSON.stringify(sc.hotspots).match(/"go":"([a-z_0-9]+)"/g)?.map(m => m.slice(6, -1)) ?? [];
|
||||||
|
assert.ok(back.some(b => seen.has(b)), `${id}: non si torna indietro`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('la catena del ghiacciolo: Tosca, cedrata, stecco, gomma, griglia, chiavetta, casetta, torcia', () => {
|
test('la catena del ghiacciolo: Tosca, cedrata, stecco, gomma, griglia, chiavetta, casetta, torcia', () => {
|
||||||
|
|||||||
@@ -35,6 +35,13 @@
|
|||||||
"speaker": "Xavier Hayasaka",
|
"speaker": "Xavier Hayasaka",
|
||||||
"filtro": "asetrate=24000*1.12,aresample=24000,atempo=0.92"
|
"filtro": "asetrate=24000*1.12,aresample=24000,atempo=0.92"
|
||||||
},
|
},
|
||||||
|
"vannino": {
|
||||||
|
"speaker": "Kumar Dahl"
|
||||||
|
},
|
||||||
|
"pierino_parigi": {
|
||||||
|
"speaker": "Eugenio Mataracı",
|
||||||
|
"filtro": "asetrate=24000*0.97,aresample=24000"
|
||||||
|
},
|
||||||
"zio_mario": {
|
"zio_mario": {
|
||||||
"speaker": "Viktor Menelaos",
|
"speaker": "Viktor Menelaos",
|
||||||
"filtro": "asetrate=24000*0.95,aresample=24000,atempo=1.04"
|
"filtro": "asetrate=24000*0.95,aresample=24000,atempo=1.04"
|
||||||
|
|||||||
Reference in New Issue
Block a user