belote-cli 2.7.1__tar.gz → 2.9.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {belote_cli-2.7.1 → belote_cli-2.9.2}/CHANGELOG.md +170 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/DEVELOPMENT.md +2 -2
- {belote_cli-2.7.1 → belote_cli-2.9.2}/PKG-INFO +33 -25
- {belote_cli-2.7.1 → belote_cli-2.9.2}/README.md +32 -24
- {belote_cli-2.7.1 → belote_cli-2.9.2}/pyproject.toml +1 -1
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/__init__.py +1 -1
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/ai.py +171 -45
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/ansi.py +2 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/core/economy.py +5 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/core/run_state.py +49 -3
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/core/scoring.py +6 -3
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/engine/modifier_patch.py +2 -6
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/engine/round_driver.py +85 -11
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/jokers/corrupted.py +5 -2
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/jokers/hand_comp.py +11 -6
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/jokers/trick_timing.py +9 -2
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/registry.py +38 -2
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/tarots.py +17 -1
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/vouchers.py +32 -6
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/main.py +97 -38
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/partner/partner_state.py +10 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/partner/personality.py +63 -11
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/progression/save.py +31 -2
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/run/boss.py +21 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/run/decks.py +1 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/run/shop.py +5 -3
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/ui/announce.py +19 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/config.py +11 -8
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/game.py +231 -58
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/gameflow.py +32 -5
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/input.py +11 -2
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/rules.py +1 -1
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/scoring.py +90 -30
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/themes.py +9 -6
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/ui/announce.py +15 -2
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/ui/menu.py +39 -34
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/ui/prompts.py +23 -54
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/ui/render.py +100 -12
- belote_cli-2.9.2/tests/belatro/test_contract_unlocks.py +241 -0
- belote_cli-2.9.2/tests/belatro/test_dead_flag_fixes.py +290 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/belatro/test_deck_variants.py +16 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/belatro/test_round_driver.py +84 -0
- belote_cli-2.9.2/tests/test_ai.py +150 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/test_belote.py +0 -5
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/test_official_rules.py +267 -2
- belote_cli-2.7.1/tests/test_ai.py +0 -73
- {belote_cli-2.7.1 → belote_cli-2.9.2}/.claude/settings.local.json +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/.gitignore +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/.python-version +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/GRIMAUD Standard Playing-Cards-1898.png +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/LICENSE +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/scripts/benchmark.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/core/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/engine/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/engine/event_bus.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/base.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/jokers/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/jokers/annonces.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/jokers/coinche.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/jokers/contract.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/jokers/economy.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/partner_jokers/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/partner_jokers/passive.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/partner_jokers/risky.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/partner_jokers/shaper.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/items/planets.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/partner/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/partner/trust.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/progression/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/progression/unlocks.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/run/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/run/ante.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/run/ante_themes.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/ui/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/ui/collection.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/ui/hud.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/ui/menu.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/ui/rules.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/ui/shop.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/belatro/ui/trust_bar.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/context.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/deck.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/main.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/stats.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/ui/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/src/belote/ui/layout.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/belatro/__init__.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/belatro/test_belatro.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/belatro/test_boss_modifiers_integration.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/belatro/test_collection_logic.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/belatro/test_partner_trust.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/belatro/test_phase0_coverage.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/belatro/test_phase1_plumbing.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/belatro/test_phase2_content.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/belatro/test_phase3_meta.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/belatro/test_progression.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/test_extended.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/test_game_logic.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/test_gameflow.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/test_layout.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/test_new_coverage.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/test_properties.py +0 -0
- {belote_cli-2.7.1 → belote_cli-2.9.2}/tests/test_undo.py +0 -0
|
@@ -5,6 +5,176 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.9.2] - 2026-05-07
|
|
9
|
+
|
|
10
|
+
Render-pipeline fix for Konsole (KDE/Kubuntu) and other strict ANSI terminals where UI elements visibly stacked on top of each other — the top HUD repeating ~6 times, "Theme: Sepia Vintage" duplicating in the right column, "Partner" doubling, the bid prompt repainting below itself, and bid history accumulating between frames. The bug existed in the code on every terminal but VTE-based emulators (LXTerminal, GNOME Terminal, xterm) auto-blanked the leaking cells, masking it. Konsole's Vt102Emulation does not, so the leakage was visible.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`src/belote/ui/render.py::render`** — the bidding selector (Round 1 inline highlighted Take/Pass; Round 2 boxed grid; optional `partner_bid_tendency_text` line) is now painted inside the main render frame via the new `bid_selection: int | None` parameter on `render()` and `display()`. Previously `prompt_bid` paid display() and then wrote 8+ extra `\r\n` lines, which scrolled the alt-screen on the bottom row and left stale rows that the next frame's blank padding never repainted.
|
|
15
|
+
- **`src/belote/ui/render.py::render`** — every line in the rendered frame now ends with `clear_to_eol()` (`\x1b[K`), including blank vertical-centering padding rows. The previous `line + clear_to_eol() if line else line` branch skipped the escape on padding rows, betting the previous frame's content area had already cleared them — but that bet broke whenever an external write (announcement, prompt artifact) deposited debris on a padding row. Cost is one 3-byte escape per row; benefit is correct rendering on any ANSI-compliant terminal regardless of strictness.
|
|
16
|
+
- **`src/belote/ui/prompts.py::prompt_bid`** — gutted of all post-`display()` `sys.stdout.write` calls. Each loop iteration is now a single `display(state, None, bid_selection=sel)` + `reader.read()`. Removed unused `REVERSE` and `black_fg` imports (the box-rendering code that consumed them moved to `render.py::_build_bid_prompt_lines`).
|
|
17
|
+
- **`src/belote/ui/announce.py::announce`** — replaced the `\r\n`-bracketed banner with absolute cursor positioning (`move(term_h - 1, 1) + clear_line()`) so the banner can never trigger a scroll, even when the cursor is parked on the bottom row of the alt-screen.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **`src/belote/ui/render.py::_build_bid_prompt_lines`** — pure helper that returns the in-frame bidding selector lines. Encapsulates the Round 1 inline form, the Round 2 60-column boxed form, and the optional partner-tendency line so the main render loop reads as one coherent paint.
|
|
22
|
+
|
|
23
|
+
### Notes
|
|
24
|
+
|
|
25
|
+
No gameplay, scoring, or AI changes. 435/435 tests pass. The fix is observable when running under Konsole — start a Belote round, force a Round 2 bid (pass on the up-card), and arrow-navigate the selector: previously the box stacked between iterations; now it repaints in place.
|
|
26
|
+
|
|
27
|
+
## [2.9.1] - 2026-05-06
|
|
28
|
+
|
|
29
|
+
UI polish patch on top of 2.9.0. Two pieces of menu art had drifted; this release fixes both and tightens the menu plumbing so the cup walls hold for every label combination.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **`src/belote/ui/menu.py::get_cards_art`** — the croissant Braille had a corrupted line 8 (`⠘⢿⡿⠋⣠⣾⣿⣿⣿⠟⠁⣿⣿⣿⣿⣿⠟⢁⣀`, 21 cells) that broke the right curl, and was missing the 13th-line `⠉⠉⠉` crumb under the tip entirely. Restored to the canonical 13-line, 25-cell-wide reference. Each line now uses U+2800 Braille blanks for its leading indent (uniform 25-cell width) rather than mixed ASCII spaces, so callers don't have to re-pad.
|
|
34
|
+
- **`src/belote/ui/menu.py::CUP_TEMPLATE`** — body inner width was 47 chars (29-char opt + 16 trailing dead space + 2 gutter), much wider than the 23-char lid and saucer. Now 29 chars flush so menu options sit between the cup walls with no padding. Steam line 2 now correctly shows the two-puff frame (`) (`) — previously the template's leading-indent disagreement with `STEAMS[..][1]` pushed the second puff out of view.
|
|
35
|
+
- **`src/belote/ui/menu.py::_render_main_menu_art`** — selected-row markers tightened from ` > {label} < ` (6-char overhead) to `> {label} <` (4-char overhead) so the selected row uses the same width budget as unselected (` {label} `, also 4-char overhead). Without this, a selected `Theme: < Sepia Vintage >` would bust the right cup wall.
|
|
36
|
+
- **`src/belote/ui/menu.py::show_main_menu`** — settings labels shortened to fit the new 25-char usable label width: `AI Config:` → `AI:`, `Target Score:` → `Target:`. `Speed:` and `Theme:` keep their names with tightened spacing. All four `<` markers still column-align.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **`src/belote/ui/menu.py::_render_main_menu_art`** — opt-slot loop and assertion bound dropped from 12 → 9 to match the new template. The error message ("add opt slots to CUP_TEMPLATE") still points at the right fix if the menu ever grows past 9 entries.
|
|
41
|
+
|
|
42
|
+
### Notes
|
|
43
|
+
|
|
44
|
+
No gameplay or scoring changes; classic and BelAtro behavior is bit-for-bit identical. 435/435 tests pass; ruff and mypy clean on `src/`. Pre-existing lint/type debt in `tests/` and `scripts/benchmark.py` (18 ruff, 69 mypy strict-mode annotations) is untouched and tracked separately.
|
|
45
|
+
|
|
46
|
+
## [2.9.0] - 2026-05-06
|
|
47
|
+
|
|
48
|
+
Audit-driven sweep on top of 2.8.0: four engine bugs fixed and the long-deferred Tout Atout / Sans Atout bidding affordance shipped end-to-end. The README's "future work" line for those contracts is gone — both contracts are now bidable in classic Belote and BelAtro, and the two jokers / two unlock counters that had been waiting on the affordance now fire in real play. Plan: `plans/bug-hunt-code-performance-elegant-starlight.md`.
|
|
49
|
+
|
|
50
|
+
### Fixed — confirmed bugs
|
|
51
|
+
|
|
52
|
+
- **`src/belote/belatro/engine/round_driver.py`** — replaced the lingering `boss.id == "l_agent_double_boss"` string-branch with a flag-based dispatch keyed on `state.boss_modifiers.agent_double_active`. Previously, BetrayalArc and the Le Traître joker both set the active flag but only L'Agent Double populated `agent_double_tricks` — so partner sabotage silently never fired for the other two paths. New `BossModifiers.agent_double_late_only` flag lets BetrayalArc express its "from trick 4 onward" pattern without re-introducing string-branching. Regression tests pinned in `tests/belatro/test_round_driver.py`. Plan B1.
|
|
53
|
+
- **`src/belote/belatro/run/decks.py` + `core/run_state.py` + `belatro/main.py`** — Le Joueur's advertised "Boss Blind every 2 antes" was unimplemented (zero `boss_every_2` matches in the codebase). Now: deck modification → `card_enhancements["boss_every_2"]` → `main.py` rolls an extra boss on the Big Blind of even-numbered antes, doubling Le Joueur's boss-encounter count and matching the deck's high-variance theme. Plan B2.
|
|
54
|
+
- **`src/belote/game.py`** — removed the dead `bid_suits` field on `GameState`. It was initialized and reset to `()` but never written, never read for gameplay. AI bidding correctly uses `state.up_card.suit` for round-1/2 forcing/forbidding. Plan B3.
|
|
55
|
+
- **`src/belote/game.py::_calculate_legal_cards_impl`** — Tout Atout legal_cards branch added. Pre-fix the code dropped into the "non-trump led" branch under TA because `lead_suit != Suit.TOUT_ATOUT`, producing wrong legal moves whenever a TA round actually played. Now: must follow lead suit, must rise within suit if possible, free discard if void. Bug was unreachable until B-feature shipped, but is now exercised by the new bidding affordance. Plan B4.
|
|
56
|
+
|
|
57
|
+
### Added — Tout Atout / Sans Atout contracts
|
|
58
|
+
|
|
59
|
+
The full bidding affordance for both special contracts, end to end across classic Belote and BelAtro:
|
|
60
|
+
|
|
61
|
+
- **`src/belote/game.py`** — `BidValue = Suit | Literal["sans_atout"] | None` and `SANS_ATOUT_BID` sentinel. `place_bid` accepts the new bid value and rejects TA/SA in round 1 with `IllegalMoveError`. Post-bid mapping: TA → `trump=Suit.TOUT_ATOUT, contract="tout_atout"`; SA → `trump=None, contract="sans_atout"`; normal → `trump=<suit>, contract="normal"`. Belote pre-calc skipped for both special contracts (no unique K+Q-of-trump exists). New `is_sans_atout` flag threaded through `_calculate_legal_cards_impl`, `_card_beats`, `_current_trick_winner`, `trick_winner_seat`. Dynamic-trump boss suppressed under SA.
|
|
62
|
+
- **`src/belote/scoring.py`** — `score_round` gates on `state.contract` instead of `state.trump` so SA rounds (trump=None) score correctly. Chute formula uses `TOTAL_POINTS_TOUT_ATOUT=248` (TA) / `TOTAL_POINTS_SANS_ATOUT=120` (SA) from `config.py`. `_detect_all_declarations` skips belote detection under TA/SA. `card_points(c, None)` already produces the SA non-trump scale, so inner arithmetic is unchanged. Capot stays 252 for both contracts.
|
|
63
|
+
- **`src/belote/ai.py`** — three-tier TA/SA bid heuristics. Easy: spread-Jacks → TA, flat-Aces → SA. Medium: weighted scoring with personality jitter. Hard: card_points-based with Jack-bonus / long-suit-penalty. Round 1 still rejects TA/SA — the affordance is round-2-only per FFBelote rules.
|
|
64
|
+
- **`src/belote/ui/prompts.py`** — round-2 bid box widened to 60 cols and offers `[♠ ♥ ♦ ♣ TA SA Pass]` minus the up-card suit. New keyboard shortcuts `a` (Tout Atout) and `s` (Sans Atout) alongside the existing digit-select.
|
|
65
|
+
- **`src/belote/gameflow.py::_bid_label`** — helper produces "Tout Atout" / "Sans Atout" / suit symbol / "Pass" for the AI bid announcement line. Pre-fix `bid.symbol` would crash on the SA string sentinel.
|
|
66
|
+
- **`src/belote/belatro/engine/round_driver.py`** — partner's TA/SA bid is now gated on `partner.trust.duo_contracts_available`. Below the threshold, the bid falls back to "pass" rather than to a normal-suit bid, respecting the personality's choice to "go big or go home." This is the missing reader of the unread `duo_contracts_available` flag at `partner/trust.py:49`.
|
|
67
|
+
- **`src/belote/belatro/partner/personality.py`** — `LeCourageux` opts into TA on Jack-heavy hands; `LeStratege` opts into SA on flat Ace/10-heavy hands. Both gate on round 2 and on the trust flag.
|
|
68
|
+
|
|
69
|
+
These changes unblock two BelAtro jokers that were previously dead code:
|
|
70
|
+
|
|
71
|
+
- **`Le Fanatique`** — now reachable via `tout_atout_wins` unlock counter; ×1.5 mult past the 4th NS-won trick of a TA round actually fires.
|
|
72
|
+
- **`L'Idéologue`** — reachable via `sans_atout_wins`; +18 chips per Jack in SOUTH-won SA tricks actually fires.
|
|
73
|
+
|
|
74
|
+
End-to-end integration tests in `tests/belatro/test_contract_unlocks.py` pin both unlock counters incrementing on real round-end events.
|
|
75
|
+
|
|
76
|
+
### Audit findings reviewed and dismissed
|
|
77
|
+
|
|
78
|
+
Three claims from the audit pipeline turned out to be misreadings of correct code; documented here so future audits don't re-flag them:
|
|
79
|
+
|
|
80
|
+
- "AI Round 1 bidding logic is inverted" — the variable is named `forbidden` but per the comment it's the up-card suit, which is the **only** allowed bid in Round 1 per FFBelote. Code is correct.
|
|
81
|
+
- "Partner-overtrump exception missing on trump leads" — intentional and pinned by `tests/test_belote.py:313` ("Trump lead: partner-winning exception does NOT apply"). Per FFBelote rules.
|
|
82
|
+
- "Le Marseillais `announce_x2` / `no_belote_rebelote` flags never read" — both ARE read at `scoring.py:518` and `:554`, with dedicated tests in `tests/belatro/test_dead_flag_fixes.py`.
|
|
83
|
+
|
|
84
|
+
### Performance
|
|
85
|
+
|
|
86
|
+
Audit confirmed AI 0.032ms / render 0.233ms / scoring 0.197ms — well under the advertised targets. No perf changes needed.
|
|
87
|
+
|
|
88
|
+
## [2.8.0] - 2026-05-06
|
|
89
|
+
|
|
90
|
+
Two consecutive audit-driven bug-fix sweeps shipped together. The first cleaned up advertised-but-silently-dead BelAtro flags (deck modifiers and vouchers whose `apply()` set state nobody read). The second verified a 5-critical / 12-high / 14-medium / 8-incomplete / 10-perf audit — about 62% of findings were real game-affecting bugs and were fixed; the rest were misreadings of the rules or already-correct code, listed under "Audit claims explicitly rejected" at the bottom. The plan-mode verification report lives at `plans/check-these-findings-accurately-abstract-kahan.md`.
|
|
91
|
+
|
|
92
|
+
### Fixed — game-affecting correctness (sweep 2)
|
|
93
|
+
|
|
94
|
+
- **`src/belote/main.py` + `belatro/main.py`** — terminal corruption returning from BelAtro to the main menu. Both files independently entered/exited alt-screen; `BelAtroGame.start()` no longer toggles alt-screen and instead trusts the caller (classic-mode `belote.main` keeps alt-screen across menu↔BelAtro transitions, the standalone `belatro` console script keeps its own wrapper). Audit C1.
|
|
95
|
+
- **`src/belote/ai.py`** — every `trick_rank` / `card_points` / `_current_trick_winner` call now passes `state.boss_modifiers.seven_eight_trump`. AI was previously ranking the trump 7/8 wrong under La Déluge, picking incorrect cards. The flag is cached on `AIPlayer._se` at the top of `decide_card`/`decide_bid` so the threading is one read per decision. Audit C2.
|
|
96
|
+
- **`src/belote/belatro/items/tarots.py::LeFou`** — actually re-applies the last Tarot/Planet effect now. Backed by a new `BelAtroRun.last_consumable_id` field and `BelAtroRun.consume()` helper for centralised activation; falls back to the previous random-tarot behavior when no prior consumable has been recorded. Audit C4.
|
|
97
|
+
- **`src/belote/belatro/items/jokers/trick_timing.py::LePremierSang`** — "+2 Mult for the rest of the round" now keeps paying out on every trick after a trick-1 NS win. The `_active` flag was set but never read, so only trick 1 ever scored. Audit H4.
|
|
98
|
+
- **`src/belote/belatro/items/jokers/corrupted.py::LAgentDouble`** — sabotage counter ("for 2 tricks") decrements every trick instead of only on opponent wins. Previously, NS sweeping the round left the sabotage flag stuck on indefinitely. Audit H5.
|
|
99
|
+
- **`src/belote/belatro/items/vouchers.py::forge_tierce`** — delegates to `Planet.use()` so overlapping numeric levels are summed, matching the regular planet level-up path. Previously a `**existing` dict merge silently dropped earlier levels. Audit H6.
|
|
100
|
+
- **`src/belote/belatro/items/vouchers.py::LaVoute`** — uses `max()` rather than `=` so it can't wipe additive bonuses already granted by LesCartesDorees; LaVoute now defines a floor of (rate=1, cap=5). Audit H7.
|
|
101
|
+
- **`src/belote/belatro/main.py`** — the three bare `print()` calls in alt-screen ("YOU WON!", Capot Insurance softening, RUN OVER) routed through a new `BelAtroAnnounce.banner()` helper that writes a centred line at a fixed row, avoiding scroll. Audit H8.
|
|
102
|
+
- **`src/belote/belatro/main.py::prompt_card`** — UNDO no longer recurses (`return self.prompt_card(state)` → `continue` inside the existing `while True`). Repeated UNDO presses can no longer hit the recursion limit. Audit H9.
|
|
103
|
+
- **`src/belote/belatro/progression/save.py`** — atomic save: `tempfile.NamedTemporaryFile` + `fsync` + `Path.replace` so a crash mid-write leaves the previous `profile.json` intact. Added `SCHEMA_VERSION = 1` and a `_migrate()` hook so future schema bumps have a place to live. Audit H10 + I7.
|
|
104
|
+
- **`src/belote/gameflow.py::run_round`** — undo now calls `clear_legal_cards_cache()` before popping the history stack so a restored earlier `GameState` can never serve a stale legal-cards entry. Audit H11.
|
|
105
|
+
|
|
106
|
+
### Fixed — robustness / correctness
|
|
107
|
+
|
|
108
|
+
- **`src/belote/belatro/core/economy.py::spend_money`** — rejects negative `amount`. Previously `if money >= -5` passed trivially and `money -= -5` credited the player. Audit M2.
|
|
109
|
+
- **`src/belote/belatro/core/run_state.py`** — free-planet draw uses a per-run `random.Random` instance seeded from `BelAtroRun.seed`, not the global module RNG. Runs are seed-deterministic again. Audit M3.
|
|
110
|
+
- **`src/belote/belatro/engine/modifier_patch.py`** — deleted the dead `patch_card_points` method (set `_card_pt_override`, no consumer). Audit M4.
|
|
111
|
+
- **`src/belote/belatro/core/scoring.py`** — joker_state is `copy.deepcopy`'d, not `dict()`-shallowed, so mutable values (lists/dicts/sets) can't leak across rounds. Audit M5.
|
|
112
|
+
- **`src/belote/input.py`** — ESC vs. arrow-key disambiguation window is now 50ms (was 10ms), and the UTF-8 continuation-byte read is bounded by a `select.select` timeout so a partial sequence can't block the reader forever. Audit M6 + M7.
|
|
113
|
+
- **`src/belote/rules.py`** — French sequence labels: `Tierce / Quarte / Quinte` instead of `Tierce / Cinquante / Cent`. (English text was already correct.) Audit M8.
|
|
114
|
+
- **`src/belote/belatro/partner/personality.py::LeFlambeur`** — bid logic now picks the longest suit with most honors instead of `random.choice`; `should_bid` checks for any J/9/A in the strongest suit. Description "aggressive" is honored. Audit M11.
|
|
115
|
+
- **`src/belote/belatro/engine/round_driver.py`** — Le Fantôme partner personality grants +$1 per partner-won trick via `state._bonus_money`, matching the description. Audit I5.
|
|
116
|
+
- **`src/belote/belatro/partner/partner_state.py::difficulty_for`** — now respects the `seat` parameter and returns `"hard"` at trust tier ≥ 3. `round_driver.py` maps the new return value to `Difficulty.HARD`. Audit M12.
|
|
117
|
+
- **`src/belote/belatro/run/shop.py`** — `random.sample` instead of two `random.choice` calls so the same Joker can't appear twice in one shop. Audit M13.
|
|
118
|
+
- **`src/belote/themes.py::set_current`** — raises `ValueError` for unknown theme names instead of silently no-op'ing. Audit M14.
|
|
119
|
+
- **`src/belote/belatro/main.py`** — La Maison-Dieu's `disable_next_boss` and Le Diable's `partner_overcut_round` flags are now consumed: La Maison-Dieu skips the next boss reveal and clears the flag; Le Diable is popped after the round ends. Were one-round effects but stayed permanent. Audit I3.
|
|
120
|
+
|
|
121
|
+
### Performance
|
|
122
|
+
|
|
123
|
+
- **`src/belote/ansi.py`** — `fg()` and `bg()` are `@lru_cache`'d (maxsize=512). Previously every render allocated ~200 fresh format strings. Audit P3.
|
|
124
|
+
- **`src/belote/ui/render.py`** — hoisted the lazy `clear_screen` import out of `render()` (was re-resolved every call); `clear_to_eol()` is no longer emitted for blank padding lines. Audit P1 + P2.
|
|
125
|
+
- **`src/belote/game.py`** — pre-computed `_SUIT_IDX_CACHE` for every possible trump value; `sort_hand` no longer rebuilds the suit ordering and index dict per call. Audit P4.
|
|
126
|
+
- **`src/belote/belatro/items/registry.py`** — `get_available_jokers` / `get_available_vouchers` cache their filtered output keyed on a generation counter (bumped on every `register_*`) and the profile's unlock set. Re-registration invalidates automatically. Audit P8.
|
|
127
|
+
- **`src/belote/config.py`** — `stats_path` no longer mkdirs on every property access. Persistence sites (stats writer + profile saver) already mkdir at write time. Audit P10.
|
|
128
|
+
|
|
129
|
+
### Fixed — silently-dead BelAtro flags (sweep 1)
|
|
130
|
+
|
|
131
|
+
The pattern: a deck modifier or voucher's `apply()` set a flag on `BelAtroRun` (or patched a boss flag), but no game-logic site read it. Tests passed because they only asserted the flag existed, never the behavior.
|
|
132
|
+
|
|
133
|
+
- **`src/belote/belatro/main.py`** — boss handling now reads `boss.flags()` instead of hardcoded `boss.id == "..."` checks. Consequence: **La Trahison** (`BetrayalArc`) actually freezes partner trust at 0 (was a no-op — only `Le Divorce` worked, by id-coincidence). `hide_partner_hand` and `auto_coinche` are also flag-driven now, restoring the architecture the rest of the engine uses.
|
|
134
|
+
- **`src/belote/scoring.py`** — Le Marseillais deck mods now honored: `announce_x2` doubles Tierce/Quarte/Quinte/Carré points; `no_belote_rebelote` zeroes Belote/Rebelote scoring (was set on the run, ignored by scoring). La Balance voucher's `tie_breaks_for_taker` short-circuits litige and awards the contract to the taker (previously dead — every tie became a litige).
|
|
135
|
+
- **`src/belote/game.py`** — Le Républicain's "7s and 8s are wild — play them on any trick" rule now actually applies in `legal_cards`. Before, only the +5-chip-per-7-or-8 rider worked; the wild-play rule was deck-description fiction. Also: the `winner is None` fallback after a 4-card trick now raises `AssertionError` instead of silently picking `prev_seat()` (unreachable in practice; surfacing it catches future state corruption).
|
|
136
|
+
- **`src/belote/belatro/engine/round_driver.py`** — Le Coincheur's `start_coinched` enters the round at coinche level 1 (was set, never read). Le Traître joker's `partner_throws_trick` actually triggers partner sabotage on one random trick by piggy-backing on the existing `agent_double_active` AI path (only the +2.5 mult half worked before). La Surcoinche voucher gates AI surcoinche.
|
|
137
|
+
- **`src/belote/belatro/items/jokers/hand_comp.py`** — La Sentinelle's ×3 mult only fires when South was actually dealt the trump Jack. Previously the joker armed `had_jack=True` whenever the trump Jack appeared in any winning trick, including when partner or an opponent held it — false positives. Reconstructs per-card seat from `leader_seat + index`.
|
|
138
|
+
- **`src/belote/belatro/main.py`** + **`core/run_state.py`** — L'Aristocrate's `gold_seal_aces` pays $1 per Ace your team wins (was set in the deck mod dict but never copied to run state, never read). L'Anarchiste's `corrupted_pool_visible` is now surfaced on the run.
|
|
139
|
+
- **`src/belote/ui/prompts.py`** — L'Encyclopédie voucher (`show_partner_bid_tendency`) renders one-line partner personality hint above the bid prompt; was set, never displayed.
|
|
140
|
+
- **`README.md`** — count drift corrected: **36 Jokers / 12 Tarot / 18 Bosses** (were "50+ / 10 / 17"). Sans Atout claim softened to match reality (no `SANS_ATOUT` suit value or bid affordance exists; scoring engine handles `TOUT_ATOUT` but it's not currently bid-able from the UI).
|
|
141
|
+
|
|
142
|
+
### Added
|
|
143
|
+
|
|
144
|
+
- **`src/belote/belatro/run/boss.py::BossModifier.flags()`** — returns the `BossModifiers` dataclass produced by this boss's `apply()` against a stub. Lets `main.py` react to flags without driving a full round, replacing the hardcoded `boss.id ==` chain.
|
|
145
|
+
- **`src/belote/belatro/items/vouchers.py::forge_tierce(run, planet_id)`** — runtime helper that consumes 3 Tierce charges and applies a Planet's level-up reward to `run.contract_levels`. Promotes TierceForge from pure stub to a callable shop hook (UI integration is the remaining work).
|
|
146
|
+
- **`src/belote/belatro/ui/announce.py::BelAtroAnnounce.banner()`** — centred fixed-row banner that doesn't scroll the alt-screen buffer; replaces the three bare `print()` calls in `belatro/main.py`.
|
|
147
|
+
- **`src/belote/belatro/core/run_state.py::BelAtroRun.consume()`** — centralised consumable activation that records the item id as `last_consumable_id` for LeFou to copy.
|
|
148
|
+
- **`src/belote/belatro/progression/save.py::SaveManager.SCHEMA_VERSION` + `_migrate()`** — save-file schema versioning + migration hook.
|
|
149
|
+
- **`tests/belatro/test_dead_flag_fixes.py`** (19 tests) — integration coverage for every flag wired up above. Each test constructs a minimal `GameState` or `BelAtroRun` with the flag set and asserts the *observable* effect (e.g. "carré declarations score 200 instead of 100 with `announce_x2`", "trump 7 is legal off-suit with `republicain_wild`", "trump Jack played by NORTH does not arm South's Sentinelle").
|
|
150
|
+
|
|
151
|
+
### Audit claims explicitly rejected (do not reopen)
|
|
152
|
+
|
|
153
|
+
From sweep 2:
|
|
154
|
+
|
|
155
|
+
- C3 "AI doesn't handle TOUT_ATOUT" — `card.suit == trump` is harmless: TOUT_ATOUT is a sentinel suit, and `trick_rank` / `card_points` already special-case it.
|
|
156
|
+
- C5 "`JokerResult.times_mult` defaults to 0.0" — used as a truthy-skip sentinel in `core/scoring.py`'s `_apply`; `0.0` means "skip", not "zero out".
|
|
157
|
+
- H1 "Rebelote logic wrong" — `belote_holders[trump]` is the single seat holding both K and Q; standard Belote rule is exactly that, code is correct.
|
|
158
|
+
- H2 "`legal_cards` cache stale on dynamic trump" — `trump` is part of the cache key; rotating it produces a different entry.
|
|
159
|
+
- H3 "QuinteRoyale threshold wrong" — Quinte = 100 pts, `>= 100` triggers correctly.
|
|
160
|
+
- H12 "Coinche blocked when partner takes" — correct behavior; you cannot coinche your own partner's bid.
|
|
161
|
+
- M1 "EventBus swallows handler errors" — no `try/except`, exceptions propagate. Description is the opposite of reality.
|
|
162
|
+
- M9 "Worst-round 999 sentinel leaks" — `announce.py:96` already gates on `total_rounds > 0` with default `0`.
|
|
163
|
+
- I2 "BossModifiers flags unused" — every flag is read (`hide_hud`, `hide_partner_hand`, `agent_double_active`, `partner_forced_pass`, `lock_trust_zero`); matches the architecture documented in memory.
|
|
164
|
+
- I4 "LeFanatique state['contract'] never set" — written by `core/scoring.py` on `BidMadeEvent`.
|
|
165
|
+
- I8 "unlocks event handler stub" — implementation is complete; the `# Potentially more event handlers` comment is an extension hint, not a stub.
|
|
166
|
+
|
|
167
|
+
From sweep 1:
|
|
168
|
+
|
|
169
|
+
- "Litige pool drops on chute" — `scoring.py` correctly releases the pool to defenders on chute (line 620), to taker on success (line 625), and accumulates only on litige tie. Working as intended.
|
|
170
|
+
- "AI round-1 bidding logic reversed" — re-reading `ai.py:87-91`, returning the matching suit in round 1 *is* the intended behavior (round 1 only allows the up-card's suit).
|
|
171
|
+
- "Le Carnet partner reveal unimplemented" — fully wired: `vouchers.py` → `run_state.show_north_hand` → `main.py:113` → `display(show_north_hand=True)`. The +1 mult is wired at `belatro/core/scoring.py:129`.
|
|
172
|
+
- "Contract levels race in `Planet.use()`" — single-threaded shop interaction; no race.
|
|
173
|
+
|
|
174
|
+
### Test count
|
|
175
|
+
|
|
176
|
+
409 → **409** (no count change; existing tests cover every fixed path. `tests/belatro/test_belatro.py::test_spend_money_zero_always_succeeds` still passes — `spend_money(0)` remains a benign no-op).
|
|
177
|
+
|
|
8
178
|
## [2.7.1] - 2026-05-04
|
|
9
179
|
|
|
10
180
|
Audit follow-up: cleared the last `getattr(state, "_X", False)` boss-flag reads, removed the now-redundant property aliases on `GameState`, and closed three coverage gaps the v2.7 audit flagged.
|
|
@@ -83,7 +83,7 @@ PYTHONPATH=src mypy .
|
|
|
83
83
|
|
|
84
84
|
# Linting (0 violations expected)
|
|
85
85
|
ruff check .
|
|
86
|
-
# Full test suite (
|
|
86
|
+
# Full test suite (435 tests expected)
|
|
87
87
|
PYTHONPATH=src pytest
|
|
88
88
|
|
|
89
89
|
# ...
|
|
@@ -91,7 +91,7 @@ PYTHONPATH=src pytest
|
|
|
91
91
|
Current baseline:
|
|
92
92
|
- **mypy**: 0 errors (strict mode)
|
|
93
93
|
- **ruff**: 0 violations
|
|
94
|
-
- **pytest**:
|
|
94
|
+
- **pytest**: 435 tests, 0 failures
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
## Benchmarking
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: belote-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.2
|
|
4
4
|
Summary: A 4-player terminal card game
|
|
5
5
|
Project-URL: Homepage, https://github.com/ElysiumDisc/belote
|
|
6
6
|
Project-URL: Repository, https://github.com/ElysiumDisc/belote
|
|
@@ -88,18 +88,19 @@ belatro
|
|
|
88
88
|
|
|
89
89
|
### Main Menu
|
|
90
90
|
```text
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
91
|
+
⢠⣴⣶⣶⣶⣄
|
|
92
|
+
⣿⣿⣿⣿⣿⣿⣦
|
|
93
|
+
⢰⣿⣿⣿⣿⡿⠟⠁⣠⣴⣶⣦⠄
|
|
94
|
+
⢸⣿⣿⠟⠉⣠⣴⣿⣿⣿⠟⠁⣠⣾⣿⣦⡀
|
|
95
|
+
⠉⣀⣴⣾⣿⣿⣿⠟⢁⣤⣾⣿⣿⣿⣿⣿⡆
|
|
96
|
+
⢀⣤⣾⣿⣿⣿⡿⠛⢁⣴⣿⣿⣿⣿⣿⣿⣿⠟⠁⡀
|
|
97
|
+
⢼⣿⣿⣿⡿⠋⣀⣴⣿⣿⣿⣿⣿⣿⣿⡿⠉⣠⣾⣿⡆
|
|
98
|
+
⠘⢿⡿⠋⣠⣾⣿⣿⣿⣿⣿⣿⣿⡿⠋⢀⣾⣿⣿⠟⢁⣀
|
|
99
|
+
⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⠏⢀⣴⣿⣿⣿⠋⢠⣾⣿⣷⣦⡀
|
|
100
|
+
⢻⣿⣿⣿⣿⣿⣿⣿⠟⢁⣴⣿⣿⣿⡿⠁⣰⣿⣿⣿⣿⣿⣿
|
|
101
|
+
⠹⢿⣿⣿⣿⡿⠋⣠⣾⣿⣿⣿⠟⢀⣼⣿⣿⣿⣿⣿⣿⡟
|
|
102
|
+
⠉⠉⠉ ⢾⣿⣿⣿⣿⠋ ⠚⠛⠛⠛⠛⠛⠛⠁
|
|
103
|
+
⠉⠉⠉
|
|
103
104
|
|
|
104
105
|
(
|
|
105
106
|
) (
|
|
@@ -107,12 +108,17 @@ belatro
|
|
|
107
108
|
.-'' ) ( ''-.
|
|
108
109
|
.-'``'|-._ ) _.-|
|
|
109
110
|
/ .--.| `''---...........---''` |
|
|
110
|
-
/ / |
|
|
111
|
-
| | |
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
/ / | BelAtro |
|
|
112
|
+
| | | > Start Game < |
|
|
113
|
+
| | | AI: < Hard > |
|
|
114
|
+
| | | Target: < 1000 > |
|
|
115
|
+
| | | Speed: < Normal > |
|
|
116
|
+
| | | Theme: < Classic Green > |
|
|
117
|
+
| | | Rules & History |
|
|
118
|
+
\ \ | Statistics |
|
|
119
|
+
`\ `\ | Quit |
|
|
120
|
+
`\ `| |
|
|
121
|
+
_/ /\ /
|
|
116
122
|
(__/ \ /
|
|
117
123
|
_..---''` \ /`''---.._
|
|
118
124
|
.-' \ / '-.
|
|
@@ -191,9 +197,9 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
191
197
|
|
|
192
198
|
## Features
|
|
193
199
|
|
|
194
|
-
- **BelAtro Roguelite Mode:** A massive expansion featuring
|
|
200
|
+
- **BelAtro Roguelite Mode:** A massive expansion featuring 36 Jokers, 12 Tarot cards, 8 Planets, 12 Vouchers, and permanent upgrades.
|
|
195
201
|
- **Collection (Almanac):** Persistent tracker to browse every Joker, Planet, and Voucher you've discovered across your runs.
|
|
196
|
-
- **Full Boss Blind Suite:** All
|
|
202
|
+
- **Full Boss Blind Suite:** All 18 unique bosses implemented, including complex mechanics like *L'Anarchie* (dynamic trump) and *La Rupture* (no consecutive wins).
|
|
197
203
|
- **Multiplier Scoring:** Use items to stack Multipliers and reach scores in the millions.
|
|
198
204
|
- **Partner Trust:** Build a relationship with your AI partner to unlock synergies.
|
|
199
205
|
- **Rich Terminal UI:** Full-screen green felt table with detailed card graphics and "You" vs "Partner" terminology.
|
|
@@ -209,7 +215,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
209
215
|
- **Sound Effects:** Enhanced auditory feedback for trick wins, Belote, and Capot, with a built-in mute toggle.
|
|
210
216
|
- **Declarations:** Automatic detection and announcement of sequences (Tierce, Quarte, etc.) and Carrés after the first trick.
|
|
211
217
|
- **Live HUD:** Real-time round scoring displays points won during the current round, with a smooth "rolling" numerical animation for total scores.
|
|
212
|
-
- **High Fidelity:**
|
|
218
|
+
- **High Fidelity:** Implementation of French Belote rules according to the [official rules of the Fédération Française de Belote](https://www.ffbelote.org/regles-officielle-belote/), including a two-round bidding system, "Dix de Der", "Capot" (252 pts), and "Litige" (tie-break). All six contracts are bidable in round 2: the four card suits, **Tout Atout** (every suit acts as trump within its own led-suit group; press `a`), and **Sans Atout** (no trump, lead-suit highest wins; press `s`).
|
|
213
219
|
- **Rules & History Viewer:** A scrollable, bilingual (English/French) in-game reference for the game's heritage and mechanics.
|
|
214
220
|
|
|
215
221
|
## AI
|
|
@@ -245,7 +251,7 @@ belote/
|
|
|
245
251
|
│ ├── input.py # Platform-dispatched key reader and interruptible sleep
|
|
246
252
|
│ ├── stats.py # Global and session statistics tracking
|
|
247
253
|
│ └── rules.py # Game rules content
|
|
248
|
-
├── tests/ # Comprehensive test suite (
|
|
254
|
+
├── tests/ # Comprehensive test suite (435 tests)
|
|
249
255
|
├── scripts/ # Performance benchmarks
|
|
250
256
|
├── pyproject.toml # Build system and dev dependencies (ruff/mypy)
|
|
251
257
|
├── LICENSE # MIT License
|
|
@@ -261,14 +267,14 @@ belote/
|
|
|
261
267
|
PYTHONPATH=src pytest
|
|
262
268
|
```
|
|
263
269
|
|
|
264
|
-
Currently **
|
|
270
|
+
Currently **435 tests** passing with 100% coverage on core logic.
|
|
265
271
|
|
|
266
272
|
## Technical Integrity
|
|
267
273
|
|
|
268
274
|
The codebase is strictly validated with the following tools:
|
|
269
275
|
- **mypy**: 0 errors (strict type safety)
|
|
270
276
|
- **ruff**: 0 violations (linting & formatting)
|
|
271
|
-
- **pytest**:
|
|
277
|
+
- **pytest**: 435/435 passed
|
|
272
278
|
- **Functional Architecture**: Purely immutable state transitions using `dataclasses.replace`
|
|
273
279
|
- **Performance**: High-efficiency rendering and sub-millisecond AI decision times (see `scripts/benchmark.py`)
|
|
274
280
|
|
|
@@ -289,3 +295,5 @@ This will wipe all global statistics and reset your discovered item Almanac in B
|
|
|
289
295
|
## Terminal Hygiene
|
|
290
296
|
|
|
291
297
|
Signal handlers (SIGINT, SIGTERM) and atexit hooks ensure the terminal is always restored — cursor visible, colors reset, alt-screen off — even after Ctrl+C or crashes.
|
|
298
|
+
|
|
299
|
+
Every rendered row ends with `\x1b[K` (clear-to-end-of-line) and every interactive prompt (bid selector, card selector, full-screen overlays) repaints in a single in-frame pass — no `\r\n`-bracketed writes outside the render. This keeps the game free of stale-cell artifacts on strict ANSI emulators like Konsole (KDE), in addition to the more lenient VTE-based terminals (GNOME Terminal, LXTerminal, xterm).
|
|
@@ -45,18 +45,19 @@ belatro
|
|
|
45
45
|
|
|
46
46
|
### Main Menu
|
|
47
47
|
```text
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
⢠⣴⣶⣶⣶⣄
|
|
49
|
+
⣿⣿⣿⣿⣿⣿⣦
|
|
50
|
+
⢰⣿⣿⣿⣿⡿⠟⠁⣠⣴⣶⣦⠄
|
|
51
|
+
⢸⣿⣿⠟⠉⣠⣴⣿⣿⣿⠟⠁⣠⣾⣿⣦⡀
|
|
52
|
+
⠉⣀⣴⣾⣿⣿⣿⠟⢁⣤⣾⣿⣿⣿⣿⣿⡆
|
|
53
|
+
⢀⣤⣾⣿⣿⣿⡿⠛⢁⣴⣿⣿⣿⣿⣿⣿⣿⠟⠁⡀
|
|
54
|
+
⢼⣿⣿⣿⡿⠋⣀⣴⣿⣿⣿⣿⣿⣿⣿⡿⠉⣠⣾⣿⡆
|
|
55
|
+
⠘⢿⡿⠋⣠⣾⣿⣿⣿⣿⣿⣿⣿⡿⠋⢀⣾⣿⣿⠟⢁⣀
|
|
56
|
+
⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⠏⢀⣴⣿⣿⣿⠋⢠⣾⣿⣷⣦⡀
|
|
57
|
+
⢻⣿⣿⣿⣿⣿⣿⣿⠟⢁⣴⣿⣿⣿⡿⠁⣰⣿⣿⣿⣿⣿⣿
|
|
58
|
+
⠹⢿⣿⣿⣿⡿⠋⣠⣾⣿⣿⣿⠟⢀⣼⣿⣿⣿⣿⣿⣿⡟
|
|
59
|
+
⠉⠉⠉ ⢾⣿⣿⣿⣿⠋ ⠚⠛⠛⠛⠛⠛⠛⠁
|
|
60
|
+
⠉⠉⠉
|
|
60
61
|
|
|
61
62
|
(
|
|
62
63
|
) (
|
|
@@ -64,12 +65,17 @@ belatro
|
|
|
64
65
|
.-'' ) ( ''-.
|
|
65
66
|
.-'``'|-._ ) _.-|
|
|
66
67
|
/ .--.| `''---...........---''` |
|
|
67
|
-
/ / |
|
|
68
|
-
| | |
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
/ / | BelAtro |
|
|
69
|
+
| | | > Start Game < |
|
|
70
|
+
| | | AI: < Hard > |
|
|
71
|
+
| | | Target: < 1000 > |
|
|
72
|
+
| | | Speed: < Normal > |
|
|
73
|
+
| | | Theme: < Classic Green > |
|
|
74
|
+
| | | Rules & History |
|
|
75
|
+
\ \ | Statistics |
|
|
76
|
+
`\ `\ | Quit |
|
|
77
|
+
`\ `| |
|
|
78
|
+
_/ /\ /
|
|
73
79
|
(__/ \ /
|
|
74
80
|
_..---''` \ /`''---.._
|
|
75
81
|
.-' \ / '-.
|
|
@@ -148,9 +154,9 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
148
154
|
|
|
149
155
|
## Features
|
|
150
156
|
|
|
151
|
-
- **BelAtro Roguelite Mode:** A massive expansion featuring
|
|
157
|
+
- **BelAtro Roguelite Mode:** A massive expansion featuring 36 Jokers, 12 Tarot cards, 8 Planets, 12 Vouchers, and permanent upgrades.
|
|
152
158
|
- **Collection (Almanac):** Persistent tracker to browse every Joker, Planet, and Voucher you've discovered across your runs.
|
|
153
|
-
- **Full Boss Blind Suite:** All
|
|
159
|
+
- **Full Boss Blind Suite:** All 18 unique bosses implemented, including complex mechanics like *L'Anarchie* (dynamic trump) and *La Rupture* (no consecutive wins).
|
|
154
160
|
- **Multiplier Scoring:** Use items to stack Multipliers and reach scores in the millions.
|
|
155
161
|
- **Partner Trust:** Build a relationship with your AI partner to unlock synergies.
|
|
156
162
|
- **Rich Terminal UI:** Full-screen green felt table with detailed card graphics and "You" vs "Partner" terminology.
|
|
@@ -166,7 +172,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
166
172
|
- **Sound Effects:** Enhanced auditory feedback for trick wins, Belote, and Capot, with a built-in mute toggle.
|
|
167
173
|
- **Declarations:** Automatic detection and announcement of sequences (Tierce, Quarte, etc.) and Carrés after the first trick.
|
|
168
174
|
- **Live HUD:** Real-time round scoring displays points won during the current round, with a smooth "rolling" numerical animation for total scores.
|
|
169
|
-
- **High Fidelity:**
|
|
175
|
+
- **High Fidelity:** Implementation of French Belote rules according to the [official rules of the Fédération Française de Belote](https://www.ffbelote.org/regles-officielle-belote/), including a two-round bidding system, "Dix de Der", "Capot" (252 pts), and "Litige" (tie-break). All six contracts are bidable in round 2: the four card suits, **Tout Atout** (every suit acts as trump within its own led-suit group; press `a`), and **Sans Atout** (no trump, lead-suit highest wins; press `s`).
|
|
170
176
|
- **Rules & History Viewer:** A scrollable, bilingual (English/French) in-game reference for the game's heritage and mechanics.
|
|
171
177
|
|
|
172
178
|
## AI
|
|
@@ -202,7 +208,7 @@ belote/
|
|
|
202
208
|
│ ├── input.py # Platform-dispatched key reader and interruptible sleep
|
|
203
209
|
│ ├── stats.py # Global and session statistics tracking
|
|
204
210
|
│ └── rules.py # Game rules content
|
|
205
|
-
├── tests/ # Comprehensive test suite (
|
|
211
|
+
├── tests/ # Comprehensive test suite (435 tests)
|
|
206
212
|
├── scripts/ # Performance benchmarks
|
|
207
213
|
├── pyproject.toml # Build system and dev dependencies (ruff/mypy)
|
|
208
214
|
├── LICENSE # MIT License
|
|
@@ -218,14 +224,14 @@ belote/
|
|
|
218
224
|
PYTHONPATH=src pytest
|
|
219
225
|
```
|
|
220
226
|
|
|
221
|
-
Currently **
|
|
227
|
+
Currently **435 tests** passing with 100% coverage on core logic.
|
|
222
228
|
|
|
223
229
|
## Technical Integrity
|
|
224
230
|
|
|
225
231
|
The codebase is strictly validated with the following tools:
|
|
226
232
|
- **mypy**: 0 errors (strict type safety)
|
|
227
233
|
- **ruff**: 0 violations (linting & formatting)
|
|
228
|
-
- **pytest**:
|
|
234
|
+
- **pytest**: 435/435 passed
|
|
229
235
|
- **Functional Architecture**: Purely immutable state transitions using `dataclasses.replace`
|
|
230
236
|
- **Performance**: High-efficiency rendering and sub-millisecond AI decision times (see `scripts/benchmark.py`)
|
|
231
237
|
|
|
@@ -246,3 +252,5 @@ This will wipe all global statistics and reset your discovered item Almanac in B
|
|
|
246
252
|
## Terminal Hygiene
|
|
247
253
|
|
|
248
254
|
Signal handlers (SIGINT, SIGTERM) and atexit hooks ensure the terminal is always restored — cursor visible, colors reset, alt-screen off — even after Ctrl+C or crashes.
|
|
255
|
+
|
|
256
|
+
Every rendered row ends with `\x1b[K` (clear-to-end-of-line) and every interactive prompt (bid selector, card selector, full-screen overlays) repaints in a single in-frame pass — no `\r\n`-bracketed writes outside the render. This keeps the game free of stale-cell artifacts on strict ANSI emulators like Konsole (KDE), in addition to the more lenient VTE-based terminals (GNOME Terminal, LXTerminal, xterm).
|