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
126 lines
9.2 KiB
JavaScript
126 lines
9.2 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { DECK_CARDS, COIN, newScopa, captures, primiera, tally, play, take, cancel, select, nudge, update, advance, finished, observation, unseen, choose, canPlay, status, TARGET } from '../src/engine/scopa.js';
|
|
import { simulate, calibrate } from '../tools/calibrate_scopa.mjs';
|
|
|
|
const card = (suit, rank, deck = 'toscane') => DECK_CARDS[deck].find(a => a.suit === suit && a.rank === rank);
|
|
|
|
test('valori: Asso 1, numeri, gobbo 8, donna 9, regio 10; primiera 7 = 21, 6 = 18, Asso 16 … figure 10', () => {
|
|
assert.deepEqual(['Asso', '5', 'Gobbo', 'Donna', 'Regio'].map(r => card('cuori', r).value), [1, 5, 8, 9, 10]);
|
|
assert.deepEqual(['7', '6', 'Asso', '5', '4', '3', '2', 'Regio'].map(r => card('fiori', r).prime), [21, 18, 16, 15, 14, 13, 12, 10]);
|
|
assert.equal(COIN.toscane, 'mattoni'); assert.equal(COIN.napoletane, 'denari');
|
|
});
|
|
|
|
test('prese: la carta uguale ha la precedenza; se no ogni somma; più carte uguali, si sceglie', () => {
|
|
const t = [card('cuori', '3'), card('fiori', '4'), card('picche', '7'), card('mattoni', '2'), card('cuori', '5')];
|
|
assert.deepEqual(captures(t, card('mattoni', '7')).map(o => o.map(a => a.id)), [[card('picche', '7').id]], 'col 7 si prende il 7, non 3+4 o 2+5');
|
|
const sums = captures([card('cuori', '3'), card('fiori', '4'), card('mattoni', '2'), card('cuori', '5')], card('picche', '7'));
|
|
assert.equal(sums.length, 2, '3+4 oppure 2+5');
|
|
assert.deepEqual(captures([card('cuori', '3'), card('fiori', '3')], card('picche', '3')).length, 2);
|
|
assert.deepEqual(captures([card('cuori', '3')], card('picche', 'Donna')), []);
|
|
});
|
|
|
|
test('primiera: la migliore per seme, e senza un seme non si fa', () => {
|
|
const all = [card('cuori', '7'), card('mattoni', '6'), card('fiori', 'Asso'), card('picche', 'Regio'), card('picche', '2')];
|
|
assert.equal(primiera(all, 'toscane'), 21 + 18 + 16 + 12);
|
|
assert.equal(primiera(all.slice(0, 3), 'toscane'), 0);
|
|
});
|
|
|
|
test('conteggio: carte, mattoni, settebello, primiera e scope; pari non si dà il punto', () => {
|
|
const deck = DECK_CARDS.toscane, mine = deck.filter(a => a.suit === 'mattoni' || (a.suit === 'cuori' && a.value <= 2));
|
|
const theirs = deck.filter(a => !mine.includes(a));
|
|
const t = tally({ player: mine, cpu: theirs }, { player: 2, cpu: 0 }, 'toscane');
|
|
const row = k => t.rows.find(r => r.key === k);
|
|
assert.equal(row('carte').winner, 'cpu'); assert.equal(row('coin').winner, 'player'); assert.equal(row('coin').label, 'Mattoni');
|
|
assert.equal(row('settebello').winner, 'player'); assert.equal(row('primiera').winner, null, 'a nessuno ci sono tutti e quattro i semi');
|
|
assert.deepEqual(t.points, { player: 4, cpu: 1 });
|
|
const half = tally({ player: deck.slice(0, 20), cpu: deck.slice(20) }, { player: 0, cpu: 0 }, 'napoletane');
|
|
assert.equal(half.rows[0].winner, null, '20 a 20: le carte non le prende nessuno');
|
|
assert.equal(tally({ player: [card('denari', '7', 'napoletane')], cpu: [] }, { player: 0, cpu: 0 }, 'napoletane').rows[2].winner, 'player');
|
|
});
|
|
|
|
test('smazzata: 4 in tavola (mai tre re), tre a testa, la presa è obbligatoria, la scelta fra più prese', () => {
|
|
for (let s = 1; s < 60; s++) assert.ok(newScopa(s).table.filter(a => a.value === 10).length < 3);
|
|
const c = newScopa(3); c.current = 'player';
|
|
assert.equal(c.table.length, 4); assert.equal(c.hands.player.length, 3); assert.equal(c.stock.length, 30);
|
|
c.table = [card('cuori', '3'), card('fiori', '4'), card('mattoni', '2'), card('cuori', '5')];
|
|
c.hands.player = [card('picche', '7'), card('picche', 'Regio')];
|
|
assert.ok(play(c, 0)); assert.equal(c.step, 'choose'); assert.equal(c.choice.options.length, 2);
|
|
assert.ok(nudge(c, 1)); assert.ok(cancel(c)); assert.equal(c.step, 'play'); assert.equal(c.hands.player.length, 2);
|
|
assert.ok(play(c, 0)); assert.ok(select(c, 1)); assert.ok(take(c));
|
|
assert.equal(c.step, 'take'); assert.equal(c.table.length, 2); assert.equal(c.taken.player.length, 3);
|
|
});
|
|
|
|
test('scopa: chi pulisce la tavola fa un punto, ma non con l\'ultima carta della smazzata', () => {
|
|
const c = newScopa(4); c.current = 'player'; c.table = [card('cuori', '3'), card('fiori', '4')];
|
|
c.hands.player = [card('picche', '7'), card('picche', '2')];
|
|
assert.ok(play(c, 0)); assert.equal(c.scope.player, 1); assert.equal(c.last.scopa, true); assert.match(status(c), /Scopa di Andrea/);
|
|
const d = newScopa(4); d.current = 'player'; d.stock = []; d.hands.cpu = []; d.table = [card('cuori', '3')];
|
|
d.hands.player = [card('picche', '3')];
|
|
assert.ok(play(d, 0)); assert.equal(d.scope.player, 0, 'all\'ultima non è scopa');
|
|
});
|
|
|
|
test('a fine smazzata le carte in tavola vanno all\'ultimo che ha preso; tutte le 40 carte si contano', () => {
|
|
const c = newScopa(5, { level: 'seria' });
|
|
while (c.step !== 'smazzata') {
|
|
if (canPlay(c)) { const m = choose(observation(c, 'player'), 'seria'); assert.ok(play(c, m.index, 'player', m.option)); }
|
|
update(c, 20);
|
|
}
|
|
assert.equal(c.taken.player.length + c.taken.cpu.length, 40);
|
|
assert.equal(c.table.length, 0);
|
|
const before = c.dealer;
|
|
if (!finished(c)) { advance(c); assert.notEqual(c.dealer, before); assert.equal(c.current, before, 'gioca per primo chi non fa le carte'); }
|
|
});
|
|
|
|
test('si va agli 11: a fine smazzata vince chi ne ha di più; pari, un\'altra; il risultato è subito definitivo', () => {
|
|
const c = newScopa(6); c.stock = []; c.hands = { player: [], cpu: [card('cuori', '4')] };
|
|
c.table = [card('fiori', '4')]; c.current = 'cpu'; c.score = { player: 9, cpu: 10 };
|
|
c.taken = { player: DECK_CARDS.toscane.filter(a => a.suit !== 'fiori' || a.value !== 4).slice(0, 30), cpu: [] };
|
|
assert.ok(play(c, 0, 'cpu')); update(c, 2);
|
|
assert.equal(c.step, 'result', 'senza passare dal riepilogo: uscire non cancella la vittoria');
|
|
assert.equal(c.result.winner, 'player');
|
|
const d = newScopa(6); d.step = 'smazzata'; assert.ok(advance(d)); assert.equal(d.step, 'play');
|
|
assert.ok(finished({ score: { player: TARGET, cpu: 9 } })); assert.ok(!finished({ score: { player: 12, cpu: 12 } }));
|
|
});
|
|
|
|
test('partite deterministiche col seme; l\'avversario vede solo la sua mano, la tavola e le prese', () => {
|
|
assert.deepEqual(simulate(31, 'male').score, simulate(31, 'male').score);
|
|
const c = newScopa(12), v = observation(c, 'cpu');
|
|
assert.equal(v.hands, undefined); assert.equal(v.stock, 30);
|
|
assert.equal(unseen(v).length, 33, 'mano dell\'altro e mazzo');
|
|
const other = structuredClone(c); other.hands.player.reverse(); other.stock.reverse();
|
|
assert.deepEqual(choose(observation(c, 'cpu'), 'male', 3), choose(observation(other, 'cpu'), 'male', 3));
|
|
});
|
|
|
|
test('l\'avversario prende il settebello e la scopa quando può, e non lascia scope facili', () => {
|
|
const v = { who: 'cpu', deck: 'toscane', hand: [card('cuori', '7'), card('fiori', 'Regio')], table: [card('mattoni', '7'), card('picche', '4')], taken: { player: [], cpu: [] }, opponentCount: 3, stock: 20, scope: { player: 0, cpu: 0 } };
|
|
const m = choose(v, 'seria'); assert.equal(v.hand[m.index].rank, '7', 'prende il settebello');
|
|
const w = { ...v, hand: [card('cuori', '7'), card('fiori', '4')], table: [card('picche', '3'), card('mattoni', '4')] };
|
|
const s = choose(w, 'male'); assert.equal(w.hand[s.index].rank, '7', 'scopa con 3+4');
|
|
const x = { ...v, hand: [card('cuori', '5'), card('fiori', 'Regio')], table: [card('picche', '2')] };
|
|
const d = choose(x, 'male'); assert.equal(x.hand[d.index].rank, 'Regio', 'col 5 lascerebbe 7 in tavola: meglio il regio (12)');
|
|
});
|
|
|
|
test('la primiera conta quel che migliora: col 6 si prende quello del seme dove si ha solo una figura (Codex)', () => {
|
|
const v = { who: 'cpu', deck: 'toscane', hand: [card('picche', '6')], table: [card('cuori', '6'), card('fiori', '6')],
|
|
taken: { player: [], cpu: [card('cuori', '7'), card('fiori', 'Regio')] }, opponentCount: 3, stock: 20, scope: { player: 0, cpu: 0 } };
|
|
for (const level of ['seria', 'male']) assert.equal(choose(v, level).captured[0].suit, 'fiori', level);
|
|
});
|
|
|
|
test('a mazzo finito «A fassi male» calcola il finale esatto: meglio perdere il settebello che regalare anche la scopa', () => {
|
|
const deck = DECK_CARDS.toscane, seen = [card('mattoni', '7'), card('cuori', '7'), card('fiori', '4'), card('picche', '3')];
|
|
// Andrea ha in mano l'ultimo 7. Posando il 4, Andrea prende 3+4 e fa scopa, e il settebello gli resta comunque;
|
|
// posando il settebello, Andrea lo prende col 7 ma niente scopa.
|
|
const v = { who: 'cpu', deck: 'toscane', hand: [card('fiori', '4'), card('mattoni', '7')], table: [card('picche', '3')], stock: 0,
|
|
taken: { player: deck.filter(a => !seen.includes(a)).slice(0, 18), cpu: deck.filter(a => !seen.includes(a)).slice(18) },
|
|
opponentCount: 1, scope: { player: 0, cpu: 0 }, lastTaker: 'player' };
|
|
assert.deepEqual(unseen(v).map(a => a.id), [card('cuori', '7').id], 'le carte dell\'altro sono quelle non viste');
|
|
assert.equal(v.hand[choose(v, 'male').index].rank, '7');
|
|
});
|
|
|
|
test('taratura: Alla bona < Seria < A fassi male', () => {
|
|
const runs = calibrate(60);
|
|
assert.ok(runs[0].playerPercent > runs[1].playerPercent, JSON.stringify(runs));
|
|
assert.ok(runs[1].playerPercent > runs[2].playerPercent, JSON.stringify(runs));
|
|
});
|