Add the Italian playtest edition (7.2.0, D53)
RULES.it.md translates RULES.md v0.9. edition/build.py renders a printable A4 HTML (edition/it/playtest.html): quick reference, rules, belt track, board overview and four board tiles drawn from the board model (Tunnel pairs numbered, Dojo entrances marked), and the 22 Nebbia cards. Adds markdown as a dev dependency. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ balance it.
|
||||
| Document | What it is |
|
||||
|---|---|
|
||||
| [`RULES.md`](RULES.md) | The rulebook (English working draft). |
|
||||
| [`RULES.it.md`](RULES.it.md) | Italian rulebook (playtest edition). |
|
||||
| [`board/FORMAT.md`](board/FORMAT.md) | How the board is written as text. |
|
||||
| [`agents/plans/`](agents/plans/) | Active work plan: decisions, phases, open questions. |
|
||||
|
||||
@@ -53,6 +54,20 @@ uv run dojo sim --seed 42 --jobs 1 # other dice; single process
|
||||
|
||||
Minutes are estimated as 45 s per turn plus 30 s per Kumite.
|
||||
|
||||
## Italian playtest edition
|
||||
|
||||
`edition/build.py` builds `edition/it/playtest.html`: quick reference, full rules, belt track,
|
||||
the board (one A4 overview and four A4 tiles to cut and join), and the 22 Nebbia cards to cut
|
||||
out. Open it in a browser and print on A4 at 100% scale (no "fit to page"), with background
|
||||
graphics on.
|
||||
|
||||
```sh
|
||||
uv run python edition/build.py
|
||||
```
|
||||
|
||||
Sources: `RULES.it.md`, `edition/it/riepilogo.md` (quick reference), `edition/it/carte.toml`
|
||||
(card texts), `edition/it/style.css`, and the board files. Never edit the HTML by hand.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
# Il Labirinto del Dojo — Regolamento
|
||||
|
||||
*Bozza per playtest v0.9, tradotta dal regolamento inglese (RULES.md) v0.9.*
|
||||
|
||||
## Scopo del gioco
|
||||
|
||||
Si parte cintura Bianca, si sale fino alla cintura Marrone, si raggiunge il Dojo Centrale e
|
||||
si supera l'Esame Finale. Il primo giocatore che indossa la Cintura Nera vince.
|
||||
|
||||
## Componenti
|
||||
|
||||
- Tabellone: griglia 16×16 con muri, stanze e tunnel; 4 Stanze Sicure negli angoli; il Dojo
|
||||
Centrale (4×4, al centro).
|
||||
- Fino a 8 pedine.
|
||||
- La scala delle cinture, con un segnalino per giocatore.
|
||||
- 2 dadi a sei facce (d6).
|
||||
- Mazzo Nebbia del Dojo (22 carte).
|
||||
- Carta riepilogo con gli effetti delle caselle.
|
||||
|
||||
## La scala delle cinture
|
||||
|
||||
Ogni giocatore ha una posizione da **0 a 17**. La tua cintura è il tratto in cui ti trovi:
|
||||
|
||||
| Posizione | Cintura |
|
||||
|---|---|
|
||||
| 0–2 | Bianca |
|
||||
| 3–5 | Gialla |
|
||||
| 6–8 | Arancione |
|
||||
| 9–11 | Verde |
|
||||
| 12–14 | Blu |
|
||||
| 15–17 | Marrone |
|
||||
|
||||
- Una **tacca** = 1 passo sulla scala. Una **cintura** = 3 tacche.
|
||||
- La posizione non scende mai sotto 0 e non sale mai oltre 17.
|
||||
- Chi è sotto 15 non è Marrone, qualunque cintura avesse prima.
|
||||
|
||||
## Preparazione
|
||||
|
||||
- 3–4 giocatori: una pedina per Stanza Sicura d'angolo.
|
||||
- 5–8 giocatori: due pedine per Stanza Sicura. Chi divide la stessa stanza non gioca mai
|
||||
subito dopo l'altro.
|
||||
- Tutti partono dalla posizione 0 (Bianca). Con 6 o più giocatori, tutti partono invece
|
||||
da 3 (Gialla).
|
||||
- *Partita più breve:* con qualunque numero di giocatori potete partire tutti da 3 (Gialla).
|
||||
Si risparmia circa un quinto del tempo di gioco.
|
||||
- Sorteggiate il primo giocatore; si gioca in senso orario.
|
||||
|
||||
## Il turno
|
||||
|
||||
1. Tira **2d6**.
|
||||
2. Muovi **esattamente** quel numero di passi:
|
||||
- solo in orizzontale o in verticale;
|
||||
- mai attraverso un muro;
|
||||
- mai rientrando in una casella già visitata durante questo movimento.
|
||||
|
||||
Se non esiste un movimento del numero intero, muovi il più possibile. (Il tabellone è
|
||||
disegnato senza vicoli ciechi, quindi non dovrebbe mai succedere: è solo una garanzia.)
|
||||
|
||||
3. **Entrare in una casella occupata da un avversario avvia un Kumite** (tu sei
|
||||
l'attaccante). Vale anche per l'ultimo passo. Uscire da una casella non avvia mai un
|
||||
Kumite.
|
||||
- Se vinci, il difensore torna alla Partenza e tu continui con i passi che ti restano.
|
||||
Puoi affrontare più avversari nello stesso movimento (catena).
|
||||
- Se perdi, torni alla Partenza e il tuo turno finisce.
|
||||
|
||||
4. **Ogni casella Fatica in cui entri costa 1 tacca**, compreso l'ultimo passo, prima di un
|
||||
eventuale Kumite su quella casella. Fermarti lì non costa altro.
|
||||
5. Quando il movimento finisce, applica l'effetto della casella su cui ti sei fermato.
|
||||
|
||||
## Zone sicure
|
||||
|
||||
**Stanze Sicure d'angolo** (3×3, due uscite su lati diversi):
|
||||
|
||||
- Dentro non si combatte nessun Kumite.
|
||||
- Nessun effetto di casella (sono tutte Tatami).
|
||||
- Più pedine possono stare sulla stessa casella.
|
||||
- Le pedine dentro non possono essere bersaglio di carte o di effetti delle caselle.
|
||||
- Ogni giocatore può entrare in qualunque Stanza Sicura.
|
||||
- "Tornare alla Partenza" significa sempre tornare nella **tua** Stanza Sicura. Metti la
|
||||
pedina sulla casella della stanza che preferisci, sia all'inizio sia quando ci torni.
|
||||
|
||||
**Dojo Centrale**: nessuna pedina vi resta mai dentro (vedi l'Esame Finale).
|
||||
|
||||
## Kumite
|
||||
|
||||
**Niente Kumite nel primo giro.** Finché tutti i giocatori non hanno fatto il loro primo
|
||||
turno, gli avversari si trattano come pedine in meditazione (vedi
|
||||
[Meditazione](#meditazione)): li attraversi, un movimento che finirebbe su uno di loro fa un
|
||||
passo in più, non puoi usare un Tunnel se la sua uscita è occupata da loro, e la Sfida non
|
||||
ha bersagli. Irimi (Scambio) funziona comunque: non è un Kumite.
|
||||
|
||||
1. Entrambi i giocatori tirano **1d6**.
|
||||
2. La cintura più alta aggiunge **+1 per ogni cintura di differenza, al massimo +2**.
|
||||
3. Dopo aver tirato entrambi i dadi, l'attaccante può giocare una carta Tecnica, poi il
|
||||
difensore può giocarne una. Ciascuno decide vedendo entrambi i dadi; il difensore vede
|
||||
anche la carta dell'attaccante. Una Tecnica si gioca solo in questo momento (anche Uke:
|
||||
la giochi prima di conoscere il risultato).
|
||||
4. Vince il totale più alto.
|
||||
5. **Regola del Senshu**: in caso di pareggio vince l'attaccante.
|
||||
|
||||
### Conseguenze
|
||||
|
||||
| Risultato | Vincitore | Sconfitto |
|
||||
|---|---|---|
|
||||
| Cinture uguali | +3 | −3, torna alla Partenza |
|
||||
| Vince la cintura più bassa | +3 | −3, torna alla Partenza |
|
||||
| Vince la cintura più alta | niente | torna alla Partenza |
|
||||
|
||||
## Le caselle
|
||||
|
||||
| Casella | Effetto quando ti fermi |
|
||||
|---|---|
|
||||
| Tatami | Niente. |
|
||||
| Allenamento | +1 tacca. |
|
||||
| Fatica | −1 tacca per **ogni** Fatica in cui entri camminando, che tu ti fermi o no (vedi [Il turno](#il-turno)); fermarti non costa altro. Essere spostato su una Fatica senza camminare (Tunnel, Passo Falso, Irimi, ritirata con Uke, Partenza) non costa niente. |
|
||||
| Nebbia del Dojo | Pesca una carta. La casella resta attiva. Quando il mazzo finisce, rimescola gli scarti. |
|
||||
| Tunnel | **Puoi** teletrasportarti al Tunnel con lo stesso numero. Il movimento è già finito; la casella d'uscita non ha effetto. Se l'uscita è occupata, inizia un Kumite (tu sei l'attaccante). Non puoi usare un'uscita occupata da una pedina in meditazione. |
|
||||
| Passo Falso | Torna alla casella da cui è partito questo movimento. I Kumite già combattuti durante il movimento restano validi. |
|
||||
| Sfida | **Devi** combattere un Kumite contro un avversario a tua scelta fuori da una zona sicura (tu sei l'attaccante). Se non ce n'è nessuno, non succede niente. |
|
||||
|
||||
## Meditazione
|
||||
|
||||
Una pedina in meditazione non può essere affrontata né presa di mira. Le altre pedine la
|
||||
attraversano liberamente. Se il tuo movimento finisce esattamente sulla sua casella, fai un
|
||||
passo in più seguendo le normali regole di movimento (può avviare un Kumite); si applica
|
||||
l'effetto della casella su cui ti fermi alla fine. Se nessun passo in più è possibile,
|
||||
resti dove sei.
|
||||
|
||||
Lo stesso passo in più si applica ogni volta che il tuo movimento finirebbe su una casella
|
||||
(fuori dalle Stanze Sicure) con una pedina che non puoi affrontare: per esempio un
|
||||
difensore che ha perso ma è stato salvato da Uke.
|
||||
|
||||
## Il mazzo Nebbia del Dojo
|
||||
|
||||
Le carte Nebbia sono imprevisti, non regali: **ogni carta va applicata**, anche quando ti
|
||||
danneggia. Se una carta richiede un bersaglio e non ce n'è nessuno, non succede niente.
|
||||
|
||||
**Le carte Tecnica sono segrete:**
|
||||
|
||||
- Quando peschi una Tecnica, non mostrarla: tienila coperta davanti a te.
|
||||
- Tutti possono vedere *che* ne hai una, non *quale*.
|
||||
- Ne tieni **al massimo una**. Quando ne peschi una seconda, tienine una e scarta l'altra
|
||||
**coperta**, così nessuno sa quale hai tenuto.
|
||||
- Una Tecnica si rivela solo quando la giochi; poi va negli scarti.
|
||||
|
||||
Tutte le altre carte si mostrano e si applicano subito.
|
||||
|
||||
| Tipo | Carta | Effetto | Copie |
|
||||
|---|---|---|---|
|
||||
| Tecnica | Kiai | +2 al tuo totale nel Kumite. | 3 |
|
||||
| Tecnica | Uke (Parata) | Se perdi, non torni alla Partenza (le tacche cambiano comunque). Un difensore sconfitto resta sulla sua casella; un attaccante sconfitto torna alla casella da cui veniva, e il suo turno finisce senza effetto di casella. | 2 |
|
||||
| Tecnica | Kaeshi (Contrattacco) | Ritira il tuo dado. | 2 |
|
||||
| Movimento | Tsugi-ashi | Tira 2d6 e fai un altro movimento completo. Quel movimento ha una sua casella di partenza (per il Passo Falso). | 2 |
|
||||
| Movimento | Tunnel Segreto | Teletrasportati su un Tunnel a tua scelta (uscita occupata: Kumite, tu sei l'attaccante). Non puoi scegliere un Tunnel occupato da una pedina in meditazione. | 1 |
|
||||
| Movimento | Irimi (Scambio) | Scambia il posto con un avversario a tua scelta fuori da una zona sicura. Nessun Kumite, nessun effetto di casella. | 2 |
|
||||
| Interazione | Sfida | Combatti un Kumite contro un avversario a tua scelta fuori da una zona sicura. Tu sei l'attaccante. | 2 |
|
||||
| Evento | Il Sensei Osserva | Il giocatore (o i giocatori) più in basso sulla scala guadagna +2. | 1 |
|
||||
| Evento | Il Peso della Cintura | Il giocatore (o i giocatori) più in alto sulla scala perde 1. | 1 |
|
||||
| Evento | Allenamento Intensivo | +1 tacca. | 2 |
|
||||
| Evento | Colpo Proibito | −1 tacca. | 1 |
|
||||
| Evento | Passo Falso | Torna alla casella da cui è partito questo movimento. | 2 |
|
||||
| Evento | Meditazione | Salti il prossimo turno. Fino alla fine del turno saltato non puoi essere affrontato né preso di mira. | 1 |
|
||||
|
||||
## Il Dojo Centrale e l'Esame Finale
|
||||
|
||||
- Solo un giocatore Marrone (posizione 15 o più) può entrare. Per tutti gli altri il Dojo è
|
||||
un muro.
|
||||
- Entrare costa un passo e non serve il numero esatto: raggiungi una casella accanto a un
|
||||
ingresso con almeno 1 passo rimasto ed entra. Il movimento finisce subito.
|
||||
- Puoi entrare nello stesso movimento in cui sei diventato Marrone vincendo un Kumite.
|
||||
- La cintura cambia subito anche al contrario: se una Fatica attraversata durante il
|
||||
movimento ti porta sotto 15, il Dojo è chiuso per il resto di quel movimento.
|
||||
- L'Esame Finale inizia subito. Difende **l'avversario più in alto sulla scala** (in caso
|
||||
di parità, lo sfidante sceglie tra loro). La pedina del difensore non si muove. Chi è in
|
||||
meditazione non può difendere; se nessuno può, torni alla casella da cui sei entrato e in
|
||||
questo turno non c'è Esame.
|
||||
- L'Esame è un Kumite con lo sfidante come attaccante; valgono il Senshu e le carte Tecnica.
|
||||
- **Il difensore combatte come Cintura Nera**, qualunque sia la sua cintura: lo sfidante
|
||||
Marrone è una cintura sotto, quindi il difensore aggiunge +1.
|
||||
- **Vince lo sfidante**: indossa la Cintura Nera e vince la partita.
|
||||
- **Perde lo sfidante**: −3 e torna alla Partenza. Il difensore non guadagna niente.
|
||||
- Se lo sfidante gioca Uke e perde, torna alla casella da cui è entrato invece che alla
|
||||
Partenza.
|
||||
|
||||
## Domande frequenti
|
||||
|
||||
**Quali effetti posso rifiutare?**
|
||||
Solo la casella Tunnel ("puoi teletrasportarti"). Tutto il resto, caselle e carte Nebbia,
|
||||
va applicato, comprese Sfida, Irimi e Tunnel Segreto.
|
||||
|
||||
**Sono stato spostato senza camminare (Passo Falso, Irimi, ritirata con Uke). Questo avvia
|
||||
un Kumite o l'effetto della casella in cui arrivo?**
|
||||
No in entrambi i casi (una Fatica costa una tacca solo quando ci entri camminando). Solo
|
||||
arrivare con un Tunnel o con il Tunnel Segreto avvia un Kumite su una casella occupata, e
|
||||
anche in quel caso la casella d'uscita non ha effetto.
|
||||
|
||||
**Sono diventato Marrone vincendo un Kumite a metà movimento. Posso entrare nel Dojo con i
|
||||
passi che mi restano?**
|
||||
Sì. La cintura cambia subito, quindi il Dojo si apre per il resto di quel movimento.
|
||||
|
||||
**Sono a 15 e la mia strada verso il Dojo passa su una Fatica. Posso prenderla?**
|
||||
No: la Fatica ti porta a 14 prima dell'ingresso, e il Dojo si chiude. Prendi una strada
|
||||
senza Fatica, oppure guadagna prima una tacca.
|
||||
|
||||
**Il mio movimento finirebbe su una pedina che non posso affrontare. Che faccio?**
|
||||
Fai un passo in più (vedi [Meditazione](#meditazione)). Se non è possibile, resti dove sei.
|
||||
|
||||
**Sono bloccato e non posso muovere il numero intero.**
|
||||
Muovi il più possibile. Su un tabellone ben disegnato non dovrebbe mai succedere.
|
||||
|
||||
**Uke mi ha salvato da attaccante. Applico l'effetto della casella in cui torno?**
|
||||
No. Perdere un Kumite fa sempre finire il turno.
|
||||
|
||||
**Ho pescato Tsugi-ashi e poi sono finito su un Passo Falso nel movimento in più. Dove
|
||||
torno?**
|
||||
Alla casella da cui è partito il movimento in più, non a quella da cui è iniziato il turno.
|
||||
|
||||
**Gli avversari possono sapere quale Tecnica ho?**
|
||||
No. Sanno solo se ne hai una. Vedi [Il mazzo Nebbia del Dojo](#il-mazzo-nebbia-del-dojo).
|
||||
@@ -84,7 +84,7 @@ Each player has a position from **0 to 17**. Your belt is the segment you are in
|
||||
**No Kumite in the first round.** Until every player has had a turn, opponents are treated
|
||||
like meditating pawns (see [Meditazione](#meditazione)): you pass through them, a move that
|
||||
would end on one takes an extra step, their Tunnel exit cannot be used, and Sfida has no
|
||||
target. Irimi still works: it is not a Kumite.
|
||||
target. Irimi (scambio) still works: it is not a Kumite.
|
||||
|
||||
1. Both players roll **1d6**.
|
||||
2. The higher belt adds **+1 per belt of difference, maximum +2**.
|
||||
|
||||
@@ -69,6 +69,7 @@ Deliverables:
|
||||
| D50 | **Length accepted as is; a failed Exam still costs 3 stripes.** L1 (Brown floor against Fatica) rejected; L2 (failed Exam −1 / 0) saves only 3–5 min on the mean and weakens comebacks (6.11.4). `exam_failure_loss` stays a simulator switch. At 4 players mixed and balanced are inside the target; casual (91 / 122) and all-aggressive (90 / 127) stay slightly outside, within the "1–2 h" promise; the shorter-game start on Yellow (D48) is the players' lever | Developer, 6.11 results. |
|
||||
| D51 | **Bots decide alike in every corner** (start square and tied paths broken in the player's own frame), overriding D40 for this bias: the old tie-break by absolute board order made corners 1 and 2 win 25–28% and corners 0 and 3 23% | Developer, 6.2.1 diagnosis. |
|
||||
| D52 | **No first-player compensation**: the seat order stays as it is. At 4 players every seat is inside 25% ± 3 pp; +1 stripe for the first player gains about 1 point (6.2.4), not worth an extra rule. `first_player_bonus` stays a simulator switch (no CLI axis) | Developer, 6.2.4 results. |
|
||||
| D53 | **Italian playtest edition format**: source `RULES.it.md` (full rules, without the tested alternatives) plus `edition/it/riepilogo.md` and `edition/it/carte.toml`; `edition/build.py` renders one local printable A4 HTML (`edition/it/playtest.html`): quick reference, rules, belt track, board overview plus four A4 tiles, 22 cut-out cards. Board drawn from the board model; card copies from the engine's deck. Japanese names keep an Italian gloss ("Uke (Parata)"). Glossary: stripe = tacca, track = scala delle cinture, Safe Room = Stanza Sicura, Start = Partenza, round = giro, attacker / defender = attaccante / difensore | Developer (format, printable board, gloss, local HTML only). |
|
||||
|
||||
## Phases
|
||||
|
||||
@@ -179,7 +180,7 @@ Deliverables:
|
||||
- One change at a time. When a change is adopted, re-run the 6.1 baseline so later steps start from the current rules, and log the decision (D-number) here and in `RULES.md`.
|
||||
- Results go in this file as a table per step (command line, scenario, key measures).
|
||||
|
||||
**Next action:** Phase 6 is complete; Phase 7 (7.1 freeze the rules) when the developer says so. 6.9 skipped (D44), 6.4.1 parked (D49). Everything up to D52 is committed; the 6.8 final run is not; commit only when the developer asks.
|
||||
**Next action:** the developer's playtest with the Italian edition (7.2.0); then 7.2.1 and 7.1. 6.9 skipped (D44), 6.4.1 parked (D49). Everything up to D52 is committed; 7.2.0 and D53 are not; commit only when the developer asks.
|
||||
|
||||
**Targets** (confirmed by the developer as a starting point, D32):
|
||||
|
||||
@@ -477,8 +478,10 @@ Deliverables:
|
||||
- At 6 players the two seats alone in a Safe Room win 17–20% vs 13–17% for the four sharing (6.2.3); accepted with D52.
|
||||
- [ ] 7.1 Freeze rules.
|
||||
- [ ] 7.2 Italian edition.
|
||||
- [x] 7.2.0 Playtest edition (D53): `RULES.it.md` translated from RULES.md v0.9; quick reference; `edition/build.py` (dev dependency `markdown`) writes `edition/it/playtest.html`, 15 A4 pages when printed (checked by printing to PDF with headless Chromium): quick reference, rules (5 pages), belt track, board overview, 4 board tiles (23 mm squares, Tunnel pairs numbered 1–8 across the board, Dojo entrances marked in gold), 3 card sheets (63 × 88 mm). No card backs.
|
||||
- [ ] 7.2.1 Playtest feedback folded into `RULES.md` and `RULES.it.md`.
|
||||
- [ ] 7.3 Physical tracking method and components; printable board.
|
||||
- [ ] Tunnel pairs need unique labels on the printed board (the text format repeats each digit in all 4 quadrants).
|
||||
- [x] Tunnel pairs need unique labels on the printed board (the text format repeats each digit in all 4 quadrants): numbered 1–8 by `edition/build.py`.
|
||||
|
||||
## Questions
|
||||
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
"""Build the printable Italian playtest edition: `uv run python edition/build.py`.
|
||||
|
||||
Writes `edition/it/playtest.html` (A4, print from the browser) from `RULES.it.md`, the
|
||||
quick reference `edition/it/riepilogo.md`, the card texts `edition/it/carte.toml`, the
|
||||
board (`board/quadrant.txt`, `board/legend.toml`) and the engine's deck. Never edit the HTML
|
||||
by hand.
|
||||
"""
|
||||
|
||||
import html
|
||||
import tomllib
|
||||
from collections.abc import Iterator, Mapping
|
||||
from pathlib import Path
|
||||
|
||||
import markdown
|
||||
|
||||
from dojo.board import BOARD_SIZE, QUADRANT_SIZE, Board, Pos, Square, load_board
|
||||
from dojo.grid import Grid
|
||||
from dojo.rules import BELTS, DEFAULT_RULES
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
EDITION = ROOT / "edition" / "it"
|
||||
OUTPUT = EDITION / "playtest.html"
|
||||
TITLE = "Il Labirinto del Dojo"
|
||||
EDITION_LABEL = "Bozza per playtest v0.9"
|
||||
|
||||
TILE_CELL_MM = 23.0
|
||||
OVERVIEW_CELL_MM = 11.5
|
||||
CARDS_PER_PAGE = 9
|
||||
BELT_NAMES = ("Bianca", "Gialla", "Arancione", "Verde", "Blu", "Marrone")
|
||||
BELT_COLOURS = ("#ffffff", "#fde047", "#fb923c", "#4ade80", "#60a5fa", "#a16207")
|
||||
TILE_NAMES = {
|
||||
(0, 0): "in alto a sinistra",
|
||||
(0, 1): "in alto a destra",
|
||||
(1, 1): "in basso a destra",
|
||||
(1, 0): "in basso a sinistra",
|
||||
}
|
||||
|
||||
# Square code -> (fill, name, symbol). Tunnels are added per pair, see tunnel_labels().
|
||||
SQUARE_STYLE: Mapping[str, tuple[str, str, str]] = {
|
||||
Square.SAFE: ("#dbeafe", "", ""),
|
||||
Square.DOJO: ("#44403c", "", ""),
|
||||
Square.TATAMI: ("#fbf7ee", "", ""),
|
||||
Square.ALLENAMENTO: ("#bbf7d0", "Allenamento", "+1"),
|
||||
Square.FATICA: ("#fecaca", "Fatica", "\u22121"),
|
||||
Square.NEBBIA: ("#ddd6fe", "Nebbia", "?"),
|
||||
Square.PASSO_FALSO: ("#fed7aa", "Passo Falso", "↩"),
|
||||
Square.SFIDA: ("#fde68a", "Sfida", "VS"),
|
||||
}
|
||||
TUNNEL_FILL = "#bae6fd"
|
||||
ENTRANCE_COLOUR = "#eab308"
|
||||
|
||||
|
||||
def tunnel_labels(board: Board) -> dict[Pos, int]:
|
||||
"""A number per Tunnel pair, unique on the whole board (the source repeats its digits)."""
|
||||
labels: dict[Pos, int] = {}
|
||||
for square in sorted(board.tunnels):
|
||||
if square not in labels:
|
||||
number = len(set(labels.values())) + 1
|
||||
labels[square] = labels[board.tunnels[square]] = number
|
||||
return labels
|
||||
|
||||
|
||||
def board_svg(board: Board, rows: range, cols: range, cell: float) -> str:
|
||||
grid = board.to_grid()
|
||||
tunnels = tunnel_labels(board)
|
||||
width, height = len(cols) * cell, len(rows) * cell
|
||||
parts = [
|
||||
f'<svg class="board" xmlns="http://www.w3.org/2000/svg" width="{width}mm" '
|
||||
f'height="{height}mm" viewBox="0 0 {width} {height}">'
|
||||
]
|
||||
for r in rows:
|
||||
for c in cols:
|
||||
x, y = (c - cols.start) * cell, (r - rows.start) * cell
|
||||
parts.append(_cell_svg(board.code(Pos(r, c)), tunnels.get(Pos(r, c)), x, y, cell))
|
||||
parts.extend(_label_svg(board, rows, cols, cell))
|
||||
parts.extend(_walls_svg(grid, rows, cols, cell))
|
||||
parts.extend(_entrances_svg(board, rows, cols, cell))
|
||||
parts.append("</svg>")
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def _cell_svg(code: str, tunnel: int | None, x: float, y: float, cell: float) -> str:
|
||||
if tunnel is not None:
|
||||
fill, name, symbol = TUNNEL_FILL, "Tunnel", str(tunnel)
|
||||
else:
|
||||
fill, name, symbol = SQUARE_STYLE[code]
|
||||
parts = [
|
||||
f'<rect x="{x}" y="{y}" width="{cell}" height="{cell}" fill="{fill}" '
|
||||
f'stroke="#a8a29e" stroke-width="{cell * 0.01}"/>'
|
||||
]
|
||||
if symbol:
|
||||
parts.append(
|
||||
f'<text x="{x + cell / 2}" y="{y + cell * 0.62}" font-size="{cell * 0.38}" '
|
||||
f'text-anchor="middle" font-weight="700">{html.escape(symbol)}</text>'
|
||||
)
|
||||
if name:
|
||||
parts.append(
|
||||
f'<text x="{x + cell / 2}" y="{y + cell * 0.9}" font-size="{cell * 0.14}" '
|
||||
f'text-anchor="middle">{html.escape(name)}</text>'
|
||||
)
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def _label_svg(board: Board, rows: range, cols: range, cell: float) -> Iterator[str]:
|
||||
""" "Stanza Sicura" and "Dojo" written across their rooms, where the room lies in view."""
|
||||
for code, text, colour in (
|
||||
(Square.SAFE, "Stanza Sicura", "#1e3a8a"),
|
||||
(Square.DOJO, "DOJO", "#fafaf9"),
|
||||
):
|
||||
squares = [p for p in board.cells_with(code) if p.row in rows and p.col in cols]
|
||||
for group in _rooms(squares):
|
||||
row = sum(p.row for p in group) / len(group) - rows.start + 0.5
|
||||
col = sum(p.col for p in group) / len(group) - cols.start + 0.5
|
||||
size = cell * (0.3 if code is Square.DOJO else 0.2)
|
||||
yield (
|
||||
f'<text x="{col * cell}" y="{row * cell + size / 3}" font-size="{size}" '
|
||||
f'text-anchor="middle" font-weight="700" fill="{colour}">{text}</text>'
|
||||
)
|
||||
|
||||
|
||||
def _rooms(squares: list[Pos]) -> list[list[Pos]]:
|
||||
"""Orthogonally connected groups of ``squares``."""
|
||||
left, groups = set(squares), []
|
||||
while left:
|
||||
stack, group = [left.pop()], []
|
||||
while stack:
|
||||
p = stack.pop()
|
||||
group.append(p)
|
||||
for q in (
|
||||
Pos(p.row + 1, p.col),
|
||||
Pos(p.row - 1, p.col),
|
||||
Pos(p.row, p.col + 1),
|
||||
Pos(p.row, p.col - 1),
|
||||
):
|
||||
if q in left:
|
||||
left.remove(q)
|
||||
stack.append(q)
|
||||
groups.append(group)
|
||||
return groups
|
||||
|
||||
|
||||
def _walls_svg(grid: Grid, rows: range, cols: range, cell: float) -> Iterator[str]:
|
||||
stroke = cell * 0.09
|
||||
style = f'stroke="#1c1917" stroke-width="{stroke}" stroke-linecap="square"'
|
||||
for r in range(rows.start, rows.stop + 1):
|
||||
for c in cols:
|
||||
if grid.h_walls[r][c]:
|
||||
x, y = (c - cols.start) * cell, (r - rows.start) * cell
|
||||
yield f'<line x1="{x}" y1="{y}" x2="{x + cell}" y2="{y}" {style}/>'
|
||||
for r in rows:
|
||||
for c in range(cols.start, cols.stop + 1):
|
||||
if grid.v_walls[r][c]:
|
||||
x, y = (c - cols.start) * cell, (r - rows.start) * cell
|
||||
yield f'<line x1="{x}" y1="{y}" x2="{x}" y2="{y + cell}" {style}/>'
|
||||
|
||||
|
||||
def _entrances_svg(board: Board, rows: range, cols: range, cell: float) -> Iterator[str]:
|
||||
"""A gold bar on every open edge into the Dojo, so the entrances stand out."""
|
||||
style = f'stroke="{ENTRANCE_COLOUR}" stroke-width="{cell * 0.12}"'
|
||||
for square in sorted(board.dojo):
|
||||
if square.row not in rows or square.col not in cols:
|
||||
continue
|
||||
for outside in board.neighbours(square):
|
||||
if outside in board.dojo:
|
||||
continue
|
||||
x = (max(square.col, outside.col) - cols.start) * cell
|
||||
y = (max(square.row, outside.row) - rows.start) * cell
|
||||
if square.row == outside.row:
|
||||
x1, y1, x2, y2 = x, y + cell * 0.2, x, y + cell * 0.8
|
||||
else:
|
||||
x1, y1, x2, y2 = x + cell * 0.2, y, x + cell * 0.8, y
|
||||
yield f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" {style}/>'
|
||||
|
||||
|
||||
def legend_html() -> str:
|
||||
items = [
|
||||
(fill, name, symbol)
|
||||
for code, (fill, name, symbol) in SQUARE_STYLE.items()
|
||||
if code not in (Square.SAFE, Square.DOJO, Square.TATAMI)
|
||||
]
|
||||
items.append((TUNNEL_FILL, "Tunnel (stesso numero)", "3"))
|
||||
items.append((ENTRANCE_COLOUR, "Ingresso del Dojo", ""))
|
||||
cells = "".join(
|
||||
f'<span class="key"><span class="swatch" style="background:{fill}">'
|
||||
f"{html.escape(symbol)}</span>{html.escape(name)}</span>"
|
||||
for fill, name, symbol in items
|
||||
)
|
||||
return f'<p class="legend">{cells}</p>'
|
||||
|
||||
|
||||
def track_html() -> str:
|
||||
per_belt = DEFAULT_RULES.stripes_per_belt
|
||||
rows = []
|
||||
for belt in range(len(BELTS)):
|
||||
cells = "".join(
|
||||
f'<td style="background:{BELT_COLOURS[belt]}">{belt * per_belt + stripe}</td>'
|
||||
for stripe in range(per_belt)
|
||||
)
|
||||
rows.append(f"<tr><th>{BELT_NAMES[belt]}</th>{cells}</tr>")
|
||||
rows.append(
|
||||
'<tr class="black"><th>Nera</th><td colspan="3">Esame Finale superato: vittoria</td></tr>'
|
||||
)
|
||||
return (
|
||||
'<section class="page"><h2>Scala delle cinture</h2>'
|
||||
"<p>Un segnalino per giocatore. Una tacca = una casella; una cintura = una riga. "
|
||||
"Dal 15 (Marrone) il Dojo Centrale è aperto.</p>"
|
||||
f'<table class="track">{"".join(reversed(rows))}</table></section>'
|
||||
)
|
||||
|
||||
|
||||
def cards_html(texts: Mapping[str, Mapping[str, str]]) -> str:
|
||||
missing = {card.value for card, _ in DEFAULT_RULES.deck} ^ set(texts)
|
||||
if missing:
|
||||
raise SystemExit(f"carte.toml and the engine's deck disagree on: {sorted(missing)}")
|
||||
cards = [texts[card.value] for card, copies in DEFAULT_RULES.deck for _ in range(copies)]
|
||||
pages = []
|
||||
for start in range(0, len(cards), CARDS_PER_PAGE):
|
||||
faces = "".join(_card_html(card) for card in cards[start : start + CARDS_PER_PAGE])
|
||||
pages.append(f'<section class="page cards">{faces}</section>')
|
||||
return "".join(pages)
|
||||
|
||||
|
||||
def _card_html(card: Mapping[str, str]) -> str:
|
||||
kind = card["tipo"]
|
||||
note = '<p class="note">Tienila coperta. Al massimo una.</p>' if kind == "Tecnica" else ""
|
||||
return (
|
||||
f'<div class="card {html.escape(kind.lower())}"><p class="kind">{html.escape(kind)}</p>'
|
||||
f'<h3>{html.escape(card["nome"])}</h3><p class="text">{html.escape(card["testo"])}</p>'
|
||||
f'{note}<p class="deck">Nebbia del Dojo</p></div>'
|
||||
)
|
||||
|
||||
|
||||
def board_pages(board: Board) -> str:
|
||||
whole = range(BOARD_SIZE)
|
||||
pages = [
|
||||
'<section class="page"><h2>Tabellone intero</h2>'
|
||||
f"{board_svg(board, whole, whole, OVERVIEW_CELL_MM)}{legend_html()}"
|
||||
"<p>Muri: linee spesse. Barre gialle: ingressi del Dojo (solo per chi è Marrone). "
|
||||
"I Tunnel con lo stesso numero sono collegati.</p></section>"
|
||||
]
|
||||
for (qr, qc), name in TILE_NAMES.items():
|
||||
rows = range(qr * QUADRANT_SIZE, (qr + 1) * QUADRANT_SIZE)
|
||||
cols = range(qc * QUADRANT_SIZE, (qc + 1) * QUADRANT_SIZE)
|
||||
pages.append(
|
||||
f'<section class="page tile"><p class="tile-name">Tabellone da gioco, foglio '
|
||||
f"{len(pages)} di 4: {name}. Ritaglia lungo il bordo esterno e accosta i fogli."
|
||||
f"</p>{board_svg(board, rows, cols, TILE_CELL_MM)}</section>"
|
||||
)
|
||||
return "".join(pages)
|
||||
|
||||
|
||||
def rules_html(path: Path) -> str:
|
||||
return markdown.markdown(path.read_text(encoding="utf-8"), extensions=["tables", "toc"])
|
||||
|
||||
|
||||
def build() -> str:
|
||||
board = load_board(ROOT / "board" / "quadrant.txt", ROOT / "board" / "legend.toml")
|
||||
with (EDITION / "carte.toml").open("rb") as handle:
|
||||
texts = tomllib.load(handle)
|
||||
style = (EDITION / "style.css").read_text(encoding="utf-8")
|
||||
return (
|
||||
'<!doctype html><html lang="it"><head><meta charset="utf-8">'
|
||||
f"<title>{TITLE}</title><style>{style}</style></head><body>"
|
||||
f'<section class="page cover"><h1>{TITLE}</h1><p class="edition">{EDITION_LABEL}</p>'
|
||||
f"{rules_html(EDITION / 'riepilogo.md')}</section>"
|
||||
f'<section class="page rules">{rules_html(ROOT / "RULES.it.md")}</section>'
|
||||
f"{track_html()}{board_pages(board)}{cards_html(texts)}"
|
||||
"</body></html>"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
OUTPUT.write_text(build(), encoding="utf-8")
|
||||
print(f"wrote {OUTPUT.relative_to(ROOT)}")
|
||||
@@ -0,0 +1,67 @@
|
||||
# Testo delle carte Nebbia del Dojo, edizione italiana. Le chiavi sono i nomi delle carte
|
||||
# nel motore (dojo.rules.Card); le copie vengono dal mazzo del motore (DEFAULT_DECK).
|
||||
|
||||
["Kiai"]
|
||||
tipo = "Tecnica"
|
||||
nome = "Kiai"
|
||||
testo = "Gioca dopo i dadi di un Kumite: +2 al tuo totale."
|
||||
|
||||
["Uke"]
|
||||
tipo = "Tecnica"
|
||||
nome = "Uke (Parata)"
|
||||
testo = "Gioca dopo i dadi di un Kumite. Se perdi, non torni alla Partenza (le tacche cambiano comunque). Da difensore resti sulla tua casella; da attaccante torni alla casella da cui venivi e il turno finisce."
|
||||
|
||||
["Kaeshi"]
|
||||
tipo = "Tecnica"
|
||||
nome = "Kaeshi (Contrattacco)"
|
||||
testo = "Gioca dopo i dadi di un Kumite: ritira il tuo dado."
|
||||
|
||||
["Tsugi-ashi"]
|
||||
tipo = "Movimento"
|
||||
nome = "Tsugi-ashi"
|
||||
testo = "Tira 2d6 e fai un altro movimento completo."
|
||||
|
||||
["Tunnel Segreto"]
|
||||
tipo = "Movimento"
|
||||
nome = "Tunnel Segreto"
|
||||
testo = "Teletrasportati su un Tunnel a tua scelta. Uscita occupata: Kumite, tu sei l'attaccante."
|
||||
|
||||
["Irimi"]
|
||||
tipo = "Movimento"
|
||||
nome = "Irimi (Scambio)"
|
||||
testo = "Scambia il posto con un avversario a tua scelta fuori da una zona sicura. Nessun Kumite, nessun effetto di casella."
|
||||
|
||||
["Sfida"]
|
||||
tipo = "Interazione"
|
||||
nome = "Sfida"
|
||||
testo = "Combatti un Kumite contro un avversario a tua scelta fuori da una zona sicura. Tu sei l'attaccante."
|
||||
|
||||
["Il Sensei Osserva"]
|
||||
tipo = "Evento"
|
||||
nome = "Il Sensei Osserva"
|
||||
testo = "Il giocatore (o i giocatori) più in basso sulla scala guadagna +2."
|
||||
|
||||
["Il Peso della Cintura"]
|
||||
tipo = "Evento"
|
||||
nome = "Il Peso della Cintura"
|
||||
testo = "Il giocatore (o i giocatori) più in alto sulla scala perde 1."
|
||||
|
||||
["Allenamento Intensivo"]
|
||||
tipo = "Evento"
|
||||
nome = "Allenamento Intensivo"
|
||||
testo = "+1 tacca."
|
||||
|
||||
["Colpo Proibito"]
|
||||
tipo = "Evento"
|
||||
nome = "Colpo Proibito"
|
||||
testo = "−1 tacca."
|
||||
|
||||
["Passo Falso"]
|
||||
tipo = "Evento"
|
||||
nome = "Passo Falso"
|
||||
testo = "Torna alla casella da cui è partito questo movimento."
|
||||
|
||||
["Meditazione"]
|
||||
tipo = "Evento"
|
||||
nome = "Meditazione"
|
||||
testo = "Salti il prossimo turno. Fino alla fine del turno saltato non puoi essere affrontato né preso di mira."
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,47 @@
|
||||
## Riepilogo
|
||||
|
||||
**Scopo:** sali da Bianca a Marrone (15), entra nel Dojo Centrale e supera l'Esame Finale.
|
||||
|
||||
### Il turno
|
||||
|
||||
1. Tira **2d6** e muovi **esattamente** quei passi: in orizzontale o verticale, mai
|
||||
attraverso un muro, mai due volte sulla stessa casella nello stesso movimento.
|
||||
2. **Entri su un avversario → Kumite** (sei l'attaccante). Vinci: lui torna alla Partenza,
|
||||
tu continui. Perdi: torni alla Partenza e il turno finisce.
|
||||
3. **Ogni Fatica in cui entri: −1 tacca**, anche se non ti fermi.
|
||||
4. Fine movimento: applica la casella su cui ti fermi.
|
||||
|
||||
**Primo giro:** niente Kumite. Gli avversari si attraversano; se ti fermeresti su uno di
|
||||
loro, fai un passo in più.
|
||||
|
||||
### Kumite
|
||||
|
||||
- Entrambi tirano **1d6**. La cintura più alta aggiunge **+1 per cintura di differenza**
|
||||
(massimo +2).
|
||||
- Visti i dadi: l'attaccante può giocare una Tecnica, poi il difensore.
|
||||
- Totale più alto vince. **Pareggio: vince l'attaccante** (Senshu).
|
||||
|
||||
| Risultato | Vincitore | Sconfitto |
|
||||
|---|---|---|
|
||||
| Cinture uguali | +3 | −3, Partenza |
|
||||
| Vince la più bassa | +3 | −3, Partenza |
|
||||
| Vince la più alta | niente | Partenza |
|
||||
|
||||
### Caselle
|
||||
|
||||
| Casella | Effetto |
|
||||
|---|---|
|
||||
| Allenamento | +1 tacca |
|
||||
| Fatica | −1 tacca per ogni Fatica attraversata o raggiunta camminando |
|
||||
| Nebbia | pesca una carta (Tecniche coperte, massimo una) |
|
||||
| Tunnel | puoi spostarti al Tunnel con lo stesso numero (uscita occupata: Kumite) |
|
||||
| Passo Falso | torna alla casella di partenza del movimento |
|
||||
| Sfida | Kumite obbligato contro un avversario a scelta |
|
||||
|
||||
### Esame Finale
|
||||
|
||||
- Solo da **Marrone (15+)**. Entri nel Dojo con almeno 1 passo rimasto; il movimento finisce.
|
||||
- Difende l'avversario **più alto sulla scala**, e combatte come **Cintura Nera** (+1).
|
||||
- Vinci: **Cintura Nera, hai vinto**. Perdi: −3 e torni alla Partenza.
|
||||
|
||||
**Stanze Sicure:** niente Kumite, niente effetti. La Partenza è sempre la tua stanza.
|
||||
@@ -0,0 +1,245 @@
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 10mm;
|
||||
}
|
||||
|
||||
:root {
|
||||
--ink: #1c1917;
|
||||
--muted: #57534e;
|
||||
--line: #d6d3d1;
|
||||
--paper: #ffffff;
|
||||
--desk: #e7e5e4;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--desk);
|
||||
color: var(--ink);
|
||||
font: 10.5pt/1.4 Georgia, "Times New Roman", serif;
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: 210mm;
|
||||
min-height: 297mm;
|
||||
margin: 8mm auto;
|
||||
padding: 10mm;
|
||||
background: var(--paper);
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 0.2);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 24pt;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 1.2em 0 0.4em;
|
||||
font-size: 15pt;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 1em 0 0.3em;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
.edition {
|
||||
margin: 0 0 4mm;
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
margin: 2mm 0 3mm;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid var(--line);
|
||||
padding: 1mm 2mm;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f5f5f4;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
margin: 1mm 0 2mm;
|
||||
padding-left: 6mm;
|
||||
}
|
||||
|
||||
.cover {
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.cover h2 {
|
||||
margin-top: 3mm;
|
||||
}
|
||||
|
||||
.board {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2mm 5mm;
|
||||
margin: 4mm 0 2mm;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.key {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 1.5mm;
|
||||
}
|
||||
|
||||
.swatch {
|
||||
display: inline-block;
|
||||
width: 7mm;
|
||||
height: 7mm;
|
||||
border: 1px solid #a8a29e;
|
||||
font-weight: 700;
|
||||
line-height: 7mm;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tile-name {
|
||||
margin: 0 0 4mm;
|
||||
color: var(--muted);
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.track {
|
||||
width: auto;
|
||||
margin: 6mm auto;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.track th {
|
||||
width: 32mm;
|
||||
font-size: 13pt;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.track td {
|
||||
width: 32mm;
|
||||
height: 30mm;
|
||||
border: 2px solid var(--ink);
|
||||
font-size: 20pt;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.track .black th,
|
||||
.track .black td {
|
||||
background: var(--ink);
|
||||
color: var(--paper);
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 63mm);
|
||||
grid-auto-rows: 88mm;
|
||||
gap: 0;
|
||||
align-content: start;
|
||||
justify-content: center;
|
||||
padding-top: 8mm;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 4mm;
|
||||
border: 0.3mm dashed #78716c;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.card .kind {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 8pt;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
margin: 1mm 0 3mm;
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
.card .text {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.card .note {
|
||||
margin: 0 0 2mm;
|
||||
font-size: 8pt;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.card .deck {
|
||||
margin: 0;
|
||||
padding-top: 1.5mm;
|
||||
border-top: 1px solid var(--line);
|
||||
color: var(--muted);
|
||||
font-size: 7.5pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card.tecnica {
|
||||
background: #eef2ff;
|
||||
}
|
||||
|
||||
.card.movimento {
|
||||
background: #ecfeff;
|
||||
}
|
||||
|
||||
.card.interazione {
|
||||
background: #fff7ed;
|
||||
}
|
||||
|
||||
.card.evento {
|
||||
background: #f7fee7;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: auto;
|
||||
min-height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
break-after: page;
|
||||
}
|
||||
|
||||
.page:last-child {
|
||||
break-after: auto;
|
||||
}
|
||||
|
||||
.cards {
|
||||
padding-top: 4mm;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ build-backend = "uv_build"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"markdown>=3.11",
|
||||
"pytest>=9.1.1",
|
||||
"ruff>=0.16.9",
|
||||
]
|
||||
|
||||
@@ -18,6 +18,7 @@ source = { editable = "." }
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "markdown" },
|
||||
{ name = "pytest" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
@@ -26,6 +27,7 @@ dev = [
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "markdown", specifier = ">=3.11" },
|
||||
{ name = "pytest", specifier = ">=9.1.1" },
|
||||
{ name = "ruff", specifier = ">=0.16.9" },
|
||||
]
|
||||
@@ -39,6 +41,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/4f/700155c8c20d9e655dd0732b5fc3c7614f291b9148da271d7388e50bf774/markdown-3.11.tar.gz", hash = "sha256:180224db6aed87ba9ce1f2781ebcd5826253de8ff637112090e24b84502bbf9f", size = 485623, upload-time = "2026-09-25T13:46:23.473Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/1e/32971905a7ab47f8b66866ed949fa48b104ba1c4a6fa57794c4f2c4b2cb8/markdown-3.11-py3-none-any.whl", hash = "sha256:cd6c89e7eb308c8b332ed673215a52d208a43f8bacc030b1419376129408719e", size = 111296, upload-time = "2026-09-25T13:46:22.163Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "26.3"
|
||||
|
||||
Reference in New Issue
Block a user