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

310 lines
19 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, cardName, counted, countRows, PLAY_TIME, TRICK_HOLD, TRICK_TIME } from './briscola.js';
import { text, panel } from './bocce-render.js';
// Una carta in volo da un rettangolo [x, y, w, h] a un altro, con k da 0 a 1 (parte svelta, arriva piano).
// La usano anche tressette e scopa.
export function flight(from, to, k) {
const e = 1 - (1 - Math.min(1, Math.max(0, k))) ** 3;
return from.map((v, i) => v + (to[i] - v) * e);
}
// Coordinate condivise da disegno e hit test: bersagli larghi anche sul telefono.
export const HAND = { x: 176, y: 230, gap: 102, w: 84, h: 120 };
export function cardAt(c, [x, y]) {
if (!canPlay(c)) return -1;
return c.hands.player.findIndex((_, i) => x >= HAND.x + i * HAND.gap && x < HAND.x + i * HAND.gap + HAND.w && y >= HAND.y && y < HAND.y + HAND.h);
}
const ink = '#38271f', paper = '#fff0cd', gold = '#dba32b', red = '#aa4034', blue = '#386a89', green = '#487348';
function rect(ctx, x, y, w, h, color) { ctx.fillStyle = color; ctx.fillRect(x, y, w, h); }
function poly(ctx, points, color) {
ctx.fillStyle = color; ctx.beginPath(); points.forEach(([x, y], i) => i ? ctx.lineTo(x, y) : ctx.moveTo(x, y)); ctx.closePath(); ctx.fill();
}
// Simboli originali: moneta incisa, calice, lama ricurva e bastone coi nodi.
function suit(ctx, name, x, y) {
ctx.save(); ctx.translate(x, y);
if (name === 'denari') {
rect(ctx, -4, -3, 8, 6, ink); rect(ctx, -3, -4, 6, 8, ink);
rect(ctx, -3, -2, 6, 4, gold); rect(ctx, -2, -3, 4, 6, gold);
rect(ctx, -2, -2, 1, 4, '#ffe58c'); rect(ctx, 0, -1, 2, 2, '#9e6b25');
} else if (name === 'coppe') {
rect(ctx, -4, -4, 8, 2, ink); rect(ctx, -3, -2, 6, 3, gold);
rect(ctx, -2, 0, 4, 2, red); rect(ctx, -1, 2, 2, 2, ink);
rect(ctx, -3, 4, 6, 1, gold); rect(ctx, -3, -3, 6, 1, '#ffdd70');
} else if (name === 'spade') {
poly(ctx, [[-4,5],[-3,1],[0,-1],[2,-5],[4,-6],[3,-1],[-1,3],[-2,6]], ink);
rect(ctx, 1, -3, 2, 3, '#9eb3ba'); rect(ctx, -1, 0, 2, 2, '#c9d8d6');
rect(ctx, -5, 3, 5, 1, gold); rect(ctx, -4, 5, 2, 2, red);
} else {
poly(ctx, [[-3,6],[-4,4],[-1,-1],[0,-6],[3,-5],[3,-2],[0,3],[-1,6]], ink);
rect(ctx, 0, -4, 2, 5, '#94632e'); rect(ctx, -2, 1, 2, 4, '#ae7a39');
rect(ctx, 2, -3, 2, 2, gold); rect(ctx, -3, 2, 2, 2, gold); rect(ctx, 3, -5, 2, 1, green);
}
ctx.restore();
}
function regolo(ctx) {
// Cornice simmetrica e un piccolo re sorridente, avvolto nelle proprie spire.
rect(ctx, 3, 3, 36, 54, blue); rect(ctx, 5, 5, 32, 50, gold); rect(ctx, 6, 6, 30, 48, '#203e44');
for (let y = 9; y < 54; y += 6) for (const x of [8, 32]) rect(ctx, x, y, 2, 2, '#63867c');
poly(ctx, [[15,32],[28,32],[32,36],[32,43],[28,47],[14,47],[10,43],[10,37]], ink);
poly(ctx, [[15,34],[27,34],[30,37],[30,42],[27,45],[15,45],[12,42],[12,38]], green);
poly(ctx, [[17,37],[26,37],[28,39],[27,41],[16,41],[15,39]], '#203e44');
rect(ctx, 14, 42, 13, 2, '#a3bc72'); rect(ctx, 16, 36, 11, 1, '#a3bc72');
poly(ctx, [[19,25],[25,25],[25,33],[23,36],[18,36],[17,33],[20,32]], green);
rect(ctx, 20, 28, 2, 6, '#d0cd83');
poly(ctx, [[15,16],[27,16],[30,19],[30,25],[27,29],[15,29],[12,25],[12,20]], ink);
poly(ctx, [[16,17],[26,17],[29,20],[29,24],[26,27],[16,27],[13,24],[13,21]], '#94b365');
rect(ctx, 15, 19, 4, 4, paper); rect(ctx, 23, 19, 4, 4, paper);
rect(ctx, 17, 20, 2, 3, ink); rect(ctx, 23, 20, 2, 3, ink);
rect(ctx, 18, 25, 6, 1, ink); rect(ctx, 16, 24, 2, 1, ink); rect(ctx, 24, 24, 2, 1, ink);
rect(ctx, 17, 12, 9, 4, gold); for (const x of [17, 21, 25]) rect(ctx, x, 9, 1, 4, '#ffe396');
rect(ctx, 21, 13, 1, 2, red);
for (const x of [15, 25]) { rect(ctx, x, 50, 2, 2, gold); rect(ctx, x + 1, 49, 1, 4, gold); }
}
function figure(ctx, rank, name) {
// Figure intere: fante in tunica, cavaliere a cavallo, re con corona e manto.
const king = rank === 'Re', horse = rank === 'Cavallo', y = horse ? 15 : 17;
if (horse) {
poly(ctx, [[8,36],[12,29],[27,30],[30,23],[35,23],[37,29],[32,33],[31,41],[12,41]], ink);
rect(ctx, 11, 33, 20, 7, '#b78044'); rect(ctx, 30, 26, 4, 8, '#c79c60');
rect(ctx, 12, 40, 3, 10, ink); rect(ctx, 27, 39, 3, 11, ink);
rect(ctx, 15, 40, 2, 8, '#a36b3c'); rect(ctx, 32, 27, 1, 1, paper);
}
if (king) poly(ctx, [[15,24],[29,24],[33,48],[10,48]], red);
poly(ctx, [[19,y],[25,y],[27,y+2],[28,y+4],[26,y+5],[26,y+8],[20,y+9],[17,y+6],[17,y+2]], ink);
poly(ctx, [[20,y+1],[25,y+1],[25,y+3],[27,y+4],[25,y+5],[25,y+7],[21,y+8],[19,y+5]], '#eab578');
rect(ctx, 18, y + 1, 2, 4, '#86502d'); rect(ctx, 20, y + 1, 4, 1, '#ffe0a2');
rect(ctx, 24, y + 2, 1, 2, ink); rect(ctx, 24, y + 6, 3, 1, ink);
rect(ctx, 17, y - 2, 10, 3, king ? gold : blue);
if (king) for (const x of [17, 21, 25]) rect(ctx, x, y - 5, 2, 4, gold);
else rect(ctx, 18, y - 5, 2, 4, red);
poly(ctx, [[18,y+9],[26,y+9],[29,y+12],[27,y+19],[29,y+24],[15,y+24],[17,y+19],[15,y+12]], ink);
poly(ctx, [[19,y+10],[25,y+10],[27,y+12],[25,y+19],[27,y+22],[17,y+22],[19,y+18],[17,y+12]], horse ? red : blue);
rect(ctx, 18, y + 10, 3, 10, king ? gold : '#6291a5'); rect(ctx, 17, y + 19, 11, 2, gold);
rect(ctx, 23, y + 11, 1, 6, paper); rect(ctx, 23, y + 19, 2, 2, ink);
rect(ctx, 12, y + 11, 4, 3, '#eab578'); rect(ctx, 28, y + 10, 4, 4, '#eab578');
if (!horse) {
poly(ctx, [[17,y+24],[21,y+24],[20,y+32],[16,y+32]], ink);
poly(ctx, [[23,y+24],[27,y+24],[29,y+32],[25,y+32]], ink);
rect(ctx, 18, y + 24, 2, 5, '#b9884f'); rect(ctx, 24, y + 24, 2, 4, '#b9884f');
rect(ctx, 15, y + 32, 6, 2, red); rect(ctx, 24, y + 32, 6, 2, red);
}
suit(ctx, name, 10, 24);
if (king) { rect(ctx, 32, 22, 1, 24, gold); rect(ctx, 31, 20, 3, 3, gold); }
}
const PIPS = {
Asso: [[21,32]], 2: [[21,22],[21,42]], 3: [[21,20],[21,32],[21,44]],
4: [[13,23],[29,23],[13,41],[29,41]],
5: [[13,21],[29,21],[21,32],[13,43],[29,43]],
6: [[13,20],[29,20],[13,32],[29,32],[13,44],[29,44]],
7: [[13,19],[29,19],[13,32],[29,32],[13,45],[29,45],[21,32]],
};
// Le toscane hanno semi francesi, nessun indice sulle numerali e figure intere.
const TUSCAN_COLORS = {
cuori: { cloth: '#326447', light: '#65936a', cloak: '#ab393b', ground: '#bc7760' },
mattoni: { cloth: '#a93832', light: '#d56a46', cloak: '#315f7e', ground: '#b3a05b' },
fiori: { cloth: '#315e85', light: '#6995ac', cloak: '#bc842e', ground: '#829968' },
picche: { cloth: '#755079', light: '#ac7d9a', cloak: '#376b66', ground: '#9e8271' },
};
const TUSCAN_PIPS = {
Asso: [[21,30]], 2: [[21,17],[21,43]], 3: [[21,15],[21,30],[21,45]],
4: [[12,16],[30,16],[12,44],[30,44]],
5: [[12,16],[30,16],[21,30],[12,44],[30,44]],
6: [[12,15],[30,15],[12,30],[30,30],[12,45],[30,45]],
7: [[10,14],[32,14],[21,22],[10,30],[32,30],[10,46],[32,46]],
};
function tuscanSuit(ctx, name, x, y, inverted = false) {
ctx.save(); ctx.translate(x, y);
if (inverted) ctx.scale(1, -1);
const color = name === 'cuori' || name === 'mattoni' ? '#c12f35' : '#202326';
// Sagome a gradini sulla griglia: nessuna sfumatura nei semi.
if (name === 'cuori') {
poly(ctx, [[-4,-4],[-1,-4],[0,-3],[1,-4],[4,-4],[5,-3],[5,0],[4,1],[4,2],[3,2],[3,3],[2,3],[2,4],[1,4],[1,5],[0,6],[-1,5],[-1,4],[-2,4],[-2,3],[-3,3],[-3,2],[-4,2],[-4,1],[-5,0],[-5,-3]], color);
} else if (name === 'mattoni') {
poly(ctx, [[0,-6],[1,-4],[2,-3],[3,-2],[5,0],[3,2],[2,3],[1,4],[0,6],[-1,4],[-2,3],[-3,2],[-5,0],[-3,-2],[-2,-3],[-1,-4]], color);
} else if (name === 'fiori') {
poly(ctx, [[-2,-5],[2,-5],[3,-4],[3,-2],[2,-1],[4,-2],[6,0],[6,2],[4,4],[2,4],[1,3],[1,4],[3,6],[-3,6],[-1,4],[-1,3],[-2,4],[-4,4],[-6,2],[-6,0],[-4,-2],[-2,-1],[-3,-2],[-3,-4]], color);
} else {
poly(ctx, [[0,-6],[1,-5],[1,-4],[3,-2],[5,0],[5,3],[3,4],[1,3],[1,4],[3,6],[-3,6],[-1,4],[-1,3],[-3,4],[-5,3],[-5,0],[-3,-2],[-1,-4],[-1,-5]], color);
}
ctx.restore();
}
function tuscanFigure(ctx, rank, name) {
const p = TUSCAN_COLORS[name], skin = '#e9b785', shade = '#bf805d', trim = '#e0b957';
const queen = rank === 'Donna', king = rank === 'Regio';
// Cornice sottile, terreno e silhouette originali, disegnati su una griglia 42×60.
rect(ctx, 3, 3, 36, 54, '#b6a48a'); rect(ctx, 3.5, 3.5, 35, 53, paper);
poly(ctx, [[4,51],[14,49],[26,50],[38,48],[38,56],[4,56]], p.ground);
if (queen) {
// Dama di profilo: diadema, manica a sbuffo, gonna a ventaglio e fazzoletto.
poly(ctx, [[21,11],[27,11],[29,14],[28,22],[31,26],[27,29],[19,24],[18,18]], ink);
poly(ctx, [[22,12],[26,12],[27,15],[25,19],[22,21],[20,18],[19,17],[21,16]], skin);
rect(ctx, 23, 12, 4, 2, '#70452c'); rect(ctx, 26, 14, 2, 7, '#70452c');
rect(ctx, 21, 15, 1, 1, ink); rect(ctx, 20, 18, 2, 1, shade);
poly(ctx, [[21,12],[21,10],[23,11],[24,9],[25,11],[27,10],[27,13]], trim);
rect(ctx, 24, 10, 1, 1, '#c12f35');
poly(ctx, [[21,21],[26,20],[30,24],[28,31],[31,39],[37,54],[12,54],[15,43],[18,33],[17,26]], ink);
poly(ctx, [[22,22],[26,21],[28,24],[26,31],[29,39],[35,53],[14,53],[17,42],[20,32],[19,26]], p.cloth);
poly(ctx, [[21,32],[25,32],[25,40],[31,52],[23,51],[19,46]], p.light);
poly(ctx, [[26,34],[29,41],[33,51],[30,48],[27,43]], trim);
poly(ctx, [[19,35],[18,45],[16,52],[20,52],[21,49],[19,47]], p.cloak);
poly(ctx, [[18,23],[22,25],[20,29],[16,31],[12,29],[13,26]], '#d8dfd7');
rect(ctx, 17, 25, 4, 2, '#94a7ad');
poly(ctx, [[14,28],[16,30],[12,33],[9,32],[10,30]], skin);
poly(ctx, [[9,31],[12,32],[10,35],[8,35],[6,40],[6,35]], '#fcf7df');
rect(ctx, 8, 34, 1, 3, '#94a7ad');
poly(ctx, [[20,29],[26,29],[27,31],[20,33],[18,32]], trim);
rect(ctx, 22, 23, 1, 3, trim); rect(ctx, 16, 54, 7, 1, ink);
} else if (king) {
// Regio anziano: corona sempre visibile, barba bianca e bordi d'ermellino.
poly(ctx, [[15,23],[27,22],[32,27],[36,54],[9,54],[11,31]], ink);
poly(ctx, [[15,24],[27,23],[30,28],[34,53],[11,53],[13,31]], p.cloak);
poly(ctx, [[18,27],[25,27],[27,52],[16,52]], p.cloth);
rect(ctx, 20, 33, 3, 18, p.light); rect(ctx, 16, 48, 11, 2, trim);
poly(ctx, [[15,24],[18,25],[16,34],[15,45],[12,52],[11,51],[13,33]], '#f6edcf');
poly(ctx, [[26,24],[28,25],[29,35],[32,51],[29,52],[26,35],[24,27]], '#f6edcf');
for (const [x,y] of [[14,29],[13,37],[12,46],[27,29],[28,38],[30,47]]) rect(ctx, x, y, 1, 2, ink);
poly(ctx, [[17,13],[24,12],[27,16],[26,23],[23,28],[18,26],[15,21],[15,16]], '#b9bfbb');
poly(ctx, [[18,14],[24,14],[25,17],[27,19],[24,20],[24,22],[19,22],[17,18]], skin);
rect(ctx, 23, 16, 1, 2, ink); rect(ctx, 18, 16, 1, 2, shade);
poly(ctx, [[16,18],[19,21],[22,20],[25,21],[24,25],[21,28],[18,25],[16,23]], '#f7f0d7');
rect(ctx, 21, 22, 3, 1, '#9ba8a3'); rect(ctx, 18, 22, 1, 3, '#c6cdc0');
poly(ctx, [[16,14],[15,8],[17,8],[18,11],[20,10],[20,7],[22,7],[22,10],[24,11],[25,8],[27,8],[26,14]], ink);
poly(ctx, [[17,13],[16,9],[17,9],[18,12],[21,11],[21,8],[22,12],[25,12],[26,9],[25,13]], trim);
rect(ctx, 17, 13, 9, 2, trim); rect(ctx, 21, 13, 2, 1, '#c12f35');
rect(ctx, 33, 23, 1, 30, '#9b6a29'); rect(ctx, 33, 24, 1, 27, trim);
poly(ctx, [[33,19],[35,21],[35,23],[33,25],[31,23],[31,21]], trim);
rect(ctx, 33, 21, 1, 2, '#fff1ae'); rect(ctx, 31, 32, 4, 3, skin);
rect(ctx, 10, 33, 4, 3, skin); rect(ctx, 15, 53, 6, 2, ink); rect(ctx, 24, 53, 6, 2, ink);
} else {
// Gobbo giovane, senza corona: berretto morbido, mantello corto e mazza.
poly(ctx, [[15,23],[26,22],[31,27],[33,39],[27,43],[11,40],[10,28]], ink);
poly(ctx, [[15,24],[25,23],[29,27],[31,38],[27,41],[12,39],[12,28]], p.cloak);
poly(ctx, [[18,37],[23,38],[21,48],[20,53],[15,53],[17,46]], ink);
poly(ctx, [[24,37],[28,37],[28,46],[31,53],[26,53],[23,46]], ink);
rect(ctx, 18, 41, 2, 9, '#d3ad60'); rect(ctx, 25, 41, 2, 6, '#d3ad60');
rect(ctx, 16, 50, 4, 4, '#744531'); rect(ctx, 27, 50, 3, 4, '#744531');
rect(ctx, 13, 54, 7, 1, ink); rect(ctx, 26, 54, 7, 1, ink);
poly(ctx, [[17,12],[24,12],[27,16],[26,23],[22,25],[17,22],[15,18]], '#70452c');
poly(ctx, [[19,14],[24,14],[24,16],[26,18],[24,19],[24,21],[21,23],[18,20]], skin);
rect(ctx, 23, 16, 1, 2, ink); rect(ctx, 23, 20, 2, 1, shade);
rect(ctx, 17, 15, 2, 7, '#70452c'); rect(ctx, 16, 19, 2, 5, '#70452c');
poly(ctx, [[16,14],[17,11],[21,10],[25,11],[26,14],[28,15],[16,15]], p.cloth);
rect(ctx, 18, 13, 8, 1, p.light);
poly(ctx, [[18,24],[24,23],[27,27],[25,33],[28,40],[15,40],[17,33],[15,27]], ink);
poly(ctx, [[19,25],[23,24],[25,27],[23,33],[26,39],[17,39],[19,33],[17,27]], p.cloth);
rect(ctx, 20, 26, 2, 7, p.light); rect(ctx, 16, 34, 10, 2, trim);
poly(ctx, [[18,23],[21,25],[20,29],[17,25]], '#f6edcf');
rect(ctx, 21, 34, 2, 2, ink);
poly(ctx, [[10,27],[13,29],[12,33],[9,34],[8,32]], skin);
poly(ctx, [[27,28],[30,30],[28,34],[25,35],[24,33],[27,32]], skin);
poly(ctx, [[7,30],[9,30],[9,46],[11,50],[11,53],[6,53],[6,49],[7,46]], ink);
rect(ctx, 8, 33, 1, 17, '#ae783a'); rect(ctx, 7, 49, 3, 3, trim);
rect(ctx, 7, 31, 3, 2, skin);
}
ctx.save(); ctx.translate(9, 8); ctx.scale(0.65, 0.65); tuscanSuit(ctx, name, 0, 0); ctx.restore();
text(ctx, queen ? 'D' : king ? 'R' : 'G', 7, 16, 4, ink);
}
function tuscanCard(ctx, card) {
// Angoli arrotondati a gradini e un solo bordo sottile; nessuna scritta al piede.
const outline = [[2,0],[40,0],[42,2],[42,58],[40,60],[2,60],[0,58],[0,2]];
ctx.save(); ctx.translate(1, 2); poly(ctx, outline, '#15231d'); ctx.restore();
poly(ctx, outline, '#89765f');
poly(ctx, [[2,1],[40,1],[41,2],[41,58],[40,59],[2,59],[1,58],[1,2]], paper);
if (TUSCAN_PIPS[card.rank]) {
for (const [x, y] of TUSCAN_PIPS[card.rank]) tuscanSuit(ctx, card.suit, x, y, y > 30);
} else tuscanFigure(ctx, card.rank, card.suit);
}
export function drawCard(ctx, card, x, y, w = 84, h = 120, back = false) {
ctx.save(); ctx.translate(Math.round(x), Math.round(y)); ctx.scale(w / 42, h / 60);
if (!back && TUSCAN_COLORS[card.suit]) { tuscanCard(ctx, card); ctx.restore(); return; }
rect(ctx, 1, 2, 42, 60, '#15231d'); rect(ctx, 0, 0, 42, 60, ink);
rect(ctx, 1, 1, 40, 58, '#c5a576'); rect(ctx, 2, 2, 38, 56, paper);
if (back) regolo(ctx);
else {
const rank = { Asso: 'A', Fante: 'F', Cavallo: 'C', Re: 'R', Gobbo: 'G', Donna: 'D', Regio: 'R' }[card.rank] ?? card.rank;
text(ctx, rank, 4, 7, 6, ink); suit(ctx, card.suit, 34, 8);
if (PIPS[card.rank]) {
for (const [px, py] of PIPS[card.rank]) {
// Una lama per simbolo: le coppie si allungano e s'incrociano al centro.
if (card.suit === 'spade' && (px === 13 || px === 29)) {
ctx.save(); ctx.translate(px === 13 ? 18 : 24, py); ctx.scale(px === 13 ? 1.8 : -1.8, 1);
suit(ctx, card.suit, 0, 0); ctx.restore();
} else suit(ctx, card.suit, px, py);
}
} else figure(ctx, card.rank, card.suit);
text(ctx, card.suit.toUpperCase(), 21, 55, 3.7, ink, 'center');
}
ctx.restore();
}
function countTable(ctx, c, names) {
panel(ctx, 12, 32, 616, 318);
const rows = countRows(c), shown = rows.slice(0, c.count), totals = counted(c);
for (const [side, who] of ['player', 'cpu'].entries()) {
const x = 25 + side * 310;
text(ctx, names[who], x, 48, 7);
const cards = shown.filter(p => p.who === who);
cards.forEach(({ card }, i) => {
const cx = x + (i % 8) * 36, cy = 64 + Math.floor(i / 8) * 49;
drawCard(ctx, card, cx, cy, 30, 40);
text(ctx, '+' + card.points, cx + 15, cy + 44, 5, '#ffe09b', 'center');
});
text(ctx, `${totals[who]} punti`, x, 321, 10, '#ffe09b');
}
const last = shown.at(-1);
if (c.step === 'count' && last) {
panel(ctx, 270, 115, 100, 140); drawCard(ctx, last.card, 278, 123);
}
text(ctx, c.step === 'result' ? c.result.winner ? `Vince ${names[c.result.winner]}!` : '60 a 60. Patta!' : last ? `${cardName(last.card)}: ${last.card.points} punti` : 'Si contano tutte le carte…', 320, 339, 6, paper, 'center');
}
// Il tavolino dell'oratorio: legno, panno verde e il titolo. Lo usa anche il tressette.
export function drawTable(ctx, title) {
rect(ctx, 0, 0, 640, 360, '#573720');
for (let y = 0; y < 360; y += 24) {
rect(ctx, 0, y, 640, 2, '#38271e');
for (let x = 0; x < 640; x += 53) rect(ctx, x + y % 17, y + 6, 29, 1, '#72462a');
}
rect(ctx, 17, 27, 606, 329, '#b28949'); rect(ctx, 20, 30, 600, 323, '#234c40');
for (let i = 0; i < 600; i++) rect(ctx, 23 + (i * 73) % 590, 33 + (i * 41) % 315, 1, 1, '#2a5143');
text(ctx, title, 320, 15, 9, '#ffe0a0', 'center');
}
export function drawBriscola(ctx, c, def) {
ctx.save(); ctx.imageSmoothingEnabled = false;
drawTable(ctx, 'BRISCOLA ALL’ORATORIO');
if (c.step === 'count' || c.step === 'result') { countTable(ctx, c, def.names); ctx.restore(); return; }
text(ctx, def.names.cpu, 320, 38, 6, paper, 'center');
c.hands.cpu.forEach((_, i) => drawCard(ctx, null, 229 + i * 62, 49, 52, 74, true));
// La briscola sporge sotto il mazzo, girata di traverso: più di metà resta scoperta, anche l'asso che ha
// un seme solo nel mezzo (le toscane non hanno numeri negli angoli).
if (c.stock.length) {
ctx.save(); ctx.translate(154, 150); ctx.rotate(Math.PI / 2);
drawCard(ctx, c.trumpCard, 0, 0, 64, 92); ctx.restore();
if (c.stock.length > 1) {
for (let i = Math.min(5, Math.ceil(c.stock.length / 7)); i >= 0; i--)
drawCard(ctx, null, 38 + i, 116 - i, 58, 83, true);
}
}
text(ctx, `Mazzo: ${c.stock.length}`, 34, 243, 7);
text(ctx, 'Briscola:', 34, 263, 7); // a 6 punti la B sembra una D
text(ctx, c.trump.toUpperCase(), 34, 278, 7, '#ffe09b');
for (const [i, who] of ['cpu', 'player'].entries()) {
const y = 74 + i * 183;
if (c.taken[who].length) for (let n = 0; n < Math.min(4, c.taken[who].length / 2); n++) drawCard(ctx, null, 526 + n * 2, y - n * 2, 42, 60, true);
text(ctx, 'Prese', 550, y + 72, 6, paper, 'center');
}
c.table.forEach((p, i) => {
// La carta vola dalla mano di chi la gioca; finita la presa, tutte e due volano sulle prese di chi ha preso.
const spot = [242 + i * 86, 130, 64, 92];
const hand = p.who === 'player' ? [HAND.x + (p.from ?? 1) * HAND.gap, HAND.y, HAND.w, HAND.h] : [229 + (p.from ?? 1) * 62, 49, 52, 74];
let r = spot;
if (i === c.table.length - 1 && c.step === 'play' || c.step === 'trick' && i === 1 && c.t < PLAY_TIME) r = flight(hand, spot, c.t / PLAY_TIME);
if (c.step === 'trick' && c.t > TRICK_HOLD) r = flight(spot, [526, c.last.winner === 'cpu' ? 74 : 257, 42, 60], (c.t - TRICK_HOLD) / (TRICK_TIME - TRICK_HOLD - 0.05));
drawCard(ctx, p.card, ...r);
});
c.hands.player.forEach((card, i) => {
const x = HAND.x + i * HAND.gap;
if (canPlay(c) && c.selected === i) rect(ctx, x - 3, HAND.y - 3, HAND.w + 6, HAND.h + 6, '#ffdb76');
drawCard(ctx, card, x, HAND.y, HAND.w, HAND.h);
text(ctx, String(i + 1), x + HAND.w / 2, 221, 7, '#fff0cd', 'center');
});
ctx.restore();
}