Webové počitadlo na LAN s historií her #2

Merged
david-spacil merged 7 commits from agents/claude/web-a-historie into main 2026-08-20 21:24:57 +02:00
Owner

Z terminálového počitadla se stává doprovod ke stolu: telefon zapisuje skóre, notebook ukazuje zápisník a hry se pamatují mezi večery. Hráči se napříč hrami poznávají podle jména, arkádově — Adam ze středečního večera je ten samý Adam jako z minulého měsíce, bez účtů a hesel.

Terminálová verze zůstává funkční a navenek beze změny.

Rozhodnutí, ze kterých to vyrostlo

  • Web, ne nativní aplikace — hosti u stolu si nic neinstalují, otevřou adresu.
  • Zapisuje jeden člověk z telefonu, ne každý hráč vlastní zařízení.
  • Jen LAN, žádné přihlašování.
  • Trvalá historie od začátku, včetně jednotlivých tahů — statistiku z dat, která se nesbírala, zpětně nedopočítáš.
  • Jednodušší = lepší: žádný build step, žádný ORM, žádné async, žádný JS framework. Závislosti jsou dvě, Flask a qrcode.

Co je uvnitř

Soubor Role
core.py pravidla hry, žádné print, input ani DB
storage.py SQLite: hráči, hry, tahy
net.py hledání adres, na kterých je počitadlo dostupné
stats.py síň slávy a kariérní statistiky
web.py Flask, routy, spouštěč
dice.py totéž v terminálu, bez ukládání

Pravidla se vytáhla z CLI

add_score() se dřív uprostřed pravidla zastavovalo na input(), takže logika nešla testovat ani použít z webu. Teď core.Game vrací, co se stalo, a volající se rozhodne, jak to ukáže.

Tři věci, na kterých to stojí:

  • Součty jsou vždy odvozené z tahů. Díky tomu je undo() jen zahození posledního zápisu — funguje to i pro vracení vynulování.
  • Vynulování se ukládá jako hodnota 0 s příznakem, ne jako záporný tah. Původní trik se zápisem -sum(...) byl v CLI elegantní, ale v databázi by rozbil statistiky: „průměr na tah" by počítal s číslem, které nikdo nehodil.
  • zero_streak() je jediný zdroj pravdy pro pravidlo i pro indikátor x/xx, a počítá se od posledního vynulování.

Zápisník

Sloupce hráči, řádky kola — mřížka, kterou lidi u kostek stejně malují na papír. Tahy zapsané před vynulováním se škrtnou červeně, sloupec hráče na tahu drží zvýrazňovač.

Tabule se doptává serveru každé dvě vteřiny a vyměňuje jen hlavičku a zápisník. Nejnovější kolo je nahoře a součty nad historií — po sto kolech byla původní chronologická verze k ničemu, na obrazovku se vešla kola 1–14 a řádek se součty skončil asi 4 500 pixelů pod okrajem. Zobrazuje se posledních 15 kol (BOARD_ROUNDS), zbytek shrne řádka „… a dalších 85 kol".

Adresy

Notebook se během večera stěhuje mezi domácí WiFi a hotspotem z telefonu, takže se adresy hledají za běhu a nabízí se jich víc. Řadí se podle rozsahu adresy a obecného typu rozhraní, ne podle seznamu konkrétních jmen: výchozí trasa, místní sítě, tailnet, virtuální sítě. Loopback a link-local vypadnou, nic dalšího se nezahazuje — o tom, co funguje, rozhodne člověk u stolu. Když se notebook přesune jinam, tabule to pozná a načte se znovu. Když se nenajde nic, řekne, co s tím, místo QR kódu naslepo.

Statistiky

Síň slávy sbírá rekordy (nejvyšší tah, nejvyšší konečné skóre, nejrychlejší výhra), kariéra ukazuje, kdo je dlouhodobě dobrý. Počítají se jen dohrané hry. Celkový součet bodů napříč hrami tam vědomě není — cílové skóre je u každé hry jiné, hry nejsou srovnatelné a metrika by odměňovala jen toho, kdo hrál nejvíc.

Vzhled

Stojí na tom, co ta appka nahrazuje: čtverečkovaný papír, modrá propiska zapisuje, červená škrtá, zvýrazňovač drží tah. Písma jsou systémová, nic se nestahuje — na LAN nemusí být internet.

Ověření

80 testů (pytest tests/): pravidla hry, úložiště, klasifikace a řazení adres nezávisle na aktuální síti, a funkční průchody webem přes Flask test client.

Ručně odzkoušeno v prohlížeči, mimo jiné že se tabule sama přepnula na dalšího hráče poté, co skóre zapsal někdo jiný, a že se z telefonu na hotspotu dá připojit. Terminálová verze projela stejný skriptovaný průchod jako před refaktorem a chová se identicky.

🤖 Generated with Claude Code

Z terminálového počitadla se stává doprovod ke stolu: **telefon zapisuje skóre, notebook ukazuje zápisník** a hry se pamatují mezi večery. Hráči se napříč hrami poznávají podle jména, arkádově — Adam ze středečního večera je ten samý Adam jako z minulého měsíce, bez účtů a hesel. Terminálová verze zůstává funkční a navenek beze změny. ## Rozhodnutí, ze kterých to vyrostlo - **Web, ne nativní aplikace** — hosti u stolu si nic neinstalují, otevřou adresu. - **Zapisuje jeden člověk** z telefonu, ne každý hráč vlastní zařízení. - **Jen LAN**, žádné přihlašování. - **Trvalá historie od začátku**, včetně jednotlivých tahů — statistiku z dat, která se nesbírala, zpětně nedopočítáš. - **Jednodušší = lepší**: žádný build step, žádný ORM, žádné async, žádný JS framework. Závislosti jsou dvě, Flask a qrcode. ## Co je uvnitř | Soubor | Role | |---|---| | `core.py` | pravidla hry, žádné `print`, `input` ani DB | | `storage.py` | SQLite: hráči, hry, tahy | | `net.py` | hledání adres, na kterých je počitadlo dostupné | | `stats.py` | síň slávy a kariérní statistiky | | `web.py` | Flask, routy, spouštěč | | `dice.py` | totéž v terminálu, bez ukládání | ### Pravidla se vytáhla z CLI `add_score()` se dřív uprostřed pravidla zastavovalo na `input()`, takže logika nešla testovat ani použít z webu. Teď `core.Game` vrací, co se stalo, a volající se rozhodne, jak to ukáže. Tři věci, na kterých to stojí: - **Součty jsou vždy odvozené z tahů.** Díky tomu je `undo()` jen zahození posledního zápisu — funguje to i pro vracení vynulování. - **Vynulování se ukládá jako hodnota 0 s příznakem**, ne jako záporný tah. Původní trik se zápisem `-sum(...)` byl v CLI elegantní, ale v databázi by rozbil statistiky: „průměr na tah" by počítal s číslem, které nikdo nehodil. - **`zero_streak()` je jediný zdroj pravdy** pro pravidlo i pro indikátor `x`/`xx`, a počítá se od posledního vynulování. ### Zápisník Sloupce hráči, řádky kola — mřížka, kterou lidi u kostek stejně malují na papír. Tahy zapsané před vynulováním se škrtnou červeně, sloupec hráče na tahu drží zvýrazňovač. Tabule se doptává serveru každé dvě vteřiny a vyměňuje jen hlavičku a zápisník. **Nejnovější kolo je nahoře a součty nad historií** — po sto kolech byla původní chronologická verze k ničemu, na obrazovku se vešla kola 1–14 a řádek se součty skončil asi 4 500 pixelů pod okrajem. Zobrazuje se posledních 15 kol (`BOARD_ROUNDS`), zbytek shrne řádka „… a dalších 85 kol". ### Adresy Notebook se během večera stěhuje mezi domácí WiFi a hotspotem z telefonu, takže se adresy hledají za běhu a nabízí se jich víc. Řadí se podle **rozsahu adresy a obecného typu rozhraní**, ne podle seznamu konkrétních jmen: výchozí trasa, místní sítě, tailnet, virtuální sítě. Loopback a link-local vypadnou, nic dalšího se nezahazuje — o tom, co funguje, rozhodne člověk u stolu. Když se notebook přesune jinam, tabule to pozná a načte se znovu. Když se nenajde nic, řekne, co s tím, místo QR kódu naslepo. ### Statistiky Síň slávy sbírá rekordy (nejvyšší tah, nejvyšší konečné skóre, nejrychlejší výhra), kariéra ukazuje, kdo je dlouhodobě dobrý. Počítají se jen dohrané hry. Celkový součet bodů napříč hrami tam **vědomě není** — cílové skóre je u každé hry jiné, hry nejsou srovnatelné a metrika by odměňovala jen toho, kdo hrál nejvíc. ## Vzhled Stojí na tom, co ta appka nahrazuje: čtverečkovaný papír, modrá propiska zapisuje, červená škrtá, zvýrazňovač drží tah. Písma jsou systémová, nic se nestahuje — na LAN nemusí být internet. ## Ověření 80 testů (`pytest tests/`): pravidla hry, úložiště, klasifikace a řazení adres nezávisle na aktuální síti, a funkční průchody webem přes Flask test client. Ručně odzkoušeno v prohlížeči, mimo jiné že se tabule sama přepnula na dalšího hráče poté, co skóre zapsal někdo jiný, a že se z telefonu na hotspotu dá připojit. Terminálová verze projela stejný skriptovaný průchod jako před refaktorem a chová se identicky. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
david-spacil added 6 commits 2026-08-20 19:53:37 +02:00
Pravidla byla propletená se vstupem a výstupem (add_score se uprostřed
zastavovalo na input()), takže nešla testovat ani použít z webu.

- core.Game drží stav a pravidla, nic nevypisuje a na nic se neptá
- součty jsou vždy odvozené z tahů, díky tomu je undo() jen zahození
  posledního tahu — funguje i pro vracení vynulování
- vynulování se ukládá jako hodnota 0 s příznakem reset místo záporného
  tahu, aby do historie nepadala čísla, která nikdo nehodil
- zero_streak() je jediný zdroj pravdy pro pravidlo i pro indikátor x/xx
  a počítá se od posledního vynulování
- pořadí na tahu plyne z počtu zápisů, globální pointer zmizel
- dice.py zůstává navenek stejné, jen deleguje na Game
- 15 testů nad pravidly

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ručně psané SQL nad pěti tabulkami, žádné ORM.

- hry ukazují na players.id, nikdy na jméno, takže se přejmenování
  propíše i do starých her
- párování hráčů napříč hrami přes normalizovaný klíč (trim, casefold),
  plus slučování pro případ překlepu
- zapisuje se každý tah hned, ne až na konci — rozehraná hra se po pádu
  serveru najde podle stavu a dá se v ní pokračovat
- součty se neukládají nikde, stav hry se rekonstruuje načtením tahů
- časy jako ISO text, sqlite3 vlastní adaptéry pro datetime zavrhl

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Flask, server-rendered HTML, formuláře přes POST/redirect/GET.
Žádný build step, žádný JS framework.

- / zakládá hru výběrem známých hráčů v pořadí u stolu
- /game/<id> je zadávací pohled: kdo je na tahu, skóre, x/xx,
  průběžné pořadí, vrácení posledního zápisu
- /board/<id> je zápisník po kolech; tahy před vynulováním se škrtnou
  červeně, sloupec hráče na tahu drží zvýrazňovač
- tabule se doptává serveru každé dvě vteřiny a vyměňuje jen hlavičku
  a zápisník, QR s adresou zůstává mimo obnovovanou část
- adresa pro telefon se hledá za běhu, QR se skládá ze čtverečků
  přímo do SVG, takže není potřeba Pillow
- 16 funkčních testů přes Flask test client

Vizuálně to stojí na tom, co appka nahrazuje: čtverečkovaný papír,
modrá propiska zapisuje, červená škrtá, zvýrazňovač drží tah.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- rekordy: nejvyšší tah, nejvyšší konečné skóre, nejrychlejší výhra
- kariéra: počet her, výher, úspěšnost, průměrné pořadí, průměr na tah
  a kolikrát koho postihlo vynulování
- do statistik jdou jen dohrané hry, nedohrané a opuštěné se ignorují
- celkové skóre počítá SQL stejným pravidlem jako core.Game.totals(),
  tedy součet tahů po posledním vynulování; testem ověřeno, že se obě
  cesty shodují
- vědomě chybí celkový součet bodů napříč hrami — cílové skóre je u každé
  hry jiné, metrika by odměňovala jen toho, kdo hrál nejvíc
- README se spuštěním a přehledem modulů

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Po sto kolech byla tabule k ničemu: na obrazovku se vešla kola 1–14
a řádek se součty skončil asi 4 500 pixelů pod okrajem. Tabule tedy
ukazovala nejstarší historii a nic z toho, co se právě děje.

- kola se řadí od nejnovějšího, poslední zápis je vždy nahoře
- součty se přesunuly z patičky nad historii, hned pod jména hráčů
- tabule ukazuje posledních 15 kol, zbytek shrne řádka "... a dalších
  85 kol"; má ukazovat stav, ne archiv, a nepřekresluje se pak zbytečně
  každé dvě vteřiny přes tři sta buněk
- skloňování kol podle češtiny (1 kolo / 2 kola / 5 kol)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Detekce vracela jedinou adresu podle výchozí trasy. Když ta nevede tam,
kam je připojený telefon, QR kód míří do prázdna a nezbývá než sahat do
konfigurace. Při ladění se ukázalo i to, že záložní větev může vrátit
127.0.0.1, tedy QR kód na localhost.

- net.py hledá adresy všech rozhraní přes if_nameindex a ioctl, se zálohou
  přes getaddrinfo; DICE_HOST má poslední slovo
- řadí se podle rozsahu adresy a obecného typu rozhraní, ne podle seznamu
  konkrétních jmen: výchozí trasa, místní sítě, tailnet, virtuální sítě
- loopback a link-local vypadnou, nic dalšího se nezahazuje — o tom, co
  funguje, rozhoduje člověk u stolu
- tailnet adresu vědomě necháváme: funguje i napříč sítěmi
- adresy se hledají při každém načtení; když se notebook přesune na jinou
  síť, tabule to pozná podle data-address a načte se znovu
- když se nenajde nic, tabule řekne, co s tím, místo QR kódu naslepo
- 20 testů nad klasifikací a řazením, nezávislých na aktuální síti
- README doplněn o řešení potíží s připojením

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
david-spacil added 1 commit 2026-08-20 21:00:31 +02:00
Trojice venv/pip/python byla zbytečná ceremonie pro appku, která se pouští
jednou za herní večer.

- web.py i dice.py si nesou závislosti v hlavičce podle PEP 723, takže
  je uv obstará sám a není co instalovat ani aktivovat
- pytest.ini staví kořen repa na pythonpath, takže testy jdou spustit
  i mimo připravené prostředí
- README nabízí uv jako hlavní cestu, venv a systémové balíčky jako
  záložní

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
david-spacil merged commit f33104b5c2 into main 2026-08-20 21:24:57 +02:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: david-spacil/dice-counter#2