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(); }