Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1dxvtsZx8NE9fwjXUNDBr
109 lines
6.2 KiB
JavaScript
109 lines
6.2 KiB
JavaScript
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();
|
||
}
|