Files
leggenda-del-regolo/src/engine/tressette-render.js
T

109 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}