import { canPlay, legal, accuseName, team, NAMES, PLAY_TIME, TRICK_HOLD, TRICK_TIME } from './tressette.js'; import { drawCard, drawTable, flight } from './briscola-render.js'; import { text, panel } from './bocce-render.js'; // La mano di Andrea a ventaglio, carte a grandezza naturale (42×60): coordinate condivise da disegno e tocco. export const CARD = { w: 42, h: 60, y: 288, lift: 8 }; export function handX(n, i) { const gap = n > 1 ? Math.min(44, (440 - CARD.w) / (n - 1)) : 0; return Math.round(320 - (gap * (n - 1) + CARD.w) / 2 + i * gap); } // Le carte si coprono: vince quella sopra, cioè la più a destra. export function cardAt(c, [x, y]) { if (!canPlay(c)) return -1; const n = c.hands[0].length; for (let i = n - 1; i >= 0; i--) { const top = CARD.y - (c.selected === i ? CARD.lift : 0), left = handX(n, i); if (x >= left && x < left + CARD.w && y >= top && y < top + CARD.h + CARD.lift) return i; } return -1; } // Dove cade la carta di ognuno al centro, e da dove arriva. const SPOT = [[299, 190], [354, 146], [299, 100], [244, 146]]; // I mucchietti delle prese: noi in basso a destra, loro in basso a sinistra. const PILE = [[560, 288], [34, 288]]; const LABEL = [[320, 280, 'center'], [612, 92, 'right'], [320, 39, 'center'], [28, 92, 'left']]; const gold = '#ffe09b', paper = '#fff0cd', ours = '#9fe3ff', theirs = '#ffb0a3'; const SAY = { busso: 'BUSSO!', striscio: 'STRISCIO!', volo: 'VOLO!' }; function others(ctx, c, names) { // Il socio di fronte, gli avversari ai lati: carte coperte, quante ne hanno. const n2 = c.hands[2].length, w2 = n2 ? (n2 - 1) * 12 + 26 : 0; for (let i = 0; i < n2; i++) drawCard(ctx, null, 320 - w2 / 2 + i * 12, 46, 26, 37, true); for (const [seat, x] of [[3, 30], [1, 584]]) { for (let i = 0; i < c.hands[seat].length; i++) drawCard(ctx, null, x, 102 + i * 12, 26, 37, true); } for (const seat of [1, 2, 3]) { // Chi deve giocare ha il nome in giallo. const [x, y, align] = LABEL[seat], turn = c.step === 'play' && c.current === seat; text(ctx, names[seat], x, y, 6, turn ? gold : seat % 2 ? theirs : ours, align); } } function piles(ctx, c) { // Quante prese ha fatto ogni coppia, a mucchietto (una carta coperta ogni due prese). for (const t of [0, 1]) { const n = c.tricks.filter(x => team(x.winner) === t).length, [x, y] = PILE[t]; for (let k = 0; k < Math.ceil(n / 2); k++) drawCard(ctx, null, x + k, y - k, 42, 60, true); text(ctx, t ? 'Prese loro' : 'Prese noi', x + 21, y - 8, 5, t ? theirs : ours, 'center'); } } function scoreboard(ctx, c) { panel(ctx, 26, 34, 118, 38); text(ctx, `Noi ${c.score[0]}`, 34, 46, 7, ours); text(ctx, `Loro ${c.score[1]}`, 136, 46, 7, theirs, 'right'); text(ctx, `Smazzata ${c.round} · ai 21`, 85, 62, 5, paper, 'center'); } function accuses(ctx, c, names) { // Le accuse dette restano in vista per tutta la smazzata. if (!c.accuse.length) return; // Largo abbastanza per «Il giocatore: tre Assi, manca mattoni +3» (200 px nel carattere a 5). panel(ctx, 396, 34, 218, 12 + c.accuse.length * 11); c.accuse.forEach((a, i) => text(ctx, `${names[a.who]}: ${accuseName(a)} +${a.points}`, 403, 41 + i * 11, 5, a.who % 2 ? theirs : ours)); } function trick(ctx, c, names) { // Da dove parte la carta: la mano di Andrea, o le carte coperte degli altri. const from = p => p.who === 0 ? [handX(c.hands[0].length + 1, p.from ?? 0), CARD.y, CARD.w, CARD.h] : p.who === 2 ? [300, 46, 26, 37] : [p.who === 1 ? 584 : 30, 102 + (p.from ?? 0) * 12, 26, 37]; c.table.forEach((p, i) => { const [x, y] = SPOT[p.who]; let r = [x, y, CARD.w, CARD.h]; if (i === c.table.length - 1 && c.t < PLAY_TIME && (c.step === 'play' || c.step === 'trick')) r = flight(from(p), r, c.t / PLAY_TIME); if (c.step === 'trick' && c.t > TRICK_HOLD) r = flight(r, [...PILE[team(c.last.winner)], 42, 60], (c.t - TRICK_HOLD) / (TRICK_TIME - TRICK_HOLD - 0.05)); drawCard(ctx, p.card, ...r); if (p.signal) { const [lx, ly] = p.who === 0 ? [x + 21, y + 70] : p.who === 2 ? [x + 21, y - 8] : p.who === 1 ? [x + 66, y + 30] : [x - 24, y + 30]; panel(ctx, lx - 30, ly - 7, 60, 14); text(ctx, SAY[p.signal], lx, ly, 6, gold, 'center'); } }); if (c.step === 'trick') text(ctx, `Prende ${names[c.last.winner]}`, 320, 262, 6, gold, 'center'); } function hand(ctx, c) { const cards = c.hands[0], n = cards.length, mine = canPlay(c), ok = mine ? legal(cards, c.table) : []; cards.forEach((card, i) => { const x = handX(n, i), up = mine && c.selected === i ? CARD.lift : 0; if (up) { ctx.fillStyle = '#ffdb76'; ctx.fillRect(x - 2, CARD.y - up - 2, CARD.w + 4, CARD.h + 4); } drawCard(ctx, card, x, CARD.y - up, CARD.w, CARD.h); // Le carte che non rispondono al seme si spengono. if (mine && !ok.includes(i)) { ctx.fillStyle = 'rgba(20,28,24,0.55)'; ctx.fillRect(x, CARD.y, CARD.w, CARD.h); } }); if (mine && !c.table.length && c.signal) text(ctx, `Segno: ${SAY[c.signal]}`, 320, 278, 6, gold, 'center'); } function summary(ctx, c, title) { const s = c.summary, row = (y, who, t, color) => text(ctx, `${who}: ${s.thirds[t]} terzi = ${s.points[t]} punti${s.accused[t] ? `, accuse +${s.accused[t]}` : ''}${s.lastTeam === t ? ' (con l’ultima)' : ''}`, 172, y, 6, color); panel(ctx, 160, 96, 320, 132); text(ctx, title, 320, 112, 8, gold, 'center'); row(140, 'Noi', 0, ours); row(158, 'Loro', 1, theirs); text(ctx, `Siamo ${c.score[0]} a ${c.score[1]}`, 320, 186, 8, paper, 'center'); text(ctx, c.step === 'result' ? 'Esc o Invio per tornare all’oratorio' : 'Invio o un tocco per la prossima smazzata', 320, 210, 5, paper, 'center'); } export function drawTressette(ctx, c, def) { const names = def?.seats ?? NAMES; ctx.save(); ctx.imageSmoothingEnabled = false; drawTable(ctx, 'TRESSETTE AI 21'); scoreboard(ctx, c); others(ctx, c, names); piles(ctx, c); accuses(ctx, c, names); hand(ctx, c); trick(ctx, c, names); if (c.step === 'smazzata') summary(ctx, c, `Fine della smazzata ${c.round}`); if (c.step === 'result') summary(ctx, c, c.result.winner === 'player' ? 'Fuori! Avete vinto' : 'Sono fuori loro'); ctx.restore(); }