diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..30d42c7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,67 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +A web implementation of **Bridžik**, a Slovak 4-player trick-taking/bidding card game played with a 32-card Slovak/German-suited deck. The codebase is in mid-migration from a legacy server-rendered HTTP frontend to a realtime Socket.IO frontend; both still live in `api/`. Code, comments, and exception messages are mostly in **Slovak** — keep new strings consistent with that. + +## Commands + +```powershell +# Run the app (starts on 0.0.0.0:5000) +python -m app # or: python app.py / python start.py — all just import `api` + +# Run via Docker (app on :5000, debugpy on :5678) +docker-compose up --build + +# Run the full test suite (pure-engine unittest) +python -m unittest tests -v + +# Run a single test case / method +python -m unittest tests.StashCase +python -m unittest tests.StashCase.test_get_winner +``` + +There is no linter or build step configured. + +## Architecture + +### Game engine — `bridzik.py` (the important part) + +Pure Python, **no Flask dependency**. All game rules live here and are exercised directly by `tests.py`. The state is a strict nested hierarchy, each level enforcing turn order and completion before delegating down: + +- **`Bridzik`** — a whole game = exactly **4 `Series`**. +- **`Series`** — exactly **8 `Round`s**; the starting player rotates per series/round. +- **`Round`** — a bidding (`guess`/"tip") phase then a play phase of `8 - round_number` **`Stash`es**. Each round deals fewer cards as `round_number` grows. +- **`Stash`** (a "kopka" = one trick) — 4 cards, one per player; `get_winner()` resolves it. + +Key rules encoded in the engine: +- **`Card_colors.HEARTS` (červeň) is the permanent trump** — any heart beats any non-heart in `Stash.get_winner()`, and follow-suit logic in `Round.play_card` forces playing a heart when you can't follow the led suit. +- **`Card_values` are ordered** C7 < C8 < C9 < C10 < LOWER < UPPER < KING < ACE via custom comparison dunders. +- **Scoring**: a player who exactly matches their guess scores `10 + guess`, else 0 (`Round.get_points_summary`). +- The total of the 4 guesses may **not** equal the number of tricks (the last bidder is constrained) — see `Round.add_player_guess`. + +Serialization: `Card.JSONEncoder` flattens a `Card` to `{color, value}` name strings. The double `json.loads(json.dumps(...))` pattern used throughout the API exists to strip escaped slashes after custom encoding. + +### Web layer — `api/` + +Two frontends coexist; **`api/__init__.py` is the active one**: + +- **`api/__init__.py` — Socket.IO realtime server (current).** Defines `app`, `socketio`, and all `@socketio.on(...)` handlers (`create_game`, `register_player`, `start_game`, `play_card`, etc.). Supports **multiple concurrent games** via the module-global `games` dict keyed by game id (`gid`), with `Game`/`Player` wrapper classes and Socket.IO rooms. **The server is started as an import side-effect** — `socketio.run(...)` runs at the bottom of this module, which is why `app.py` and `start.py` are one-line `from api import app`. + - Known WIP/fixme spots: `create_game` hardcodes `gid = 'a'`; `play_card` references `get_player_cards` without calling it correctly. Expect rough edges here. + +- **`api/routes.py` + `api/templates/` + `api/forms.py` — legacy server-rendered HTTP frontend (dormant/broken).** It imports `bridzikInstance` from `api`, but that single shared instance is **no longer defined** in `api/__init__.py` (commented out), so these routes will not run as-is. Treat this as reference for the old single-game flow, not as live code, unless you are deliberately reviving it. + +- **`api/utils.py`** — `sort_card_list` (orders a hand by suit then value) and `get_points_sums` (totals standings across all series/rounds). Used by the legacy routes. + +### Config & infra + +- `config.py` holds a `Config` class with a dev `SECRET_KEY`; note the active `api/__init__.py` sets its own `SECRET_KEY` inline rather than loading this. +- `Dockerfile` targets Python 3.9, runs `python -m app`, and bundles `debugpy` for remote debugging on port 5678. +- `requirements.txt` pins `Flask-SocketIO` + `eventlet` (the chosen `async_mode`); the trailing `socket-cli` is marked for removal. + +## Conventions + +- New game-rule logic belongs in `bridzik.py` and should come with a `unittest` case in `tests.py` — keep the engine independent of Flask/Socket.IO. +- Raise `BridzikException` (Slovak message) for rule violations; the API layer catches it and re-emits a generic Slovak error to the client. diff --git a/PRAVIDLA.md b/PRAVIDLA.md new file mode 100644 index 0000000..fe8e64a --- /dev/null +++ b/PRAVIDLA.md @@ -0,0 +1,129 @@ +# Pravidlá hry Bridžik + +Tento dokument popisuje pravidlá hry tak, ako sú implementované v hernom jadre +(`bridzik.py`). Slúži ako referencia pre hráčov aj vývojárov. + +## Karty + +Hrá sa s **32-kartovým balíčkom** sedmových (slovenských/nemeckých) kariet. + +### Farby (4) + +| Názov v kóde | Slovenský názov | +| ------------ | --------------- | +| `HEARTS` | červeň | +| `LEAVES` | zeleň | +| `ACORNS` | žaluď | +| `BELLS` | guľa | + +**Červeň je vždy tromf (adut).** Červeň prebíja každú inú farbu. + +### Hodnoty (8) — od najnižšej po najvyššiu + +`sedmička (7) < osmička (8) < deviatka (9) < desiatka (10) < dolník < horník < kráľ < eso` + +V kóde: `C7 < C8 < C9 < C10 < LOWER < UPPER < KING < ACE`. + +## Štruktúra hry + +Hru hrajú **4 hráči** (očíslovaní 0, 1, 2, 3). Celá hra má pevnú štruktúru: + +- **Hra** = 4 **série** (séria 0 až 3). +- **Séria** = 8 **kôl** (kolo 0 až 7). +- **Kolo** = `8 − číslo_kola` **kopiek** (zdvihov). + +V každom kole dostane každý hráč `8 − číslo_kola` kariet, takže počet kariet +postupne klesá: + +| Číslo kola | Kariet na hráča | Počet kopiek v kole | +| ---------: | --------------: | ------------------: | +| 0 | 8 | 8 | +| 1 | 7 | 7 | +| 2 | 6 | 6 | +| 3 | 5 | 5 | +| 4 | 4 | 4 | +| 5 | 3 | 3 | +| 6 | 2 | 2 | +| 7 | 1 | 1 | + +Celá hra teda obsahuje 4 × 8 = 32 kôl. + +## Rozdávanie + +Na začiatku každého kola sa balíček zamieša. Z vrchu sa odloží `4 × číslo_kola` +kariet a zvyšok sa rozdá: každý zo 4 hráčov dostane `8 − číslo_kola` kariet. + +## Kto začína + +- **Sériu** otvára hráč s rovnakým číslom, ako je číslo série (séria 0 → hráč 0, + séria 1 → hráč 1, …). +- **Kolo** otvára hráč `(prvý_hráč_série + číslo_kola) mod 4`, čiže začínajúci + hráč sa medzi kolami posúva. + +## Priebeh kola + +Každé kolo má dve fázy: **tipovanie** a následne **hranie kariet**. + +### 1. Tipovanie (bridžik) + +Hráči postupne (počnúc začínajúcim hráčom kola, v smere poradia) zadávajú **tip** — +koľko kopiek (zdvihov) v tomto kole získajú. + +- Tip musí byť v rozsahu `0` až `počet kopiek v kole` (`8 − číslo_kola`). +- **Pravidlo bridžika:** posledný (štvrtý) tipujúci hráč nesmie zadať taký tip, + pri ktorom by sa **súčet všetkých štyroch tipov rovnal počtu kopiek** v kole. + Inými slovami, súčet tipov sa nikdy nesmie presne rovnať počtu zdvihov — vždy + musí niekto „prebrať" alebo „nedobrať". +- Každý hráč zadáva tip iba raz a iba keď je na rade. + +### 2. Hranie kariet + +Po dokončení tipovania sa hrá `8 − číslo_kola` kopiek (zdvihov). + +- **Prvú kopku** otvára hráč s **najvyšším tipom**. Pri zhode tipov začína ten, + kto je skôr v poradí (počítané od začínajúceho hráča kola). +- **Každú ďalšiu kopku** otvára **víťaz predchádzajúcej kopky**. + +#### Povinnosť priznať farbu + +Keď je na stole vynesená (prvá) karta kopky, ďalší hráči musia dodržať: + +1. Ak má hráč farbu vynesenej karty → **musí priznať farbu** (zahrať kartu tej + istej farby). +2. Ak farbu vynesenej karty nemá, ale má **červeň** → **musí zahrať červeň** + (tromf). +3. Ak nemá ani vynesenú farbu, ani červeň → môže zahrať **ľubovoľnú** kartu. + +Hráč môže zahrať len kartu, ktorú má v ruke, a iba keď je na rade. + +#### Vyhodnotenie kopky + +Po zahraní všetkých 4 kariet sa určí víťaz kopky: + +- **Červeň prebíja** každú inú farbu. Ak v kopke padla aspoň jedna červeň, + vyhráva **najvyššia červeň**. +- Ak nepadla žiadna červeň, vyhráva **najvyššia karta vynesenej farby**. +- Karty iných farieb (ktoré nie sú ani vynesená farba, ani červeň) kopku + vyhrať nemôžu. + +Víťaz kopky vynáša do nasledujúcej kopky. + +## Bodovanie + +Body sa počítajú po každom dokončenom kole: + +- Ak sa hráčov **tip presne zhoduje** s počtom kopiek, ktoré v kole získal, + dostane **`10 + tip`** bodov. +- Ak sa tip nezhoduje (získal viac alebo menej kopiek), dostane **0 bodov**. + +Príklad: hráč tipoval 3 a získal presne 3 kopky → 13 bodov. Ak by získal 2 alebo +4 kopky → 0 bodov. + +Celkové skóre hráča je súčet bodov zo všetkých kôl všetkých sérií. Vyhráva hráč +s najvyšším celkovým súčtom po dohraní všetkých 4 sérií. + +## Ukončenie + +- **Kolo** je ukončené, keď sú odohrané všetky kopky. +- **Séria** je ukončená po 8 kolách. +- **Hra** je ukončená po 4 sériách.