Giochi di carte dopo la revisione di Codex sui tre insieme; player dei video «Come si gioca» e primo copione
Esito che vale e si salva appena deciso, per tutti i minigiochi (anche tornando al titolo dalla schermata del risultato); briscola: uscire durante il conteggio non perde più la vittoria; tressette: si esce ai 21 col primo evento che ci porta, accusa compresa (prima vinceva sempre Andrea se ci arrivavano tutte e due), il carico che fa vincere si dà anche se è il 2, accuse che non escono dal tavolo, convenzioni scritte nell'aiuto; scopa: primiera valutata per quel che migliora, finale esatto a mazzo finito, presa sulla tavola al rilascio con scelta e conferma, frecce e Invio sempre sulla presa scelta. Tastiera: Invio e Spazio giocano sempre la carta scelta. Tavola descritta a parole. Classi CSS comuni ai tre giochi. Video: player nel gioco (pulsante «Guarda il video» nel «Come si gioca», compare quando il video esiste), video/narra.py per la voce del narratore, pezzi comuni dei copioni e il copione della briscola; scaletta in video/SCALETTA-CARTE.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1dxvtsZx8NE9fwjXUNDBr
This commit is contained in:
+45
-4
@@ -146,7 +146,7 @@ export function advance(c) {
|
||||
// La sola porta d'ingresso dell'avversario: la sua mano, la tavola e le carte prese (si son viste tutte).
|
||||
export function observation(c, who = 'cpu') {
|
||||
return { who, deck: c.deck, hand: [...c.hands[who]], table: [...c.table], taken: { player: [...c.taken.player], cpu: [...c.taken.cpu] },
|
||||
opponentCount: c.hands[other(who)].length, stock: c.stock.length, scope: { ...c.scope } };
|
||||
opponentCount: c.hands[other(who)].length, stock: c.stock.length, scope: { ...c.scope }, lastTaker: c.lastTaker };
|
||||
}
|
||||
// Le carte che non si sono viste: in mano all'altro o nel mazzo.
|
||||
export function unseen(v) {
|
||||
@@ -159,7 +159,11 @@ export function unseen(v) {
|
||||
export function score(v, card, captured, counting) {
|
||||
const coin = COIN[v.deck], last = !v.stock && v.hand.length === 1 && !v.opponentCount;
|
||||
const left = captured ? v.table.filter(a => !captured.includes(a)) : [...v.table, card];
|
||||
const worth = a => 1 + (a.suit === coin ? 1.5 : 0) + (isSettebello(a, v.deck) ? 12 : 0) + (a.value === 7 ? 3 : a.value === 6 ? 1.2 : a.value === 1 ? 0.8 : 0);
|
||||
// La primiera vale per quanto migliora la propria: un 7 di un seme dove si ha già un 7 non aggiunge niente,
|
||||
// la prima carta di un seme che manca aggiunge parecchio.
|
||||
const mine = v.taken[v.who] ?? [], best = suit => Math.max(0, ...mine.filter(a => a.suit === suit).map(a => a.prime));
|
||||
const prime = a => Math.max(0, a.prime - best(a.suit)) / 4 + (best(a.suit) ? 0 : 1);
|
||||
const worth = a => 1 + (a.suit === coin ? 1.5 : 0) + (isSettebello(a, v.deck) ? 12 : 0) + prime(a);
|
||||
let value = captured ? [card, ...captured].reduce((n, a) => n + worth(a), 0) + (!left.length && !last ? 20 : 0) : -worth(card) * 0.6;
|
||||
const sum = left.reduce((n, a) => n + a.value, 0);
|
||||
if (left.length && sum <= 10 && v.opponentCount) {
|
||||
@@ -181,11 +185,46 @@ export function moves(v) {
|
||||
});
|
||||
}
|
||||
// «Alla bona» gioca a caso; «Seria» e «A fassi male» scelgono la mossa che vale di più.
|
||||
// Taratura (tools/calibrate_scopa.mjs, 300 partite): Andrea vince 92% contro «Alla bona», 64% contro «Seria», 52% contro «A fassi male».
|
||||
// Taratura (tools/calibrate_scopa.mjs, 300 partite): Andrea vince 96% contro «Alla bona», 76% contro «Seria», 50% contro «A fassi male».
|
||||
// A mazzo finito le carte dell'altro sono proprio quelle non viste: si calcola il finale esatto, fino al conteggio
|
||||
// (carte, mattoni, settebello, primiera, scope, e la tavola all'ultimo che prende).
|
||||
export function endgame(v) {
|
||||
const me = v.who, them = other(me), memo = new Map();
|
||||
const final = (taken, scope, table, last) => {
|
||||
const all = { player: [...taken.player], cpu: [...taken.cpu] };
|
||||
if (last) all[last].push(...table);
|
||||
const t = tally(all, scope, v.deck);
|
||||
return t.points[me] - t.points[them];
|
||||
};
|
||||
const solve = (hands, table, taken, scope, last, turn) => {
|
||||
if (!hands.player.length && !hands.cpu.length) return final(taken, scope, table, last);
|
||||
const key = `${turn}|${hands[me].map(a => a.id)}|${hands[them].map(a => a.id)}|${table.map(a => a.id).sort()}|${last}|${taken.player.length}|${scope.player}${scope.cpu}`;
|
||||
if (memo.has(key)) return memo.get(key);
|
||||
let best = turn === me ? -Infinity : Infinity;
|
||||
for (const m of moves({ hand: hands[turn], table })) {
|
||||
const r = step(hands, table, taken, scope, last, turn, m);
|
||||
const value = solve(...r);
|
||||
best = turn === me ? Math.max(best, value) : Math.min(best, value);
|
||||
}
|
||||
memo.set(key, best); return best;
|
||||
};
|
||||
const step = (hands, table, taken, scope, last, turn, m) => {
|
||||
const card = hands[turn][m.index], rest = { ...hands, [turn]: hands[turn].filter((_, i) => i !== m.index) };
|
||||
if (!m.captured) return [rest, [...table, card], taken, scope, last, other(turn)];
|
||||
const ids = new Set(m.captured.map(a => a.id)), left = table.filter(a => !ids.has(a.id));
|
||||
const end = !rest.player.length && !rest.cpu.length;
|
||||
return [rest, left, { ...taken, [turn]: [...taken[turn], card, ...m.captured] },
|
||||
!left.length && !end ? { ...scope, [turn]: scope[turn] + 1 } : scope, turn, other(turn)];
|
||||
};
|
||||
const hands = { [me]: v.hand, [them]: unseen(v) };
|
||||
const start = [hands, v.table, v.taken, v.scope ?? { player: 0, cpu: 0 }, v.lastTaker ?? null, me];
|
||||
return moves(v).map(m => ({ m, value: solve(...step(...start, m)) })).reduce((a, b) => b.value > a.value ? b : a).m;
|
||||
}
|
||||
export function choose(v, level = 'seria', seed = 1) {
|
||||
const all = moves(v);
|
||||
if (level === 'bona') return all[Math.floor(rng(seed)() * all.length)];
|
||||
const counting = level === 'male';
|
||||
if (counting && !v.stock && unseen(v).length === v.opponentCount) return endgame(v);
|
||||
return all.reduce((a, b) => score(v, v.hand[b.index], b.captured, counting) > score(v, v.hand[a.index], a.captured, counting) ? b : a);
|
||||
}
|
||||
export function update(c, dt) {
|
||||
@@ -206,5 +245,7 @@ export function status(c, names = { player: 'Andrea', cpu: 'Il giocatore' }) {
|
||||
const l = c.last;
|
||||
return l.scopa ? `Scopa di ${names[l.who]}!` : l.captured.length ? `${names[l.who]} prende con ${cardName(l.card)}.` : `${names[l.who]} posa ${cardName(l.card)}.`;
|
||||
}
|
||||
return c.current === 'player' ? 'Tocca una carta per giocarla. Frecce o 1–2–3 scelgono, Invio gioca.' : `${names.cpu} pensa…`;
|
||||
// A parole anche la tavola, per chi non vede il disegno.
|
||||
const table = `In tavola: ${c.table.map(cardName).join(', ') || 'niente'}.`;
|
||||
return c.current === 'player' ? `${table} Tocca una carta per giocarla. Frecce o 1–2–3 scelgono, Invio gioca.` : `${table} ${names.cpu} pensa…`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user