Flask pod pěkně naformátovanou úvodní hlášku vysypal červené varování, že
tohle není produkční server. Waitress je čistě pythonní, takže ho PyInstaller
zabalí bez řečí, a osm vláken pobere telefony u stolu i tabuli, která se
doptává každé dvě vteřiny.
Cestou se ukázalo, že hláška se nikdy nedostala do přesměrovaného výstupu:
mimo konzoli drží Python výstup v bloku a pouští ho, až program skončí —
jenže server neskončí. Zkouška binárky u Gitey si logovala do souboru a pak
ho vypisovala, takže celou dobu tiskla prázdno. Řeší to řádkové bufferování
v console.utf8(), kam to patří vedle té kódové stránky.
Venku jsou čtyři soubory na release, tři systémy a k tomu zdrojáky. Když
někdo napíše, že mu něco nefunguje, není jak zjistit, co vlastně pustil.
Verzi zapisuje build.sh do verze.txt, kostky.spec ji přibalí a version.py ji
z rozbalené binárky přečte. Ze zdrojáků má přednost git describe, který ví
i o commitech nad tagem a o rozdělané práci. V CI se na tagu vnucuje
proměnnou, protože runner klonuje jeden commit bez značek.
Hlásí se přes --version, v úvodní hlášce serveru a v patičce každé stránky.
Zkouška v obou workflow ověří, že se do binárky opravdu dostala.
Dokud to běželo jen doma, šlo dice.db v nejhorším smazat. Teď jsou venku
binárky, které si zakládají databázi v %LOCALAPPDATA% a v Library, a ta data
patří cizím lidem — první sloupec, který přibude, na nich prostě nebude.
Verze se drží v PRAGMA user_version. Existující databáze mají nulu a plné
schéma zároveň, což vychází: SCHEMA je samé IF NOT EXISTS, takže se na nich
jen orazítkuje a nic se neztratí.
Databázi z novější verze aplikace radši odmítneme otevřít. Srozumitelná
hláška je lepší než tiše rozbitá data, ke kterým neexistuje záloha.
Bez zamčených verzí si každá stavba stáhne to, co je zrovna nejnovější:
binárka z dneška a binárka z příštího jara obsahují něco jiného a není jak
zjistit co. Zároveň může vydání kterékoli knihovny rozbít build, aniž by se
sáhlo na řádku kódu — a nejspíš zrovna ve chvíli, kdy se tagne release.
Volné seznamy jsou nově v `.in`, zamčené v `.txt`. Rozděleno na tři: běh,
testy a stavba. Pytest se tak přestane instalovat do prostředí, kde se
staví binárka, a PyInstaller má konečně taky zamčenou verzi — ta na podobu
výsledku sahá ze všech nejvíc.
Zůstává, že se verze píšou i v hlavičce PEP 723, jinak by přestalo fungovat
`uv run web.py`. Aby se ta dvě místa nerozešla, hlídá je test.
Windowsová binárka spadla dřív, než stihla vypsat adresu:
print("Počitadlo je dostupné na:")
UnicodeEncodeError: 'charmap' codec can't encode character 'č'
Windows sahá po historické kódové stránce všude, kde výstup není konzole —
přesměrovaný do souboru, puštěný ze skriptu, ve frontě CI. První háček nad
takovým proudem shodí celý program.
Oba vstupní body si teď výstup přepnou na UTF-8. Na konzoli i na ostatních
systémech je to prázdná operace, protože tam je UTF-8 dávno výchozí.
Pojistka errors="replace" je pro zbytek případů: rozsypaný háček je pořád
lepší než spadlý server.
Chytila to zkouška po stavbě, ne uživatel. Test to reprodukuje přes
TextIOWrapper s cp1252, takže se to bez opravy neprojde ani na Linuxu.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
První ostrý běh na GitHubu spadl hned v přípravě jobu:
Unable to resolve action `astral-sh/setup-uv@v10`
Vydání v10.0.1 existuje, plovoucí značka v10 ne — setup-uv je přestal
vydávat po v7. Obě akce se teď píšou přesnou verzí; u třetí strany je to
stejně poctivější, protože je vidět, co se doopravdy pouští.
Druhá věc: macos-13 GitHub zrušil, takže ten job jen visel ve frontě.
Intel se staví na macos-15-intel.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PyInstaller neumí křížovou kompilaci — binárku pro každý systém musí
postavit ten systém. Vlastní runner pro Windows a hlavně pro macOS by byl
nepoměr, tak si tyhle stroje půjčujeme od GitHub Actions.
GitHub je jen půjčená dílna, ne druhý domov projektu: repozitář se tam
z Gitey zrcadlí, hotové soubory se posílají zpátky na zdejší release přes
Gitea API a stránka s releasy zůstává jedna. Linux se tam schválně
nestaví; doma to jde proti glibc 2.17 a je to zároveň pojistka, kdyby
GitHub vypadl.
build.sh je společný pro všechny tři systémy, liší se jen tím, že Windows
dává spustitelné soubory venvu do Scripts/ místo bin/. Ořezání symbolů
zůstává jen Linuxu: na macOS by rozbilo podpis, který si PyInstaller sám
přidává a bez kterého se binárka na Apple Silicon vůbec nespustí.
Zkouška po stavbě je stejná jako u linuxové: nastartovat, stáhnout si
stránku i styly a ověřit, že se založila databáze.
Nepodepsané binárky si Gatekeeper ani SmartScreen nenechají líbit, tak je
v README napsané, co s tím — certifikáty za tisíce ročně by na počitadlo
kostek byly nesmysl.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Než začneme rozdávat binárky pro macOS a Windows, musí se na nich chovat
rozumně to, co dosud počítalo s Linuxem.
Adresy rozhraní se hledaly linuxovým ioctl. Na Windows chybí fcntl, na
macOS to číslo znamená něco jiného, takže obojí propadlo až na poslední
zálohu přes hostname — a ta na macOS vrátí jednu adresu. Tabule by tam
nabídla jedinou volbu, přesně to, co jsme minule opravovali. Přibyla
mezizáloha přes ifconfig; spouští se jen tam, kde ioctl nic nenašel,
takže na Linuxu se neplatí nic. Parsování je zvlášť jako čistá funkce,
ať jde otestovat i bez macOS pod rukama.
Databáze z binárky mířila do XDG adresáře na všech systémech. Na Windows
by to znamenalo C:\Users\...\.local\share\, což tam nikdo nečeká. Teď se
volí podle systému: LOCALAPPDATA, Library/Application Support, XDG.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tabule dosud zbylé adresy jen vypsala, ale naskenovat se dala pouze ta
první. Teď se na ně dá kliknout: vybraná se přesune nahoru velkým písmem,
QR kód se přepne na ni a ta předchozí se objeví v seznamu, takže se dá
kroužit dokola.
Kódy jsou vykreslené všechny předem a přepíná se jen ten viditelný —
žádné doptávání serveru, žádná QR knihovna na klientovi. Aby to stránku
nenafouklo, skládá se SVG ze souvislých úseků místo jednotlivých
čtverečků; tabule se třemi adresami má 23 kB.
Otisk v data-address zahrnuje všechny adresy, ne jen tu první. Když se
změní kterákoli z nich, nabídka i QR kódy jsou k ničemu a tabule se
načte znovu.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Věší se jako druhá příloha ve formátu sha256sum, počítaná nad jménem, pod
kterým se soubor stahuje — aby se dala ověřit rovnou přes
sha256sum -c, bez přejmenovávání.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Matice Python 3.11 až 3.14 — u projektu, který deklaruje >=3.11, se hodí
vědět, že to na všech čtyřech opravdu jede. Pythony obstará uv, na
hostiteli nemusí být žádný.
K pytestu ještě skriptovaná partie v terminálové verzi. Pytest sahá na
core.py, prompty v dice.py nikdo netestoval; tohle projde dvě nuly, třetí
s vynulováním a výhru tak, jak to odehraje člověk.
Runner sdílí pracovní adresář mezi variantami matice, proto max-parallel 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PyInstaller hledá objdumpem, na kterých knihovnách binárka visí. Runner
v host módu ho nemá — je to jediné, co musí být na hostiteli, zbytek si
obstará uv.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Runner běží v host módu, bez dockeru, takže se v něm kontejner na AlmaLinux
spustit nedá. Ukázalo se ale, že kontejner potřeba není: stačí stavět proti
samostatnému CPythonu od uv. Ten je slinkovaný s glibc 2.17 a nic
z hostitele se do binárky nedostane — změřeno objdumpem přes všechno, co
se z ní rozbaluje. Výsledek je tedy přenositelnější než přes AlmaLinux 8
(2.28) a build.sh je o polovinu kratší.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Otagovaný commit postaví binárku na runneru, projede testy, zkusí ji
nastartovat a pověsí ji na release. Nad pull requesty běží totéž bez
posledního kroku, ať se rozbitá stavba pozná dřív než na tagu.
Staví se v kontejneru na AlmaLinux 8 ze stejného důvodu jako build.sh —
kvůli glibc. Záměrně bez hotových akcí z marketplace: na vlastním runneru
tím odpadá starost, jestli je v kontejneru node.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stáhni a spusť: jednosouborová binárka pro Linux, uvnitř Python, Flask,
šablony i styly. Staví se ./build.sh v kontejneru na AlmaLinux 8, protože
binárka slinkovaná proti glibc z Fedory 44 by nešla spustit nikde se
starším systémem.
Databáze tím dostává dvě různá místa. Ze zdrojáků zůstává dice.db
v pracovním adresáři, ať se dá mít víc sad vedle sebe. Z binárky jde do
~/.local/share/kostky/ — binárka se rozbaluje do dočasného adresáře
a pouští se odkudkoli, takže relativní cesta by databázi rozsypala po
disku. DICE_DB přebije obojí.
Server teď cestu k databázi vypisuje při startu, ať se na ni nemusí ptát.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>
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>
- 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>
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>
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>
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>
Sliceování [-2:][0] vrátí u seznamu s jediným záznamem tentýž
prvek jako [-1:][0], takže po první nule se zobrazilo rovnou "xx".
Nahrazeno přímým indexováním s kontrolou délky.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Převzato 1:1 z kostky/kostky.py a přizpůsobeno počitadlu:
- volitelné finální skóre při zakládání hry (výchozí 10 000)
- vynulování skóre po třech nulových hodech za sebou
- indikátor "x"/"xx" u hráče na tahu
- check_win() + to_sorted_tuple() + win() s výslednou tabulkou
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>