Tressette ai 21 all'oratorio: in quattro a coppie, Andrea e il socio contro il giocatore di carte e l'altro. Obbligo di seme, punti in terzi e ultima presa, accuse (napoletana, tre o quattro Assi, 2 o 3), segni onesti di chi esce (busso, striscio, volo) coi loro suoni, si va ai 21 smazzata dopo smazzata. Avversari «Alla bona», «Seria» (gioca a regola ma non conta) e «A fassi male» (conta, legge semi finiti, accuse e busso del socio). Tavolo a quattro, pulsanti dei segni e della mano, tastiera B/S/V; test, taratura e prova nel browser

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A1dxvtsZx8NE9fwjXUNDBr
This commit is contained in:
2026-10-26 15:14:46 +01:00
co-authored by Claude Opus 5.5
parent fef236dc54
commit e8152563d8
21 changed files with 803 additions and 11 deletions
+61 -6
View File
@@ -12,6 +12,8 @@ import * as bocce from './engine/bocce.js';
import * as biliardo from './engine/biliardo.js';
import * as briscola from './engine/briscola.js';
import { cardAt } from './engine/briscola-render.js';
import * as tressette from './engine/tressette.js';
import { cardAt as tressetteAt } from './engine/tressette-render.js';
import { VERSION, versioned } from './version.js';
const $ = id => document.getElementById(id);
@@ -401,9 +403,12 @@ canvas.addEventListener('pointerdown', e => {
if (!running || e.button > 0) return;
// Nel primo piano un tocco sfoglia (o salta la battuta): gli oggetti della scena dietro non si toccano.
if (game.closeup) {
if (story.closeups[game.closeup.id].type === 'briscola') {
const deck = CARD_AT[story.closeups[game.closeup.id].type];
if (deck) {
if (game.skip()) return;
press = { card: cardAt(game.closeup, scenePoint(e)), state: game.closeup };
// Tressette: fra una smazzata e l'altra un tocco va avanti.
if (tressette.advance(game.closeup)) return;
press = { card: deck(game.closeup, scenePoint(e)), state: game.closeup, at: deck };
canvas.setPointerCapture(e.pointerId);
return;
}
@@ -425,7 +430,7 @@ canvas.addEventListener('pointerup', e => {
if (!press) return;
if (press.card !== undefined) {
const p = press; press = null;
if (running && game.closeup === p.state && p.card >= 0 && cardAt(game.closeup, scenePoint(e)) === p.card) game.closeupPlay(p.card);
if (running && game.closeup === p.state && p.card >= 0 && p.at(game.closeup, scenePoint(e)) === p.card) game.closeupPlay(p.card);
return;
}
if (press.closeup) { press = null; if (running && !game.closeupRelease() && !game.skip()) game.closeupTap(); return; }
@@ -458,6 +463,7 @@ canvas.addEventListener('pointerleave', () => { mouse = null; });
// Comandi dei minigiochi separati dai verbi: carte oppure mira, forza e lancio.
const MINI = {
tressette: { ...tressette, back: 'Torna all’oratorio', label: 'Tressette: tocca una carta per giocarla. Frecce scelgono, Invio gioca; B, S, V fanno busso, striscio e volo. Esc esce.' },
briscola: { ...briscola, back: 'Torna all’oratorio', label: 'Briscola: tocca una carta per giocarla. Frecce o 1, 2, 3 scelgono; Invio gioca. Esc esce.' },
bocce: { ...bocce, nudge: (c, dir) => bocce.aim(c, c.aim.u, c.aim.w + dir * 0.3), back: 'Torna al pallaio', label: 'Bocce: tocca il campo o il dettaglio per mirare. Regola la forza e premi Lancia.' },
biliardo: { ...biliardo, back: 'Torna al bar', label: 'Biliardo: clic o tocco fissa la mira. Frecce per affinarla, Maiusc per passi più piccoli. Regola la forza e premi Tira.' },
@@ -466,6 +472,15 @@ const miniState = () => running && game?.closeup && MINI[story.closeups[game.clo
const mini = () => MINI[story.closeups[game.closeup.id].type];
$('bocce-power').addEventListener('input', e => { const c = miniState(); if (c) mini().setPower?.(c, Number(e.target.value) / 100); });
$('bocce-throw').addEventListener('click', () => { const c = miniState(); if (c && !game.speech) mini().shoot?.(c); });
// Le carte da gioco: dove si tocca la carta sul tavolo, gioco per gioco.
const CARD_AT = { briscola: cardAt, tressette: tressetteAt };
for (let i = 0; i < 10; i++) {
const button = $('tressette-card-' + i);
button.addEventListener('click', () => game?.closeupPlay(i));
button.addEventListener('focus', () => { if (game?.closeup?.id === 'tressette') tressette.select(game.closeup, i); });
}
for (const signal of tressette.SIGNALS) $('tressette-' + signal).addEventListener('click', () => { if (game?.closeup?.id === 'tressette') tressette.setSignal(game.closeup, signal); });
$('tressette-next').addEventListener('click', () => { if (game?.closeup?.id === 'tressette') tressette.advance(game.closeup); });
for (let i = 0; i < 3; i++) {
const button = $('briscola-card-' + i);
button.addEventListener('click', () => game?.closeupPlay(i));
@@ -560,6 +575,25 @@ window.addEventListener('keydown', e => {
if (!$('map').hidden) { if (e.key === 'Escape' || e.key === 'p' || e.key === 'P') $('map').hidden = true; return; }
// Nel primo piano: Invio, spazio o freccia sfogliano (o saltano la battuta), Esc chiude il libro.
if (running && game.closeup) {
if (story.closeups[game.closeup.id].type === 'tressette') {
const inControls = e.target.closest?.('#tressette-controls'), c = game.closeup;
if (e.repeat && ['Enter', ' '].includes(e.key)) { e.preventDefault(); return; }
if (inControls && ['BUTTON', 'SUMMARY'].includes(e.target.tagName) && ['Enter', ' '].includes(e.key)) return;
const key = e.key.toLowerCase(), signal = { b: 'busso', s: 'striscio', v: 'volo' }[key];
if (['arrowleft', 'arrowright', 'arrowup', 'arrowdown', 'enter', ' '].includes(key) || /^[0-9]$/.test(key) || signal) {
e.preventDefault();
if (e.repeat || game.skip()) return;
if (signal) tressette.setSignal(c, signal);
else if (/^[0-9]$/.test(key)) tressette.select(c, (Number(key) + 9) % 10);
else if (key.startsWith('arrow')) tressette.nudge(c, ['arrowleft', 'arrowup'].includes(key) ? -1 : 1);
else if (c.step === 'result') game.closeupClose();
else if (!tressette.advance(c) && key === 'enter') game.closeupPlay();
return;
}
if (e.key === 'Escape') game.closeupClose();
else if (key === 'm') toggleMute();
return;
}
if (story.closeups[game.closeup.id].type === 'briscola') {
const inControls = e.target.closest?.('#briscola-controls');
if (e.repeat && ['Enter', ' '].includes(e.key)) { e.preventDefault(); return; }
@@ -666,12 +700,33 @@ function updateUi() {
updateChoices();
updateSpeech();
const mg = miniState() && mini(), def = mg && story.closeups[c.id];
const cards = def?.type === 'briscola';
const cards = def?.type === 'briscola' || def?.type === 'tressette';
$('bocce-controls').hidden = !mg || cards;
$('bocce-controls').inert = !running;
$('briscola-controls').hidden = !cards;
$('briscola-controls').hidden = def?.type !== 'briscola';
$('briscola-controls').inert = !running;
if (cards) {
$('tressette-controls').hidden = def?.type !== 'tressette';
$('tressette-controls').inert = !running;
if (def?.type === 'tressette') {
$('panel').hidden = $('choices').hidden = true;
const line = tressette.status(c, def.seats);
if ($('tressette-status').textContent !== line) $('tressette-status').textContent = line;
const mine = tressette.canPlay(c) && !game.speech, ok = mine ? tressette.legal(c.hands[0], c.table) : [];
for (let i = 0; i < 10; i++) {
const button = $('tressette-card-' + i), card = c.hands[0][i];
const label = card ? `${(i + 1) % 10}. ${tressette.cardName(card)}` : '—';
if (button.textContent !== label) button.textContent = label;
button.hidden = !card;
button.disabled = !ok.includes(i);
button.setAttribute('aria-pressed', String(c.selected === i && !!card));
}
for (const signal of tressette.SIGNALS) {
$('tressette-' + signal).disabled = !mine || c.table.length > 0;
$('tressette-' + signal).setAttribute('aria-pressed', String(c.signal === signal));
}
$('tressette-next').hidden = c.step !== 'smazzata';
}
if (def?.type === 'briscola') {
$('panel').hidden = $('choices').hidden = true;
const line = briscola.status(c, def.names);
if ($('briscola-status').textContent !== line) $('briscola-status').textContent = line;