Vai al contenuto

Progetto:BridgeShape API

Da BridgePedia.
Versione del 23 ott 2026 alle 05:49 di Lpala (discussione | contributi) (BridgeShape: documentazione DSL v2)

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