Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f0f60d186 | ||
|
|
5135689293 | ||
|
|
8693bff0c7 | ||
|
|
c6812db291 | ||
|
|
670626afa5 | ||
|
|
9bf41d01af | ||
|
|
16252fb04a | ||
|
|
99d4dede80 | ||
|
|
36eda83fe4 | ||
|
|
58174e68d2 | ||
|
|
ff770af3b5 | ||
|
|
41e5fe2f6b | ||
|
|
9d823375aa | ||
|
|
00f9f15370 | ||
|
|
9fe6e8d0ff | ||
|
|
0f8081d8f0 | ||
|
|
500885ffe0 | ||
|
|
259be2c8b0 | ||
|
|
1706592b81 | ||
|
|
6758e6287e | ||
|
|
a699115a03 |
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 105 KiB |
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"image": "taglia.png",
|
||||
"height": 120,
|
||||
"frames": {
|
||||
"stand": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 54,
|
||||
"h": 120,
|
||||
"ax": 27.7,
|
||||
"ay": 120
|
||||
},
|
||||
"blink": {
|
||||
"x": 56,
|
||||
"y": 0,
|
||||
"w": 54,
|
||||
"h": 120,
|
||||
"ax": 27.7,
|
||||
"ay": 120
|
||||
},
|
||||
"talk": {
|
||||
"x": 112,
|
||||
"y": 0,
|
||||
"w": 75,
|
||||
"h": 120,
|
||||
"ax": 32.2,
|
||||
"ay": 120
|
||||
},
|
||||
"explain": {
|
||||
"x": 189,
|
||||
"y": 0,
|
||||
"w": 64,
|
||||
"h": 120,
|
||||
"ax": 33.6,
|
||||
"ay": 120
|
||||
},
|
||||
"laugh": {
|
||||
"x": 255,
|
||||
"y": 0,
|
||||
"w": 59,
|
||||
"h": 121,
|
||||
"ax": 25.2,
|
||||
"ay": 121
|
||||
},
|
||||
"aim": {
|
||||
"x": 316,
|
||||
"y": 0,
|
||||
"w": 97,
|
||||
"h": 114,
|
||||
"ax": 55.6,
|
||||
"ay": 114
|
||||
},
|
||||
"shrug": {
|
||||
"x": 415,
|
||||
"y": 0,
|
||||
"w": 91,
|
||||
"h": 117,
|
||||
"ax": 44.2,
|
||||
"ay": 117
|
||||
},
|
||||
"throw": {
|
||||
"x": 508,
|
||||
"y": 0,
|
||||
"w": 71,
|
||||
"h": 120,
|
||||
"ax": 30.3,
|
||||
"ay": 120
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 102 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 537 KiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 190 KiB |
@@ -683,6 +683,10 @@
|
||||
"file": "andrea-c716ab2381.mp3",
|
||||
"sec": 7.95
|
||||
},
|
||||
"andrea|Il Taglia. Io sono un metro e ottanta, e lui mi guarda dall'alto. Con la Condor in mano.": {
|
||||
"file": "andrea-f1663e7892.mp3",
|
||||
"sec": 8.52
|
||||
},
|
||||
"andrea|Il giardino coi cedri e le panchine. All'ombra, d'estate, ci si fa la siesta a turno.": {
|
||||
"file": "andrea-4fd811d0c7.mp3",
|
||||
"sec": 6.43
|
||||
@@ -1631,6 +1635,18 @@
|
||||
"file": "andrea-a193a43f3e.mp3",
|
||||
"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|Imposto: 10 luglio 1997, ore 18 e 30. Il pokerino da Vannino.": {
|
||||
"file": "andrea-3e60a383c4.mp3",
|
||||
"sec": 8.2
|
||||
},
|
||||
"arcu|Monto anch'io: davanti, però.": {
|
||||
"file": "arcu-664d65b6d1.mp3",
|
||||
"sec": 2.34
|
||||
},
|
||||
"andrea|Nessuno ha mai capito davvero il tuo immenso valore.": {
|
||||
"file": "andrea-3e85200509.mp3",
|
||||
"sec": 2.93
|
||||
@@ -1871,6 +1887,38 @@
|
||||
"file": "andrea-d38b6e49ef.mp3",
|
||||
"sec": 3.49
|
||||
},
|
||||
"andrea|Lo zio Mario nel suo orto, col sole che va giù dietro la casa.": {
|
||||
"file": "andrea-9309e50d5c.mp3",
|
||||
"sec": 3.78
|
||||
},
|
||||
"zio_mario|Ti s'aspetta a cena, che c'è la vitella di maiale.": {
|
||||
"file": "zio_mario-39ff0c666f.mp3",
|
||||
"sec": 2.71
|
||||
},
|
||||
"andrea|Non ti avevo visto, pensavo tu fossi andato all'estero.": {
|
||||
"file": "andrea-f847f224df.mp3",
|
||||
"sec": 3.35
|
||||
},
|
||||
"zio_mario|Io ho girato tanto... il Falcione.": {
|
||||
"file": "zio_mario-d0d592db52.mp3",
|
||||
"sec": 2.73
|
||||
},
|
||||
"andrea|Zio, che sei stanco?": {
|
||||
"file": "andrea-21195c6cac.mp3",
|
||||
"sec": 2.31
|
||||
},
|
||||
"zio_mario|No, dormo!": {
|
||||
"file": "zio_mario-a852e9c43f.mp3",
|
||||
"sec": 1.19
|
||||
},
|
||||
"andrea|Zio, a me mi garba Michael Jackson!": {
|
||||
"file": "andrea-599b75d7c7.mp3",
|
||||
"sec": 2.6
|
||||
},
|
||||
"zio_mario|Mai col gesso!": {
|
||||
"file": "zio_mario-5773e3350f.mp3",
|
||||
"sec": 1.13
|
||||
},
|
||||
"andrea|Le case coi balconi pieni di vasi: qui ogni finestra è un giardino.": {
|
||||
"file": "andrea-ec81875c47.mp3",
|
||||
"sec": 5.22
|
||||
@@ -1879,6 +1927,54 @@
|
||||
"file": "andrea-d32e1a2a87.mp3",
|
||||
"sec": 3.45
|
||||
},
|
||||
"andrea|Si torna nel 2007. Ciao Vannino, ciao Pierino.": {
|
||||
"file": "andrea-baa95acb36.mp3",
|
||||
"sec": 4.41
|
||||
},
|
||||
"arcu|Riportata dove l'ho presa. Se ti riviene voglia di pokerini, chiedi.": {
|
||||
"file": "arcu-5df7e4b863.mp3",
|
||||
"sec": 4.24
|
||||
},
|
||||
"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|L'Arcu, venuto con me nel 1997. Guarda le carte di tutti e non dice nulla. Per ora.": {
|
||||
"file": "andrea-4175b94c54.mp3",
|
||||
"sec": 8.75
|
||||
},
|
||||
"andrea|Arcu, tu non giochi?": {
|
||||
"file": "andrea-833a07768c.mp3",
|
||||
"sec": 1.81
|
||||
},
|
||||
"arcu|Io guardo. Chi guarda un perde mai.": {
|
||||
"file": "arcu-8a1840569a.mp3",
|
||||
"sec": 3.46
|
||||
},
|
||||
"arcu|E quando vuoi si torna: la macchina è fuori, basta dirlo.": {
|
||||
"file": "arcu-89d19833e7.mp3",
|
||||
"sec": 3.71
|
||||
},
|
||||
"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|Un pino a ombrello enorme. Alla ricreazione, tutti all'ombra lì sotto. Sembra ieri.": {
|
||||
"file": "andrea-0a073729fd.mp3",
|
||||
"sec": 7.3
|
||||
@@ -2511,6 +2607,10 @@
|
||||
"file": "andrea-a960f89e7a.mp3",
|
||||
"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.": {
|
||||
"file": "andrea-191021ad7c.mp3",
|
||||
"sec": 1.68
|
||||
@@ -2539,6 +2639,30 @@
|
||||
"file": "arcu-fcaee733df.mp3",
|
||||
"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-9924ed4281.mp3",
|
||||
"sec": 3.83
|
||||
},
|
||||
"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'ho trovato da Alvarino. Per un pelo stavano arrivando i Libici a prenderglielo tutto.": {
|
||||
"file": "arcu-c294047de1.mp3",
|
||||
"sec": 6.09
|
||||
},
|
||||
"arcu|Basta salire e mettere la data.": {
|
||||
"file": "arcu-dd10a321b8.mp3",
|
||||
"sec": 2.9
|
||||
},
|
||||
"andrea|Alla bona, un giro di paese.": {
|
||||
"file": "andrea-b35a4d8cc0.mp3",
|
||||
"sec": 2.15
|
||||
@@ -2659,6 +2783,122 @@
|
||||
"file": "andrea-78ad61a67a.mp3",
|
||||
"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-8104163151.mp3",
|
||||
"sec": 3.3
|
||||
},
|
||||
"andrea|Taglia! Si fa una gara con la Condor?": {
|
||||
"file": "andrea-99f683209e.mp3",
|
||||
"sec": 3.41
|
||||
},
|
||||
"andrea|A cosa si tira?": {
|
||||
"file": "andrea-3f77b83f35.mp3",
|
||||
"sec": 0.87
|
||||
},
|
||||
"andrea|E una partita a campana, Taglia?": {
|
||||
"file": "andrea-595df0a426.mp3",
|
||||
"sec": 2.23
|
||||
},
|
||||
"andrea|Ma quanto sei alto, Taglia?": {
|
||||
"file": "andrea-01ecdf3ed8.mp3",
|
||||
"sec": 1.96
|
||||
},
|
||||
"andrea|Ci si vede, Taglia.": {
|
||||
"file": "andrea-5d4d911c06.mp3",
|
||||
"sec": 1.42
|
||||
},
|
||||
"taglia|Io ho già tirato: ora tocca a te. Dodici pallini gialli, e devi fare più di me.": {
|
||||
"file": "taglia-2658ec8482.mp3",
|
||||
"sec": 6.05
|
||||
},
|
||||
"taglia|Come la vuoi?": {
|
||||
"file": "taglia-32a067db10.mp3",
|
||||
"sec": 0.62
|
||||
},
|
||||
"taglia|Il cartone sull'albero, le lattine sulla panchina, le bottigliette sulla porta e il palloncino all'altalena.": {
|
||||
"file": "taglia-670f9b4ef8.mp3",
|
||||
"sec": 7.15
|
||||
},
|
||||
"taglia|E se la vuoi seria, ogni tanto ti tiro una lattina in aria.": {
|
||||
"file": "taglia-ac012a1066.mp3",
|
||||
"sec": 4.21
|
||||
},
|
||||
"taglia|A campana? Alla nostra età?": {
|
||||
"file": "taglia-0a7f69770d.mp3",
|
||||
"sec": 2.77
|
||||
},
|
||||
"taglia|Va bene: il gesso ce l'ho in tasca, il sassolino lo scelgo io. Sul vialetto, dalla 1 alla 8, e chi pesta la riga passa la mano.": {
|
||||
"file": "taglia-c00be84faf.mp3",
|
||||
"sec": 11.54
|
||||
},
|
||||
"taglia|Abbastanza da vedere i pallini arrivare prima di te.": {
|
||||
"file": "taglia-d99c875252.mp3",
|
||||
"sec": 2.57
|
||||
},
|
||||
"taglia|Alla bona: ti faccio vedere dove arriva il pallino.": {
|
||||
"file": "taglia-757564b65e.mp3",
|
||||
"sec": 3.12
|
||||
},
|
||||
"taglia|Seria. Occhio alla lattina in aria.": {
|
||||
"file": "taglia-06abe0c427.mp3",
|
||||
"sec": 3.06
|
||||
},
|
||||
"taglia|A fassi male? Allora tiro più forte, e più spesso.": {
|
||||
"file": "taglia-4117f6613d.mp3",
|
||||
"sec": 4.84
|
||||
},
|
||||
"taglia|Alla bona: le righe un contano. Comincia te.": {
|
||||
"file": "taglia-328d930124.mp3",
|
||||
"sec": 4.12
|
||||
},
|
||||
"taglia|Seria: chi tocca la riga è fuori. Comincia te.": {
|
||||
"file": "taglia-1266463538.mp3",
|
||||
"sec": 4.23
|
||||
},
|
||||
"taglia|A fassi male? Io la campana la facevo a occhi chiusi.": {
|
||||
"file": "taglia-7938d8d5e4.mp3",
|
||||
"sec": 5.05
|
||||
},
|
||||
"andrea|Si fa una gara alle macchinine?!?!": {
|
||||
"file": "andrea-17216997e2.mp3",
|
||||
"sec": 2.74
|
||||
@@ -2707,10 +2947,6 @@
|
||||
"file": "andrea-fc5a08bbd6.mp3",
|
||||
"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.": {
|
||||
"file": "chiaru-d31cc223f6.mp3",
|
||||
"sec": 5.08
|
||||
@@ -3075,6 +3311,22 @@
|
||||
"file": "bertovo-ca941bee91.mp3",
|
||||
"sec": 5.41
|
||||
},
|
||||
"vannino|Tu m'ha pelato. La prossima volta porta il Barone.": {
|
||||
"file": "vannino-2267bb5adf.mp3",
|
||||
"sec": 3.18
|
||||
},
|
||||
"pierino_parigi|Le fiches restano al banco. E tu torna quando hai imparato a bluffare.": {
|
||||
"file": "pierino_parigi-a80d04fe5b.mp3",
|
||||
"sec": 5.41
|
||||
},
|
||||
"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.": {
|
||||
"file": "andrea-585cd65f93.mp3",
|
||||
"sec": 5.2
|
||||
@@ -3087,6 +3339,54 @@
|
||||
"file": "andrea-c7dcb11e66.mp3",
|
||||
"sec": 5.52
|
||||
},
|
||||
"andrea|Battuto il Taglia! Con la sua Condor, per giunta.": {
|
||||
"file": "andrea-3ff6547461.mp3",
|
||||
"sec": 3.73
|
||||
},
|
||||
"taglia|Fortuna. Domani la rivinco.": {
|
||||
"file": "taglia-39627c5b3a.mp3",
|
||||
"sec": 2.78
|
||||
},
|
||||
"andrea|Il Taglia ha la mano ferma. Io ho il fiatone.": {
|
||||
"file": "andrea-6eabf3443c.mp3",
|
||||
"sec": 3.83
|
||||
},
|
||||
"taglia|Si torna quando vuoi. I pallini gialli un mancano mai.": {
|
||||
"file": "taglia-c3c705219c.mp3",
|
||||
"sec": 4.52
|
||||
},
|
||||
"andrea|Pari. Si rifà?": {
|
||||
"file": "andrea-5625b5f1fd.mp3",
|
||||
"sec": 2.21
|
||||
},
|
||||
"taglia|Si rifà.": {
|
||||
"file": "taglia-18973374a5.mp3",
|
||||
"sec": 0.76
|
||||
},
|
||||
"andrea|La Condor la rendo al Taglia. Per ora.": {
|
||||
"file": "andrea-aba12c5241.mp3",
|
||||
"sec": 3.1
|
||||
},
|
||||
"andrea|Dalla 1 fino in cima, e ritorno. A ventisett'anni, davanti alle poste.": {
|
||||
"file": "andrea-68378e4c21.mp3",
|
||||
"sec": 6.15
|
||||
},
|
||||
"taglia|Te lo dico io: se ti vede qualcuno, domani lo sa tutto il paese.": {
|
||||
"file": "taglia-1e6660932d.mp3",
|
||||
"sec": 3.34
|
||||
},
|
||||
"andrea|Il Taglia, su un piede solo, è più alto di me su due.": {
|
||||
"file": "andrea-3ed6165062.mp3",
|
||||
"sec": 3.31
|
||||
},
|
||||
"taglia|È questione di gambe lunghe.": {
|
||||
"file": "taglia-9782816264.mp3",
|
||||
"sec": 1.71
|
||||
},
|
||||
"andrea|Il gesso lo lascio lì. Ci giocherà qualche bambino, che è più in allenamento di me.": {
|
||||
"file": "andrea-356a1afe8d.mp3",
|
||||
"sec": 5.91
|
||||
},
|
||||
"chiaru|Hai vinto te. Ma la mia aveva le ruote piene di gesso, sia chiaro.": {
|
||||
"file": "chiaru-91a4b23b22.mp3",
|
||||
"sec": 4.44
|
||||
|
||||
@@ -31,10 +31,16 @@ ne è un'opera derivata. Le foto scattate da Andrea non hanno problemi; quelle p
|
||||
| `provinciale-pignone.png`, `piazzetta-cambray-digny.png`, `via-cipriani.png`, `via-massorondinaio.png`, `mozzete.png`, `ponticino-mozzete.png`, `coldaia.png`, `falegnameria-coldaia.png`, `gabbiano.png` | Codex | screenshot di Google Street View | Google | **no**: da rifare con foto di Andrea |
|
||||
| `closeups/micro-parcheggio.png`, `closeups/micro/*.png` (macchinine) | Codex, solo testo | nessuna | — | sì |
|
||||
| `characters/chiaru.png` | Codex | `foto/chiaru.jpg` | Andrea (29 ottobre) | sì (il Chiaru consenziente) |
|
||||
| `characters/taglia.png` | Codex | `foto/taglia.png` | Andrea (29 ottobre) | sì (il Taglia consenziente) |
|
||||
| `via-liguri-magelli-muro.png` | Codex, ritocco di `via-liguri-magelli.png` con la Ghiacciomobile nel muro | come `via-liguri-magelli.png` | Street View | **no**: come lo sfondo da cui viene |
|
||||
| `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 |
|
||||
| `closeups/muro-cerbottana.png` | Codex, solo testo (stile dalla piazza) | nessuna | — | sì |
|
||||
| `closeups/macchina-dietro.png`, `macchina-lampi.png`, `macchina-davanti.png` | Codex, dallo sprite della macchina del tempo | nessuna | — | sì |
|
||||
| `closeups/campone-condor.png` | Codex, dallo sfondo del campone delle poste | nessuna | — | sì |
|
||||
| `closeups/campone-campana.png` | Codex, solo testo (stile dal campone delle poste) | 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 |
|
||||
| `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 |
|
||||
|
||||
@@ -18,6 +18,39 @@ Online su 50037.manzolo.it tutto fino agli effetti ambientali di Codex. Aperto,
|
||||
|
||||
## Dove siamo arrivati
|
||||
|
||||
- **La campana al campone delle poste (30 ottobre)**: dal Taglia, «E una partita a campana, Taglia?», poi il livello.
|
||||
Primo piano `campana` visto dall'alto sul vialetto (`campone-campana.png`, Codex); la campana col gesso la disegna il
|
||||
codice (`campana.js`, `campana-render.js`): 1-2-3, coppia 4-5, 6, coppia 7-8 e in cima il mezzo cerchio con scritto
|
||||
CAMPANA (Andrea: non «cielo»), ogni casella tinta tenue col suo gesso. Il sassolino: la mira dà la direzione, tenendo
|
||||
premuto la forza va e torna, si lascia; deve cadere nella casella che tocca senza toccare le righe (alla bona le
|
||||
righe non contano e il puntino giallo mostra dove cade). I salti: la lancetta dell'equilibrio attraversa la barra e
|
||||
si preme nel verde (presto: giù l'altro piede; tardi: pestata la riga); si scavalca la casella col sassolino e
|
||||
tornando lo si raccoglie. Chi ce la fa passa alla casella dopo e ritira, chi sbaglia passa la mano; il Taglia gioca
|
||||
da solo (in simulazione gli servono circa 22, 10 e 6 turni secondo il livello). Vince chi finisce la 8 (flag
|
||||
`campana_vinta`). Test `campana.test.mjs`, prova `npm run browser:campana`. [DA ANDREA] le battute.
|
||||
- **La macchina del tempo da fuori (30 ottobre)**: dopo il cruscotto, la partenza giù per Via Liguri Magelli
|
||||
(primo piano `corsa`, `src/engine/corsa.js`): accelera, il fulmine la prende, il bianco, e restano due sgommate di
|
||||
fuoco che bruciano e si spengono; al ritorno dal 1997 l'arrivo: il fulmine in fondo alla strada, lei viene avanti e
|
||||
ci passa accanto lasciando le strisce. La macchina da dietro e davanti (coi lampi) è di Codex.
|
||||
- **Lo zio Mario negli orti al tramonto (30 ottobre)**: dal tramonto (`indizio_regolo`) non è più davanti alla
|
||||
Ghiacciomobile ma accanto al suo orto (`orti_zio_mario`), con una battuta provvisoria [DA ANDREA].
|
||||
- **La Condor al campone delle poste (29 ottobre)**: la gara col Taglia (Andrea Tagliaferri, consenso dato) con la
|
||||
pistola ad aria compressa coi pallini gialli (`condor.js`, `condor-render.js`, primo piano `condor`, sfondo di Codex).
|
||||
Come la cerbottana: il mirino ondeggia col respiro; tenere premuto trattiene il fiato (il mirino si ferma), lasciare
|
||||
spara, troppo e il colpo parte da solo. Il pallino cala con la distanza (alla porta in fondo si mira sopra). Il
|
||||
cartone sull'albero (10/5/2), le lattine sulla panchina (3), le bottigliette sulla traversa (5), il palloncino
|
||||
all'altalena (6), la lattina tirata in aria dal Taglia a seria e a fassi male (8). Si deve battere il punteggio del
|
||||
Taglia (22, 34, 44 secondo il livello, ±3). Il Taglia sta al campone (sprite di Codex dalla sua foto, più alto di Andrea); si gareggia
|
||||
parlandogli. [DA ANDREA] le battute.
|
||||
- **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 o il pulsante «Torna al 2007», `scene.back` e `game.goBack()`, riportano nel 2007 e la macchina
|
||||
sparisce: per rifare il viaggio si richiede all'Arcu), con l'Arcu che viene a guardare, 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
|
||||
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
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<button id="map-button" hidden title="Mappa (P)">Mappa</button>
|
||||
<button id="notebook-button" hidden title="Taccuino (N)">Taccuino</button>
|
||||
<button id="spots-button" hidden title="Mostra cosa si può toccare (H)">?</button>
|
||||
<button id="back-button" hidden></button>
|
||||
</div>
|
||||
<div id="sound">
|
||||
<button id="mute" aria-label="Togli l'audio" title="Audio (M)">♪</button>
|
||||
@@ -171,6 +172,22 @@
|
||||
Tastiera: B, S, V; frecce e 1–0 scelgono, Invio gioca, Esc lascia il tavolo.</p>
|
||||
</details>
|
||||
</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">
|
||||
<p id="briscola-status" role="status"></p>
|
||||
<div class="card-hand" aria-label="La mano di Andrea">
|
||||
@@ -255,6 +272,28 @@
|
||||
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="condor-help" hidden><summary>Come si gioca</summary>
|
||||
<p>La Condor, la pistola ad aria compressa coi pallini gialli, al campone delle poste: dodici pallini, e il Taglia ha
|
||||
già tirato. Il suo punteggio è in alto: per vincere bisogna fare di più. Il cartone sull'albero vale 10, 5 o 2 punti
|
||||
secondo il cerchio, ogni lattina sulla panchina 3, ogni bottiglietta sulla traversa della porta 5, il palloncino
|
||||
legato all'altalena 6, la lattina che il Taglia tira in aria (Seria e A fassi male) 8.</p>
|
||||
<p>Muovi il mouse o tocca il campo per mirare: il mirino ondeggia col respiro (di più salendo di livello). Tieni
|
||||
premuto per trattenere il fiato: il mirino si ferma (diventa verde); lascia per sparare. Se lo trattieni troppo il
|
||||
fiato finisce, il mirino trema e il colpo parte da solo. Il pallino cala con la distanza: alla porta in fondo si
|
||||
mira un po' più sopra; alla bona il puntino giallo segna dove arriva. Il palloncino e la lattina in aria si
|
||||
muovono: si mira dove saranno. Si può anche premere Spara, senza trattenere il fiato. Frecce per spostare il mirino.</p>
|
||||
</details>
|
||||
<details id="campana-help" hidden><summary>Come si gioca</summary>
|
||||
<p>La campana col gesso sul vialetto del campone, a turni col Taglia. Si tira il sassolino nella casella che tocca,
|
||||
dalla 1 alla 8: deve cadere dentro, senza toccare le righe (alla bona le righe non contano). Poi si salta tutta la
|
||||
campana fino in cima (il mezzo cerchio) e indietro, su un piede nelle caselle sole e a gambe larghe nella 4-5 e nella 7-8, senza mai
|
||||
mettere il piede dove sta il sassolino; tornando lo si raccoglie dalla casella prima. Chi ce la fa passa alla casella
|
||||
dopo e ritira; chi sbaglia passa la mano. Vince chi finisce la 8.</p>
|
||||
<p>Il tiro: muovi il mouse o tocca la campana per mirare, tieni premuto per la forza (sale e scende) e lascia; oppure
|
||||
regola la forza e premi Tira. Alla bona il puntino giallo segna dove cade. Frecce su e giù per la mira, destra e
|
||||
sinistra per la forza. I salti: la lancetta dell'equilibrio attraversa la barra; premi (o Spazio, o Salta) quando è
|
||||
nel verde. Troppo presto metti giù l'altro piede, troppo tardi pesti la riga.</p>
|
||||
</details>
|
||||
<details id="cerbottana-help" hidden><summary>Come si gioca</summary>
|
||||
<p>La cerbottana, con lo stucco verde di Pierin di Frilli: dodici pallini contro il muro della piazza. Il bersaglio
|
||||
di gesso vale 10, 5 o 2 punti secondo il cerchio, ogni lattina sul muretto 3, il barattolo appeso che dondola (Seria
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "leggenda-del-regolo",
|
||||
"version": "0.1.52",
|
||||
"version": "0.1.57",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -10,6 +10,8 @@
|
||||
"browser:micro": "node tests/micro.browser.mjs",
|
||||
"browser:gara": "node tests/gara.browser.mjs",
|
||||
"browser:cerbottana": "node tests/cerbottana.browser.mjs",
|
||||
"browser:condor": "node tests/condor.browser.mjs",
|
||||
"browser:campana": "node tests/campana.browser.mjs",
|
||||
"browser:tressette": "node tests/tressette.browser.mjs",
|
||||
"browser:scopa": "node tests/scopa.browser.mjs",
|
||||
"browser:briscola": "node tests/briscola.browser.mjs",
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import { W, CY, START, CIELO, FROM, BOXES, LAST, FLIGHT, HOP, center, landing, needle, canAdjust } from './campana.js';
|
||||
import { text, panel } from './bocce-render.js';
|
||||
|
||||
function rect(ctx, x, y, w, h, color) { ctx.fillStyle = color; ctx.fillRect(Math.round(x), Math.round(y), Math.round(w), Math.round(h)); }
|
||||
// Il gesso: tratti a puntini un po' storti, con dei buchi (sempre gli stessi, da un piccolo hash: non tremano).
|
||||
const hash = n => { const s = Math.sin(n * 127.1 + 311.7) * 43758.5453; return s - Math.floor(s); };
|
||||
function chalkLine(ctx, x1, y1, x2, y2, seed) {
|
||||
const n = Math.max(2, Math.round(Math.hypot(x2 - x1, y2 - y1)));
|
||||
for (let i = 0; i <= n; i++) {
|
||||
const k = i / n, h = hash(seed * 1000 + i);
|
||||
if (h < 0.07) continue;
|
||||
ctx.fillStyle = h > 0.8 ? 'rgba(255,255,250,0.95)' : 'rgba(240,238,228,0.78)';
|
||||
ctx.fillRect(Math.round(x1 + (x2 - x1) * k + (hash(seed + i) - 0.5)), Math.round(y1 + (y2 - y1) * k + (hash(seed * 7 + i) - 0.5)), 2, 2);
|
||||
}
|
||||
}
|
||||
// I gessi colorati: ogni casella ripassata tenue col suo colore, a puntini come il gesso sull'asfalto (idea di Andrea).
|
||||
const TINTS = { 1: [244, 160, 180], 2: [250, 222, 120], 3: [150, 200, 240], 4: [160, 220, 150], 5: [200, 170, 230], 6: [250, 180, 120], 7: [140, 220, 210], 8: [240, 150, 150], top: [255, 240, 200] };
|
||||
function tint(ctx, [r, g, b], x, y, w, h, seed, inside = () => true) {
|
||||
ctx.fillStyle = `rgba(${r},${g},${b},0.16)`;
|
||||
for (let yy = y; yy < y + h; yy += 2) for (let xx = x; xx < x + w; xx += 2) {
|
||||
if (!inside(xx, yy)) continue;
|
||||
const k = hash(seed * 7777 + xx * 13 + yy * 7);
|
||||
if (k < 0.18) continue; // l'asfalto che spunta fra il gesso
|
||||
ctx.fillStyle = `rgba(${r},${g},${b},${k > 0.85 ? 0.42 : 0.24})`; ctx.fillRect(xx, yy, 2, 2);
|
||||
}
|
||||
}
|
||||
// Il gesso non cambia mai: tinte e righe si disegnano una volta su una tela a parte (a puntini, a ogni fotogramma,
|
||||
// rallentavano la pagina). Fuori dal browser (nei test) si disegnano direttamente.
|
||||
let layer = null;
|
||||
function chalkwork(ctx) {
|
||||
let s = 1;
|
||||
for (const [id, b] of Object.entries(BOXES)) tint(ctx, TINTS[id], b.x + 1, b.y + 1, W - 2, W - 2, Number(id));
|
||||
tint(ctx, TINTS.top, CIELO.x + 1, CY - CIELO.r, CIELO.r, CIELO.r * 2, 9, (x, y) => Math.hypot(x + 1 - CIELO.x, y + 1 - CY) < CIELO.r - 2);
|
||||
for (const b of Object.values(BOXES)) {
|
||||
chalkLine(ctx, b.x, b.y, b.x + W, b.y, s++); chalkLine(ctx, b.x, b.y + W, b.x + W, b.y + W, s++);
|
||||
chalkLine(ctx, b.x, b.y, b.x, b.y + W, s++); chalkLine(ctx, b.x + W, b.y, b.x + W, b.y + W, s++);
|
||||
}
|
||||
// In cima la campana: il mezzo cerchio in fondo, dove ci si gira.
|
||||
for (let i = 0; i < 40; i++) {
|
||||
const a1 = -Math.PI / 2 + i / 40 * Math.PI, a2 = -Math.PI / 2 + (i + 1) / 40 * Math.PI;
|
||||
chalkLine(ctx, CIELO.x + Math.cos(a1) * CIELO.r, CY + Math.sin(a1) * CIELO.r, CIELO.x + Math.cos(a2) * CIELO.r, CY + Math.sin(a2) * CIELO.r, 200 + i);
|
||||
}
|
||||
// La riga di partenza.
|
||||
chalkLine(ctx, START - 14, CY - W, START - 14, CY + W, 300);
|
||||
}
|
||||
function grid(ctx, c) {
|
||||
if (typeof document === 'undefined') chalkwork(ctx);
|
||||
else {
|
||||
if (!layer) { layer = document.createElement('canvas'); layer.width = 640; layer.height = 360; chalkwork(layer.getContext('2d')); }
|
||||
ctx.drawImage(layer, 0, 0);
|
||||
}
|
||||
// Il numero in alto nella casella: il mezzo resta libero per il sassolino e i piedi. Quella che tocca ha il bordo giallo.
|
||||
for (const [id, b] of Object.entries(BOXES)) {
|
||||
const p = center(id), next = Number(id) === c.target[c.current] && c.step !== 'result';
|
||||
if (next) { ctx.strokeStyle = 'rgba(255,236,140,0.55)'; ctx.lineWidth = 2; ctx.strokeRect(b.x + 4, b.y + 4, W - 8, W - 8); }
|
||||
text(ctx, id, p.x, p.y - 17, 14, next ? '#fff3a8' : 'rgba(245,243,232,0.88)', 'center');
|
||||
}
|
||||
text(ctx, 'CAMPANA', CIELO.x + W * 0.53, CY - 26, 8, 'rgba(245,243,232,0.88)', 'center');
|
||||
}
|
||||
// Il sassolino: grigio, col lume in alto; in volo, più su e con l'ombra sotto.
|
||||
function stone(ctx, x, y, lift = 0) {
|
||||
ctx.fillStyle = 'rgba(30,25,20,0.35)'; ctx.beginPath(); ctx.ellipse(x + 1, y + 2, 5, 3, 0, 0, Math.PI * 2); ctx.fill();
|
||||
const s = 1 + lift / 60, yy = y - lift;
|
||||
ctx.fillStyle = '#4a4744'; ctx.beginPath(); ctx.ellipse(x, yy, 5 * s, 4 * s, 0.3, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.fillStyle = '#8d8984'; ctx.beginPath(); ctx.ellipse(x - 0.5, yy - 0.5, 4 * s, 3 * s, 0.3, 0, Math.PI * 2); ctx.fill();
|
||||
rect(ctx, x - 2 * s, yy - 2 * s, 2, 1, '#cfcac2');
|
||||
}
|
||||
// Dove stanno i piedi: a gambe larghe nelle coppie e nel cielo, su un piede nelle caselle sole, alla partenza tutti e due.
|
||||
function feet(beat) {
|
||||
if (!beat || beat.out) return [{ x: FROM.x + 2, y: CY - 9 }, { x: FROM.x + 2, y: CY + 9 }];
|
||||
if (beat.boxes[0] === 'cielo') { const p = center('cielo'); return [{ x: p.x, y: p.y - 12 }, { x: p.x, y: p.y + 12 }]; }
|
||||
return beat.boxes.map(id => center(id));
|
||||
}
|
||||
// Una scarpa vista dall'alto: la punta verso dove si va.
|
||||
function shoe(ctx, x, y, dir, scale, [sole, upper, lace]) {
|
||||
ctx.save(); ctx.translate(Math.round(x), Math.round(y)); ctx.scale(dir * scale, scale);
|
||||
ctx.fillStyle = 'rgba(30,25,20,0.3)'; ctx.fillRect(-9, -3, 21, 9);
|
||||
ctx.fillStyle = sole; ctx.fillRect(-10, -5, 20, 10); ctx.fillRect(10, -4, 2, 8);
|
||||
ctx.fillStyle = upper; ctx.fillRect(-9, -4, 18, 8);
|
||||
ctx.fillStyle = lace; ctx.fillRect(-1, -2, 7, 1); ctx.fillRect(-1, 1, 7, 1);
|
||||
ctx.fillStyle = '#2a2420'; ctx.fillRect(-8, -2, 4, 4); // la caviglia, il buco della scarpa
|
||||
ctx.restore();
|
||||
}
|
||||
const SHOES = { player: ['#e8e6e0', '#3d6fb8', '#ffffff'], cpu: ['#2b2b2b', '#b8322a', '#f0d8a8'] };
|
||||
function walker(ctx, c) {
|
||||
const beat = c.beats?.[c.beat], prev = c.beat > 0 ? c.beats[c.beat - 1] : null, now = feet(c.beat >= 0 ? beat : null);
|
||||
const k = c.hop ? Math.min(1, c.hop.t / HOP) : 1, from = feet(c.beat > 0 ? prev : null);
|
||||
const mid = list => list.reduce((a, p) => ({ x: a.x + p.x / list.length, y: a.y + p.y / list.length }), { x: 0, y: 0 });
|
||||
const a = mid(from), b = mid(now), off = { x: (a.x - b.x) * (1 - k), y: (a.y - b.y) * (1 - k) };
|
||||
const scale = (c.current === 'cpu' ? 1.45 : 1.3) * (1 + 0.35 * Math.sin(Math.PI * k));
|
||||
const dir = beat?.back ? -1 : 1;
|
||||
for (const p of now) shoe(ctx, p.x + off.x, p.y + off.y, dir, scale, SHOES[c.current]);
|
||||
}
|
||||
|
||||
export function drawCampana(ctx, c, def, bg) {
|
||||
ctx.save(); ctx.imageSmoothingEnabled = false;
|
||||
if (bg) ctx.drawImage(bg, 0, 0, 640, 360); else { ctx.fillStyle = '#9a948a'; ctx.fillRect(0, 0, 640, 360); }
|
||||
grid(ctx, c);
|
||||
if (c.stone) stone(ctx, c.stone.x, c.stone.y);
|
||||
walker(ctx, c);
|
||||
if (c.shot) {
|
||||
const k = Math.min(1, c.shot.t / FLIGHT), { from, to } = c.shot;
|
||||
stone(ctx, from.x + (to.x - from.x) * k, from.y + (to.y - from.y) * k, 40 * Math.sin(Math.PI * k));
|
||||
}
|
||||
// La mira: un cerchietto di gesso dove si punta; alla bona, il puntino dove cade il sassolino con questa forza.
|
||||
if (canAdjust(c)) {
|
||||
ctx.strokeStyle = '#fff8dc'; ctx.lineWidth = 1;
|
||||
ctx.beginPath(); ctx.arc(c.aim.x, c.aim.y, 6, 0, Math.PI * 2); ctx.stroke();
|
||||
if (c.skill.guide) { const l = landing(c); rect(ctx, l.x - 2, l.y - 2, 4, 4, '#f5d02a'); rect(ctx, l.x - 1, l.y - 1, 2, 2, '#7a5a10'); }
|
||||
// La forza: la barra in basso a sinistra.
|
||||
const x = 16, y = 296, h = 50, f = Math.round(h * c.power);
|
||||
rect(ctx, x - 3, y - 3, 18, h + 6, '#17160fd0');
|
||||
rect(ctx, x, y + h - f, 12, f, c.holding ? '#ffd23c' : '#d9ccba');
|
||||
text(ctx, 'FORZA', x + 6, y - 9, 6, '#efd49e', 'center');
|
||||
}
|
||||
// L'equilibrio: la lancetta attraversa la barra; si salta quando è nel verde.
|
||||
if (c.step === 'hop' && c.meter) {
|
||||
const x = 220, y = 318, w = 200, zone = c.skill.zone * w / 2, u = needle(c);
|
||||
rect(ctx, x - 4, y - 14, w + 8, 30, '#17160fd8');
|
||||
rect(ctx, x, y, w, 8, '#7a3a2a'); rect(ctx, x + w / 2 - zone, y, zone * 2, 8, '#5fae4a');
|
||||
rect(ctx, x + w / 2 + u * w / 2 - 1, y - 3, 3, 14, '#fff3a8');
|
||||
text(ctx, 'EQUILIBRIO', x + w / 2, y - 7, 6, '#efd49e', 'center');
|
||||
}
|
||||
// In alto: a che casella è ciascuno, di chi è il turno, cosa è successo.
|
||||
rect(ctx, 0, 0, 640, 22, '#17160fe8');
|
||||
text(ctx, 'LA CAMPANA', 8, 7, 8, '#efd49e');
|
||||
text(ctx, { bona: 'alla bona', seria: 'seria', male: 'a fassi male' }[c.level], 8, 16, 7, '#d9ccba');
|
||||
const at = n => n > LAST ? 'finita' : `alla ${n}`;
|
||||
text(ctx, `Andrea ${at(c.target.player)}`, 220, 11, 9, c.current === 'player' ? '#fff0c8' : '#a8a08c', 'center');
|
||||
text(ctx, `Il Taglia ${at(c.target.cpu)}`, 360, 11, 9, c.current === 'cpu' ? '#9fd0ff' : '#7f95a8', 'center');
|
||||
text(ctx, c.current === 'player' ? 'tocca a te' : 'tocca al Taglia', 560, 11, 8, '#d9ccba', 'center');
|
||||
if (c.line) { rect(ctx, 150, 24, 340, 14, '#17160fd0'); text(ctx, c.line, 320, 31, 8, /riga|fuori|Troppo|equilibrio|piede|non nella/i.test(c.line) ? '#ffbf9f' : '#9fd0ff', 'center'); }
|
||||
if (c.step === 'result') {
|
||||
const won = c.result.winner === 'player';
|
||||
panel(ctx, 170, 140, 300, 76);
|
||||
text(ctx, won ? 'BATTUTO IL TAGLIA!' : 'HA VINTO IL TAGLIA', 320, 160, 12, won ? '#b9e5b3' : '#efd49e', 'center');
|
||||
text(ctx, won ? 'Tutta la campana, dalla 1 alla 8.' : `Tu eri arrivato alla ${c.target.player}.`, 320, 182, 8, '#efd49e', 'center');
|
||||
text(ctx, 'Torna al campone per uscire.', 320, 200, 8, '#d9ccba', 'center');
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
// La campana al campone delle poste (idea di Andrea, 30 ottobre): disegnata col gesso sul vialetto, a turni col Taglia.
|
||||
// Si tira il sassolino nella casella che tocca (dalla 1 alla 8): se cade dentro, senza toccare le righe, si salta tutta
|
||||
// la campana fino in cima e indietro, su un piede nelle caselle sole e a gambe larghe nelle coppie, senza mai mettere
|
||||
// il piede dove sta il sassolino; tornando, lo si raccoglie dalla casella prima. Chi sbaglia (sassolino fuori o sulla
|
||||
// riga, salto fuori tempo) passa la mano; chi ce la fa va avanti alla casella dopo e ritira. Vince chi finisce la 8.
|
||||
// Il tiro: la mira dà la direzione, tenere premuto carica la forza (va e torna), lasciare tira. I salti: a ogni salto
|
||||
// la lancetta dell'equilibrio attraversa la barra; si preme quando passa nel verde. Unità: i pixel del primo piano
|
||||
// 640×360, visto dall'alto (sfondo di Codex: assets/closeups/campone-campana.png).
|
||||
|
||||
export const W = 66, CY = 181, START = 80, CIELO = { x: START + 6 * W, r: W }, FROM = { x: 40, y: CY }, REACH = 520;
|
||||
export const TAP = 0.14, CHARGE = 1.3, FLIGHT = 0.55, HOP = 0.22, REST = 1.6, DONE = 1.1, CPU_WAIT = 0.9, CPU_BEAT = 0.42;
|
||||
// Le caselle: 1, 2, 3 da sole; 4 e 5 in coppia (la 4 a sinistra di chi salta, cioè in alto); la 6; 7 e 8 in coppia;
|
||||
// in fondo la campana (il mezzo cerchio dove ci si gira; nel codice «cielo», come la chiamano in altri posti).
|
||||
export const BOXES = {
|
||||
1: { x: START, y: CY - W / 2 }, 2: { x: START + W, y: CY - W / 2 }, 3: { x: START + 2 * W, y: CY - W / 2 },
|
||||
4: { x: START + 3 * W, y: CY - W }, 5: { x: START + 3 * W, y: CY }, 6: { x: START + 4 * W, y: CY - W / 2 },
|
||||
7: { x: START + 5 * W, y: CY - W }, 8: { x: START + 5 * W, y: CY },
|
||||
};
|
||||
export const GROUPS = [[1], [2], [3], [4, 5], [6], [7, 8], ['cielo']];
|
||||
export const LAST = 8;
|
||||
// I livelli: quanto si sparpaglia il sassolino (px), se si vede dove cade, quanto è larga la riga che non si può
|
||||
// toccare, quanto ci mette la lancetta dell'equilibrio e quanto è largo il verde; il Taglia: quante volte il sassolino
|
||||
// va dove vuole e quanto spesso, a ogni salto, mette giù il piede.
|
||||
export const LEVELS = {
|
||||
bona: { scatter: 4, guide: true, line: 0, sweep: 1.25, zone: 0.32, cpu: { throw: 0.5, slip: 0.045 } },
|
||||
seria: { scatter: 7, guide: false, line: 3, sweep: 1, zone: 0.2, cpu: { throw: 0.7, slip: 0.025 } },
|
||||
male: { scatter: 9, guide: false, line: 4, sweep: 0.8, zone: 0.15, cpu: { throw: 0.8, slip: 0.018 } },
|
||||
};
|
||||
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 center(id) {
|
||||
if (id === 'cielo') return { x: CIELO.x + W * 0.42, y: CY };
|
||||
const b = BOXES[id]; return { x: b.x + W / 2, y: b.y + W / 2 };
|
||||
}
|
||||
// In che casella cade un punto, e se tocca la riga (entro `tol` pixel da un bordo).
|
||||
export function boxAt([x, y], tol = 0) {
|
||||
for (const [id, b] of Object.entries(BOXES)) {
|
||||
if (x < b.x || x > b.x + W || y < b.y || y > b.y + W) continue;
|
||||
return { id: Number(id), line: Math.min(x - b.x, b.x + W - x, y - b.y, b.y + W - y) < tol };
|
||||
}
|
||||
const d = Math.hypot(x - CIELO.x, y - CY);
|
||||
if (x >= CIELO.x && d <= CIELO.r) return { id: 'cielo', line: Math.min(x - CIELO.x, CIELO.r - d) < tol };
|
||||
return null;
|
||||
}
|
||||
|
||||
// I salti per la casella `n`: all'andata tutte le caselle, saltando quella col sassolino (nella coppia si mette un
|
||||
// piede solo nell'altra); il cielo; al ritorno, dalla casella prima si raccoglie il sassolino; poi fuori.
|
||||
export function beats(n) {
|
||||
const step = (g, back) => g.includes(n) ? (g.length > 1 ? [{ boxes: g.filter(k => k !== n), back }] : []) : [{ boxes: g, back }];
|
||||
const out = GROUPS.flatMap(g => step(g, false));
|
||||
for (let i = GROUPS.length - 2; i >= 0; i--) {
|
||||
if (GROUPS[i].includes(n)) out.push({ ...out[out.length - 1], back: true, pick: true });
|
||||
out.push(...step(GROUPS[i], true));
|
||||
}
|
||||
out.push({ boxes: [], back: true, out: true });
|
||||
return out;
|
||||
}
|
||||
|
||||
export function newCampana(seed = Date.now() >>> 0, def = {}) {
|
||||
const level = LEVELS[def.level] ? def.level : 'seria';
|
||||
return { step: 'aim', t: 0, seed, level, skill: LEVELS[level], current: 'player', target: { player: 1, cpu: 1 },
|
||||
aim: center(1), power: 0.2, holding: false, holdTime: 0, charging: false, stone: null, shot: null,
|
||||
beats: null, beat: -1, meter: null, hop: null, line: null, next: null, events: [], visual: { time: 0 } };
|
||||
}
|
||||
export const canThrow = c => c.current === 'player' && (c.step === 'aim' || c.step === 'hop');
|
||||
export const canAdjust = c => c.current === 'player' && c.step === 'aim';
|
||||
export const aimFree = c => !canAdjust(c);
|
||||
|
||||
// Dove cade il sassolino tirato con questa forza, senza lo sparpaglio.
|
||||
export function landing(c, power = c.power) {
|
||||
const dx = c.aim.x - FROM.x, dy = c.aim.y - FROM.y, d = Math.hypot(dx, dy) || 1;
|
||||
return { x: FROM.x + dx / d * power * REACH, y: FROM.y + dy / d * power * REACH };
|
||||
}
|
||||
function toss(c, to) {
|
||||
c.shot = { from: { ...FROM }, to, t: 0 }; c.stone = null;
|
||||
c.step = 'flying'; c.line = null;
|
||||
c.events.push({ kind: 'campana-lancio', power: 0.6 });
|
||||
}
|
||||
function throwStone(c) {
|
||||
const p = landing(c), s = c.skill.scatter, spread = () => (random(c) + random(c) + random(c) - 1.5) * s * 1.4;
|
||||
toss(c, { x: p.x + spread(), y: p.y + spread() });
|
||||
return true;
|
||||
}
|
||||
const who = c => c.current === 'cpu' ? 'Il Taglia: ' : '';
|
||||
const cap = line => line[0].toUpperCase() + line.slice(1);
|
||||
// Il sassolino è arrivato: nella casella giusta si salta, se no passa la mano.
|
||||
function land(c) {
|
||||
const to = c.shot.to, n = c.target[c.current], hit = boxAt([to.x, to.y], c.skill.line);
|
||||
c.shot = null; c.stone = to;
|
||||
c.events.push({ kind: 'campana-sasso', power: 0.8 });
|
||||
if (hit && hit.id === n && !hit.line) {
|
||||
c.line = cap(`${who(c)}il sassolino è nella ${n}. Salta!`);
|
||||
c.beats = beats(n); c.beat = -1; c.hop = null; c.t = 0;
|
||||
c.step = c.current === 'cpu' ? 'cpuHop' : 'hop';
|
||||
if (c.step === 'hop') c.meter = { t: -0.5, dir: 1 };
|
||||
return;
|
||||
}
|
||||
fail(c, `${who(c)}${hit?.line ? 'sulla riga!' : hit ? `nella ${hit.id === 'cielo' ? 'campana in cima' : hit.id}, non nella ${n}.` : 'fuori dalla campana.'}`);
|
||||
}
|
||||
function fail(c, line) {
|
||||
c.line = cap(line); c.step = 'rest'; c.t = 0; c.meter = null;
|
||||
c.next = c.current === 'player' ? 'cpu' : 'player';
|
||||
c.events.push({ kind: 'campana-riga', power: 0.7 });
|
||||
}
|
||||
// Un salto riuscito: la casella dopo, o il sassolino raccolto, o fuori dalla campana (e la casella è fatta).
|
||||
function advance(c) {
|
||||
c.beat++;
|
||||
const b = c.beats[c.beat];
|
||||
c.hop = { t: 0 };
|
||||
if (b.pick) { c.stone = null; c.line = cap(`${who(c)}preso il sassolino!`); c.events.push({ kind: 'campana-sasso', power: 0.5 }); }
|
||||
else c.events.push({ kind: 'campana-salto', power: 0.6 });
|
||||
if (b.out) {
|
||||
const n = c.target[c.current]++;
|
||||
if (n >= LAST) {
|
||||
c.step = 'result'; c.meter = null;
|
||||
c.result = { winner: c.current, player: c.target.player - 1, cpu: c.target.cpu - 1 };
|
||||
return;
|
||||
}
|
||||
c.line = cap(`${who(c)}fatta la ${n}! Ora la ${n + 1}.`); c.step = 'done'; c.t = 0; c.meter = null;
|
||||
return;
|
||||
}
|
||||
if (c.meter) c.meter = { t: 0, dir: -c.meter.dir };
|
||||
}
|
||||
// La lancetta dell'equilibrio, da -1 a 1 (o al contrario, un salto sì e uno no).
|
||||
export function needle(c) {
|
||||
if (!c.meter) return 0;
|
||||
return c.meter.dir * (-1 + 2 * clamp(c.meter.t, 0, c.skill.sweep) / c.skill.sweep);
|
||||
}
|
||||
function jump(c) {
|
||||
if (c.hop && c.hop.t < HOP) return true; // ancora in aria: il tocco non conta
|
||||
const u = needle(c);
|
||||
if (Math.abs(u) <= c.skill.zone) { advance(c); return true; }
|
||||
const early = u * c.meter.dir < 0;
|
||||
fail(c, early ? 'Troppo presto: giù l\'altro piede!' : 'Troppo tardi: pestata la riga!');
|
||||
return true;
|
||||
}
|
||||
// Tocca all'altro, o allo stesso per la casella dopo: dalla partenza, senza sassolino.
|
||||
function turn(c, current) {
|
||||
Object.assign(c, { current, stone: null, beats: null, beat: -1, hop: null, meter: null, t: 0, next: null,
|
||||
step: current === 'player' ? 'aim' : 'cpuAim' });
|
||||
if (current === 'player') c.aim = center(c.target.player);
|
||||
}
|
||||
|
||||
export function update(c, dt) {
|
||||
if (!Number.isFinite(dt) || dt <= 0) return;
|
||||
dt = Math.min(dt, 0.25); c.t += dt; c.visual.time += dt;
|
||||
if (c.hop) c.hop.t += dt;
|
||||
if (c.holding) {
|
||||
c.holdTime += dt; c.charging = c.holdTime >= TAP;
|
||||
const k = (c.holdTime / CHARGE) % 2; c.power = k <= 1 ? k : 2 - k;
|
||||
}
|
||||
if (c.step === 'flying') { c.shot.t += dt; if (c.shot.t >= FLIGHT) land(c); }
|
||||
else if (c.step === 'hop') {
|
||||
if (!c.hop || c.hop.t >= HOP) c.meter.t += dt;
|
||||
if (c.meter.t > c.skill.sweep) fail(c, 'Hai perso l\'equilibrio!');
|
||||
} else if (c.step === 'cpuAim' && c.t >= CPU_WAIT) {
|
||||
// Il Taglia: o nella casella, ben dentro, o mezza casella più in là (sulla riga o nell'altra).
|
||||
const n = c.target.cpu, p = center(n), ok = random(c) < c.skill.cpu.throw, j = () => (random(c) - 0.5) * 20;
|
||||
toss(c, ok ? { x: p.x + j(), y: p.y + j() } : { x: p.x + (random(c) < 0.5 ? -1 : 1) * W * (0.5 + random(c) * 0.3), y: p.y + j() });
|
||||
} else if (c.step === 'cpuHop' && c.t >= CPU_BEAT) {
|
||||
c.t = 0;
|
||||
if (random(c) < c.skill.cpu.slip) fail(c, random(c) < 0.5 ? 'Il Taglia ha pestato la riga!' : 'Il Taglia ha messo giù l\'altro piede!');
|
||||
else advance(c);
|
||||
} else if (c.step === 'rest' && c.t >= REST) turn(c, c.next);
|
||||
else if (c.step === 'done' && c.t >= DONE) turn(c, c.current);
|
||||
}
|
||||
|
||||
// --- Comandi: la mira segue il mouse o il dito; tenere premuto carica la forza, lasciare tira; ai salti si preme.
|
||||
export function aimScreen(c, [x, y]) {
|
||||
if (!canAdjust(c) || c.holding || !Number.isFinite(x) || !Number.isFinite(y)) return false;
|
||||
c.aim = { x: clamp(x, FROM.x + 30, 630), y: clamp(y, 80, 285) }; return true;
|
||||
}
|
||||
export function setPower(c, v) { if (!canAdjust(c) || c.holding) return false; c.power = clamp(v, 0, 1); return true; }
|
||||
export function nudge(c, dir) { if (!canAdjust(c) || c.holding) return false; c.aim.y = clamp(c.aim.y + dir * 3, 80, 285); return true; }
|
||||
// Frecce: su e giù la mira, destra e sinistra la forza (più in là, più in qua).
|
||||
export function key(c, key, down) {
|
||||
const move = { ArrowUp: [0, -3], ArrowDown: [0, 3], ArrowLeft: [-0.01, 0], ArrowRight: [0.01, 0] }[key];
|
||||
if (!move) return false;
|
||||
if (down && canAdjust(c) && !c.holding) { c.aim.y = clamp(c.aim.y + move[1], 80, 285); c.power = clamp(c.power + move[0], 0, 1); }
|
||||
return true;
|
||||
}
|
||||
export function press(c, pointerId) {
|
||||
if (c.current !== 'player') return false;
|
||||
if (c.step === 'hop') return jump(c);
|
||||
if (!canAdjust(c) || c.holding) return false;
|
||||
c.pointerId = pointerId; c.holding = true; c.charging = false; c.holdTime = 0; c.power = 0;
|
||||
return true;
|
||||
}
|
||||
export function cancel(c, pointerId) {
|
||||
if (pointerId !== undefined && c.pointerId !== pointerId) return false;
|
||||
if (!c.holding) return false;
|
||||
c.holding = c.charging = false; c.holdTime = 0; return true;
|
||||
}
|
||||
export function release(c, pointerId) {
|
||||
if (c.pointerId !== pointerId || !c.holding) return false;
|
||||
if (c.holdTime < TAP) return cancel(c, pointerId); // il tocco secco mira soltanto
|
||||
c.holding = c.charging = false; c.holdTime = 0;
|
||||
return throwStone(c);
|
||||
}
|
||||
// Il pulsante: tira con la forza del cursore, o salta.
|
||||
export function shoot(c) {
|
||||
if (c.current !== 'player' || c.holding) return false;
|
||||
if (c.step === 'hop') return jump(c);
|
||||
return canAdjust(c) && throwStone(c);
|
||||
}
|
||||
|
||||
const box = n => n > LAST ? 'finito' : `alla ${n}`;
|
||||
export function button(c) {
|
||||
if (c.step === 'result') return 'Finita';
|
||||
if (c.current === 'cpu') return 'Tocca al Taglia…';
|
||||
return { aim: 'Tira', flying: 'In volo…', hop: 'Salta' }[c.step] ?? 'Aspetta…';
|
||||
}
|
||||
export function status(c) {
|
||||
const score = `Andrea ${box(c.target.player)}, il Taglia ${box(c.target.cpu)}.`;
|
||||
if (c.step === 'result') return `${c.result.winner === 'player' ? 'Campana finita: battuto il Taglia! ' : 'Il Taglia ha finito la campana. '}${score}`;
|
||||
const hint = c.current === 'cpu' ? 'Tocca al Taglia.' : c.step === 'hop' ? 'Salta quando la lancetta è nel verde.'
|
||||
: `Tira il sassolino nella ${c.target.player}: mira, tieni premuto per la forza e lascia.`;
|
||||
return `${score} ${c.line ?? hint}`;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { CARTONE, BENCH, CAN, BAR, BOTTLE, BALLOON, VOLO, PELLETS, STILL, BREATH, GASP, rings, reticle, landing, balloon, voloAt, canThrow, breathless } from './condor.js';
|
||||
import { text, panel } from './bocce-render.js';
|
||||
|
||||
function rect(ctx, x, y, w, h, color) { ctx.fillStyle = color; ctx.fillRect(Math.round(x), Math.round(y), Math.round(w), Math.round(h)); }
|
||||
// Il cartone inchiodato al tronco: tre cerchi col pennarello, i chiodini in alto e i buchi dei pallini.
|
||||
function cartone(ctx, c) {
|
||||
const { x, y, w, h } = CARTONE;
|
||||
rect(ctx, x - w / 2 + 2, y - h / 2 + 2, w, h, 'rgba(30,20,10,0.35)');
|
||||
rect(ctx, x - w / 2, y - h / 2, w, h, '#c9a46a');
|
||||
rect(ctx, x - w / 2, y - h / 2, w, 2, '#e0bf86'); rect(ctx, x - w / 2, y + h / 2 - 2, w, 2, '#a8844e');
|
||||
rect(ctx, x - w / 2 + 3, y - h / 2 + 2, 2, 2, '#5a5a5a'); rect(ctx, x + w / 2 - 5, y - h / 2 + 2, 2, 2, '#5a5a5a');
|
||||
ctx.strokeStyle = '#2a1f1a'; ctx.lineWidth = 1.2;
|
||||
for (const [r] of rings(c)) { ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.stroke(); }
|
||||
ctx.fillStyle = '#2a1f1a'; ctx.beginPath(); ctx.arc(x, y, 2, 0, Math.PI * 2); ctx.fill();
|
||||
for (const p of c.holes) { rect(ctx, p.x - 1, p.y - 1, 2, 2, '#1a120c'); rect(ctx, p.x, p.y - 1, 1, 1, '#f2d24a'); }
|
||||
}
|
||||
// Una lattina (sulla panchina o in aria), rossa con la fascia chiara.
|
||||
function can(ctx, x, bottom, angle = 0, w = CAN.w, h = CAN.h, color = '#c8321e') {
|
||||
ctx.save(); ctx.translate(Math.round(x), Math.round(bottom)); ctx.rotate(angle);
|
||||
ctx.fillStyle = color; ctx.fillRect(-w / 2, -h, w, h);
|
||||
ctx.fillStyle = '#f2e6c8'; ctx.fillRect(-w / 2, -h + Math.round(h * 0.35), w, 3);
|
||||
ctx.fillStyle = '#c9cdd2'; ctx.fillRect(-w / 2, -h, w, 2); ctx.fillRect(-w / 2, -2, w, 2);
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.35)'; ctx.fillRect(-w / 2 + 2, -h + 2, 1, h - 4);
|
||||
ctx.restore();
|
||||
}
|
||||
// Una bottiglietta d'acqua, vuota: plastica azzurrina e il tappo blu.
|
||||
function bottle(ctx, x, bottom, angle = 0) {
|
||||
const { w, h } = BOTTLE;
|
||||
ctx.save(); ctx.translate(Math.round(x), Math.round(bottom)); ctx.rotate(angle);
|
||||
ctx.fillStyle = 'rgba(190,228,240,0.85)'; ctx.fillRect(-w / 2, -h + 4, w, h - 4); ctx.fillRect(-w / 2 + 1, -h + 2, w - 2, 2);
|
||||
ctx.fillStyle = '#2f6fb0'; ctx.fillRect(-1, -h, 3, 2);
|
||||
ctx.fillStyle = '#ffffff'; ctx.fillRect(-w / 2 + 1, -h + 5, 1, h - 7);
|
||||
ctx.restore();
|
||||
}
|
||||
// Il pallino giallo.
|
||||
function pellet(ctx, x, y, r) {
|
||||
ctx.fillStyle = '#7a5a10'; ctx.beginPath(); ctx.arc(x, y, r + 0.7, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.fillStyle = '#f5d02a'; ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.fill();
|
||||
if (r > 1.5) rect(ctx, x - r / 2, y - r / 2, 1, 1, '#fff6b0');
|
||||
}
|
||||
// La Condor in basso, nera, puntata verso il mirino: il calcio di legno dritto in giù (la mano lo tiene così), la canna
|
||||
// che gira verso il mirino, il mirino in punta.
|
||||
function pistol(ctx, to) {
|
||||
const from = { x: 430, y: 330 }, a = Math.atan2(to.y - from.y, to.x - from.x);
|
||||
rect(ctx, from.x - 13, from.y + 2, 26, 34, '#1a120c'); rect(ctx, from.x - 12, from.y + 2, 24, 32, '#5a3a22');
|
||||
rect(ctx, from.x - 9, from.y + 5, 4, 26, '#7a5232'); rect(ctx, from.x - 2, from.y + 8, 2, 20, '#3e2816');
|
||||
ctx.save(); ctx.translate(from.x, from.y); ctx.rotate(a);
|
||||
ctx.fillStyle = '#0b0b0e'; ctx.fillRect(-16, -11, 84, 20);
|
||||
ctx.fillStyle = '#26252c'; ctx.fillRect(-14, -9, 80, 5);
|
||||
ctx.fillStyle = '#4a4955'; ctx.fillRect(-14, -9, 80, 1);
|
||||
ctx.fillStyle = '#0b0b0e'; ctx.fillRect(62, -14, 5, 4); ctx.fillRect(66, -5, 6, 10);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
export function drawCondor(ctx, c, def, bg) {
|
||||
ctx.save(); ctx.imageSmoothingEnabled = false;
|
||||
if (bg) ctx.drawImage(bg, 0, 0, 640, 360); else { ctx.fillStyle = '#7fb24a'; ctx.fillRect(0, 0, 640, 360); }
|
||||
cartone(ctx, c);
|
||||
for (const k of c.bottles) if (k.fall < 1) bottle(ctx, k.x + k.fall * 6, BAR + k.fall * 10, k.fall * 1.6);
|
||||
for (const k of c.cans) if (k.fall < 1) can(ctx, k.x, BENCH + k.fall * 8, -k.fall * 1.4);
|
||||
// Il palloncino rosso legato alla sbarra dell'altalena; scoppiato, resta lo spago e qualche brandello.
|
||||
const b = balloon(c);
|
||||
ctx.strokeStyle = '#e8e2d0'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(BALLOON.x, BALLOON.knot);
|
||||
ctx.quadraticCurveTo(BALLOON.x + (b.x - BALLOON.x) * 0.3 + 3, (BALLOON.knot + b.y) / 2, b.x, c.balloon.popped ? b.y + 14 : b.y + BALLOON.r); ctx.stroke();
|
||||
if (!c.balloon.popped) {
|
||||
ctx.fillStyle = '#d8283a'; ctx.beginPath(); ctx.ellipse(b.x, b.y, BALLOON.r, BALLOON.r * 1.2, 0, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.fillStyle = '#ff8a8a'; ctx.fillRect(Math.round(b.x - 4), Math.round(b.y - 6), 2, 3);
|
||||
rect(ctx, b.x - 1, b.y + BALLOON.r * 1.2 - 1, 3, 2, '#a01828');
|
||||
} else if (c.balloon.pop < 1) {
|
||||
ctx.fillStyle = '#d8283a';
|
||||
for (let i = 0; i < 6; i++) { const a = i / 6 * Math.PI * 2, d = 4 + c.balloon.pop * 14; ctx.fillRect(Math.round(b.x + Math.cos(a) * d), Math.round(b.y + Math.sin(a) * d), 2, 2); }
|
||||
}
|
||||
// La lattina tirata in aria dal Taglia (o che ricade, colpita).
|
||||
const v = voloAt(c);
|
||||
if (c.volo?.fall) can(ctx, c.volo.fall.x, c.volo.fall.y + VOLO.h / 2, c.visual.time * 14, VOLO.w, VOLO.h, '#2f6fb0');
|
||||
else if (v) can(ctx, v.x, v.y + VOLO.h / 2, v.a, VOLO.w, VOLO.h, '#2f6fb0');
|
||||
// Il pallino in volo: grande vicino alla pistola, piccolo verso il bersaglio.
|
||||
if (c.shot) {
|
||||
const k = Math.min(1, c.shot.t / c.shot.time), { from, to } = c.shot;
|
||||
pellet(ctx, from.x + (to.x - from.x) * k, from.y + (to.y - from.y) * k, 4 - 2.8 * k);
|
||||
}
|
||||
const r = reticle(c);
|
||||
if (canThrow(c)) {
|
||||
// Alla bona: dove arriva il pallino, un puntino giallo sotto il mirino.
|
||||
if (c.skill.guide) { const l = landing(c); rect(ctx, l.x - 1, l.y - 1, 3, 3, '#f5d02a'); }
|
||||
const out = breathless(c);
|
||||
ctx.strokeStyle = out ? '#ff6a4a' : c.holding && c.holdTime >= STILL ? '#b9e5b3' : '#fff8dc'; ctx.lineWidth = 1;
|
||||
ctx.beginPath(); ctx.arc(r.x, r.y, 7, 0, Math.PI * 2); ctx.stroke();
|
||||
ctx.beginPath(); ctx.moveTo(r.x - 11, r.y); ctx.lineTo(r.x - 3, r.y); ctx.moveTo(r.x + 3, r.y); ctx.lineTo(r.x + 11, r.y);
|
||||
ctx.moveTo(r.x, r.y - 11); ctx.lineTo(r.x, r.y - 3); ctx.moveTo(r.x, r.y + 3); ctx.lineTo(r.x, r.y + 11); ctx.stroke();
|
||||
}
|
||||
pistol(ctx, r);
|
||||
// Il fiato trattenuto: la barra si riempie fino a «fermo» (verde), poi si svuota in rosso finché il colpo parte.
|
||||
if (canThrow(c) && c.holding) {
|
||||
const x = 16, y = 300, h = 50, still = Math.min(1, c.holdTime / STILL), left = c.holdTime > BREATH ? 1 - (c.holdTime - BREATH) / GASP : 1;
|
||||
rect(ctx, x - 3, y - 3, 18, h + 6, '#17160fd0');
|
||||
const f = Math.round(h * (c.holdTime > BREATH ? left : still));
|
||||
rect(ctx, x, y + h - f, 12, f, c.holdTime > BREATH ? '#ff6a4a' : still >= 1 ? '#9be07c' : '#ffd23c');
|
||||
text(ctx, 'FIATO', x + 6, y - 9, 6, '#efd49e', 'center');
|
||||
}
|
||||
// In alto: i punti, quelli del Taglia, i pallini rimasti e cosa è successo.
|
||||
rect(ctx, 0, 0, 640, 22, '#17160fe8');
|
||||
text(ctx, 'LA CONDOR', 8, 7, 8, '#efd49e');
|
||||
text(ctx, { bona: 'alla bona', seria: 'seria', male: 'a fassi male' }[c.level], 8, 16, 7, '#d9ccba');
|
||||
text(ctx, `Punti ${c.points}`, 200, 11, 9, c.points > c.taglia ? '#b9e5b3' : '#fff0c8', 'center');
|
||||
text(ctx, `Il Taglia ${c.taglia}`, 310, 11, 9, '#9fd0ff', 'center');
|
||||
for (let i = 0; i < PELLETS; i++) pellet(ctx, 430 + i * 15, 11, i < c.left ? 3 : 1.2);
|
||||
const line = breathless(c) ? 'Il fiato finisce: spara!' : c.line;
|
||||
if (line) { rect(ctx, 170, 24, 300, 14, '#17160fd0'); text(ctx, line, 320, 31, 8, breathless(c) || /Niente|fuori|Senza/.test(line) ? '#ffbf9f' : '#9fd0ff', 'center'); }
|
||||
if (c.step === 'result') {
|
||||
const w = c.result.winner;
|
||||
panel(ctx, 170, 140, 300, 76);
|
||||
text(ctx, { player: 'BATTUTO IL TAGLIA!', draw: 'PARI COL TAGLIA', cpu: 'HA VINTO IL TAGLIA' }[w], 320, 160, 12, w === 'player' ? '#b9e5b3' : '#efd49e', 'center');
|
||||
text(ctx, `Tu ${c.result.points}, il Taglia ${c.result.taglia}`, 320, 182, 8, '#efd49e', 'center');
|
||||
text(ctx, 'Torna al campone per uscire.', 320, 200, 8, '#d9ccba', 'center');
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
// La Condor al campone delle poste (idea di Andrea e del Taglia, 29 ottobre): la pistola ad aria compressa coi pallini
|
||||
// gialli. Si gareggia col Taglia: lui ha già tirato, e il suo punteggio è quello da battere. Come la cerbottana: il
|
||||
// mirino ondeggia col respiro; tenere premuto trattiene il fiato (il mirino si ferma), lasciare spara; chi tiene troppo
|
||||
// resta senza fiato, il mirino trema e il colpo parte da solo. Il pallino cala con la distanza: l'albero è vicino, la
|
||||
// panchina a metà, l'altalena e la porta in fondo (lì si mira più sopra). Bersagli: il cartone inchiodato all'albero, le lattine sulla panchina rossa, le
|
||||
// bottigliette sulla traversa della porta, il palloncino legato all'altalena e (a seria e a fassi male) la lattina che
|
||||
// il Taglia tira in aria. Unità: i pixel del primo piano 640×360.
|
||||
|
||||
export const PELLETS = 12, TAP = 0.14, REST = 0.6, STILL = 0.8, BREATH = 2.6, GASP = 1, DROP = 26;
|
||||
// Dove stanno le cose nello sfondo di Codex (assets/closeups/campone-condor.png): il cartone sul tronco, il sedile
|
||||
// della panchina, la traversa della porta, la sbarra dell'altalena dove è legato il palloncino.
|
||||
export const CARTONE = { x: 98, y: 140, w: 46, h: 50, rings: [[5, 10], [11, 5], [17, 2]] };
|
||||
export const BENCH = 210, CAN = { w: 10, h: 14, points: 3 };
|
||||
export const BAR = 177, BOTTLE = { w: 6, h: 12, points: 5 };
|
||||
export const BALLOON = { x: 428, y: 134, knot: 170, r: 8, points: 6 };
|
||||
export const VOLO = { w: 12, h: 16, points: 8 };
|
||||
// I livelli: quanto ondeggia il mirino (px), se si vede dove arriva il pallino, la lattina in aria (ogni quanti
|
||||
// secondi, quanto ci mette ad attraversare), quanto sono grandi i cerchi del cartone e quanto ha fatto il Taglia.
|
||||
export const LEVELS = {
|
||||
bona: { sway: 3, guide: true, volo: null, rings: 1.25, taglia: 22 },
|
||||
seria: { sway: 6, guide: false, volo: { every: 6, cross: 2.2 }, rings: 1, taglia: 34 },
|
||||
male: { sway: 10, guide: false, volo: { every: 4.5, cross: 1.7 }, rings: 0.85, taglia: 44 },
|
||||
};
|
||||
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 newCondor(seed = Date.now() >>> 0, def = {}) {
|
||||
const level = LEVELS[def.level] ? def.level : 'seria';
|
||||
const c = { step: 'aim', t: 0, seed, level, skill: LEVELS[level], aim: { x: CARTONE.x, y: CARTONE.y }, power: 0,
|
||||
holding: false, holdTime: 0, charging: false, left: PELLETS, points: 0, events: [], shot: null, holes: [], line: null,
|
||||
cans: [246, 276, 306, 336].map(x => ({ x, down: false, fall: 0 })),
|
||||
bottles: [538, 560, 582].map(x => ({ x, down: false, fall: 0 })),
|
||||
balloon: { popped: false, pop: 0 }, volo: null, nextVolo: 2.5, visual: { time: 0 } };
|
||||
// Il punteggio del Taglia: quello del livello, qualche punto in più o in meno.
|
||||
c.taglia = c.skill.taglia + Math.floor(random(c) * 7) - 3;
|
||||
return c;
|
||||
}
|
||||
export const canThrow = c => c.step === 'aim' && c.left > 0;
|
||||
export const canAdjust = canThrow;
|
||||
export const rings = c => CARTONE.rings.map(([r, p]) => [r * c.skill.rings, p]);
|
||||
|
||||
// Il fiato: fermo dopo STILL secondi di respiro trattenuto (il mirino ondeggia un terzo), poi dopo BREATH comincia a
|
||||
// mancare e il mirino trema sempre di più; dopo GASP ancora il colpo parte da solo.
|
||||
export function steady(c) {
|
||||
if (!c.holding) return 1;
|
||||
const h = c.holdTime;
|
||||
if (h < BREATH) return 1 - 0.7 * Math.min(1, h / STILL);
|
||||
return 0.3 + (h - BREATH) * 2.5;
|
||||
}
|
||||
export function sway(c) {
|
||||
const t = c.visual.time, a = c.skill.sway * steady(c), shake = c.holding && c.holdTime > BREATH ? Math.sin(t * 31) * 0.4 : 0;
|
||||
return { x: a * (Math.sin(t * 1.7) + 0.5 * Math.sin(t * 3.1 + 1) + shake), y: a * 0.8 * (Math.sin(t * 1.3 + 2) + 0.5 * Math.sin(t * 2.9) + shake) };
|
||||
}
|
||||
export function reticle(c) { const s = sway(c); return { x: c.aim.x + s.x, y: c.aim.y + s.y }; }
|
||||
// Quanto è lontano un punto, a strisce: l'albero, la panchina, l'altalena, la porta in fondo.
|
||||
export const far = x => x < 170 ? 0.45 : x < 380 ? 0.6 : x < 490 ? 0.85 : 1;
|
||||
// Dove arriva il pallino: sotto il mirino, tanto più quanto il punto è lontano.
|
||||
export function landing(c) { const r = reticle(c); return { x: r.x, y: r.y + DROP * far(r.x) ** 2 }; }
|
||||
export const flight = x => 0.22 + 0.2 * far(x);
|
||||
// Il palloncino legato all'altalena: dondola col vento.
|
||||
export function balloon(c) {
|
||||
const t = c.visual.time;
|
||||
return { x: BALLOON.x + 7 * Math.sin(t * 1.1), y: BALLOON.y + 4 * Math.sin(t * 1.7 + 1) };
|
||||
}
|
||||
// La lattina che tira il Taglia: da destra in basso, una parabola verso sinistra.
|
||||
export function voloAt(c, t = c.volo?.t) {
|
||||
if (!c.volo) return null;
|
||||
const k = t / c.volo.cross;
|
||||
if (k < 0 || k > 1) return null;
|
||||
return { x: 610 - 560 * k, y: 300 - 4 * 230 * k * (1 - k), a: k * 9 };
|
||||
}
|
||||
|
||||
export function shoot(c) {
|
||||
if (!canThrow(c) || c.holding) return false;
|
||||
return fire(c);
|
||||
}
|
||||
function fire(c, gasp = false) {
|
||||
const to = landing(c);
|
||||
c.left--;
|
||||
// La lattina in aria è vicina (la tira il Taglia, accanto): lì il pallino non fa in tempo a calare.
|
||||
c.shot = { from: { x: 352, y: 372 }, to, aim: reticle(c), t: 0, time: flight(to.x), gasp };
|
||||
c.step = 'flying'; c.line = null;
|
||||
c.events.push({ kind: 'condor-pam', power: 1 });
|
||||
return true;
|
||||
}
|
||||
const inBox = (x, y, cx, bottom, w, h) => Math.abs(x - cx) <= w / 2 + 1 && y >= bottom - h - 1 && y <= bottom + 1;
|
||||
// Arrivato: prima quello che si muove (la lattina in aria, il palloncino), poi bottiglie, lattine e il cartone.
|
||||
function land(c) {
|
||||
const { x, y } = c.shot.to;
|
||||
let got = 0, what = null;
|
||||
const v = voloAt(c), a = c.shot.aim;
|
||||
if (v && !c.volo.hit && Math.hypot(a.x - v.x, a.y - v.y) <= VOLO.w / 2 + 2) {
|
||||
got = VOLO.points; c.volo.hit = true; c.volo.fall = { x: v.x, y: v.y, vy: -60 }; what = `Presa al volo! ${got} punti.`;
|
||||
c.events.push({ kind: 'cerbottana-latta', power: 1 });
|
||||
}
|
||||
if (!what && !c.balloon.popped) {
|
||||
const b = balloon(c);
|
||||
if (Math.hypot(x - b.x, y - b.y) <= BALLOON.r + 1) {
|
||||
c.balloon.popped = true; got = BALLOON.points; what = `Bum! Il palloncino. ${got} punti.`;
|
||||
c.events.push({ kind: 'condor-palloncino', power: 1 });
|
||||
}
|
||||
}
|
||||
if (!what) {
|
||||
const bottle = c.bottles.find(k => !k.down && inBox(x, y, k.x, BAR, BOTTLE.w, BOTTLE.h));
|
||||
if (bottle) { bottle.down = true; got = BOTTLE.points; what = `Bottiglietta giù! ${got} punti.`; c.events.push({ kind: 'condor-plastica', power: 0.8 }); }
|
||||
}
|
||||
if (!what) {
|
||||
const can = c.cans.find(k => !k.down && inBox(x, y, k.x, BENCH, CAN.w, CAN.h));
|
||||
if (can) { can.down = true; got = CAN.points; what = `Lattina giù! ${got} punti.`; c.events.push({ kind: 'cerbottana-latta', power: 0.8 }); }
|
||||
}
|
||||
if (!what) {
|
||||
const inCard = Math.abs(x - CARTONE.x) <= CARTONE.w / 2 && Math.abs(y - CARTONE.y) <= CARTONE.h / 2;
|
||||
if (inCard) {
|
||||
c.holes.push({ x, y });
|
||||
const d = Math.hypot(x - CARTONE.x, y - CARTONE.y), ring = rings(c).find(([r]) => d <= r);
|
||||
got = ring ? ring[1] : 0;
|
||||
what = got ? `Nel cartone: ${got} ${got === 1 ? 'punto' : 'punti'}.` : 'Nel cartone, fuori dai cerchi.';
|
||||
c.events.push({ kind: 'cerbottana-muro', power: 0.5 });
|
||||
} else what = 'Niente: il pallino giallo è finito nel prato.';
|
||||
}
|
||||
if (c.shot.gasp && !got) what = 'Senza fiato! ' + what;
|
||||
c.points += got; c.line = what;
|
||||
c.shot = null; c.step = 'rest'; c.t = 0;
|
||||
}
|
||||
|
||||
export function update(c, dt) {
|
||||
if (!Number.isFinite(dt) || dt <= 0) return;
|
||||
dt = Math.min(dt, 0.25); c.t += dt; c.visual.time += dt;
|
||||
for (const k of [...c.cans, ...c.bottles]) if (k.down) k.fall = Math.min(1, k.fall + dt * 3);
|
||||
if (c.balloon.popped) c.balloon.pop = Math.min(1, c.balloon.pop + dt * 4);
|
||||
// La lattina in aria: il Taglia la tira ogni tanto (non a fine partita).
|
||||
const v = c.skill.volo;
|
||||
if (v && c.step !== 'result') {
|
||||
if (c.volo) {
|
||||
c.volo.t += dt;
|
||||
if (c.volo.fall) { c.volo.fall.vy += 600 * dt; c.volo.fall.y += c.volo.fall.vy * dt; c.volo.fall.x -= 40 * dt; }
|
||||
if (c.volo.t > c.volo.cross + 0.6) { c.volo = null; c.nextVolo = v.every * (0.8 + random(c) * 0.4); }
|
||||
} else if ((c.nextVolo -= dt) <= 0) {
|
||||
c.volo = { t: 0, cross: v.cross, hit: false, fall: null };
|
||||
c.events.push({ kind: 'condor-lancio', power: 0.6 });
|
||||
}
|
||||
}
|
||||
if (c.holding) {
|
||||
c.holdTime += dt; c.charging = c.holdTime >= TAP;
|
||||
c.power = clamp(c.holdTime / STILL, 0, 1);
|
||||
if (c.holdTime > BREATH + GASP) { c.holding = c.charging = false; c.holdTime = 0; c.power = 0; fire(c, true); }
|
||||
}
|
||||
if (c.step === 'flying') {
|
||||
c.shot.t += dt; if (c.shot.t >= c.shot.time) land(c);
|
||||
} else if (c.step === 'rest' && c.t >= REST) {
|
||||
if (c.left > 0) c.step = 'aim';
|
||||
else {
|
||||
c.step = 'result';
|
||||
const winner = c.points > c.taglia ? 'player' : c.points === c.taglia ? 'draw' : 'cpu';
|
||||
c.result = { winner, points: c.points, taglia: c.taglia };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Comandi: il mirino segue il mouse o il dito; tenere premuto trattiene il fiato, lasciare spara.
|
||||
export function aimScreen(c, [x, y]) {
|
||||
if (!canThrow(c) || c.holding || !Number.isFinite(x) || !Number.isFinite(y)) return false;
|
||||
c.aim = { x: clamp(x, 10, 630), y: clamp(y, 30, 320) }; return true;
|
||||
}
|
||||
export function nudge(c, dir) { if (!canThrow(c) || c.holding) return false; c.aim.x = clamp(c.aim.x + dir * 4, 10, 630); return true; }
|
||||
export function key(c, key, down) {
|
||||
const move = { ArrowLeft: [-4, 0], ArrowRight: [4, 0], ArrowUp: [0, -4], ArrowDown: [0, 4] }[key];
|
||||
if (!move) return false;
|
||||
if (down && canThrow(c) && !c.holding) c.aim = { x: clamp(c.aim.x + move[0], 10, 630), y: clamp(c.aim.y + move[1], 30, 320) };
|
||||
return true;
|
||||
}
|
||||
export function press(c, pointerId) {
|
||||
if (!canThrow(c) || c.holding) return false;
|
||||
c.pointerId = pointerId; c.holding = true; c.charging = false; c.holdTime = 0; c.power = 0;
|
||||
return true;
|
||||
}
|
||||
export function cancel(c, pointerId) {
|
||||
if (pointerId !== undefined && c.pointerId !== pointerId) return false;
|
||||
if (!c.holding) return false;
|
||||
c.holding = c.charging = false; c.holdTime = 0; c.power = 0; return true;
|
||||
}
|
||||
export function release(c, pointerId) {
|
||||
if (c.pointerId !== pointerId || !c.holding) return false;
|
||||
if (c.holdTime < TAP) return cancel(c, pointerId); // il tocco secco mira soltanto
|
||||
c.holding = c.charging = false; c.holdTime = 0; c.power = 0;
|
||||
return fire(c);
|
||||
}
|
||||
export const breathless = c => c.holding && c.holdTime > BREATH;
|
||||
|
||||
export function button(c) { return c.step === 'result' ? 'Finita' : c.step === 'aim' ? 'Spara' : c.step === 'flying' ? 'In volo…' : 'Ricarico…'; }
|
||||
export function status(c) {
|
||||
const score = `Punti ${c.points}, il Taglia ne ha fatti ${c.taglia}; pallini ${c.left} di ${PELLETS}.`;
|
||||
if (c.step === 'result') return `${{ player: 'Battuto il Taglia! ', draw: 'Pari col Taglia. ', cpu: 'Ha vinto il Taglia. ' }[c.result.winner]}${score}`;
|
||||
return `${score} ${c.line ?? 'Mira, tieni premuto per trattenere il fiato e lascia per sparare.'}`;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// La macchina del tempo vista da fuori (story.closeups, type 'corsa'; Andrea, 30 ottobre). In partenza ('parti') va via
|
||||
// giù per la strada accelerando, il fulmine la prende, il bianco, e restano due sgommate di fuoco che bruciano e si
|
||||
// spengono. All'arrivo ('arriva') il fulmine in fondo alla strada, e lei viene avanti e ci passa accanto, lasciando le
|
||||
// stesse strisce (davanti a noi: dietro alla macchina non si vedrebbero). Sopra lo sfondo della scena (`scene`), coi fotogrammi di Codex (`frames`: dietro, dietro coi lampi,
|
||||
// davanti coi lampi). Unità: i pixel della scena 640×360.
|
||||
|
||||
// I tempi, in secondi. Partenza: ferma che si carica, la corsa, il fulmine, il bianco, le fiamme, la fine.
|
||||
export const PARTI = { charge: 0.6, run: 2.2, bolt: 2.3, flash: 0.35, burn: 4.6, end: 5.2 };
|
||||
// Arrivo: il fulmine in fondo, la corsa verso di noi fin fuori dal quadro, le fiamme, la fine.
|
||||
export const ARRIVA = { bolt: 0.3, flash: 0.35, run: 1.5, burn: 4, end: 4.6 };
|
||||
|
||||
const lerp = (a, b, k) => a + (b - a) * k;
|
||||
const clamp01 = k => Math.max(0, Math.min(1, k));
|
||||
// Dove sta la macchina a un punto `k` del percorso (0 vicina, 1 in fondo): posizione delle ruote e grandezza.
|
||||
export function along(def, k) {
|
||||
const [a, b] = def.path;
|
||||
return { x: lerp(a[0], b[0], k), y: lerp(a[1], b[1], k), scale: lerp(a[2], b[2], k) };
|
||||
}
|
||||
// Il punto del percorso al tempo t: in partenza accelera (da fermo); all'arrivo viene avanti dal fondo fin oltre il
|
||||
// punto 0 (fuori dal quadro, accanto a noi).
|
||||
export function progress(c, def) {
|
||||
if (def.mode === 'arriva') return 1 - clamp01((c.t - ARRIVA.bolt) / ARRIVA.run);
|
||||
const k = clamp01((c.t - PARTI.charge) / (PARTI.run - PARTI.charge));
|
||||
return Math.min(0.92, k * k);
|
||||
}
|
||||
// Si vede la macchina? In partenza fino al fulmine; all'arrivo dal fulmine in poi.
|
||||
export function carShown(c, def) {
|
||||
return def.mode === 'arriva' ? c.t >= ARRIVA.bolt + 0.05 && c.t < ARRIVA.bolt + ARRIVA.run : c.t < PARTI.bolt + 0.05;
|
||||
}
|
||||
// Dove arriva il fulmine: sulla macchina in partenza, in fondo alla strada all'arrivo.
|
||||
export function boltAt(def) { return def.mode === 'arriva' ? along(def, 1) : along(def, progress({ t: PARTI.bolt }, def)); }
|
||||
const T = def => def.mode === 'arriva' ? ARRIVA : PARTI;
|
||||
|
||||
export function newCorsa(id) { return { id, t: 0, age: 0, events: [] }; }
|
||||
// Il tempo che passa e i suoni; vero quando è finita.
|
||||
export function updateCorsa(c, def, dt) {
|
||||
c.t += dt;
|
||||
const times = T(def);
|
||||
if (def.mode !== 'arriva' && !c.charge && c.t >= 0.1) { c.charge = true; c.events.push({ kind: 'gara-turbo', power: 1 }); }
|
||||
if (!c.boom && c.t >= times.bolt) { c.boom = true; c.events.push({ kind: 'corsa-tuono', power: 1 }); }
|
||||
if (!c.fire && c.t >= (def.mode === 'arriva' ? ARRIVA.bolt + 0.2 : PARTI.charge + 0.3)) { c.fire = true; c.events.push({ kind: 'corsa-fuoco', power: 1 }); }
|
||||
return c.t >= times.end;
|
||||
}
|
||||
|
||||
// Le due ruote posteriori (o anteriori) a un punto del percorso: una a sinistra e una a destra.
|
||||
function wheels(def, k) {
|
||||
const p = along(def, k), half = def.track * p.scale;
|
||||
return [[p.x - half, p.y], [p.x + half, p.y]];
|
||||
}
|
||||
// Le sgommate di fuoco: da dove è partita a dove è arrivata, fiammelle che tremano, più piccole in fondo, che si
|
||||
// spengono dopo `burn`.
|
||||
function fire(ctx, c, def) {
|
||||
const times = T(def), shown = def.mode === 'arriva' ? [Math.max(progress(c, def), 0), 1] : [0, progress({ t: Math.min(c.t, times.bolt) }, def)];
|
||||
const fade = c.t < times.burn ? 1 : clamp01(1 - (c.t - times.burn) / (times.end - times.burn));
|
||||
if (fade <= 0 || shown[1] - shown[0] < 0.005) return;
|
||||
for (let side = 0; side < 2; side++) {
|
||||
// Prima il segno nero bruciato, poi le fiamme sopra.
|
||||
const [a, b] = [wheels(def, shown[0])[side], wheels(def, shown[1])[side]];
|
||||
ctx.strokeStyle = `rgba(30,18,10,${0.7 * Math.max(fade, 0.5)})`; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
|
||||
const n = 70;
|
||||
for (let i = 0; i <= n; i++) {
|
||||
const k = lerp(shown[0], shown[1], i / n), [x, y] = wheels(def, k)[side], s = Math.max(0.12, along(def, k).scale);
|
||||
// Lingue di fuoco di altezza diversa, che tremano: rosso fuori, arancio, il cuore giallo.
|
||||
const flick = 0.55 + 0.45 * Math.sin(c.t * 19 + i * 2.3 + side * 2) * Math.sin(c.t * 7 + i * 0.9);
|
||||
const h = (16 + 22 * flick) * s * fade, w = Math.max(2, 9 * s);
|
||||
if (h < 1.5) continue;
|
||||
ctx.fillStyle = `rgba(255,120,30,${0.18 * fade})`; ctx.fillRect(Math.round(x - w), Math.round(y - h * 1.2), Math.ceil(w * 2), Math.ceil(h * 1.2));
|
||||
ctx.fillStyle = `rgba(220,50,20,${0.9 * fade})`; ctx.fillRect(Math.round(x - w / 2), Math.round(y - h), Math.ceil(w), Math.ceil(h));
|
||||
ctx.fillStyle = `rgba(255,${Math.round(140 + 60 * flick)},30,${0.95 * fade})`; ctx.fillRect(Math.round(x - w / 3), Math.round(y - h * 0.7), Math.ceil(w * 0.66), Math.ceil(h * 0.7));
|
||||
ctx.fillStyle = `rgba(255,245,170,${fade})`; ctx.fillRect(Math.round(x - w / 6), Math.round(y - h * 0.35), Math.max(1, Math.ceil(w / 3)), Math.ceil(h * 0.35));
|
||||
// Un filo di fumo che sale quando le fiamme calano.
|
||||
if (c.t > times.burn - 1 && i % 8 === 0) {
|
||||
ctx.fillStyle = `rgba(90,90,96,${0.25 * fade})`;
|
||||
ctx.fillRect(Math.round(x - 2 * s), Math.round(y - h - 6 * s - ((c.t * 18 + i) % 14) * s), Math.ceil(4 * s), Math.ceil(4 * s));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Il fulmine: una saetta spezzata dal cielo fino al punto, con due rami.
|
||||
function bolt(ctx, c, def) {
|
||||
const times = T(def), dt = c.t - times.bolt;
|
||||
if (dt < -0.12 || dt > 0.28) return;
|
||||
const p = boltAt(def), seed = Math.floor(c.t * 30);
|
||||
ctx.save();
|
||||
for (const [width, color] of [[6, 'rgba(140,200,255,0.5)'], [2, '#ffffff']]) {
|
||||
ctx.strokeStyle = color; ctx.lineWidth = width; ctx.beginPath();
|
||||
let x = p.x + 40, y = 0; ctx.moveTo(x, y);
|
||||
const steps = 9;
|
||||
for (let i = 1; i <= steps; i++) {
|
||||
x = lerp(p.x + 40, p.x, i / steps) + (i < steps ? ((seed * (i + 5) * 53) % 30) - 15 : 0);
|
||||
y = lerp(0, p.y - 12 * p.scale, i / steps); ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
export function drawCorsa(ctx, c, def, { bg, frames, tint }) {
|
||||
const times = T(def);
|
||||
ctx.save(); ctx.imageSmoothingEnabled = false;
|
||||
// La strada trema un po' quando la macchina si carica e quando arriva il fulmine.
|
||||
const shake = (def.mode !== 'arriva' && c.t < PARTI.charge + 0.4 ? 1.5 : 0) + (Math.abs(c.t - times.bolt) < 0.25 ? 3 : 0);
|
||||
ctx.translate(Math.round(Math.sin(c.t * 57) * shake), Math.round(Math.cos(c.t * 43) * shake));
|
||||
if (bg) ctx.drawImage(bg, 0, 0, 640, 360); else { ctx.fillStyle = '#4a4a50'; ctx.fillRect(0, 0, 640, 360); }
|
||||
if (tint) { ctx.fillStyle = tint; ctx.fillRect(0, 0, 640, 360); }
|
||||
fire(ctx, c, def);
|
||||
if (carShown(c, def)) {
|
||||
const k = progress(c, def), p = along(def, k);
|
||||
// Da dietro: coi lampi mentre si carica e quando corre forte; davanti (all'arrivo) coi lampi finché frena.
|
||||
const img = def.mode === 'arriva' ? frames?.davanti : c.t > PARTI.charge * 0.5 && Math.floor(c.t * 14) % 3 !== 0 ? frames?.lampi : frames?.dietro;
|
||||
if (img) {
|
||||
const w = def.width * p.scale, h = w * img.height / img.width;
|
||||
ctx.drawImage(img, Math.round(p.x - w / 2), Math.round(p.y - h), Math.round(w), Math.round(h));
|
||||
} else { ctx.fillStyle = '#c8ccd2'; ctx.fillRect(Math.round(p.x - 40 * p.scale), Math.round(p.y - 40 * p.scale), Math.round(80 * p.scale), Math.round(40 * p.scale)); }
|
||||
}
|
||||
bolt(ctx, c, def);
|
||||
ctx.restore();
|
||||
// Il bianco del fulmine, che sfuma.
|
||||
const f = c.t - times.bolt;
|
||||
if (f >= 0 && f < times.flash) { ctx.fillStyle = `rgba(255,255,255,${1 - f / times.flash})`; ctx.fillRect(0, 0, 640, 360); }
|
||||
}
|
||||
@@ -12,6 +12,10 @@ import * as scopa from './scopa.js';
|
||||
import * as gara from './gara.js';
|
||||
import * as micro from './micro.js';
|
||||
import * as cerbottana from './cerbottana.js';
|
||||
import { newCorsa, updateCorsa } from './corsa.js';
|
||||
import * as condor from './condor.js';
|
||||
import * as campana from './campana.js';
|
||||
import * as poker from './poker.js';
|
||||
|
||||
// I minigiochi in primo piano: comandi opzionali per il tiro o per la scelta di una carta.
|
||||
export const MINIGAMES = {
|
||||
@@ -23,7 +27,10 @@ export const MINIGAMES = {
|
||||
rinquarto: { create: (seed, level) => rinquarto.newRinquarto(seed, { level }), update: rinquarto.update, press: rinquarto.press, release: rinquarto.release, cancel: rinquarto.cancel, aim: rinquarto.aimScreen },
|
||||
basket: { create: (seed, level) => basket.newBasket(seed, { level }), update: basket.update, press: basket.press, release: basket.release, cancel: basket.cancel, aim: basket.aimScreen },
|
||||
pesca: { create: (seed, level) => pesca.newPesca(seed, { level }), update: pesca.update, press: pesca.press, release: pesca.release, cancel: pesca.cancel, aim: pesca.aimScreen },
|
||||
poker: { create: (seed, level) => poker.newPoker(seed, { level }), update: poker.update, play: poker.play },
|
||||
cerbottana: { create: (seed, level) => cerbottana.newCerbottana(seed, { level }), update: cerbottana.update, press: cerbottana.press, release: cerbottana.release, cancel: cerbottana.cancel, aim: cerbottana.aimScreen },
|
||||
condor: { create: (seed, level) => condor.newCondor(seed, { level }), update: condor.update, press: condor.press, release: condor.release, cancel: condor.cancel, aim: condor.aimScreen },
|
||||
campana: { create: (seed, level) => campana.newCampana(seed, { level }), update: campana.update, press: campana.press, release: campana.release, cancel: campana.cancel, aim: campana.aimScreen },
|
||||
micro: { create: (seed, level) => micro.newMicro(seed, { level }), update: micro.update, press: micro.press, release: micro.release, cancel: micro.cancel, aim: micro.aimScreen },
|
||||
gara: { create: (seed, level, deck, def) => gara.newGara(seed, { level, riders: def.riders }), update: gara.update, press: gara.press, release: gara.release, cancel: gara.cancel, aim: gara.aimScreen },
|
||||
};
|
||||
@@ -51,6 +58,9 @@ const WALK = ['walk_1', 'walk_2', 'walk_3', 'walk_4'], STEP = 15, REACH = 0.45;
|
||||
// Il primo piano di un libro (story.closeups, effetto `closeup`): copertina → frontespizio → una pagina girata →
|
||||
// l'ultima pagina, poi dopo una pausa il commento. Tempi in secondi.
|
||||
export const PAGE_TURN = 0.7, CLOSEUP_PAUSE = 1.4;
|
||||
// I tempi del viaggio nel tempo, in secondi: quando comincia a comparire la data, quanto dura ogni tasto, quando si
|
||||
// carica (tremori e lampi), quando arriva il bianco, quando si chiude.
|
||||
export const TRIP = { type: 0.8, key: 0.16, charge: 3.6, flash: 5.4, end: 6.6 };
|
||||
|
||||
// Condizioni: 'flag', '!flag', 'has:oggetto', '!has:oggetto', oppure un elenco (tutte vere).
|
||||
export function check(cond, state) {
|
||||
@@ -265,6 +275,17 @@ export class Game {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Il pulsante delle scene fuori dal tempo (scene.back, «Torna al 2007»): le sue azioni partono da dove si è.
|
||||
goBack() {
|
||||
const back = this.scene?.back;
|
||||
if (!back || this.busy || this.dialogue || this.state.ended) return false;
|
||||
this.task = null;
|
||||
this.path = [];
|
||||
this.walking = false;
|
||||
this.queue = [...back.actions];
|
||||
return true;
|
||||
}
|
||||
|
||||
skip() {
|
||||
if (this.speech) { this.speech = null; return true; }
|
||||
return false;
|
||||
@@ -306,6 +327,8 @@ export class Game {
|
||||
if (c.step === 'done') this.queue.unshift(...def.complete);
|
||||
return true;
|
||||
}
|
||||
// Il viaggio si chiude da solo alla fine (o uscendo prima): si arriva comunque.
|
||||
if (def?.type === 'viaggio' || def?.type === 'corsa') { this.queue.unshift(...def.complete); return true; }
|
||||
// I minigiochi: a partita finita (risultato deciso, anche se il conteggio si sta ancora mostrando) la battuta
|
||||
// di chi ha vinto; uscendo prima, quella di chi lascia.
|
||||
if (MINIGAMES[def?.type]) {
|
||||
@@ -336,6 +359,18 @@ export class Game {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Il viaggio nel tempo (story.closeups, type 'viaggio'): la data si compone sul cruscotto una cifra alla volta, poi
|
||||
// tremori, lampi e il bianco; alla fine si chiude da solo e parte `complete` (di solito il `go` nella scena d'arrivo).
|
||||
updateTrip(dt) {
|
||||
const c = this.closeup, def = this.story.closeups[c.id], T = TRIP;
|
||||
c.t += dt;
|
||||
const typed = Math.max(0, Math.min(def.date.length, Math.floor((c.t - T.type) / T.key) + 1));
|
||||
for (; c.typed < typed; c.typed++) if (def.date[c.typed] !== ' ') c.events.push({ kind: 'card-tap', power: 0.7 });
|
||||
if (!c.charge && c.t >= T.charge) { c.charge = true; c.events.push({ kind: 'gara-turbo', power: 1 }); }
|
||||
if (!c.boom && c.t >= T.flash) { c.boom = true; c.events.push({ kind: 'gara-buca', power: 1 }); }
|
||||
if (c.t >= T.end) this.closeupClose();
|
||||
}
|
||||
|
||||
updateTorch(dt) {
|
||||
const c = this.closeup, def = this.story.closeups[c.id];
|
||||
c.t += dt;
|
||||
@@ -453,6 +488,8 @@ export class Game {
|
||||
const c = this.closeup;
|
||||
if (c) {
|
||||
if (this.story.closeups[c.id].type === 'torch') { this.updateTorch(dt); return; }
|
||||
if (this.story.closeups[c.id].type === 'viaggio') { this.updateTrip(dt); return; }
|
||||
if (this.story.closeups[c.id].type === 'corsa') { if (updateCorsa(c, this.story.closeups[c.id], dt)) this.closeupClose(); return; }
|
||||
const m = this.minigame();
|
||||
if (m) {
|
||||
m.update(c, dt);
|
||||
@@ -494,6 +531,8 @@ export class Game {
|
||||
else if (e.closeup) {
|
||||
const type = this.story.closeups[e.closeup].type;
|
||||
this.closeup = type === 'torch' ? { id: e.closeup, step: 'search', t: 0, age: 0, found: 0, dwell: 0, beam: [320, 180] }
|
||||
: type === 'viaggio' ? { id: e.closeup, t: 0, age: 0, events: [], typed: 0 }
|
||||
: type === 'corsa' ? newCorsa(e.closeup)
|
||||
// `level`: «Alla bona», «Seria» o «A fassi male», scelto nel dialogo con l'avversario.
|
||||
// `decks`: il mazzo da usare quando il suo flag è acceso (briscola: le napoletane al posto delle toscane).
|
||||
: MINIGAMES[type] ? { id: e.closeup, ...MINIGAMES[type].create(Math.floor(Math.random() * 4294967296), e.level,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||