Macchinine: primo e secondo accanto ai nomi mentre si va; nel risultato il perché (chi è arrivato a quale spinta, e se ci arrivano tutti e due vince chi va più in là, di quanti centimetri) (Andrea)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
This commit is contained in:
2026-10-29 10:20:46 +01:00
co-authored by Claude Opus 5.5
parent 1b7511f6ca
commit e3d5d8b0d3
3 changed files with 48 additions and 14 deletions
+15 -9
View File
@@ -1,4 +1,4 @@
import { HALF, CAR, CARS, canThrow, guide, done, overHeld, carImage, choiceAt } from './micro.js'; import { HALF, CAR, CARS, canThrow, guide, done, overHeld, carImage, choiceAt, why, rank } from './micro.js';
import { text, panel } from './bocce-render.js'; import { text, panel } from './bocce-render.js';
const NAME = { player: 'Andrea', cpu: 'Chiaru' }; const NAME = { player: 'Andrea', cpu: 'Chiaru' };
@@ -124,21 +124,27 @@ export function drawMicro(ctx, c, def, bg, image) {
const level = { bona: 'alla bona', seria: 'seria', male: 'a fassi male' }[c.level]; const level = { bona: 'alla bona', seria: 'seria', male: 'a fassi male' }[c.level];
text(ctx, `${level} · spinte ${c.flicks.player}`, 8, 16, 7, '#d9ccba'); text(ctx, `${level} · spinte ${c.flicks.player}`, 8, 16, 7, '#d9ccba');
const tint = id => CARS.find(x => x.id === id).color; const tint = id => CARS.find(x => x.id === id).color;
for (const [who, x0, color] of [['player', 168, tint(c.colors.player)], ['cpu', 404, tint(c.colors.cpu)]]) { for (const [who, x0, color] of [['player', 200, tint(c.colors.player)], ['cpu', 430, tint(c.colors.cpu)]]) {
// Primo o secondo, mentre si va (Andrea, 29 ottobre).
const place = rank(c, who);
text(ctx, `${place}°`, x0 - 4, 11, 9, place === 1 ? '#ffe28a' : '#a99d86', 'right');
text(ctx, NAME[who], x0, 11, 8, '#fff0c8'); text(ctx, NAME[who], x0, 11, 8, '#fff0c8');
ctx.fillStyle = '#3a372e'; ctx.fillRect(x0 + 52, 7, 150, 8); ctx.fillStyle = '#3a372e'; ctx.fillRect(x0 + 52, 7, 140, 8);
ctx.fillStyle = color; ctx.fillRect(x0 + 52, 7, Math.round(150 * done(c, who)), 8); ctx.fillStyle = color; ctx.fillRect(x0 + 52, 7, Math.round(140 * done(c, who)), 8);
ctx.strokeStyle = '#897b57'; ctx.lineWidth = 1; ctx.strokeRect(x0 + 52.5, 7.5, 149, 7); ctx.strokeStyle = '#897b57'; ctx.lineWidth = 1; ctx.strokeRect(x0 + 52.5, 7.5, 139, 7);
} }
const turn = c.step === 'result' ? null : c.current === 'cpu' ? 'Spinge il Chiaru…' : c.step === 'aim' ? 'Tocca a te: mira e spingi' : null; const turn = c.step === 'result' ? null : c.current === 'cpu' ? 'Spinge il Chiaru…' : c.step === 'aim' ? 'Tocca a te: mira e spingi' : null;
const line = overHeld(c) ? 'Lascia! Sennò è accompagnè' : c.line ?? turn; const line = overHeld(c) ? 'Lascia! Sennò è accompagnè' : c.line ?? turn;
if (line) { ctx.fillStyle = '#17160fd0'; ctx.fillRect(170, 24, 300, 14); text(ctx, line, 320, 31, 8, /^(Fuori|Tagliè|Cappottè|Accompagnè)!/.test(c.line ?? '') ? '#ffbf9f' : '#9fd0ff', 'center'); } if (line) { ctx.fillStyle = '#17160fd0'; ctx.fillRect(170, 24, 300, 14); text(ctx, line, 320, 31, 8, /^(Fuori|Tagliè|Cappottè|Accompagnè)!/.test(c.line ?? '') ? '#ffbf9f' : '#9fd0ff', 'center'); }
if (c.step === 'result') { if (c.step === 'result') {
const w = c.result.winner, title = w === 'player' ? 'HAI BATTUTO IL CHIARU!' : w === 'cpu' ? 'HA VINTO IL CHIARU' : 'ARRIVATI PARI'; const w = c.result.winner, title = w === 'player' ? 'HAI BATTUTO IL CHIARU!' : w === 'cpu' ? 'HA VINTO IL CHIARU' : 'ARRIVATI PARI';
panel(ctx, 170, 140, 300, 72); // Il perché, in due righe: chi è arrivato a quale spinta, e di quanto è andato più in là.
text(ctx, title, 320, 160, 12, w === 'player' ? '#b9e5b3' : '#efd49e', 'center'); const [first, second] = why(c);
text(ctx, `Un giro, ${c.result.flicks.player} spinte a testa`, 320, 182, 8, '#efd49e', 'center'); panel(ctx, 130, 132, 380, 92);
text(ctx, 'Torna dal Chiaru per uscire.', 320, 198, 8, '#d9ccba', 'center'); text(ctx, title, 320, 150, 12, w === 'player' ? '#b9e5b3' : '#efd49e', 'center');
text(ctx, first, 320, 172, 8, '#efd49e', 'center');
text(ctx, second, 320, 186, 8, '#efd49e', 'center');
text(ctx, 'Torna dal Chiaru per uscire.', 320, 208, 8, '#d9ccba', 'center');
} }
ctx.restore(); ctx.restore();
} }
+17 -3
View File
@@ -265,7 +265,7 @@ function settle(c) {
const end = c.laps * c.track.length, over = { player: c.cars.player.progress - end, cpu: c.cars.cpu.progress - end }; const end = c.laps * c.track.length, over = { player: c.cars.player.progress - end, cpu: c.cars.cpu.progress - end };
if (c.shooter === 'cpu' && (over.player >= 0 || over.cpu >= 0)) { if (c.shooter === 'cpu' && (over.player >= 0 || over.cpu >= 0)) {
const winner = over.player >= 0 && over.cpu >= 0 ? (Math.abs(over.player - over.cpu) < 1 ? 'draw' : over.player > over.cpu ? 'player' : 'cpu') : over.player >= 0 ? 'player' : 'cpu'; const winner = over.player >= 0 && over.cpu >= 0 ? (Math.abs(over.player - over.cpu) < 1 ? 'draw' : over.player > over.cpu ? 'player' : 'cpu') : over.player >= 0 ? 'player' : 'cpu';
c.step = 'result'; c.result = { winner, flicks: { ...c.flicks } }; c.step = 'result'; c.result = { winner, flicks: { ...c.flicks }, over };
return; return;
} }
// Chi ha già tagliato il traguardo aspetta; spinge l'altro. // Chi ha già tagliato il traguardo aspetta; spinge l'altro.
@@ -389,12 +389,26 @@ export function button(c) {
if (c.step === 'rolling' || c.step === 'settle') return 'In movimento…'; if (c.step === 'rolling' || c.step === 'settle') return 'In movimento…';
return c.current === 'cpu' ? 'Spinge il Chiaru…' : 'Spingi'; return c.current === 'cpu' ? 'Spinge il Chiaru…' : 'Spingi';
} }
// Il perché del risultato, da scrivere (Andrea, 29 ottobre: «ho tagliato prima io, ma ha vinto il Chiaru»). Le
// macchinine vere sono lunghe 4 centimetri e mezzo: un pixel è quasi due millimetri.
export const CM = 4.5 / CAR.len;
export function why(c) {
const { winner, flicks, over } = c.result, n = flicks.player;
if (over.player >= 0 && over.cpu >= 0) {
const cm = Math.max(1, Math.round(Math.abs(over.player - over.cpu) * CM));
if (winner === 'draw') return [`Tutti e due al traguardo alla spinta ${n},`, 'pari al millimetro.'];
return [`Tutti e due al traguardo alla spinta ${n}:`, `vince chi va più in là, ${winner === 'player' ? 'Andrea' : 'il Chiaru'} di ${cm} ${cm === 1 ? 'centimetro' : 'centimetri'}.`];
}
return winner === 'player' ? [`Andrea al traguardo alla spinta ${n};`, 'al Chiaru non è bastata la sua.'] : [`Il Chiaru al traguardo alla spinta ${n};`, 'Andrea non c\'è arrivato.'];
}
// Chi è davanti adesso (1 o 2), per strada fatta.
export const rank = (c, who) => c.cars[who].progress >= c.cars[OTHER[who]].progress ? 1 : 2;
// Quanta pista manca al traguardo, in percentuale, per il tabellone. // Quanta pista manca al traguardo, in percentuale, per il tabellone.
export const done = (c, who) => clamp(c.cars[who].progress / (c.laps * c.track.length), 0, 1); export const done = (c, who) => clamp(c.cars[who].progress / (c.laps * c.track.length), 0, 1);
export function status(c) { export function status(c) {
if (c.step === 'choose') return `Scegli la macchinina: ${CARS[c.choice].name}. Tocca quella che vuoi, o frecce e Scegli.`; if (c.step === 'choose') return `Scegli la macchinina: ${CARS[c.choice].name}. Tocca quella che vuoi, o frecce e Scegli.`;
const where = `Andrea ${Math.round(done(c, 'player') * 100)}%, Chiaru ${Math.round(done(c, 'cpu') * 100)}% del giro.`; const where = `Andrea ${rank(c, 'player')}° (${Math.round(done(c, 'player') * 100)}%), Chiaru ${rank(c, 'cpu')}° (${Math.round(done(c, 'cpu') * 100)}%).`;
if (c.step === 'result') return `${where} ${c.result.winner === 'player' ? 'Hai battuto il Chiaru!' : c.result.winner === 'cpu' ? 'Ha vinto il Chiaru.' : 'Arrivati pari.'}`; if (c.step === 'result') return `${c.result.winner === 'player' ? 'Hai battuto il Chiaru!' : c.result.winner === 'cpu' ? 'Ha vinto il Chiaru.' : 'Arrivati pari.'} ${why(c).join(' ')}`;
if (c.step === 'rolling' || c.step === 'settle') return `${where} ${c.line ?? ''}`.trim(); if (c.step === 'rolling' || c.step === 'settle') return `${where} ${c.line ?? ''}`.trim();
if (c.current === 'cpu') return `${where} Spinge il Chiaru…`; if (c.current === 'cpu') return `${where} Spinge il Chiaru…`;
return `${where} ${c.line ? c.line + ' ' : ''}Tocca a te: mira e spingi.`; return `${where} ${c.line ? c.line + ' ' : ''}Tocca a te: mira e spingi.`;
+16 -2
View File
@@ -96,6 +96,19 @@ test('si parte scegliendo la macchinina: tocco, frecce, pulsante; il Chiaru tien
for (const car of m.CARS) assert.ok(existsSync(new URL('../' + m.carImage(car.id, true), import.meta.url)) && existsSync(new URL('../' + m.carImage(car.id), import.meta.url))); for (const car of m.CARS) assert.ok(existsSync(new URL('../' + m.carImage(car.id, true), import.meta.url)) && existsSync(new URL('../' + m.carImage(car.id), import.meta.url)));
}); });
test('primo e secondo mentre si va; nel risultato il perché', () => {
const c = newMicro(1);
c.cars.player.progress = 100; c.cars.cpu.progress = 80;
assert.equal(m.rank(c, 'player'), 1); assert.equal(m.rank(c, 'cpu'), 2);
assert.match(m.status(c), /Andrea 1°.*Chiaru 2°/);
c.result = { winner: 'cpu', flicks: { player: 20, cpu: 20 }, over: { player: 5, cpu: 22 } };
assert.deepEqual(m.why(c), ['Tutti e due al traguardo alla spinta 20:', 'vince chi va più in là, il Chiaru di 3 centimetri.']);
c.result = { winner: 'player', flicks: { player: 9, cpu: 9 }, over: { player: 5, cpu: -40 } };
assert.deepEqual(m.why(c), ['Andrea al traguardo alla spinta 9;', 'al Chiaru non è bastata la sua.']);
c.result = { winner: 'draw', flicks: { player: 9, cpu: 9 }, over: { player: 5, cpu: 5.4 } };
assert.match(m.why(c).join(' '), /pari/);
});
test('le penalità alla francese: tagliè, cappottè, accompagnè; per tutte si torna indietro', () => { test('le penalità alla francese: tagliè, cappottè, accompagnè; per tutte si torna indietro', () => {
// Tagliè: dal tratto di mezzo della esse, dritti in su attraverso l'erba verso il tratto di sopra. // Tagliè: dal tratto di mezzo della esse, dritti in su attraverso l'erba verso il tratto di sopra.
const t = newMicro(1, { level: 'bona' }), [sx, sy] = t.track.pts[Math.round(t.track.n * 0.48)]; const t = newMicro(1, { level: 'bona' }), [sx, sy] = t.track.pts[Math.round(t.track.n * 0.48)];
@@ -184,7 +197,8 @@ test('il disegno: gesso, macchinine, guida e risultato; lo stato non cambia', ()
assert.ok(calls.some(([op, img]) => op === 'drawImage' && img === bg)); assert.ok(calls.some(([op, img]) => op === 'drawImage' && img === bg));
assert.ok(calls.filter(([op, img]) => op === 'drawImage' && img?.car === 'player').length >= 2, 'la macchinina e il suo fantasma'); assert.ok(calls.filter(([op, img]) => op === 'drawImage' && img?.car === 'player').length >= 2, 'la macchinina e il suo fantasma');
assert.ok(calls.some(([op, t]) => op === 'fillText' && t === 'Chiaru')); assert.ok(calls.some(([op, t]) => op === 'fillText' && t === 'Chiaru'));
c.step = 'result'; c.result = { winner: 'cpu', flicks: { player: 12, cpu: 12 } }; calls.length = 0; draw(); c.step = 'result'; c.result = { winner: 'cpu', flicks: { player: 12, cpu: 12 }, over: { player: 3, cpu: 20 } }; calls.length = 0; draw();
assert.ok(calls.some(([op, t]) => op === 'fillText' && t === 'vince chi va più in là, il Chiaru di 3 centimetri.'));
assert.ok(calls.some(([op, t]) => op === 'fillText' && t === 'HA VINTO IL CHIARU')); assert.ok(calls.some(([op, t]) => op === 'fillText' && t === 'HA VINTO IL CHIARU'));
}); });
@@ -201,7 +215,7 @@ test('il Chiaru al parcheggio di Cardetole: si chiacchiera, si sceglie il livell
g.choose(g.options().findIndex(o => o.text === 'Si fa una gara alle macchinine?!?!')); settle(g); g.choose(g.options().findIndex(o => o.text === 'Si fa una gara alle macchinine?!?!')); settle(g);
g.choose(g.options().findIndex(o => o.text === 'A fassi male.')); settle(g); g.choose(g.options().findIndex(o => o.text === 'A fassi male.')); settle(g);
assert.equal(g.closeup?.id, 'micro'); assert.equal(g.closeup.level, 'male'); assert.equal(g.closeup?.id, 'micro'); assert.equal(g.closeup.level, 'male');
g.closeup.step = 'result'; g.closeup.result = { winner: 'player', flicks: { player: 11, cpu: 11 } }; g.closeup.step = 'result'; g.closeup.result = { winner: 'player', flicks: { player: 11, cpu: 11 }, over: { player: 4, cpu: -30 } };
g.closeupClose(); settle(g); g.closeupClose(); settle(g);
assert.ok(g.state.flags.micro_vinto); assert.ok(g.state.flags.micro_vinto);
}); });