Vai al contenuto

Progetto:BridgeShape API: differenze tra le versioni

Da BridgePedia.
[versione verificata][versione verificata]
Documentazione API BridgeShape per generatori di smazzate
 
BridgeShape: documentazione DSL v2
Riga 1: Riga 1:
Questa pagina documenta l'API dell'estensione '''BridgeShape''' per sviluppatori di tool esterni che generano smazzate di test a partire dai vincoli di distribuzione definiti nelle pagine di convenzione.
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="bash">
<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 !! Descrizione !! Parametri
! Azione
! Descrizione
! Parametri
|-
|-
| <code>get</code> || Vincoli per una singola convenzione || <code>pageid</code> oppure <code>title</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> || Elenco convenzioni con vincoli definiti || —
| <code><nowiki>list</nowiki></code>
| Elenco convenzioni con vincoli definiti
| —
|-
|-
| <code>listall</code> || Elenco tutte le convenzioni (con/senza vincoli) || —
| <code><nowiki>listall</nowiki></code>
| Elenco tutte le convenzioni (con/senza vincoli)
| —
|-
|-
| <code>validate</code> || Valida una stringa DSL senza bisogno di una pagina wiki || <code>dsl</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="bash">
<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 !! Ruolo
! Nome
! Ruolo
|-
|-
| <code>opener</code> || Chi apre o fa la prima licita convenzionale
| <code><nowiki>opener</nowiki></code>
| Chi apre o fa la prima licita convenzionale
|-
|-
| <code>responder</code> || Il compagno dell'opener
| <code><nowiki>responder</nowiki></code>
| Il compagno dell'opener
|-
|-
| <code>opp1</code> || Avversario a sinistra dell'opener
| <code><nowiki>opp1</nowiki></code>
| Avversario a sinistra dell'opener
|-
|-
| <code>opp2</code> || Avversario a destra dell'opener (compagno di opp1)
| <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="bash">
<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="bash">
<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="bash">
<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="bash">
<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="bash">
<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="bash">
<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>&#124;</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="bash">
<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 !! Significato
! Sintassi
! Significato
|-
|-
| <code>5+</code> || Almeno 5 (min=5, max=null)
| <code><nowiki>5+</nowiki></code>
| Almeno 5 (min=5, max=null)
|-
|-
| <code>0-1</code> || Da 0 a 1 (min=0, max=1)
| <code><nowiki>0-1</nowiki></code>
| Da 0 a 1 (min=0, max=1)
|-
|-
| <code>3</code> || Esattamente 3 (min=3, max=3)
| <code><nowiki>3</nowiki></code>
| Esattamente 3 (min=3, max=3)
|-
|-
| <code>12-21</code> || Da 12 a 21 (min=12, max=21)
| <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&#124;us&#124;them&#124;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="javascript">
<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="bash">
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="bash">
<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="bash">
<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="bash">
<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="bash">
<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="bash">
<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">

  1. Per titolo pagina

curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=get&title=Splinter&format=json"

  1. Per page ID

curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=get&pageid=42&format=json"

  1. Elenco pagine con vincoli

curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=list&format=json"

  1. 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_cards per quel seme che non li elenca;
  • rispettare comunque suits e suit_hcp (la validazione segnala le contraddizioni, es. S=KQ Shcp:0-2 o 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 (anche 10), 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 . (o x) = 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 opener o responder, o a livello di scenario → coppia dichiarante
  • Se appare su opp1 o opp2 → 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:

  1. Scegli lo scenario: random tra quelli disponibili, oppure lascia scegliere all'utente
  2. Risolvi wildcard:
    • Se una posizione ha x:, scegli un seme casuale tra quelli non esplicitamente vincolati
    • xhcp: si applica allo stesso seme scelto per x:
  3. Risolvi fit:
    • Se fit.suit è specificato, entrambi i compagni devono avere un totale >= fit.min in 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.pair indica quale coppia: "declarer" (opener+responder) o "defense" (opp1+opp2)
  4. 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
  5. Posizioni non menzionate: se uno scenario non menziona opp1/opp2, distribuisci le carte rimanenti casualmente tra gli avversari

Priorità dei vincoli

  1. Vincoli di fit (cross-posizione) — determinano la distribuzione di base
  2. Vincoli di seme specifico (S:, H:, D:, C:) — limitano i singoli semi
  3. Vincoli wildcard (x:) — dopo aver fissato i semi specifici
  4. Vincoli HCP (hcp:, Shcp:, xhcp:) — filtro finale
  5. 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 raw nella risposta get contiene la stringa DSL originale, utile per debug
  • L'azione validate permette di testare stringhe DSL senza creare pagine wiki
  • I calcoli HCP usano la scala standard: A=4, K=3, Q=2, J=1