Progetto:BridgeShape API: differenze tra le versioni
| [versione verificata] | [versione verificata] |
Documentazione API BridgeShape per generatori di smazzate |
Link a Editor e Laboratorio |
||
| (4 versioni intermedie di uno stesso utente non sono mostrate) | |||
| Riga 1: | Riga 1: | ||
Documentazione BridgeShape: [[Progetto:BridgeShape Motore e frontend|Motore e frontend]] · [[Progetto:BridgeShape Ricette DSL|Ricette DSL]] · [[Progetto:BridgeShape Smazzate di gioco|Smazzate di gioco]] · [[Progetto:BridgeShape Editor e Laboratorio|Editor e Laboratorio]] | |||
= BridgeShape — API per Generatori di Smazzate = | = BridgeShape — API per Generatori di Smazzate = | ||
| Riga 10: | Riga 10: | ||
Ogni pagina wiki di convenzione su BridgePedia può definire nel template | Ogni pagina wiki di convenzione su BridgePedia può definire nel template | ||
<code>{{Convenzione}}</code> un campo <code>shape_constraints</code> che descrive, in un formato | <code><nowiki>{{Convenzione}}</nowiki></code> un campo <code><nowiki>shape_constraints</nowiki></code> che descrive, in un formato | ||
DSL compatto, i vincoli di distribuzione delle mani dei 4 giocatori al tavolo. | DSL compatto, i vincoli di distribuzione delle mani dei 4 giocatori al tavolo. | ||
L'estensione '''BridgeShape''' espone un'API REST/JSON per leggere questi | L'estensione '''BridgeShape''' espone un'API REST/JSON per leggere questi | ||
vincoli in modo strutturato. | vincoli in modo strutturato. | ||
---- | |||
== API Endpoint == | == API Endpoint == | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
GET /api.php?action=bridgeshape&shapeaction=ACTION&format=json | GET /api.php?action=bridgeshape&shapeaction=ACTION&format=json | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Riga 25: | Riga 27: | ||
{| class="wikitable" | {| class="wikitable" | ||
! Azione | ! Azione | ||
! Descrizione | |||
! Parametri | |||
|- | |- | ||
| <code>get</code> | | <code><nowiki>get</nowiki></code> | ||
| Vincoli per una singola convenzione | |||
| <code><nowiki>pageid</nowiki></code> oppure <code><nowiki>title</nowiki></code> | |||
|- | |- | ||
| <code>list</code> | | <code><nowiki>list</nowiki></code> | ||
| Elenco convenzioni con vincoli definiti | |||
| — | |||
|- | |- | ||
| <code>listall</code> | | <code><nowiki>listall</nowiki></code> | ||
| Elenco tutte le convenzioni (con/senza vincoli) | |||
| — | |||
|- | |- | ||
| <code>validate</code> | | <code><nowiki>validate</nowiki></code> | ||
| Valida una stringa DSL senza bisogno di una pagina wiki | |||
| <code><nowiki>dsl</nowiki></code> | |||
|} | |} | ||
| Riga 51: | Riga 63: | ||
curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=validate&dsl=opener+H:5%2B+hcp:12-21&format=json" | curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=validate&dsl=opener+H:5%2B+hcp:12-21&format=json" | ||
</syntaxhighlight> | </syntaxhighlight> | ||
---- | |||
== Formato risposta JSON == | == Formato risposta JSON == | ||
=== <code>shapeaction=get</code> === | === <code><nowiki>shapeaction=get</nowiki></code> === | ||
<syntaxhighlight lang="json"> | <syntaxhighlight lang="json"> | ||
| Riga 89: | Riga 103: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== <code>shapeaction=list</code> === | '''Descrizioni leggibili.''' Insieme a <code><nowiki>scenarios</nowiki></code>, <code><nowiki>get</nowiki></code> e <code><nowiki>validate</nowiki></code> restituiscono | ||
<code><nowiki>descriptions</nowiki></code> (stesso ordine): per ogni scenario <code><nowiki>label</nowiki></code>, <code><nowiki>positions</nowiki></code> | |||
(per posizione, elenco di frasi già tradotte, es. <code><nowiki>"♠: ha K J 10 (e non A Q 9)"</nowiki></code>), | |||
<code><nowiki>positionLabels</nowiki></code> (nome del ruolo, es. "Attaccante" se la pagina ha <code><nowiki>shape_roles</nowiki></code>; | |||
altrimenti "Apertore (chi usa la convenzione)", "Compagno", ...) e <code><nowiki>fit</nowiki></code>. | |||
Parametro opzionale <code><nowiki>lang</nowiki></code> (it, en, ...; default lingua dell'API, di solito <code><nowiki>it</nowiki></code>). | |||
Serve a mostrare nell'interfaccia di Play '''perché''' la mano è fatta così. | |||
Nella posizione compare anche <code><nowiki>quiet: 1</nowiki></code> se è stata usata la macro <code><nowiki>quiet</nowiki></code>. | |||
=== <code><nowiki>shapeaction=list</nowiki></code> === | |||
<syntaxhighlight lang="json"> | <syntaxhighlight lang="json"> | ||
| Riga 109: | Riga 132: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
---- | |||
== Formato DSL == | == Formato DSL == | ||
Il DSL (Domain Specific Language) è la stringa scritta nel campo | Il DSL (Domain Specific Language) è la stringa scritta nel campo | ||
<code>shape_constraints</code> del template wiki. | <code><nowiki>shape_constraints</nowiki></code> del template wiki. | ||
=== Struttura === | === Struttura === | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
etichetta: vincoli posizione; vincoli posizione // etichetta: vincoli // ... | etichetta: vincoli posizione; vincoli posizione // etichetta: vincoli // ... | ||
</syntaxhighlight> | </syntaxhighlight> | ||
* <code>//</code> separa gli scenari (il generatore ne sceglie uno random o li presenta all'utente) | * '''<code><nowiki>//</nowiki></code>''' separa gli scenari (il generatore ne sceglie uno random o li presenta all'utente) | ||
* <code>;</code> separa le posizioni dentro uno scenario | * '''<code><nowiki>;</nowiki></code>''' separa le posizioni dentro uno scenario | ||
* Ogni scenario ha un''''etichetta''' opzionale seguita da <code>:</code> | * Ogni scenario ha un''''etichetta''' opzionale seguita da <code><nowiki>:</nowiki></code> | ||
* Ogni posizione inizia col nome del ruolo, seguito da vincoli separati da spazi | * Ogni posizione inizia col nome del ruolo, seguito da vincoli separati da spazi | ||
| Riga 129: | Riga 154: | ||
{| class="wikitable" | {| class="wikitable" | ||
! Nome | ! Nome | ||
! Ruolo | |||
|- | |- | ||
| <code>opener</code> | | <code><nowiki>opener</nowiki></code> | ||
| Chi apre o fa la prima licita convenzionale | |||
|- | |- | ||
| <code>responder</code> | | <code><nowiki>responder</nowiki></code> | ||
| Il compagno dell'opener | |||
|- | |- | ||
| <code>opp1</code> | | <code><nowiki>opp1</nowiki></code> | ||
| Avversario a sinistra dell'opener | |||
|- | |- | ||
| <code>opp2</code> | | <code><nowiki>opp2</nowiki></code> | ||
| Avversario a destra dell'opener (compagno di opp1) | |||
|} | |} | ||
| Riga 144: | Riga 174: | ||
==== Lunghezza seme (specifico) ==== | ==== Lunghezza seme (specifico) ==== | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
S:5+ — almeno 5 picche | S:5+ — almeno 5 picche | ||
H:0-1 — 0 o 1 cuori | H:0-1 — 0 o 1 cuori | ||
| Riga 151: | Riga 181: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Semi: <code>S</code> = Spades/Picche, <code>H</code> = Hearts/Cuori, <code>D</code> = Diamonds/Quadri, <code>C</code> = Clubs/Fiori | Semi: <code><nowiki>S</nowiki></code> = Spades/Picche, <code><nowiki>H</nowiki></code> = Hearts/Cuori, <code><nowiki>D</nowiki></code> = Diamonds/Quadri, <code><nowiki>C</nowiki></code> = Clubs/Fiori | ||
==== Lunghezza seme (wildcard) ==== | ==== Lunghezza seme (wildcard) ==== | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
x:0-1 — un seme qualsiasi (tra quelli non specificati) con 0-1 carte | x:0-1 — un seme qualsiasi (tra quelli non specificati) con 0-1 carte | ||
x:6+ — un seme non specificato con almeno 6 carte | x:6+ — un seme non specificato con almeno 6 carte | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Il generatore sceglie casualmente quale seme assegnare a <code>x</code> tra quelli | Il generatore sceglie casualmente quale seme assegnare a <code><nowiki>x</nowiki></code> tra quelli | ||
non già vincolati esplicitamente (S/H/D/C). | non già vincolati esplicitamente (S/H/D/C). | ||
==== HCP totali ==== | ==== HCP totali ==== | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
hcp:15-17 — da 15 a 17 punti onore | hcp:15-17 — da 15 a 17 punti onore | ||
hcp:12+ — almeno 12 punti | hcp:12+ — almeno 12 punti | ||
| Riga 173: | Riga 203: | ||
==== HCP nel seme (specifico) ==== | ==== HCP nel seme (specifico) ==== | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
Shcp:3+ — almeno 3 HCP a picche | Shcp:3+ — almeno 3 HCP a picche | ||
Chcp:0 — 0 HCP a fiori (nessun onore) | Chcp:0 — 0 HCP a fiori (nessun onore) | ||
| Riga 181: | Riga 211: | ||
==== HCP nel seme (wildcard) ==== | ==== HCP nel seme (wildcard) ==== | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
xhcp:0 — 0 HCP nel seme wildcard (x) | xhcp:0 — 0 HCP nel seme wildcard (x) | ||
xhcp:0-1 — 0-1 HCP nel seme x | xhcp:0-1 — 0-1 HCP nel seme x | ||
| Riga 188: | Riga 218: | ||
==== Shape class ==== | ==== Shape class ==== | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
shape:balanced — distribuzione bilanciata (4333, 4432, 5332) | shape:balanced — distribuzione bilanciata (4333, 4432, 5332) | ||
shape:semi_balanced — semibilanciata (5422, 6322) | |||
shape:one_suiter — monocolore | shape:one_suiter — monocolore | ||
shape:two_suiter — bicolore | shape:two_suiter — bicolore | ||
| Riga 195: | Riga 226: | ||
shape:any — qualsiasi (default) | shape:any — qualsiasi (default) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== Onori esatti in un seme (<code><nowiki>S=KJT</nowiki></code>) ==== | |||
<syntaxhighlight lang="text"> | |||
S=KJT — in picche ha K, J e 10; NON ha A, Q, 9 (le carte basse 8-2 completano) | |||
S=KT9 — sequenza interna: K, 10, 9; NON ha A, Q, J | |||
H=Q — a cuori ha la Q come unico onore tra A K Q J 10 | |||
D=x — nessun onore a quadri (solo carte basse) | |||
</syntaxhighlight> | |||
Le lettere ammesse sono <code><nowiki>A K Q J T 9</nowiki></code> (anche <code><nowiki>10</nowiki></code>). La regola è: '''le carte elencate sono possedute, le altre tra A K Q J 10 9 NON lo sono'''; le carte | |||
dall'8 in giù sono libere e riempiono fino alla lunghezza del seme. Serve a | |||
descrivere attacchi (sequenza interrotta <code><nowiki>KJT</nowiki></code>, interna <code><nowiki>KT</nowiki></code>, sotto onore <code><nowiki>K</nowiki></code> o | |||
<code><nowiki>Q</nowiki></code>) e le carte del compagno. Si combina con la lunghezza (<code><nowiki>S:5 S=KJT</nowiki></code>). | |||
Nel JSON compare <code><nowiki>suit_cards</nowiki></code> nella posizione, es. <code><nowiki>{"S": "KJT", "H": ""}</nowiki></code> | |||
(stringa vuota = nessuna carta alta). Il generatore deve: | |||
* assegnare esattamente quegli onori alla mano e '''escluderli''' dalle altre mani del seme; gli onori non nominati da nessuna posizione sono distribuiti a caso, ma mai a una posizione con <code><nowiki>suit_cards</nowiki></code> per quel seme che non li elenca; | |||
* rispettare comunque <code><nowiki>suits</nowiki></code> e <code><nowiki>suit_hcp</nowiki></code> (la validazione segnala le contraddizioni, es. <code><nowiki>S=KQ Shcp:0-2</nowiki></code> o lo stesso onore a due mani). | |||
==== Pattern di mano in un seme (<code><nowiki>S~...</nowiki></code>, stile regex) ==== | |||
Descrive '''l'intera mano in quel seme''', dall'alto in basso, come una piccola | |||
espressione regolare ancorata (inizio e fine): | |||
<syntaxhighlight lang="text"> | |||
S~AKQ. — A, K, Q + una carta bassa (4 carte) | |||
S~A... — A + tre carte basse (4 carte) | |||
S~AJT9. — A, J, 10, 9 + una bassa | |||
S~AKQ.|A... — alternative: una OPPURE l'altra | |||
S~[KQ]x{2,3} — K o Q (come unico onore) + 2 o 3 carte basse | |||
S~KJT.* — K, J, 10 + qualunque numero di basse (anche zero) | |||
</syntaxhighlight> | |||
Elementi: | |||
* '''Carte alte''' <code><nowiki>A K Q J T 9</nowiki></code> (anche <code><nowiki>10</nowiki></code>), '''sempre in ordine decrescente''', ciascuna posseduta; quelle non nominate tra A K Q J 10 9 NON sono possedute. | |||
* '''Classe''' <code><nowiki>[KQ]</nowiki></code> = una carta tra quelle (si espande in alternative). | |||
* '''Carta bassa''' <code><nowiki>.</nowiki></code> (o <code><nowiki>x</nowiki></code>) = una carta qualsiasi tra 8 e 2; compare solo in fondo. Quantificatori: <code><nowiki>?</nowiki></code> (0-1), <code><nowiki>*</nowiki></code> (0 o più), <code><nowiki>+</nowiki></code> (1 o più), <code><nowiki>{n}</nowiki></code>, <code><nowiki>{n,m}</nowiki></code>. Più punti si sommano (<code><nowiki>...</nowiki></code> = tre basse). | |||
* '''Alternative''' <code><nowiki>|</nowiki></code>: il generatore ne sceglie una a caso per ogni mano. | |||
È la forma più espressiva di <code><nowiki>S=KJT</nowiki></code> (che resta valida): il pattern fissa anche | |||
la lunghezza (<code><nowiki>S~A...</nowiki></code> = 4 carte). Se c'è anche <code><nowiki>S:N</nowiki></code> i due vincoli devono | |||
essere compatibili (la validazione API segnala le incompatibilità). | |||
Nel JSON compare <code><nowiki>suit_patterns</nowiki></code> nella posizione, già '''normalizzato''' (classi | |||
espanse, quantificatori risolti), così il generatore non deve interpretare la | |||
regex: | |||
<syntaxhighlight lang="json"> | |||
"suit_patterns": { | |||
"S": [ | |||
{"cards": "AKQ", "low_min": 1, "low_max": 1}, | |||
{"cards": "A", "low_min": 3, "low_max": 3} | |||
] | |||
} | |||
</syntaxhighlight> | |||
<code><nowiki>cards</nowiki></code> = carte alte possedute (ordine A K Q J T 9); <code><nowiki>low_min</nowiki></code>/<code><nowiki>low_max</nowiki></code> = | |||
numero di carte basse (2-8), <code><nowiki>low_max: null</nowiki></code> = nessun limite. La lunghezza del | |||
seme è <code><nowiki>len(cards) + numero di basse</nowiki></code>. Il generatore sceglie una alternativa e | |||
rispetta anche <code><nowiki>suits</nowiki></code> e <code><nowiki>suit_hcp</nowiki></code>. Gli onori di <code><nowiki>cards</nowiki></code> vanno esclusi dalle altre | |||
mani; quelli non nominati nella mano vanno ad altri a caso. | |||
==== Avversari "quiet" (nessun intervento) ==== | |||
<syntaxhighlight lang="text"> | |||
opp1 quiet — solo per opp1/opp2 | |||
opp2 quiet:0-7 — come quiet, ma con HCP personalizzati | |||
</syntaxhighlight> | |||
<code><nowiki>quiet</nowiki></code> è una '''macro espansa dall'API''': nel JSON non compare mai la parola | |||
<code><nowiki>quiet</nowiki></code>, ma i vincoli già risolti. Chi legge l'API (il generatore) non deve | |||
implementare nulla di nuovo. Si espande in: | |||
<syntaxhighlight lang="text"> | |||
S:0-4 H:0-4 D:0-5 C:0-5 hcp:0-9 Shcp:0-3 Hhcp:0-3 Dhcp:0-4 Chcp:0-4 | |||
(con quiet:0-7 → hcp:0-7) | |||
</syntaxhighlight> | |||
Significato: gli avversari possono avere colori lunghi (così le mani sono | |||
varie), ma poveri di onori e con pochi punti: nessun colore da 6+ carte | |||
(niente apertura di sbarramento), nessun contro informativo, nessun intervento | |||
naturale credibile. Le distribuzioni ammesse sono quindi molte, ma con punti | |||
ridotti: il generatore ha meno margine sugli HCP della coppia dichiarante | |||
(40 punti totali: apertore+rispondente restano sopra ~22). | |||
I token espliciti scritti sulla stessa posizione '''sostituiscono''' i default, | |||
in qualsiasi ordine: <code><nowiki>opp1 quiet S:0-4</nowiki></code> lascia le picche 0-4 e tiene il resto. | |||
Su <code><nowiki>opener</nowiki></code>/<code><nowiki>responder</nowiki></code> la macro viene ignorata. | |||
==== Fit (vincolo cross-posizione) ==== | ==== Fit (vincolo cross-posizione) ==== | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
fit:H8+ — almeno 8 cuori totali tra i compagni | fit:H8+ — almeno 8 cuori totali tra i compagni | ||
fit:S9+ — almeno 9 picche totali tra i compagni | fit:S9+ — almeno 9 picche totali tra i compagni | ||
| Riga 207: | Riga 326: | ||
Il fit si applica tra '''compagni''': | Il fit si applica tra '''compagni''': | ||
* Se appare su <code>opener</code> o <code>responder</code>, o a livello di scenario → coppia dichiarante | * Se appare su <code><nowiki>opener</nowiki></code> o <code><nowiki>responder</nowiki></code>, o a livello di scenario → coppia dichiarante | ||
* Se appare su <code>opp1</code> o <code>opp2</code> → coppia difensiva | * Se appare su <code><nowiki>opp1</nowiki></code> o <code><nowiki>opp2</nowiki></code> → coppia difensiva | ||
=== Formato range === | === Formato range === | ||
{| class="wikitable" | {| class="wikitable" | ||
! Sintassi | ! Sintassi | ||
! Significato | |||
|- | |- | ||
| <code>5+</code> | | <code><nowiki>5+</nowiki></code> | ||
| Almeno 5 (min=5, max=null) | |||
|- | |- | ||
| <code>0-1</code> | | <code><nowiki>0-1</nowiki></code> | ||
| Da 0 a 1 (min=0, max=1) | |||
|- | |- | ||
| <code>3</code> | | <code><nowiki>3</nowiki></code> | ||
| Esattamente 3 (min=3, max=3) | |||
|- | |- | ||
| <code>12-21</code> | | <code><nowiki>12-21</nowiki></code> | ||
| Da 12 a 21 (min=12, max=21) | |||
|} | |} | ||
Nella risposta JSON, <code>max: null</code> significa "nessun limite superiore". | Nella risposta JSON, <code><nowiki>max: null</nowiki></code> significa "nessun limite superiore". | ||
=== Semi non menzionati === | === Semi non menzionati === | ||
I semi '''non vincolati''' e non coperti da <code>x:</code> sono distribuiti '''casualmente''' | I semi '''non vincolati''' e non coperti da <code><nowiki>x:</nowiki></code> sono distribuiti '''casualmente''' | ||
dal generatore, rispettando il vincolo che il totale delle carte per mano sia 13. | dal generatore, rispettando il vincolo che il totale delle carte per mano sia 13. | ||
---- | |||
== DSL versione 2 (<code><nowiki>dslVersion: 2</nowiki></code>) == | |||
Tutto ciò che sopra resta valido e '''invariato''': i campi v1 hanno lo stesso significato di prima. | |||
La v2 aggiunge sintassi nuova; '''ogni scenario ha nel JSON il campo <code><nowiki>requires</nowiki></code>''' (elenco di tag, | |||
vuoto se usa solo la v1; vedi sotto) così un motore parziale può rifiutare uno scenario con un | |||
errore esplicito invece di generare mani sbagliate. <code><nowiki>descriptions[].context</nowiki></code> è sempre presente | |||
(array, eventualmente vuoto). | |||
Le definizioni normative (cosa è una fermata, i punti totali, le perdenti...) e | |||
l'architettura del motore sono in '''<code><nowiki>DEAL_ENGINE_SPEC.md</nowiki></code>'''. | |||
=== Macro di scrittura (<code><nowiki>@NOME=v1,v2</nowiki></code>) — risolte dal parser, il motore non le vede === | |||
<syntaxhighlight lang="text"> | |||
@M=H,S 1${M}: opener ${M}:5+ hcp:12-21; responder ${M}:3+ | |||
</syntaxhighlight> | |||
Genera due scenari (<code><nowiki>1H</nowiki></code>, <code><nowiki>1S</nowiki></code>) sostituendo <code><nowiki>${M}</nowiki></code> ovunque, etichetta compresa. | |||
Più prefissi fanno il prodotto cartesiano (max 64 scenari): <code><nowiki>@M=H,S @N=15-17,12-14</nowiki></code>. | |||
I valori possono essere intervalli (<code><nowiki>hcp:${N}</nowiki></code>): servono anche come parametri del | |||
sistema (SA 15-17 contro 14-16). | |||
=== Semi variabili <code><nowiki>$a $b $c $d</nowiki></code> === | |||
Un seme "da scegliere" condiviso da più posizioni dello stesso scenario. Ogni | |||
variabile è un seme diverso dalle altre. Dominio dichiarato a livello di scenario | |||
(prima delle posizioni): | |||
<syntaxhighlight lang="text"> | |||
Landy: $a=M $b=any opener hcp:12-21 ; opp1 $a:5+ $b:4+ ; responder $a:0-2 | |||
</syntaxhighlight> | |||
<code><nowiki>$a=M</nowiki></code> nobili, <code><nowiki>$a=m</nowiki></code> minori, <code><nowiki>$a=any</nowiki></code> (default) qualunque, <code><nowiki>$a=SH</nowiki></code> insieme esplicito. | |||
Una variabile non dichiarata vale <code><nowiki>any</nowiki></code> (tutti i semi). Si usano al posto di <code><nowiki>S H D C</nowiki></code> in: <code><nowiki>$a:5+</nowiki></code> (lunghezza), <code><nowiki>$ahcp:</nowiki></code>, <code><nowiki>$a=KQ</nowiki></code>, <code><nowiki>$a~...</nowiki></code>, | |||
<code><nowiki>stop:$a</nowiki></code>, <code><nowiki>has:$aQ</nowiki></code>, <code><nowiki>kc:$a3</nowiki></code>, confronti (<code><nowiki>$a>$b</nowiki></code>), <code><nowiki>longest:$a</nowiki></code>. | |||
Differisce da <code><nowiki>x:</nowiki></code> (che vale per una sola posizione). | |||
JSON: lo stesso dizionario <code><nowiki>suits</nowiki></code>, <code><nowiki>suit_hcp</nowiki></code>, ... ma con chiave <code><nowiki>"$a"</nowiki></code>; | |||
a livello di scenario <code><nowiki>"vars": {"$a": ["S","H"]}</nowiki></code>. | |||
=== Controlli, carte chiave, carte precise === | |||
{| class="wikitable" | |||
! Token | |||
! Significato | |||
|- | |||
| <code><nowiki>aces:2</nowiki></code> <code><nowiki>kings:1-2</nowiki></code> <code><nowiki>queens:0</nowiki></code> | |||
| numero di assi / re / dame nella mano | |||
|- | |||
| <code><nowiki>controls:6+</nowiki></code> | |||
| controlli (A=2, K=1) | |||
|- | |||
| <code><nowiki>kc:H3</nowiki></code> <code><nowiki>kc:H2-3</nowiki></code> <code><nowiki>kc:$a3</nowiki></code> | |||
| carte chiave con atout H: i 4 assi + il Re di H (0-5) | |||
|- | |||
| <code><nowiki>has:HQ</nowiki></code> <code><nowiki>has:S10</nowiki></code> | |||
| possiede quella carta precisa | |||
|- | |||
| <code><nowiki>lacks:HQ</nowiki></code> | |||
| NON possiede quella carta | |||
|- | |||
| <code><nowiki>stop:S</nowiki></code> <code><nowiki>nostop:S</nowiki></code> | |||
| ha / non ha una fermata nel seme (definizione in SPEC) | |||
|} | |||
JSON: <code><nowiki>aces</nowiki></code>, <code><nowiki>kings</nowiki></code>, <code><nowiki>queens</nowiki></code>, <code><nowiki>controls</nowiki></code> (Range); <code><nowiki>keycards: {"H": Range}</nowiki></code>; | |||
<code><nowiki>cards_held: ["HQ"]</nowiki></code>, <code><nowiki>cards_not_held: ["SK"]</nowiki></code>; <code><nowiki>stoppers: {"S": 1, "H": 0}</nowiki></code> | |||
(1 = deve averla, 0 = non deve averla). | |||
=== Punti e perdenti === | |||
<code><nowiki>tp:</nowiki></code> punti totali (PO + 1 per ogni carta oltre la quarta in ogni seme), | |||
<code><nowiki>dp:</nowiki></code> punti distribuzione (PO + 3 chicane + 2 singolo + 1 doubleton), | |||
<code><nowiki>ltc:</nowiki></code> perdenti (Losing Trick Count). A livello di scenario: <code><nowiki>pairhcp:25+</nowiki></code> | |||
(PO della coppia dichiarante), <code><nowiki>defhcp:0-14</nowiki></code> (coppia avversaria), <code><nowiki>pairtp:26+</nowiki></code>. | |||
JSON: <code><nowiki>tp</nowiki></code>, <code><nowiki>dp</nowiki></code>, <code><nowiki>ltc</nowiki></code> (Range) nella posizione; <code><nowiki>pair_hcp: {"declarer": R, "defense": R}</nowiki></code>, | |||
<code><nowiki>pair_tp: {"declarer": R}</nowiki></code> nello scenario. | |||
=== Confronti tra semi e forme precise === | |||
<syntaxhighlight lang="text"> | |||
H>S H>=S H<S H<=S H==S $a>$b — confronto di lunghezze nella stessa mano | |||
longest:H — H è tra i semi più lunghi (pari merito ammesso) | |||
shape:5-4-x-x shape:5422 shape:54xx — lunghezze ordinate in modo decrescente, x = qualsiasi | |||
shape:semi_balanced — 5422 e 6322 (si aggiunge alle classi esistenti) | |||
</syntaxhighlight> | |||
JSON: <code><nowiki>compare: [{"left":"H","op":">=","right":"S"}]</nowiki></code>, <code><nowiki>longest: ["H"]</nowiki></code>, | |||
<code><nowiki>shape_pattern: "54xx"</nowiki></code>. | |||
=== Alternative e negazioni nella stessa mano: <code><nowiki>any(...)</nowiki></code> e <code><nowiki>not(...)</nowiki></code> === | |||
<syntaxhighlight lang="text"> | |||
opener any(S:4+ hcp:12+, H:4+) not(D:6+) | |||
</syntaxhighlight> | |||
<code><nowiki>any(A, B)</nowiki></code>: la mano soddisfa '''almeno una''' delle alternative (separate da virgola; | |||
ogni alternativa è una congiunzione di token). <code><nowiki>not(A, B)</nowiki></code>: la mano non soddisfa | |||
'''nessuna''' delle alternative. Si possono scrivere più gruppi (si combinano in AND); | |||
niente annidamenti. | |||
JSON: <code><nowiki>any: [[altA, altB], ...]</nowiki></code> (lista di gruppi; ogni alternativa è un oggetto | |||
con gli stessi campi di una posizione), <code><nowiki>not: [altA, altB]</nowiki></code>. | |||
=== Contesto dello scenario === | |||
Token prima delle posizioni: <code><nowiki>weight:3</nowiki></code> (frequenza relativa, 1-100, default 1), | |||
<code><nowiki>dealer:opener</nowiki></code>, <code><nowiki>vul:none|us|them|both</nowiki></code> (<code><nowiki>us</nowiki></code> = apertore+rispondente), | |||
<code><nowiki>auction:1SA,P,2C,P</nowiki></code> (licite, notazione BridgeCards; SA = NT), <code><nowiki>contract:4H</nowiki></code>, | |||
<code><nowiki>declarer:responder</nowiki></code>, <code><nowiki>lead:SK</nowiki></code>. | |||
JSON: <code><nowiki>weight</nowiki></code> (int) e <code><nowiki>context: {"dealer":..,"vul":..,"auction":[..],"contract":"4H","declarer":..,"lead":"SK"}</nowiki></code>. | |||
Il motore deve usare <code><nowiki>dealer</nowiki></code>/<code><nowiki>vul</nowiki></code> quando li espone (per la numerazione del tavolo) e, | |||
se c'è <code><nowiki>lead</nowiki></code>, la carta deve stare nella mano dell'attaccante (a sinistra del | |||
dichiarante) e rispettare i vincoli di quella mano. | |||
=== Tag <code><nowiki>requires</nowiki></code> === | |||
<code><nowiki>suit_cards</nowiki></code>, <code><nowiki>suit_patterns</nowiki></code>, <code><nowiki>vars</nowiki></code>, <code><nowiki>stoppers</nowiki></code>, <code><nowiki>card_presence</nowiki></code> (has/lacks), | |||
<code><nowiki>controls</nowiki></code> (aces/kings/queens/controls/kc), <code><nowiki>points</nowiki></code> (tp/dp/ltc), <code><nowiki>pair_points</nowiki></code>, | |||
<code><nowiki>compare</nowiki></code> (confronti, longest), <code><nowiki>shape_pattern</nowiki></code>, <code><nowiki>any_not</nowiki></code>, <code><nowiki>context</nowiki></code>, <code><nowiki>weight</nowiki></code>. | |||
Le macro <code><nowiki>@</nowiki></code> e <code><nowiki>quiet</nowiki></code> non compaiono: sono già espanse. | |||
=== Avvisi di parsing (<code><nowiki>warnings</nowiki></code>) === | |||
<code><nowiki>get</nowiki></code> e <code><nowiki>validate</nowiki></code> restituiscono <code><nowiki>warnings</nowiki></code> quando un frammento del DSL non è stato | |||
capito e quindi '''scartato''': nome di posizione sbagliato, token sconosciuto o con valore | |||
non valido (<code><nowiki>aces:5</nowiki></code>, <code><nowiki>S=Kx</nowiki></code>, <code><nowiki>shape:4-5-x-x</nowiki></code>), token di scenario dopo le posizioni, posizione | |||
definita due volte, <code><nowiki>quiet</nowiki></code> su opener/responder, gruppo <code><nowiki>any()</nowiki></code>/<code><nowiki>not()</nowiki></code> con alternative non | |||
valide (in tal caso '''l'intero gruppo è ignorato''', mai applicato a metà). <code><nowiki>validate.valid</nowiki></code> è 1 | |||
solo se non ci sono né <code><nowiki>errors</nowiki></code> né <code><nowiki>warnings</nowiki></code>. Un motore non deve generare se ci sono | |||
<code><nowiki>warnings</nowiki></code> o <code><nowiki>errors</nowiki></code>. | |||
Limiti (oltre i quali il parser avvisa e tronca): 60.000 caratteri di input, 200 scenari, 6 | |||
prefissi <code><nowiki>@</nowiki></code>, 64 valori per macro, 64 scenari per espansione, 8 gruppi <code><nowiki>any/not</nowiki></code> e 16 alternative | |||
per posizione. Le etichette non possono contenere <code><nowiki>:</nowiki></code>. | |||
---- | |||
== Struttura JSON di uno scenario == | == Struttura JSON di uno scenario == | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="typescript"> | ||
interface Scenario { | interface Scenario { | ||
label: string; // Etichetta (es. "1H-4C"), vuota se non specificata | label: string; // Etichetta (es. "1H-4C"), vuota se non specificata | ||
| Riga 243: | Riga 500: | ||
interface Position { | interface Position { | ||
suit_patterns?: { // Pattern regex-like normalizzati: {"S":[{"cards":"AKQ","low_min":1,"low_max":1}]} | |||
[suit: string]: { cards: string; low_min: number; low_max: number | null }[]; | |||
}; | |||
suit_cards?: { [suit: string]: string }; // Onori esatti, es. {"S":"KJT"}; "" = nessuna carta alta | |||
suits?: { // Vincoli lunghezza seme (solo semi esplicitati) | suits?: { // Vincoli lunghezza seme (solo semi esplicitati) | ||
[suit: string]: Range; // "S", "H", "D", "C" | [suit: string]: Range; // "S", "H", "D", "C" | ||
| Riga 267: | Riga 528: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
---- | |||
== Logica per il generatore == | == Logica per il generatore == | ||
| Riga 276: | Riga 539: | ||
# '''Scegli lo scenario''': random tra quelli disponibili, oppure lascia scegliere all'utente | # '''Scegli lo scenario''': random tra quelli disponibili, oppure lascia scegliere all'utente | ||
# '''Risolvi wildcard''': | # '''Risolvi wildcard''': | ||
#* Se una posizione ha <code>x:</code>, scegli un seme casuale tra quelli non esplicitamente vincolati | #* Se una posizione ha <code><nowiki>x:</nowiki></code>, scegli un seme casuale tra quelli non esplicitamente vincolati | ||
#* <code>xhcp:</code> si applica allo stesso seme scelto per <code>x:</code> | #* <code><nowiki>xhcp:</nowiki></code> si applica allo stesso seme scelto per <code><nowiki>x:</nowiki></code> | ||
# '''Risolvi fit''': | # '''Risolvi fit''': | ||
#* Se <code>fit.suit</code> è specificato, entrambi i compagni devono avere un totale >= <code>fit.min</code> in quel seme | #* Se <code><nowiki>fit.suit</nowiki></code> è specificato, entrambi i compagni devono avere un totale >= <code><nowiki>fit.min</nowiki></code> in quel seme | ||
#* Se <code>fit.suit_class</code> è "major", scegli tra H e S; se "minor", tra D e C | #* Se <code><nowiki>fit.suit_class</nowiki></code> è "major", scegli tra H e S; se "minor", tra D e C | ||
#* Se entrambi sono null, scegli un seme qualsiasi | #* Se entrambi sono null, scegli un seme qualsiasi | ||
#* <code>fit.pair</code> indica quale coppia: "declarer" (opener+responder) o "defense" (opp1+opp2) | #* <code><nowiki>fit.pair</nowiki></code> indica quale coppia: "declarer" (opener+responder) o "defense" (opp1+opp2) | ||
# '''Genera le mani''': | # '''Genera le mani''': | ||
#* Per ogni posizione, genera una mano che rispetti tutti i vincoli | #* Per ogni posizione, genera una mano che rispetti tutti i vincoli | ||
| Riga 288: | Riga 551: | ||
#* Il totale delle carte per mano deve essere esattamente 13 | #* Il totale delle carte per mano deve essere esattamente 13 | ||
#* Il totale delle carte per seme (tutte e 4 le mani) deve essere esattamente 13 | #* Il totale delle carte per seme (tutte e 4 le mani) deve essere esattamente 13 | ||
# '''Posizioni non menzionate''': se uno scenario non menziona <code>opp1</code>/<code>opp2</code>, distribuisci le carte rimanenti casualmente tra gli avversari | # '''Posizioni non menzionate''': se uno scenario non menziona <code><nowiki>opp1</nowiki></code>/<code><nowiki>opp2</nowiki></code>, distribuisci le carte rimanenti casualmente tra gli avversari | ||
=== Priorità dei vincoli === | === Priorità dei vincoli === | ||
| Riga 298: | Riga 561: | ||
# Shape class (shape:) — validazione della forma complessiva | # Shape class (shape:) — validazione della forma complessiva | ||
=== Gestione di <code>max: null</code> === | === Gestione di <code><nowiki>max: null</nowiki></code> === | ||
Quando <code>max</code> è <code>null</code>, non c'è limite superiore esplicito. I limiti fisici | Quando <code><nowiki>max</nowiki></code> è <code><nowiki>null</nowiki></code>, non c'è limite superiore esplicito. I limiti fisici | ||
del bridge si applicano comunque: | del bridge si applicano comunque: | ||
* Massimo 13 carte per seme (tutte e 4 le mani) | * Massimo 13 carte per seme (tutte e 4 le mani) | ||
| Riga 306: | Riga 569: | ||
* Massimo 10 HCP per seme (A+K+Q+J = 4+3+2+1) | * Massimo 10 HCP per seme (A+K+Q+J = 4+3+2+1) | ||
* Massimo 37 HCP per mano (raro ma teoricamente possibile con tutti gli onori) | * Massimo 37 HCP per mano (raro ma teoricamente possibile con tutti gli onori) | ||
---- | |||
== Esempi completi == | == Esempi completi == | ||
| Riga 312: | Riga 577: | ||
DSL nel template wiki: | DSL nel template wiki: | ||
<syntaxhighlight lang="text"> | |||
<syntaxhighlight lang=" | |||
1H-4C: opener H:5+ hcp:12-21; responder H:4+ C:0-1 Chcp:0 hcp:10-15 // | 1H-4C: opener H:5+ hcp:12-21; responder H:4+ C:0-1 Chcp:0 hcp:10-15 // | ||
1H-4D: opener H:5+ hcp:12-21; responder H:4+ D:0-1 Dhcp:0 hcp:10-15 // | 1H-4D: opener H:5+ hcp:12-21; responder H:4+ D:0-1 Dhcp:0 hcp:10-15 // | ||
| Riga 324: | Riga 588: | ||
=== Splinter (compatto con fit e wildcard) === | === Splinter (compatto con fit e wildcard) === | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
dopo 1H: fit:H8+ opener H:5+ hcp:12-21; responder x:0-1 xhcp:0 hcp:10-15 // | dopo 1H: fit:H8+ opener H:5+ hcp:12-21; responder x:0-1 xhcp:0 hcp:10-15 // | ||
dopo 1S: fit:S8+ opener S:5+ hcp:12-21; responder x:0-1 xhcp:0 hcp:10-15 | dopo 1S: fit:S8+ opener S:5+ hcp:12-21; responder x:0-1 xhcp:0 hcp:10-15 | ||
| Riga 331: | Riga 595: | ||
=== Stayman === | === Stayman === | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
fit a cuori: fit:H8+ opener hcp:15-17 S:2-4 H:2-4 D:2-5 C:2-5; responder H:4+ hcp:8+ // | fit a cuori: fit:H8+ opener hcp:15-17 S:2-4 H:2-4 D:2-5 C:2-5; responder H:4+ hcp:8+ // | ||
fit a picche: fit:S8+ opener hcp:15-17 S:2-4 H:2-4 D:2-5 C:2-5; responder S:4+ hcp:8+ | fit a picche: fit:S8+ opener hcp:15-17 S:2-4 H:2-4 D:2-5 C:2-5; responder S:4+ hcp:8+ | ||
| Riga 338: | Riga 602: | ||
=== Contro informativo === | === Contro informativo === | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
base: opener hcp:12-21 x:5+; opp1 hcp:12+ S:3+ H:3+ D:3+ C:3+; responder hcp:0-8 // | base: opener hcp:12-21 x:5+; opp1 hcp:12+ S:3+ H:3+ D:3+ C:3+; responder hcp:0-8 // | ||
con intervento: opener hcp:12-21 x:5+; opp1 hcp:12+ S:3+ H:3+ D:3+ C:3+; opp2 hcp:8+ x:5+ | con intervento: opener hcp:12-21 x:5+; opp1 hcp:12+ S:3+ H:3+ D:3+ C:3+; opp2 hcp:8+ x:5+ | ||
| Riga 345: | Riga 609: | ||
=== Preempt 3-a-livello === | === Preempt 3-a-livello === | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
opener H:7+ x:0-1 hcp:5-10 | opener H:7+ x:0-1 hcp:5-10 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Riga 351: | Riga 615: | ||
=== Overcall con fit avversari === | === Overcall con fit avversari === | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
overcall semplice: opener hcp:12-21 x:5+; opp1 hcp:10+ x:5+; opp2 fit:8+ // | overcall semplice: opener hcp:12-21 x:5+; opp1 hcp:10+ x:5+; opp2 fit:8+ // | ||
con barrage: opener hcp:12-21 x:5+; opp1 hcp:6-10 x:6+; opp2 fit:9+ | con barrage: opener hcp:12-21 x:5+; opp1 hcp:6-10 x:6+; opp2 fit:9+ | ||
</syntaxhighlight> | </syntaxhighlight> | ||
---- | |||
== Note implementative == | == Note implementative == | ||
* L'API è read-only, non richiede token CSRF | * L'API è read-only, non richiede token CSRF | ||
* L'accesso anonimo è abilitato di default (<code>BridgeShapeAllowAnonymous: true</code>) perché i dati sono pubblici e i tool esterni ne hanno bisogno | * L'accesso anonimo è abilitato di default (<code><nowiki>BridgeShapeAllowAnonymous: true</nowiki></code>) perché i dati sono pubblici e i tool esterni ne hanno bisogno | ||
* Il campo <code>raw</code> nella risposta <code>get</code> contiene la stringa DSL originale, utile per debug | * Il campo <code><nowiki>raw</nowiki></code> nella risposta <code><nowiki>get</nowiki></code> contiene la stringa DSL originale, utile per debug | ||
* L'azione <code>validate</code> permette di testare stringhe DSL senza creare pagine wiki | * L'azione <code><nowiki>validate</nowiki></code> permette di testare stringhe DSL senza creare pagine wiki | ||
* I calcoli HCP usano la scala standard: A=4, K=3, Q=2, J=1 | * I calcoli HCP usano la scala standard: A=4, K=3, Q=2, J=1 | ||
[[Categoria:Documentazione tecnica]] | [[Categoria:Documentazione tecnica]] | ||
Versione attuale delle 07:05, 28 ott 2026
Documentazione BridgeShape: Motore e frontend · Ricette DSL · Smazzate di gioco · Editor e Laboratorio
BridgeShape — API per Generatori di Smazzate
Documentazione tecnica per chi sviluppa un generatore di smazzate di test che legge i vincoli di distribuzione (shape constraints) dalle convenzioni di BridgePedia.
Panoramica
Ogni pagina wiki di convenzione su BridgePedia può definire nel template
{{Convenzione}} un campo shape_constraints che descrive, in un formato
DSL compatto, i vincoli di distribuzione delle mani dei 4 giocatori al tavolo.
L'estensione BridgeShape espone un'API REST/JSON per leggere questi vincoli in modo strutturato.
API Endpoint
<syntaxhighlight lang="text"> GET /api.php?action=bridgeshape&shapeaction=ACTION&format=json </syntaxhighlight>
Azioni disponibili
| Azione | Descrizione | Parametri |
|---|---|---|
get
|
Vincoli per una singola convenzione | pageid oppure title
|
list
|
Elenco convenzioni con vincoli definiti | — |
listall
|
Elenco tutte le convenzioni (con/senza vincoli) | — |
validate
|
Valida una stringa DSL senza bisogno di una pagina wiki | dsl
|
Esempi di chiamata
<syntaxhighlight lang="bash">
- Per titolo pagina
curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=get&title=Splinter&format=json"
- Per page ID
curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=get&pageid=42&format=json"
- Elenco pagine con vincoli
curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=list&format=json"
- Validare una stringa DSL
curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=validate&dsl=opener+H:5%2B+hcp:12-21&format=json" </syntaxhighlight>
Formato risposta JSON
shapeaction=get
<syntaxhighlight lang="json"> {
"shapeconstraints": {
"pageId": 42,
"raw": "1H-4C: opener H:5+ hcp:12-21; responder H:4+ C:0-1 Chcp:0 hcp:10-15 // ...",
"scenarios": [
{
"label": "1H-4C",
"positions": {
"opener": {
"suits": {
"H": { "min": 5, "max": null }
},
"hcp": { "min": 12, "max": 21 }
},
"responder": {
"suits": {
"H": { "min": 4, "max": null },
"C": { "min": 0, "max": 1 }
},
"suit_hcp": {
"C": { "min": 0, "max": 0 }
},
"hcp": { "min": 10, "max": 15 }
}
},
"fit": []
}
]
}
} </syntaxhighlight>
Descrizioni leggibili. Insieme a scenarios, get e validate restituiscono
descriptions (stesso ordine): per ogni scenario label, positions
(per posizione, elenco di frasi già tradotte, es. "♠: ha K J 10 (e non A Q 9)"),
positionLabels (nome del ruolo, es. "Attaccante" se la pagina ha shape_roles;
altrimenti "Apertore (chi usa la convenzione)", "Compagno", ...) e fit.
Parametro opzionale lang (it, en, ...; default lingua dell'API, di solito it).
Serve a mostrare nell'interfaccia di Play perché la mano è fatta così.
Nella posizione compare anche quiet: 1 se è stata usata la macro quiet.
shapeaction=list
<syntaxhighlight lang="json"> {
"pages": [
{
"pageId": 42,
"title": "Splinter",
"scenarioCount": 6,
"labels": ["1H-4C", "1H-4D", "1H-4S", "1S-4C", "1S-4D", "1S-4H"]
},
{
"pageId": 15,
"title": "Stayman",
"scenarioCount": 2,
"labels": ["fit a cuori", "fit a picche"]
}
]
} </syntaxhighlight>
Formato DSL
Il DSL (Domain Specific Language) è la stringa scritta nel campo
shape_constraints del template wiki.
Struttura
<syntaxhighlight lang="text"> etichetta: vincoli posizione; vincoli posizione // etichetta: vincoli // ... </syntaxhighlight>
//separa gli scenari (il generatore ne sceglie uno random o li presenta all'utente);separa le posizioni dentro uno scenario- Ogni scenario ha un'etichetta opzionale seguita da
: - Ogni posizione inizia col nome del ruolo, seguito da vincoli separati da spazi
Posizioni
| Nome | Ruolo |
|---|---|
opener
|
Chi apre o fa la prima licita convenzionale |
responder
|
Il compagno dell'opener |
opp1
|
Avversario a sinistra dell'opener |
opp2
|
Avversario a destra dell'opener (compagno di opp1) |
Vincoli
Lunghezza seme (specifico)
<syntaxhighlight lang="text"> S:5+ — almeno 5 picche H:0-1 — 0 o 1 cuori D:3 — esattamente 3 quadri C:2-5 — da 2 a 5 fiori </syntaxhighlight>
Semi: S = Spades/Picche, H = Hearts/Cuori, D = Diamonds/Quadri, C = Clubs/Fiori
Lunghezza seme (wildcard)
<syntaxhighlight lang="text"> x:0-1 — un seme qualsiasi (tra quelli non specificati) con 0-1 carte x:6+ — un seme non specificato con almeno 6 carte </syntaxhighlight>
Il generatore sceglie casualmente quale seme assegnare a x tra quelli
non già vincolati esplicitamente (S/H/D/C).
HCP totali
<syntaxhighlight lang="text"> hcp:15-17 — da 15 a 17 punti onore hcp:12+ — almeno 12 punti hcp:0-7 — da 0 a 7 punti </syntaxhighlight>
HCP nel seme (specifico)
<syntaxhighlight lang="text"> Shcp:3+ — almeno 3 HCP a picche Chcp:0 — 0 HCP a fiori (nessun onore) Hhcp:0-2 — da 0 a 2 HCP a cuori </syntaxhighlight>
HCP nel seme (wildcard)
<syntaxhighlight lang="text"> xhcp:0 — 0 HCP nel seme wildcard (x) xhcp:0-1 — 0-1 HCP nel seme x </syntaxhighlight>
Shape class
<syntaxhighlight lang="text"> shape:balanced — distribuzione bilanciata (4333, 4432, 5332) shape:semi_balanced — semibilanciata (5422, 6322) shape:one_suiter — monocolore shape:two_suiter — bicolore shape:three_suiter — tricolore shape:any — qualsiasi (default) </syntaxhighlight>
Onori esatti in un seme (S=KJT)
<syntaxhighlight lang="text"> S=KJT — in picche ha K, J e 10; NON ha A, Q, 9 (le carte basse 8-2 completano) S=KT9 — sequenza interna: K, 10, 9; NON ha A, Q, J H=Q — a cuori ha la Q come unico onore tra A K Q J 10 D=x — nessun onore a quadri (solo carte basse) </syntaxhighlight>
Le lettere ammesse sono A K Q J T 9 (anche 10). La regola è: le carte elencate sono possedute, le altre tra A K Q J 10 9 NON lo sono; le carte
dall'8 in giù sono libere e riempiono fino alla lunghezza del seme. Serve a
descrivere attacchi (sequenza interrotta KJT, interna KT, sotto onore K o
Q) e le carte del compagno. Si combina con la lunghezza (S:5 S=KJT).
Nel JSON compare suit_cards nella posizione, es. {"S": "KJT", "H": ""}
(stringa vuota = nessuna carta alta). Il generatore deve:
- assegnare esattamente quegli onori alla mano e escluderli dalle altre mani del seme; gli onori non nominati da nessuna posizione sono distribuiti a caso, ma mai a una posizione con
suit_cardsper quel seme che non li elenca; - rispettare comunque
suitsesuit_hcp(la validazione segnala le contraddizioni, es.S=KQ Shcp:0-2o lo stesso onore a due mani).
Pattern di mano in un seme (S~..., stile regex)
Descrive l'intera mano in quel seme, dall'alto in basso, come una piccola espressione regolare ancorata (inizio e fine):
<syntaxhighlight lang="text"> S~AKQ. — A, K, Q + una carta bassa (4 carte) S~A... — A + tre carte basse (4 carte) S~AJT9. — A, J, 10, 9 + una bassa S~AKQ.|A... — alternative: una OPPURE l'altra S~[KQ]x{2,3} — K o Q (come unico onore) + 2 o 3 carte basse S~KJT.* — K, J, 10 + qualunque numero di basse (anche zero) </syntaxhighlight>
Elementi:
- Carte alte
A K Q J T 9(anche10), sempre in ordine decrescente, ciascuna posseduta; quelle non nominate tra A K Q J 10 9 NON sono possedute. - Classe
[KQ]= una carta tra quelle (si espande in alternative). - Carta bassa
.(ox) = una carta qualsiasi tra 8 e 2; compare solo in fondo. Quantificatori:?(0-1),*(0 o più),+(1 o più),{n},{n,m}. Più punti si sommano (...= tre basse). - Alternative
|: il generatore ne sceglie una a caso per ogni mano.
È la forma più espressiva di S=KJT (che resta valida): il pattern fissa anche
la lunghezza (S~A... = 4 carte). Se c'è anche S:N i due vincoli devono
essere compatibili (la validazione API segnala le incompatibilità).
Nel JSON compare suit_patterns nella posizione, già normalizzato (classi
espanse, quantificatori risolti), così il generatore non deve interpretare la
regex:
<syntaxhighlight lang="json"> "suit_patterns": {
"S": [
{"cards": "AKQ", "low_min": 1, "low_max": 1},
{"cards": "A", "low_min": 3, "low_max": 3}
]
} </syntaxhighlight>
cards = carte alte possedute (ordine A K Q J T 9); low_min/low_max =
numero di carte basse (2-8), low_max: null = nessun limite. La lunghezza del
seme è len(cards) + numero di basse. Il generatore sceglie una alternativa e
rispetta anche suits e suit_hcp. Gli onori di cards vanno esclusi dalle altre
mani; quelli non nominati nella mano vanno ad altri a caso.
Avversari "quiet" (nessun intervento)
<syntaxhighlight lang="text"> opp1 quiet — solo per opp1/opp2 opp2 quiet:0-7 — come quiet, ma con HCP personalizzati </syntaxhighlight>
quiet è una macro espansa dall'API: nel JSON non compare mai la parola
quiet, ma i vincoli già risolti. Chi legge l'API (il generatore) non deve
implementare nulla di nuovo. Si espande in:
<syntaxhighlight lang="text"> S:0-4 H:0-4 D:0-5 C:0-5 hcp:0-9 Shcp:0-3 Hhcp:0-3 Dhcp:0-4 Chcp:0-4 (con quiet:0-7 → hcp:0-7) </syntaxhighlight>
Significato: gli avversari possono avere colori lunghi (così le mani sono
varie), ma poveri di onori e con pochi punti: nessun colore da 6+ carte
(niente apertura di sbarramento), nessun contro informativo, nessun intervento
naturale credibile. Le distribuzioni ammesse sono quindi molte, ma con punti
ridotti: il generatore ha meno margine sugli HCP della coppia dichiarante
(40 punti totali: apertore+rispondente restano sopra ~22).
I token espliciti scritti sulla stessa posizione sostituiscono i default,
in qualsiasi ordine: opp1 quiet S:0-4 lascia le picche 0-4 e tiene il resto.
Su opener/responder la macro viene ignorata.
Fit (vincolo cross-posizione)
<syntaxhighlight lang="text"> fit:H8+ — almeno 8 cuori totali tra i compagni fit:S9+ — almeno 9 picche totali tra i compagni fit:M8+ — almeno 8 carte in un seme maggiore (H o S) fit:m8+ — almeno 8 carte in un seme minore (D o C) fit:8+ — almeno 8 carte in un seme qualsiasi </syntaxhighlight>
Il fit si applica tra compagni:
- Se appare su
openeroresponder, o a livello di scenario → coppia dichiarante - Se appare su
opp1oopp2→ coppia difensiva
Formato range
| Sintassi | Significato |
|---|---|
5+
|
Almeno 5 (min=5, max=null) |
0-1
|
Da 0 a 1 (min=0, max=1) |
3
|
Esattamente 3 (min=3, max=3) |
12-21
|
Da 12 a 21 (min=12, max=21) |
Nella risposta JSON, max: null significa "nessun limite superiore".
Semi non menzionati
I semi non vincolati e non coperti da x: sono distribuiti casualmente
dal generatore, rispettando il vincolo che il totale delle carte per mano sia 13.
DSL versione 2 (dslVersion: 2)
Tutto ciò che sopra resta valido e invariato: i campi v1 hanno lo stesso significato di prima.
La v2 aggiunge sintassi nuova; ogni scenario ha nel JSON il campo requires (elenco di tag,
vuoto se usa solo la v1; vedi sotto) così un motore parziale può rifiutare uno scenario con un
errore esplicito invece di generare mani sbagliate. descriptions[].context è sempre presente
(array, eventualmente vuoto).
Le definizioni normative (cosa è una fermata, i punti totali, le perdenti...) e
l'architettura del motore sono in DEAL_ENGINE_SPEC.md.
Macro di scrittura (@NOME=v1,v2) — risolte dal parser, il motore non le vede
<syntaxhighlight lang="text">
@M=H,S 1${M}: opener ${M}:5+ hcp:12-21; responder ${M}:3+
</syntaxhighlight>
Genera due scenari (1H, 1S) sostituendo ${M} ovunque, etichetta compresa.
Più prefissi fanno il prodotto cartesiano (max 64 scenari): @M=H,S @N=15-17,12-14.
I valori possono essere intervalli (hcp:${N}): servono anche come parametri del
sistema (SA 15-17 contro 14-16).
Semi variabili $a $b $c $d
Un seme "da scegliere" condiviso da più posizioni dello stesso scenario. Ogni variabile è un seme diverso dalle altre. Dominio dichiarato a livello di scenario (prima delle posizioni):
<syntaxhighlight lang="text">
Landy: $a=M $b=any opener hcp:12-21 ; opp1 $a:5+ $b:4+ ; responder $a:0-2
</syntaxhighlight>
$a=M nobili, $a=m minori, $a=any (default) qualunque, $a=SH insieme esplicito.
Una variabile non dichiarata vale any (tutti i semi). Si usano al posto di S H D C in: $a:5+ (lunghezza), $ahcp:, $a=KQ, $a~...,
stop:$a, has:$aQ, kc:$a3, confronti ($a>$b), longest:$a.
Differisce da x: (che vale per una sola posizione).
JSON: lo stesso dizionario suits, suit_hcp, ... ma con chiave "$a";
a livello di scenario "vars": {"$a": ["S","H"]}.
Controlli, carte chiave, carte precise
| Token | Significato |
|---|---|
aces:2 kings:1-2 queens:0
|
numero di assi / re / dame nella mano |
controls:6+
|
controlli (A=2, K=1) |
kc:H3 kc:H2-3 kc:$a3
|
carte chiave con atout H: i 4 assi + il Re di H (0-5) |
has:HQ has:S10
|
possiede quella carta precisa |
lacks:HQ
|
NON possiede quella carta |
stop:S nostop:S
|
ha / non ha una fermata nel seme (definizione in SPEC) |
JSON: aces, kings, queens, controls (Range); keycards: {"H": Range};
cards_held: ["HQ"], cards_not_held: ["SK"]; stoppers: {"S": 1, "H": 0}
(1 = deve averla, 0 = non deve averla).
Punti e perdenti
tp: punti totali (PO + 1 per ogni carta oltre la quarta in ogni seme),
dp: punti distribuzione (PO + 3 chicane + 2 singolo + 1 doubleton),
ltc: perdenti (Losing Trick Count). A livello di scenario: pairhcp:25+
(PO della coppia dichiarante), defhcp:0-14 (coppia avversaria), pairtp:26+.
JSON: tp, dp, ltc (Range) nella posizione; pair_hcp: {"declarer": R, "defense": R},
pair_tp: {"declarer": R} nello scenario.
Confronti tra semi e forme precise
<syntaxhighlight lang="text">
H>S H>=S H<S H<=S H==S $a>$b — confronto di lunghezze nella stessa mano
longest:H — H è tra i semi più lunghi (pari merito ammesso)
shape:5-4-x-x shape:5422 shape:54xx — lunghezze ordinate in modo decrescente, x = qualsiasi
shape:semi_balanced — 5422 e 6322 (si aggiunge alle classi esistenti)
</syntaxhighlight>
JSON: compare: [{"left":"H","op":">=","right":"S"}], longest: ["H"],
shape_pattern: "54xx".
Alternative e negazioni nella stessa mano: any(...) e not(...)
<syntaxhighlight lang="text">
opener any(S:4+ hcp:12+, H:4+) not(D:6+)
</syntaxhighlight>
any(A, B): la mano soddisfa almeno una delle alternative (separate da virgola;
ogni alternativa è una congiunzione di token). not(A, B): la mano non soddisfa
nessuna delle alternative. Si possono scrivere più gruppi (si combinano in AND);
niente annidamenti.
JSON: any: [[altA, altB], ...] (lista di gruppi; ogni alternativa è un oggetto
con gli stessi campi di una posizione), not: [altA, altB].
Contesto dello scenario
Token prima delle posizioni: weight:3 (frequenza relativa, 1-100, default 1),
dealer:opener, vul:none|us|them|both (us = apertore+rispondente),
auction:1SA,P,2C,P (licite, notazione BridgeCards; SA = NT), contract:4H,
declarer:responder, lead:SK.
JSON: weight (int) e context: {"dealer":..,"vul":..,"auction":[..],"contract":"4H","declarer":..,"lead":"SK"}.
Il motore deve usare dealer/vul quando li espone (per la numerazione del tavolo) e,
se c'è lead, la carta deve stare nella mano dell'attaccante (a sinistra del
dichiarante) e rispettare i vincoli di quella mano.
Tag requires
suit_cards, suit_patterns, vars, stoppers, card_presence (has/lacks),
controls (aces/kings/queens/controls/kc), points (tp/dp/ltc), pair_points,
compare (confronti, longest), shape_pattern, any_not, context, weight.
Le macro @ e quiet non compaiono: sono già espanse.
Avvisi di parsing (warnings)
get e validate restituiscono warnings quando un frammento del DSL non è stato
capito e quindi scartato: nome di posizione sbagliato, token sconosciuto o con valore
non valido (aces:5, S=Kx, shape:4-5-x-x), token di scenario dopo le posizioni, posizione
definita due volte, quiet su opener/responder, gruppo any()/not() con alternative non
valide (in tal caso l'intero gruppo è ignorato, mai applicato a metà). validate.valid è 1
solo se non ci sono né errors né warnings. Un motore non deve generare se ci sono
warnings o errors.
Limiti (oltre i quali il parser avvisa e tronca): 60.000 caratteri di input, 200 scenari, 6
prefissi @, 64 valori per macro, 64 scenari per espansione, 8 gruppi any/not e 16 alternative
per posizione. Le etichette non possono contenere :.
Struttura JSON di uno scenario
<syntaxhighlight lang="typescript"> interface Scenario {
label: string; // Etichetta (es. "1H-4C"), vuota se non specificata
positions: {
[posName: string]: Position; // "opener", "responder", "opp1", "opp2"
};
fit: FitConstraint[]; // Vincoli di fit (array, può essere vuoto)
}
interface Position {
suit_patterns?: { // Pattern regex-like normalizzati: {"S":[{"cards":"AKQ","low_min":1,"low_max":1}]}
[suit: string]: { cards: string; low_min: number; low_max: number | null }[];
};
suit_cards?: { [suit: string]: string }; // Onori esatti, es. {"S":"KJT"}; "" = nessuna carta alta
suits?: { // Vincoli lunghezza seme (solo semi esplicitati)
[suit: string]: Range; // "S", "H", "D", "C"
};
hcp?: Range; // HCP totali
suit_hcp?: { // HCP per seme specifico
[suit: string]: Range;
};
x?: Range; // Lunghezza seme wildcard
x_hcp?: Range; // HCP seme wildcard
shape?: string; // Shape class: "balanced", "one_suiter", etc.
}
interface Range {
min: number; max: number | null; // null = nessun limite superiore
}
interface FitConstraint {
suit: string | null; // "S","H","D","C" o null suit_class: string | null; // "major", "minor" o null min: number; // Numero minimo di carte (es. 8) pair: string; // "declarer" o "defense"
} </syntaxhighlight>
Logica per il generatore
Algoritmo base
Per ogni scenario:
- Scegli lo scenario: random tra quelli disponibili, oppure lascia scegliere all'utente
- Risolvi wildcard:
- Se una posizione ha
x:, scegli un seme casuale tra quelli non esplicitamente vincolati xhcp:si applica allo stesso seme scelto perx:
- Se una posizione ha
- Risolvi fit:
- Se
fit.suitè specificato, entrambi i compagni devono avere un totale >=fit.minin quel seme - Se
fit.suit_classè "major", scegli tra H e S; se "minor", tra D e C - Se entrambi sono null, scegli un seme qualsiasi
fit.pairindica quale coppia: "declarer" (opener+responder) o "defense" (opp1+opp2)
- Se
- Genera le mani:
- Per ogni posizione, genera una mano che rispetti tutti i vincoli
- I semi senza vincoli vengono riempiti casualmente
- Il totale delle carte per mano deve essere esattamente 13
- Il totale delle carte per seme (tutte e 4 le mani) deve essere esattamente 13
- Posizioni non menzionate: se uno scenario non menziona
opp1/opp2, distribuisci le carte rimanenti casualmente tra gli avversari
Priorità dei vincoli
- Vincoli di fit (cross-posizione) — determinano la distribuzione di base
- Vincoli di seme specifico (S:, H:, D:, C:) — limitano i singoli semi
- Vincoli wildcard (x:) — dopo aver fissato i semi specifici
- Vincoli HCP (hcp:, Shcp:, xhcp:) — filtro finale
- Shape class (shape:) — validazione della forma complessiva
Gestione di max: null
Quando max è null, non c'è limite superiore esplicito. I limiti fisici
del bridge si applicano comunque:
- Massimo 13 carte per seme (tutte e 4 le mani)
- Massimo 13 carte per mano
- Massimo 10 HCP per seme (A+K+Q+J = 4+3+2+1)
- Massimo 37 HCP per mano (raro ma teoricamente possibile con tutti gli onori)
Esempi completi
Splinter (tutti gli scenari specifici)
DSL nel template wiki: <syntaxhighlight lang="text"> 1H-4C: opener H:5+ hcp:12-21; responder H:4+ C:0-1 Chcp:0 hcp:10-15 // 1H-4D: opener H:5+ hcp:12-21; responder H:4+ D:0-1 Dhcp:0 hcp:10-15 // 1H-4S: opener H:5+ hcp:12-21; responder H:4+ S:0-1 Shcp:0 hcp:10-15 // 1S-4C: opener S:5+ hcp:12-21; responder S:4+ C:0-1 Chcp:0 hcp:10-15 // 1S-4D: opener S:5+ hcp:12-21; responder S:4+ D:0-1 Dhcp:0 hcp:10-15 // 1S-4H: opener S:5+ hcp:12-21; responder S:4+ H:0-1 Hhcp:0 hcp:10-15 </syntaxhighlight>
Splinter (compatto con fit e wildcard)
<syntaxhighlight lang="text"> dopo 1H: fit:H8+ opener H:5+ hcp:12-21; responder x:0-1 xhcp:0 hcp:10-15 // dopo 1S: fit:S8+ opener S:5+ hcp:12-21; responder x:0-1 xhcp:0 hcp:10-15 </syntaxhighlight>
Stayman
<syntaxhighlight lang="text"> fit a cuori: fit:H8+ opener hcp:15-17 S:2-4 H:2-4 D:2-5 C:2-5; responder H:4+ hcp:8+ // fit a picche: fit:S8+ opener hcp:15-17 S:2-4 H:2-4 D:2-5 C:2-5; responder S:4+ hcp:8+ </syntaxhighlight>
Contro informativo
<syntaxhighlight lang="text"> base: opener hcp:12-21 x:5+; opp1 hcp:12+ S:3+ H:3+ D:3+ C:3+; responder hcp:0-8 // con intervento: opener hcp:12-21 x:5+; opp1 hcp:12+ S:3+ H:3+ D:3+ C:3+; opp2 hcp:8+ x:5+ </syntaxhighlight>
Preempt 3-a-livello
<syntaxhighlight lang="text"> opener H:7+ x:0-1 hcp:5-10 </syntaxhighlight>
Overcall con fit avversari
<syntaxhighlight lang="text"> overcall semplice: opener hcp:12-21 x:5+; opp1 hcp:10+ x:5+; opp2 fit:8+ // con barrage: opener hcp:12-21 x:5+; opp1 hcp:6-10 x:6+; opp2 fit:9+ </syntaxhighlight>
Note implementative
- L'API è read-only, non richiede token CSRF
- L'accesso anonimo è abilitato di default (
BridgeShapeAllowAnonymous: true) perché i dati sono pubblici e i tool esterni ne hanno bisogno - Il campo
rawnella rispostagetcontiene la stringa DSL originale, utile per debug - L'azione
validatepermette di testare stringhe DSL senza creare pagine wiki - I calcoli HCP usano la scala standard: A=4, K=3, Q=2, J=1