Progetto:BridgeShape API: differenze tra le versioni
| [versione verificata] | [versione verificata] |
Documentazione API BridgeShape per generatori di smazzate |
BridgeShape: documentazione DSL v2 |
||
| Riga 1: | Riga 1: | ||
Documentazione BridgeShape: [[Progetto:BridgeShape Motore e frontend|Motore e frontend]] · [[Progetto:BridgeShape Ricette DSL|Ricette DSL]] | |||
= 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:one_suiter — monocolore | shape:one_suiter — monocolore | ||
| Riga 195: | Riga 225: | ||
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 325: | ||
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''': una pagina che usa solo la v1 | |||
produce lo stesso JSON di prima. La v2 aggiunge sintassi nuova; ogni scenario che | |||
la usa ha nel JSON il campo <code><nowiki>requires</nowiki></code> (elenco di tag, vedi sotto) così un motore | |||
parziale può rifiutarlo con un errore esplicito invece di generare mani sbagliate. | |||
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. | |||
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 fuori posto). Un | |||
motore non deve generare se ci sono <code><nowiki>warnings</nowiki></code> o <code><nowiki>errors</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 490: | ||
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 518: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
---- | |||
== Logica per il generatore == | == Logica per il generatore == | ||
| Riga 276: | Riga 529: | ||
# '''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 541: | ||
#* 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 551: | ||
# 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 559: | ||
* 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 567: | ||
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 578: | ||
=== 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 585: | ||
=== 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 592: | ||
=== 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 599: | ||
=== 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 605: | ||
=== 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 delle 05:49, 23 ott 2026
Documentazione BridgeShape: Motore e frontend · Ricette DSL
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: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: una pagina che usa solo la v1
produce lo stesso JSON di prima. La v2 aggiunge sintassi nuova; ogni scenario che
la usa ha nel JSON il campo requires (elenco di tag, vedi sotto) così un motore
parziale può rifiutarlo con un errore esplicito invece di generare mani sbagliate.
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.
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 fuori posto). Un
motore non deve generare se ci sono warnings o errors.
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