Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
78 lines
4.8 KiB
JavaScript
78 lines
4.8 KiB
JavaScript
import { LEN, WID, R, VIEW, WINDUP } from './biliardo.js';
|
|
import { table, sphere, pin, cue } from './biliardo-render.js';
|
|
import { guide, canThrow, shooter } from './rinquarto.js';
|
|
import { text, panel } from './bocce-render.js';
|
|
|
|
const sx = x => VIEW.x + x * VIEW.k, sy = y => VIEW.y + y * VIEW.k;
|
|
const NAME = { andrea: 'Andrea', bertovo: 'Trappizzio Berti' };
|
|
// La traiettoria del tiro: resta sul tavolo finché non si va avanti, con le sponde numerate e il contatto che decide.
|
|
function trail(ctx, c) {
|
|
if (c.trail.length < 2) return;
|
|
const good = c.attempt.point, done = c.step === 'review';
|
|
ctx.strokeStyle = done ? (good ? '#b9e5b3' : '#ffbf9f') : '#fff8dccc'; ctx.lineWidth = 1.5;
|
|
ctx.beginPath(); ctx.moveTo(sx(c.trail[0][0]), sy(c.trail[0][1]));
|
|
for (const [x, y] of c.trail.slice(1)) ctx.lineTo(sx(x), sy(y));
|
|
ctx.stroke();
|
|
c.attempt.marks.forEach((m, i) => {
|
|
ctx.fillStyle = '#17160f'; ctx.beginPath(); ctx.arc(sx(m.x), sy(m.y), 5, 0, Math.PI * 2); ctx.fill();
|
|
text(ctx, String(i + 1), sx(m.x), sy(m.y) + 0.5, 7, '#ffe28a', 'center');
|
|
});
|
|
if (done && c.attempt.hit) {
|
|
const { x, y } = c.attempt.hit;
|
|
ctx.strokeStyle = good ? '#b9e5b3' : '#ff6a4a'; ctx.lineWidth = 2;
|
|
ctx.beginPath(); ctx.arc(sx(x), sy(y), R * VIEW.k + 4, 0, Math.PI * 2); ctx.stroke();
|
|
}
|
|
}
|
|
export function drawRinquarto(ctx, c, def, bg) {
|
|
ctx.save(); ctx.imageSmoothingEnabled = false;
|
|
table(ctx, bg);
|
|
ctx.save(); ctx.beginPath(); ctx.rect(VIEW.x - 15, VIEW.y - 15, LEN * VIEW.k + 30, WID * VIEW.k + 30); ctx.clip();
|
|
if (canThrow(c)) {
|
|
const points = guide(c);
|
|
if (points.length) {
|
|
ctx.strokeStyle = '#fff8dc'; ctx.lineWidth = 1; ctx.setLineDash([4, 3]);
|
|
ctx.beginPath(); ctx.moveTo(sx(points[0][0]), sy(points[0][1]));
|
|
for (const [x, y] of points.slice(1)) ctx.lineTo(sx(x), sy(y));
|
|
ctx.stroke(); ctx.setLineDash([]);
|
|
}
|
|
const b = c.balls.player;
|
|
cue(ctx, b.x, b.y, c.angle, R * VIEW.k + 6 + (c.charging ? 26 : 10));
|
|
} else if (c.visual.shot && c.windup > 0) {
|
|
const s = c.visual.shot;
|
|
cue(ctx, s.x, s.y, s.angle, R * VIEW.k + 32 * c.windup / WINDUP);
|
|
}
|
|
trail(ctx, c);
|
|
for (const p of c.pins) pin(ctx, p, c.visual.time);
|
|
// Il bersaglio ha un anello che pulsa; i disturbi una croce grossa, che si vede anche sul telefono.
|
|
const tgt = c.balls.cpu, pulse = 1 + Math.sin(c.visual.time * 4) * 0.5;
|
|
ctx.strokeStyle = '#ffe28a'; ctx.lineWidth = 1.5;
|
|
ctx.beginPath(); ctx.arc(sx(tgt.x), sy(tgt.y), R * VIEW.k + 3 + pulse, 0, Math.PI * 2); ctx.stroke();
|
|
for (const b of Object.values(c.balls)) {
|
|
sphere(ctx, b.role, sx(b.x), sy(b.y), R * VIEW.k);
|
|
if (!['player', 'cpu'].includes(b.role)) {
|
|
const k = R * VIEW.k;
|
|
ctx.strokeStyle = '#1b0d29'; ctx.lineWidth = 2;
|
|
ctx.beginPath(); ctx.moveTo(sx(b.x) - k, sy(b.y) - k); ctx.lineTo(sx(b.x) + k, sy(b.y) + k);
|
|
ctx.moveTo(sx(b.x) + k, sy(b.y) - k); ctx.lineTo(sx(b.x) - k, sy(b.y) + k); ctx.stroke();
|
|
}
|
|
}
|
|
ctx.restore();
|
|
// In alto: chi tira, il punteggio, le sponde; l'esito sta qui, così non copre il tavolo.
|
|
ctx.fillStyle = '#17160ff0'; ctx.fillRect(54, 4, 532, 64);
|
|
text(ctx, c.skill.training ? 'I RINQUARTI DI TRAPPIZZIO BERTI · ALLENAMENTO' : 'I RINQUARTI DI TRAPPIZZIO BERTI', 320, 13, 9, '#efd49e', 'center');
|
|
const score = c.skill.training ? 'senza punti' : `Andrea ${c.score.andrea} Trappizzio Berti ${c.score.bertovo}`;
|
|
text(ctx, `Posizione ${c.position}/${c.skill.positions} ${score} Sponde ${c.attempt.cushions}/${c.skill.cushions}`, 320, 29, 9, '#fff0c8', 'center');
|
|
const who = NAME[shooter(c)];
|
|
const line = c.step === 'review' ? c.line : c.step === 'think' ? 'Tira Trappizzio Berti…' : c.step === 'rolling' ? `Tira ${who}…` : `Tocca ad ${who}: «Oh tirami questo rinquarto!?»`;
|
|
text(ctx, line, 320, 45, 8, c.step === 'review' ? (c.attempt.point ? '#b9e5b3' : '#ffbf9f') : '#9fd0ff', 'center');
|
|
if (c.step === 'review') text(ctx, c.last ? 'Tocca per il risultato' : 'Tocca o premi Prossima', 320, 59, 7, '#d9ccba', 'center');
|
|
if (c.step === 'result') {
|
|
const r = c.result, title = r.training ? 'ALLENAMENTO FINITO' : r.winner === 'player' ? 'HAI BATTUTO TRAPPIZZIO BERTI!' : r.winner === 'cpu' ? 'HA VINTO TRAPPIZZIO BERTI' : 'PARI';
|
|
panel(ctx, 155, 145, 330, 80);
|
|
text(ctx, title, 320, 166, 12, r.winner === 'player' ? '#b9e5b3' : '#efd49e', 'center');
|
|
if (!r.training) text(ctx, `Andrea ${r.points.andrea} · Trappizzio Berti ${r.points.bertovo}, su ${c.skill.positions} posizioni`, 320, 190, 8, '#efd49e', 'center');
|
|
text(ctx, 'Torna da Trappizzio Berti per uscire.', 320, 210, 8, '#d9ccba', 'center');
|
|
}
|
|
ctx.restore();
|
|
}
|