belote-cli 4.9.5__tar.gz → 4.9.7__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-4.9.5 → belote_cli-4.9.7}/.gitignore +4 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/CHANGELOG.md +127 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/DEVELOPMENT.md +112 -41
- {belote_cli-4.9.5 → belote_cli-4.9.7}/PKG-INFO +9 -8
- {belote_cli-4.9.5 → belote_cli-4.9.7}/README.md +7 -6
- {belote_cli-4.9.5 → belote_cli-4.9.7}/pyproject.toml +1 -1
- {belote_cli-4.9.5 → belote_cli-4.9.7}/scripts/benchmark.py +8 -8
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/__init__.py +1 -1
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/a11y.py +60 -7
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/achievements.py +11 -3
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/ai.py +21 -21
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/ansi.py +71 -11
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/core/run_state.py +9 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/core/scoring.py +16 -3
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/engine/event_bus.py +20 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/engine/round_driver.py +99 -42
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/ghost_run.py +10 -2
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/base.py +12 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/jokers/contract.py +5 -3
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/jokers/economy.py +8 -1
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/jokers/trick_timing.py +11 -2
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/partner_jokers/shaper.py +2 -2
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/tarots.py +4 -1
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/main.py +88 -23
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/progression/save.py +82 -23
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/progression/unlocks.py +3 -2
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/run/ante_themes.py +19 -3
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/run/shop.py +43 -1
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/ui/announce.py +12 -1
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/ui/history.py +4 -2
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/ui/hud.py +10 -5
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/ui/rules.py +2 -2
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/ui/shop.py +71 -5
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/ui/trust_bar.py +31 -7
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/config.py +12 -1
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/game.py +35 -6
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/gameflow.py +16 -1
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/input.py +77 -14
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/main.py +11 -6
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/scoring.py +14 -7
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/stats.py +89 -13
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/themes.py +25 -5
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/ui/menu.py +17 -3
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/ui/render.py +28 -11
- belote_cli-4.9.7/tests/belatro/test_audit_round_fixes.py +558 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_belatro.py +17 -3
- belote_cli-4.9.7/tests/belatro/test_design_calls.py +115 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_phase3_meta.py +13 -2
- belote_cli-4.9.7/tests/belatro/test_registry_integrity.py +61 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_round_driver.py +19 -6
- belote_cli-4.9.7/tests/conftest.py +24 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_a11y.py +35 -0
- belote_cli-4.9.7/tests/test_achievements.py +151 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_alt_screen_scroll.py +2 -2
- belote_cli-4.9.7/tests/test_hud_fit.py +72 -0
- belote_cli-4.9.7/tests/test_input_escape_seqs.py +136 -0
- belote_cli-4.9.7/tests/test_menu_target_and_exit.py +134 -0
- belote_cli-4.9.7/tests/test_render_golden.py +82 -0
- belote_cli-4.9.7/tests/test_scoring_totals.py +62 -0
- belote_cli-4.9.7/tests/test_stats_persistence.py +193 -0
- belote_cli-4.9.7/tests/test_wcwidth.py +88 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/uv.lock +1 -1
- belote_cli-4.9.5/.claude/settings.local.json +0 -19
- belote_cli-4.9.5/tests/test_achievements.py +0 -58
- {belote_cli-4.9.5 → belote_cli-4.9.7}/.python-version +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/LICENSE +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/core/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/core/economy.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/core/round_ledger.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/engine/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/engine/modifier_patch.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/jokers/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/jokers/annonces.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/jokers/coinche.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/jokers/corrupted.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/jokers/hand_comp.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/partner_jokers/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/partner_jokers/passive.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/partner_jokers/risky.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/planets.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/registry.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/items/vouchers.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/partner/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/partner/partner_state.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/partner/personality.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/partner/trust.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/progression/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/run/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/run/ante.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/run/boss.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/run/decks.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/run_summary.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/ui/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/ui/collection.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/ui/consumables.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/ui/inventory.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/belatro/ui/menu.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/context.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/deck.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/replay.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/rules.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/ui/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/ui/anim.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/ui/announce.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/ui/fit_guard.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/ui/layout.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/src/belote/ui/prompts.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/__init__.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_boss_contracts.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_boss_modifiers_integration.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_collection_logic.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_competition_malediction.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_consumables_ui.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_contract_unlocks.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_dead_flag_fixes.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_deck_variants.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_decks_4_5.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_endless.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_event_bus.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_ghost_run.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_handler_registry.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_heist.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_history_overlay.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_hud_synergy.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_hud_toggle.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_inventory_overlay.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_joker_callouts.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_joker_contracts.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_jokers_4_5.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_partner_jokers.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_partner_trust.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_phase0_coverage.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_phase1_plumbing.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_phase2_content.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_progression.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_run_summary.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_shop_animations.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_shop_empty_pools.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_slot_machine_tally.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/belatro/test_voucher_idempotency.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/perf_baselines.json +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_ai.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_ai_deluge_voids.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_ai_sans_atout.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_anim_helpers.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_announce_stats.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_ansi_helpers.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_belote.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_belote_stinger.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_benchmark_smoke.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_bidding_all_pass.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_declaration_tiebreak.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_deluge_legal_cards.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_extended.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_g1_coinche_classic.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_g2_ai_signals.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_game_logic.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_gameflow.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_hand_auto_sort.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_history_overlay_cache.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_input_eof.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_input_wasd.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_invariants.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_layout.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_new_coverage.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_no_color.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_official_rules.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_perf_regression.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_properties.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_render_diff.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_render_felt_polish.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_render_hand_lift.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_replay.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_undo.py +0 -0
- {belote_cli-4.9.5 → belote_cli-4.9.7}/tests/test_winner_glow.py +0 -0
|
@@ -5,6 +5,133 @@ 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
|
+
## [4.9.7] - 2026-11-01
|
|
9
|
+
|
|
10
|
+
Repo-wide audit: four parallel reviewers swept the classic engine, the
|
|
11
|
+
classic UI/terminal layer, the BelAtro engine/run/progression code and the
|
|
12
|
+
BelAtro items/UI; every finding was verified (most with a repro script) before
|
|
13
|
+
fixing. Test count 1111 → 1228.
|
|
14
|
+
|
|
15
|
+
### Fixed — test suite
|
|
16
|
+
|
|
17
|
+
- **Tests no longer depend on (or write to) your real profile.** A new
|
|
18
|
+
`tests/conftest.py` points `HOME` / `XDG_CONFIG_HOME` / `XDG_DATA_HOME` at a
|
|
19
|
+
temp dir. The suite used to render with whatever theme the developer last
|
|
20
|
+
picked (the golden-frame hashes only passed with `moonlit_tavern` saved) and
|
|
21
|
+
`theme_manager.set_current()` rewrote `~/.config/belote/theme.json`.
|
|
22
|
+
Golden hashes re-pinned to the default `classic_green` theme.
|
|
23
|
+
- Hardcoded `/home/mrrobot/belote/` paths in `tests/test_alt_screen_scroll.py`
|
|
24
|
+
replaced with a path relative to the test file.
|
|
25
|
+
- `scripts/benchmark.py` is now `mypy --strict` clean.
|
|
26
|
+
|
|
27
|
+
### Fixed — classic game
|
|
28
|
+
|
|
29
|
+
- **L'Anarchie scored every trick with the end-of-round trump.** Tricks now
|
|
30
|
+
record the trump they were played under (`GameState.trick_trumps`) and final
|
|
31
|
+
scoring uses it (was wrong in ~99% of L'Anarchie rounds).
|
|
32
|
+
- A **worthless carré of 7s/8s outranked the other team's sequences** and
|
|
33
|
+
wiped out their points. 0-point carrés no longer take part in declarations.
|
|
34
|
+
- **Hard AI threw high off-suit cards to the opponents**, believing an
|
|
35
|
+
off-suit Ace beat a led King (normal and Tout Atout contracts).
|
|
36
|
+
- **Tout Atout under L'Anarchie** silently became a one-suit game while still
|
|
37
|
+
being scored as Tout Atout; trump no longer rotates under TA.
|
|
38
|
+
- Capots by the opponents counted as **your** capots in stats/achievements;
|
|
39
|
+
`first_capot` used `== 1`; "Triple Coup" read lifetime instead of session capots.
|
|
40
|
+
- 6+ card sequences were labelled "0" in the round summary.
|
|
41
|
+
|
|
42
|
+
### Fixed — terminal, UI and files
|
|
43
|
+
|
|
44
|
+
- `--target 750` (non-preset) crashed the main menu.
|
|
45
|
+
- Stats file: atomic writes; corrupt / wrong-shaped files no longer crash the
|
|
46
|
+
game (moved aside to `stats.json.bak`).
|
|
47
|
+
- Unknown escape sequences leaked as keystrokes (F2 quit the game, Ctrl+→
|
|
48
|
+
played card 5, Delete typed `~`); Ctrl-C now quits.
|
|
49
|
+
- In-game `FitAbortedError` (terminal too small → Q) exits cleanly.
|
|
50
|
+
- Spacious HUD overflowed at 120–143 columns and scrolled the screen.
|
|
51
|
+
- Game-over screen "staircased" (bare `\n` in raw mode).
|
|
52
|
+
- Pressing T crashed on a read-only config dir; theme saves are atomic.
|
|
53
|
+
- The game can now start on Windows (`termios` imported only on Unix); classic
|
|
54
|
+
stats honour `%APPDATA%`.
|
|
55
|
+
- `BELOTE_A11Y=1` no longer scribbles over the game screen — see
|
|
56
|
+
DEVELOPMENT.md → Accessibility (`BELOTE_A11Y_OUT`).
|
|
57
|
+
|
|
58
|
+
### Fixed — BelAtro
|
|
59
|
+
|
|
60
|
+
- **All-pass deals ended the run**; they now redeal (and round-end jokers no
|
|
61
|
+
longer fire on the abandoned deal).
|
|
62
|
+
- Partner bidding was backwards in round 1 (could take any suit except the
|
|
63
|
+
legal one; L'Économe could never bid).
|
|
64
|
+
- L'Architecte "buy contract" crashed in round 1 after taking $10.
|
|
65
|
+
- Le Traître / L'Agent Double replaced the boss's larger sabotage set.
|
|
66
|
+
- L'Avocat's doubled target was ignored for payout, Le Puriste, trust and history.
|
|
67
|
+
- Ghost runs: real seeds, seeded rounds, unique file names.
|
|
68
|
+
- Corrupt / newer-schema `profile.json` no longer crashes launch.
|
|
69
|
+
- Capots by EW counted as yours (unlocks, Pluto); Mercury/Pluto paid on EW contracts.
|
|
70
|
+
- The shop offered jokers you already own (duplicates corrupted each other's
|
|
71
|
+
state); Le Jugement too.
|
|
72
|
+
- Le Patriote, L'Illusionniste and La Sentinelle (P) mishandled Tout Atout;
|
|
73
|
+
Le Passeur paid $2 when North bid Sans Atout.
|
|
74
|
+
- Shop cards: frame misaligned, long names overflowed, editions invisible.
|
|
75
|
+
- Compact HUD: trust bar overwrote the synergy tooltip.
|
|
76
|
+
- History overlay overflowed 90–99 column terminals.
|
|
77
|
+
- An ante-8 victory continued into endless mode was recorded as a loss.
|
|
78
|
+
|
|
79
|
+
### Changed — design calls
|
|
80
|
+
|
|
81
|
+
- **Le Diable** now works: for one round North over-cuts with its lowest
|
|
82
|
+
winning trump whenever it can — even over your winning card.
|
|
83
|
+
- **Sell jokers** in the shop with `S` for half their cost (same value as
|
|
84
|
+
La Tempérance). Corrupted jokers can't be sold; selling a Negative joker
|
|
85
|
+
returns its slot.
|
|
86
|
+
- **Le Sergent** grows with the streak: +0.5, +1.0, +1.5, then +2.0 per trick
|
|
87
|
+
(was a flat +0.5 that ignored "consecutive").
|
|
88
|
+
- **Le Tournoi** really offers you a coinche; theme flags no longer outlive
|
|
89
|
+
their ante. **Le Café**'s +25 chips is a petit-blind bonus, not permanent.
|
|
90
|
+
- **Auto-Capot** (trust 9+) is no longer advertised — it was never implemented.
|
|
91
|
+
- **Trust 3+ shows North's void suits** (`✦ Void: ♠♦`) instead of the whole
|
|
92
|
+
hand; seeing the full hand needs Le Carnet.
|
|
93
|
+
- **Pluto** pays only when you took the contract and made it as a capot.
|
|
94
|
+
|
|
95
|
+
## [4.9.6] - 2026-06-01
|
|
96
|
+
|
|
97
|
+
Follow-up bug-hunt, logic, and performance audit. Three parallel Explore agents
|
|
98
|
+
re-swept the classic engine, BelAtro mechanics, and the UI/render layer; every
|
|
99
|
+
"major" finding was then traced to exact code and verified before acting. The
|
|
100
|
+
result: **no critical or major bug survived verification.** The flagged items
|
|
101
|
+
were all either false positives (Le Passeur "double-payout" — the post-coinche
|
|
102
|
+
`BidMadeEvent` re-emit carries the taker's seat + a real trump, so the
|
|
103
|
+
North-pass gate never matches; Le Carnet's +1 Mult *is* wired via
|
|
104
|
+
`acc.carnet_active`; Tout Streak / Quinte Royale re-emit guards are already
|
|
105
|
+
present; `modifier_patch` is a fresh per-instance proxy each round) or
|
|
106
|
+
intentional, test-pinned behaviour (Le Patriote's `// 2`). Benchmarks confirmed
|
|
107
|
+
the render/scoring/AI paths are already well within budget (render ≈0.4 ms, E2E
|
|
108
|
+
round ≈4–6 ms). This release therefore lands the one genuine latent gap the
|
|
109
|
+
audit surfaced plus a layer of behaviour-locking tests. Test count 1079 → 1111.
|
|
110
|
+
|
|
111
|
+
### Added
|
|
112
|
+
|
|
113
|
+
- **Minimal wide-character (wcwidth) support** (`src/belote/ansi.py`). `visible_len()`
|
|
114
|
+
previously counted codepoints, so `ansi_center()` / `ansi_ljust()` and the
|
|
115
|
+
render diff's per-row width would mis-align by one cell per wide glyph (emoji,
|
|
116
|
+
CJK). A new dependency-free `char_width()` (stdlib `unicodedata`) returns 0
|
|
117
|
+
for combining/zero-width marks, 2 for East-Asian Wide/Fullwidth, 1 otherwise,
|
|
118
|
+
and `visible_len()` now measures display **cells**. A pure-ASCII escape-free
|
|
119
|
+
fast path keeps the hot render path allocation-free and byte-identical, so
|
|
120
|
+
there is no performance regression. A new `ansi_truncate()` (cell-aware,
|
|
121
|
+
escape-skipping) replaces the raw byte-slices that trimmed BelAtro HUD joker
|
|
122
|
+
names and synergy lines (`belatro/ui/hud.py`), which could previously split a
|
|
123
|
+
wide glyph.
|
|
124
|
+
- **Behaviour-locking tests** that freeze what the audit confirmed correct:
|
|
125
|
+
`tests/test_render_golden.py` (full-frame snapshot + per-row cell-width
|
|
126
|
+
fingerprint — the regression guard for the wcwidth change),
|
|
127
|
+
`tests/test_wcwidth.py` (per-character widths and helper cell-correctness),
|
|
128
|
+
`tests/test_scoring_totals.py` (the canonical 152 / 120 / 248 point totals,
|
|
129
|
+
belote 20, capot bases 252 / 220 / 348, dix de der +10), and
|
|
130
|
+
`tests/belatro/test_registry_integrity.py` (item-census counts 42 jokers /
|
|
131
|
+
8 planets / 12 tarots / 12 vouchers, id↔key consistency, no duplicate display
|
|
132
|
+
names — so a duplicate-id registration that silently overwrites an item now
|
|
133
|
+
fails a test).
|
|
134
|
+
|
|
8
135
|
## [4.9.5] - 2026-05-28
|
|
9
136
|
|
|
10
137
|
Bug-hunt, logic, and performance audit pass. Three parallel Explore agents
|
|
@@ -2,6 +2,61 @@
|
|
|
2
2
|
|
|
3
3
|
Welcome to the Belote development guide. This project is structured as a standard Python package.
|
|
4
4
|
|
|
5
|
+
## Cheat Sheet
|
|
6
|
+
|
|
7
|
+
Everything below assumes you're in the repo with the venv active:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
cd ~/Projects/belote
|
|
11
|
+
source .venv/bin/activate # gives you `python`, `pip`, pytest, mypy, ruff
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**1. Check everything is green (do this before every commit):**
|
|
15
|
+
```bash
|
|
16
|
+
PYTHONPATH=src python -m pytest -q && \
|
|
17
|
+
PYTHONPATH=src python -m mypy --strict src/ && \
|
|
18
|
+
python -m ruff check src/ tests/
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**2. Push code to GitHub (no PyPI release):**
|
|
22
|
+
```bash
|
|
23
|
+
git status # see what changed — don't commit editor swap files
|
|
24
|
+
git add <files> # or: git add -A (after checking git status)
|
|
25
|
+
git commit -m "what changed"
|
|
26
|
+
git push origin master
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**3. Release a new version to PyPI** (example: 4.9.7):
|
|
30
|
+
```bash
|
|
31
|
+
# a) bump the version in BOTH files (they must match):
|
|
32
|
+
# pyproject.toml -> version = "4.9.7"
|
|
33
|
+
# src/belote/__init__.py -> __version__ = "4.9.7"
|
|
34
|
+
# b) add a "## 4.9.7" entry at the top of CHANGELOG.md
|
|
35
|
+
# c) run the gates (step 1), then commit, tag and push:
|
|
36
|
+
git add -A
|
|
37
|
+
git commit -m "Release v4.9.7"
|
|
38
|
+
git tag -a v4.9.7 -m "v4.9.7"
|
|
39
|
+
git push origin master --tags
|
|
40
|
+
# d) build, check and upload:
|
|
41
|
+
rm -rf dist/ build/ *.egg-info/
|
|
42
|
+
pipx run build --sdist --wheel
|
|
43
|
+
pipx run twine check dist/*
|
|
44
|
+
pipx run twine upload dist/*
|
|
45
|
+
# username: __token__
|
|
46
|
+
# password: your PyPI API token (starts with pypi-)
|
|
47
|
+
# make one at https://pypi.org/manage/account/token/
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**4. Update the game installed with pipx** (from PyPI — https://pypi.org/project/belote-cli/):
|
|
51
|
+
```bash
|
|
52
|
+
pipx install belote-cli # first time only
|
|
53
|
+
pipx upgrade belote-cli # after each release
|
|
54
|
+
belote --version # confirm the new version
|
|
55
|
+
```
|
|
56
|
+
PyPI can take a minute to show a new upload. If `pipx upgrade` says you're
|
|
57
|
+
already on the latest, wait a moment and retry, or force it with
|
|
58
|
+
`pipx install --force belote-cli`.
|
|
59
|
+
|
|
5
60
|
## Setup
|
|
6
61
|
|
|
7
62
|
1. **Clone the repository:**
|
|
@@ -10,15 +65,17 @@ Welcome to the Belote development guide. This project is structured as a standar
|
|
|
10
65
|
cd belote
|
|
11
66
|
```
|
|
12
67
|
|
|
13
|
-
2. **Create a virtual environment
|
|
68
|
+
2. **Create a virtual environment** (use `python3` — many Linux distros don't
|
|
69
|
+
ship a bare `python` or `pip` outside a venv):
|
|
14
70
|
```bash
|
|
15
|
-
|
|
71
|
+
python3 -m venv .venv
|
|
16
72
|
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
17
73
|
```
|
|
18
74
|
|
|
19
|
-
3. **Install in editable mode
|
|
75
|
+
3. **Install in editable mode with the test + dev tools** (pytest, pytest-cov,
|
|
76
|
+
mypy, ruff):
|
|
20
77
|
```bash
|
|
21
|
-
pip install -e .
|
|
78
|
+
pip install -e ".[test,dev]"
|
|
22
79
|
```
|
|
23
80
|
|
|
24
81
|
## Running the Game
|
|
@@ -42,29 +99,31 @@ PYTHONPATH=src python3 -m belote.main
|
|
|
42
99
|
|
|
43
100
|
## Testing
|
|
44
101
|
|
|
45
|
-
We use `pytest`
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
102
|
+
We use `pytest` (installed by `pip install -e ".[test,dev]"` above).
|
|
103
|
+
|
|
104
|
+
`tests/conftest.py` points `HOME`, `XDG_CONFIG_HOME` and `XDG_DATA_HOME` at a
|
|
105
|
+
throwaway temp dir for the whole run, so the suite never reads or writes your
|
|
106
|
+
real `~/.config/belote/theme.json`, stats or BelAtro saves, and always renders
|
|
107
|
+
with the default theme (the golden-frame snapshot depends on that).
|
|
49
108
|
|
|
50
109
|
Run tests:
|
|
51
110
|
```bash
|
|
52
111
|
# Run all tests (Classic + BelAtro)
|
|
53
112
|
PYTHONPATH=src pytest
|
|
54
113
|
|
|
55
|
-
# Run only Classic Belote tests
|
|
56
|
-
PYTHONPATH=src pytest tests/
|
|
114
|
+
# Run only Classic Belote tests (tests/belatro/ lives inside tests/)
|
|
115
|
+
PYTHONPATH=src pytest tests/ --ignore=tests/belatro
|
|
57
116
|
|
|
58
117
|
# Run only BelAtro tests
|
|
59
118
|
PYTHONPATH=src pytest tests/belatro/
|
|
60
119
|
|
|
61
120
|
# Run a single test file
|
|
62
|
-
PYTHONPATH=src pytest tests/
|
|
63
|
-
PYTHONPATH=src pytest tests/belatro/
|
|
121
|
+
PYTHONPATH=src pytest tests/test_game_logic.py
|
|
122
|
+
PYTHONPATH=src pytest tests/belatro/test_round_driver.py
|
|
64
123
|
|
|
65
124
|
# Run a single test by name
|
|
66
|
-
PYTHONPATH=src pytest tests/
|
|
67
|
-
PYTHONPATH=src pytest -k "
|
|
125
|
+
PYTHONPATH=src pytest tests/test_render_golden.py::test_golden_frame_sha256
|
|
126
|
+
PYTHONPATH=src pytest -k "capot"
|
|
68
127
|
|
|
69
128
|
# Run with verbose output
|
|
70
129
|
PYTHONPATH=src pytest -v
|
|
@@ -84,7 +143,7 @@ PYTHONPATH=src mypy --strict src/
|
|
|
84
143
|
# Linting (0 violations expected)
|
|
85
144
|
ruff check src/ tests/
|
|
86
145
|
|
|
87
|
-
# Full test suite (
|
|
146
|
+
# Full test suite (1228 tests expected)
|
|
88
147
|
PYTHONPATH=src pytest
|
|
89
148
|
```
|
|
90
149
|
|
|
@@ -92,7 +151,7 @@ Run all gates before committing:
|
|
|
92
151
|
|
|
93
152
|
```bash
|
|
94
153
|
PYTHONPATH=src python -m pytest --tb=short -q && \
|
|
95
|
-
python -m mypy --strict src/ && \
|
|
154
|
+
PYTHONPATH=src python -m mypy --strict src/ && \
|
|
96
155
|
python -m ruff check src/ tests/
|
|
97
156
|
```
|
|
98
157
|
|
|
@@ -116,8 +175,18 @@ Use these as a regression-detection floor for future changes.
|
|
|
116
175
|
|
|
117
176
|
Set `BELOTE_A11Y=1` to emit one-line plain-text descriptions of card plays,
|
|
118
177
|
trick winners, locked contracts (4.6.5), declarations (4.6.5), and round
|
|
119
|
-
results
|
|
120
|
-
|
|
178
|
+
results — readable by terminal screen readers (Orca, NVDA over WSL,
|
|
179
|
+
VoiceOver via iTerm2).
|
|
180
|
+
|
|
181
|
+
Where the lines go (4.9.7 — writing them onto the full-screen game used to
|
|
182
|
+
scroll and scramble the frame):
|
|
183
|
+
|
|
184
|
+
- `BELOTE_A11Y_OUT=/path` — appended to that file or terminal device, e.g.
|
|
185
|
+
run `tty` in a second terminal and use its `/dev/pts/N`.
|
|
186
|
+
- otherwise stderr, when it's redirected (`belote 2>a11y.txt`) or is a
|
|
187
|
+
different terminal from the game.
|
|
188
|
+
- otherwise `~/.local/share/belote/a11y.log` (`%APPDATA%\belote\a11y.log` on
|
|
189
|
+
Windows) — follow it with `tail -f` in another terminal.
|
|
121
190
|
|
|
122
191
|
The env var is read once at import; tests that mutate it must call
|
|
123
192
|
`belote.a11y._refresh_enabled_from_env()` after the patch. Hook points live
|
|
@@ -136,8 +205,8 @@ once at startup; toggling mid-run has no effect.
|
|
|
136
205
|
affects scoring. Backed by `src/belote/replay.py`.
|
|
137
206
|
- `BELOTE_GHOST=1` — silently record every BelAtro run (seed, deck,
|
|
138
207
|
bids, plays, round outcomes) to
|
|
139
|
-
`~/.local/share/belote/ghosts/<label>-<seed>.json`. The file
|
|
140
|
-
once when the run ends. Useful for sharing or replaying interesting
|
|
208
|
+
`~/.local/share/belote/ghosts/<label>-<seed>-<timestamp>.json`. The file
|
|
209
|
+
is written once when the run ends and never overwrites an earlier one. Useful for sharing or replaying interesting
|
|
141
210
|
runs. Backed by `src/belote/belatro/ghost_run.py`.
|
|
142
211
|
- `NO_COLOR=<any-non-empty>` — suppress truecolor SGR escapes from
|
|
143
212
|
`fg()` / `bg()` per the [no-color.org](https://no-color.org/) spec.
|
|
@@ -164,9 +233,9 @@ once at startup; toggling mid-run has no effect.
|
|
|
164
233
|
|
|
165
234
|
## Releasing a New Version
|
|
166
235
|
|
|
167
|
-
|
|
236
|
+
The short version is in the [Cheat Sheet](#cheat-sheet) at the top. Details:
|
|
168
237
|
|
|
169
|
-
|
|
238
|
+
### Code-only update (push to GitHub without releasing a new PyPI version)
|
|
170
239
|
|
|
171
240
|
```bash
|
|
172
241
|
git add <files>
|
|
@@ -174,32 +243,32 @@ git commit -m "<what changed>"
|
|
|
174
243
|
git push origin master
|
|
175
244
|
```
|
|
176
245
|
|
|
177
|
-
|
|
246
|
+
### Full release (GitHub + PyPI)
|
|
178
247
|
|
|
179
|
-
1. **
|
|
248
|
+
1. **Run all gates** (see Code Quality) — don't release a red build.
|
|
249
|
+
2. **Bump the version** in BOTH `pyproject.toml` AND `src/belote/__init__.py`
|
|
180
250
|
(they must stay in sync — `belote --version` / `belatro --version` read
|
|
181
251
|
`__version__` while PyPI / pipx read `pyproject.toml`).
|
|
182
|
-
|
|
183
|
-
|
|
252
|
+
3. **Add a CHANGELOG entry** at the top of `CHANGELOG.md`.
|
|
253
|
+
4. **Commit, tag and push:**
|
|
184
254
|
```bash
|
|
185
|
-
|
|
255
|
+
git add -A # check `git status` first
|
|
256
|
+
git commit -m "Release vX.Y.Z"
|
|
257
|
+
git tag -a vX.Y.Z -m "vX.Y.Z"
|
|
258
|
+
git push origin master --tags
|
|
186
259
|
```
|
|
187
|
-
|
|
260
|
+
5. **Clean stale build artifacts, then build, validate and upload:**
|
|
188
261
|
```bash
|
|
262
|
+
rm -rf dist/ build/ *.egg-info/
|
|
189
263
|
pipx run build --sdist --wheel
|
|
190
264
|
pipx run twine check dist/*
|
|
191
265
|
pipx run twine upload dist/*
|
|
192
266
|
```
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
git add pyproject.toml CHANGELOG.md
|
|
199
|
-
git commit -m "Release vX.Y.Z"
|
|
200
|
-
git tag -a vX.Y.Z -m "vX.Y.Z"
|
|
201
|
-
git push origin master --tags
|
|
202
|
-
```
|
|
267
|
+
`twine upload` uses `~/.pypirc` if present; otherwise it prompts — enter
|
|
268
|
+
`__token__` as the username and a PyPI API token as the password.
|
|
269
|
+
PyPI never accepts the same version twice, so a botched upload means
|
|
270
|
+
bumping to the next patch version.
|
|
271
|
+
6. **Update your own install:** `pipx upgrade belote-cli`.
|
|
203
272
|
|
|
204
273
|
## Performance measurement
|
|
205
274
|
|
|
@@ -221,8 +290,10 @@ PYTHONPATH=src python scripts/benchmark.py
|
|
|
221
290
|
If you suspect a per-frame allocation regression, the canonical hot paths to
|
|
222
291
|
profile live (e.g. with `py-spy record --rate 100`) are `render()` in
|
|
223
292
|
`src/belote/ui/render.py` and `_calculate_legal_cards_impl` in
|
|
224
|
-
`src/belote/game.py`. The 4.9.4 / 4.9.5 audit passes found only minor wins
|
|
293
|
+
`src/belote/game.py`. The 4.9.4 / 4.9.5 / 4.9.6 audit passes found only minor wins
|
|
225
294
|
beyond those already landed in the 4.6.x / 4.7.x perf cleanups (4.9.5 bumped
|
|
226
295
|
the `_pip_at` cache to 8192, trimmed `visible_len`'s cache to ANSI strings,
|
|
227
|
-
and memoised the BelAtro history overlay
|
|
228
|
-
|
|
296
|
+
and memoised the BelAtro history overlay; 4.9.6 made `visible_len` measure
|
|
297
|
+
terminal *cells* via a minimal `char_width`, keeping the allocation-free
|
|
298
|
+
ASCII fast path so the hot path is unchanged) — start from a profiler trace,
|
|
299
|
+
not from guesswork, before adding new caches.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: belote-cli
|
|
3
|
-
Version: 4.9.
|
|
3
|
+
Version: 4.9.7
|
|
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
|
|
@@ -157,8 +157,8 @@ belatro
|
|
|
157
157
|
# Install in editable mode (recommended for development)
|
|
158
158
|
pip install -e .
|
|
159
159
|
|
|
160
|
-
# Or install from PyPI (
|
|
161
|
-
|
|
160
|
+
# Or install from PyPI (recommended for playing)
|
|
161
|
+
pipx install belote-cli # update later with: pipx upgrade belote-cli
|
|
162
162
|
|
|
163
163
|
# Play using the belote command
|
|
164
164
|
belote
|
|
@@ -189,6 +189,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
189
189
|
**BelAtro (Roguelite):**
|
|
190
190
|
- `1`-`5`: Inspect specific Jokers in the Shop
|
|
191
191
|
- `C`: Open the Consumables tray in the Shop (use Tarots / Planets you've purchased)
|
|
192
|
+
- `S` *(4.9.7)*: Sell a joker in the Shop for half its cost (corrupted jokers can't be sold)
|
|
192
193
|
|
|
193
194
|
**Gameplay:**
|
|
194
195
|
- `←` `→` or `↑` `↓` or **`W` `A` `S` `D`** *(4.5.0)*: Move selection
|
|
@@ -211,7 +212,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
211
212
|
- **Collection (Almanac):** Persistent tracker to browse every Joker, Planet, and Voucher you've discovered across your runs.
|
|
212
213
|
- **Full Boss Blind Suite:** All 21 unique bosses implemented, including complex mechanics like *L'Anarchie* (dynamic trump) and *La Rupture* (no consecutive wins).
|
|
213
214
|
- **Multiplier Scoring:** Use items to stack Multipliers and reach scores in the millions.
|
|
214
|
-
- **Partner Trust:** Build a relationship with your AI partner to unlock synergies.
|
|
215
|
+
- **Partner Trust:** Build a relationship with your AI partner to unlock synergies. At trust 3+ the trust bar shows which suits North is void in (`✦ Void: ♠♦`); seeing North's full hand requires the Le Carnet voucher.
|
|
215
216
|
- **Rich Terminal UI:** Full-screen themed felt table with detailed card graphics and "You" vs "Partner" terminology.
|
|
216
217
|
- **Enhanced Hard AI**: Advanced void inference and 2-ply lookahead for critical tricks (Dix de Der).
|
|
217
218
|
- **Customizable Themes:** Switch between thirteen color palettes (Classic Green, Dark Mode, Blue Velvet, Red Casino, Sepia Vintage, High Contrast, Colorblind, Forest Night, Moonlit Tavern, Royal Purple, Emerald Isle, Sunset Magma, Provence Lavande) using the `T` key during gameplay.
|
|
@@ -264,7 +265,7 @@ belote/
|
|
|
264
265
|
│ ├── input.py # Platform-dispatched key reader and interruptible sleep
|
|
265
266
|
│ ├── stats.py # Global and session statistics tracking
|
|
266
267
|
│ └── rules.py # Game rules content
|
|
267
|
-
├── tests/ # Comprehensive test suite (
|
|
268
|
+
├── tests/ # Comprehensive test suite (1228 tests)
|
|
268
269
|
├── scripts/ # Performance benchmarks
|
|
269
270
|
├── pyproject.toml # Build system and dev dependencies (ruff/mypy)
|
|
270
271
|
├── LICENSE # MIT License
|
|
@@ -279,14 +280,14 @@ belote/
|
|
|
279
280
|
PYTHONPATH=src pytest
|
|
280
281
|
```
|
|
281
282
|
|
|
282
|
-
Currently **
|
|
283
|
+
Currently **1228 tests** passing with 100% coverage on game-logic modules (4.9.7).
|
|
283
284
|
|
|
284
285
|
## Technical Integrity
|
|
285
286
|
|
|
286
287
|
The codebase is strictly validated with the following tools:
|
|
287
288
|
- **mypy**: 0 errors (strict type safety)
|
|
288
289
|
- **ruff**: 0 violations (linting & formatting)
|
|
289
|
-
- **pytest**:
|
|
290
|
+
- **pytest**: 1228/1228 passed
|
|
290
291
|
- **Functional Architecture**: Purely immutable state transitions using `dataclasses.replace`
|
|
291
292
|
- **Performance**: High-efficiency rendering and sub-millisecond AI decision times (see `scripts/benchmark.py`)
|
|
292
293
|
|
|
@@ -114,8 +114,8 @@ belatro
|
|
|
114
114
|
# Install in editable mode (recommended for development)
|
|
115
115
|
pip install -e .
|
|
116
116
|
|
|
117
|
-
# Or install from PyPI (
|
|
118
|
-
|
|
117
|
+
# Or install from PyPI (recommended for playing)
|
|
118
|
+
pipx install belote-cli # update later with: pipx upgrade belote-cli
|
|
119
119
|
|
|
120
120
|
# Play using the belote command
|
|
121
121
|
belote
|
|
@@ -146,6 +146,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
146
146
|
**BelAtro (Roguelite):**
|
|
147
147
|
- `1`-`5`: Inspect specific Jokers in the Shop
|
|
148
148
|
- `C`: Open the Consumables tray in the Shop (use Tarots / Planets you've purchased)
|
|
149
|
+
- `S` *(4.9.7)*: Sell a joker in the Shop for half its cost (corrupted jokers can't be sold)
|
|
149
150
|
|
|
150
151
|
**Gameplay:**
|
|
151
152
|
- `←` `→` or `↑` `↓` or **`W` `A` `S` `D`** *(4.5.0)*: Move selection
|
|
@@ -168,7 +169,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
168
169
|
- **Collection (Almanac):** Persistent tracker to browse every Joker, Planet, and Voucher you've discovered across your runs.
|
|
169
170
|
- **Full Boss Blind Suite:** All 21 unique bosses implemented, including complex mechanics like *L'Anarchie* (dynamic trump) and *La Rupture* (no consecutive wins).
|
|
170
171
|
- **Multiplier Scoring:** Use items to stack Multipliers and reach scores in the millions.
|
|
171
|
-
- **Partner Trust:** Build a relationship with your AI partner to unlock synergies.
|
|
172
|
+
- **Partner Trust:** Build a relationship with your AI partner to unlock synergies. At trust 3+ the trust bar shows which suits North is void in (`✦ Void: ♠♦`); seeing North's full hand requires the Le Carnet voucher.
|
|
172
173
|
- **Rich Terminal UI:** Full-screen themed felt table with detailed card graphics and "You" vs "Partner" terminology.
|
|
173
174
|
- **Enhanced Hard AI**: Advanced void inference and 2-ply lookahead for critical tricks (Dix de Der).
|
|
174
175
|
- **Customizable Themes:** Switch between thirteen color palettes (Classic Green, Dark Mode, Blue Velvet, Red Casino, Sepia Vintage, High Contrast, Colorblind, Forest Night, Moonlit Tavern, Royal Purple, Emerald Isle, Sunset Magma, Provence Lavande) using the `T` key during gameplay.
|
|
@@ -221,7 +222,7 @@ belote/
|
|
|
221
222
|
│ ├── input.py # Platform-dispatched key reader and interruptible sleep
|
|
222
223
|
│ ├── stats.py # Global and session statistics tracking
|
|
223
224
|
│ └── rules.py # Game rules content
|
|
224
|
-
├── tests/ # Comprehensive test suite (
|
|
225
|
+
├── tests/ # Comprehensive test suite (1228 tests)
|
|
225
226
|
├── scripts/ # Performance benchmarks
|
|
226
227
|
├── pyproject.toml # Build system and dev dependencies (ruff/mypy)
|
|
227
228
|
├── LICENSE # MIT License
|
|
@@ -236,14 +237,14 @@ belote/
|
|
|
236
237
|
PYTHONPATH=src pytest
|
|
237
238
|
```
|
|
238
239
|
|
|
239
|
-
Currently **
|
|
240
|
+
Currently **1228 tests** passing with 100% coverage on game-logic modules (4.9.7).
|
|
240
241
|
|
|
241
242
|
## Technical Integrity
|
|
242
243
|
|
|
243
244
|
The codebase is strictly validated with the following tools:
|
|
244
245
|
- **mypy**: 0 errors (strict type safety)
|
|
245
246
|
- **ruff**: 0 violations (linting & formatting)
|
|
246
|
-
- **pytest**:
|
|
247
|
+
- **pytest**: 1228/1228 passed
|
|
247
248
|
- **Functional Architecture**: Purely immutable state transitions using `dataclasses.replace`
|
|
248
249
|
- **Performance**: High-efficiency rendering and sub-millisecond AI decision times (see `scripts/benchmark.py`)
|
|
249
250
|
|
|
@@ -4,6 +4,7 @@ import random
|
|
|
4
4
|
import statistics
|
|
5
5
|
import sys
|
|
6
6
|
import time
|
|
7
|
+
from dataclasses import replace
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
9
10
|
# Add src to sys.path
|
|
@@ -23,7 +24,6 @@ def benchmark_render(iterations: int = 100) -> float:
|
|
|
23
24
|
rng = random.Random(42)
|
|
24
25
|
state = start_round(state, rng)
|
|
25
26
|
# Mock some state transitions to get into PLAYING phase
|
|
26
|
-
from belote.game import replace
|
|
27
27
|
|
|
28
28
|
state = replace(state, phase=Phase.PLAYING, trump=Suit.SPADES, taker=Seat.SOUTH)
|
|
29
29
|
|
|
@@ -50,7 +50,6 @@ def benchmark_ai(difficulty: Difficulty, iterations: int = 50) -> float:
|
|
|
50
50
|
state = new_game()
|
|
51
51
|
rng = random.Random(42)
|
|
52
52
|
state = start_round(state, rng)
|
|
53
|
-
from belote.game import replace
|
|
54
53
|
|
|
55
54
|
state = replace(state, phase=Phase.PLAYING, trump=Suit.SPADES, taker=Seat.SOUTH)
|
|
56
55
|
|
|
@@ -69,12 +68,13 @@ def benchmark_ai(difficulty: Difficulty, iterations: int = 50) -> float:
|
|
|
69
68
|
def benchmark_belatro_bus(num_jokers: int = 5, iterations: int = 1000) -> float:
|
|
70
69
|
from belote.belatro.core.scoring import ScoreAccumulator
|
|
71
70
|
from belote.belatro.engine.event_bus import TrickWonEvent
|
|
71
|
+
from belote.belatro.items.base import Joker
|
|
72
72
|
from belote.belatro.items.jokers.contract import LeDiplomate
|
|
73
73
|
from belote.deck import Card, Rank
|
|
74
74
|
|
|
75
75
|
print(f"Benchmarking BelAtro State Update ({num_jokers} Jokers) over {iterations} iterations...")
|
|
76
76
|
|
|
77
|
-
jokers = [LeDiplomate() for _ in range(num_jokers)]
|
|
77
|
+
jokers: list[Joker] = [LeDiplomate() for _ in range(num_jokers)]
|
|
78
78
|
acc = ScoreAccumulator()
|
|
79
79
|
acc.attach_jokers(jokers)
|
|
80
80
|
|
|
@@ -102,7 +102,7 @@ def benchmark_belatro_bus(num_jokers: int = 5, iterations: int = 1000) -> float:
|
|
|
102
102
|
|
|
103
103
|
def benchmark_scoring(iterations: int = 1000) -> float:
|
|
104
104
|
from belote.deck import Card, Rank
|
|
105
|
-
from belote.game import Phase, TrickCard
|
|
105
|
+
from belote.game import Phase, TrickCard
|
|
106
106
|
from belote.scoring import score_round
|
|
107
107
|
|
|
108
108
|
print(f"Benchmarking score_round() over {iterations} iterations...")
|
|
@@ -147,7 +147,7 @@ def benchmark_deal(iterations: int = 1000) -> float:
|
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
def benchmark_legal_cards(iterations: int = 1000) -> float:
|
|
150
|
-
from belote.game import clear_legal_cards_cache, legal_cards
|
|
150
|
+
from belote.game import clear_legal_cards_cache, legal_cards
|
|
151
151
|
print(f"Benchmarking legal_cards() (cache cleared per call) over {iterations} iterations...")
|
|
152
152
|
|
|
153
153
|
state = new_game()
|
|
@@ -172,7 +172,7 @@ def benchmark_legal_cards_cached(iterations: int = 1000) -> float:
|
|
|
172
172
|
`benchmark_legal_cards` above invalidates every iteration and so reflects
|
|
173
173
|
worst-case-only time.
|
|
174
174
|
"""
|
|
175
|
-
from belote.game import clear_legal_cards_cache, legal_cards
|
|
175
|
+
from belote.game import clear_legal_cards_cache, legal_cards
|
|
176
176
|
print(f"Benchmarking legal_cards() (warm cache) over {iterations} iterations...")
|
|
177
177
|
|
|
178
178
|
state = new_game()
|
|
@@ -198,7 +198,7 @@ def benchmark_trick_scoring(iterations: int = 1000) -> float:
|
|
|
198
198
|
(final round score). One of the hottest functions in a played round.
|
|
199
199
|
"""
|
|
200
200
|
from belote.deck import Card, Rank
|
|
201
|
-
from belote.game import TrickCard
|
|
201
|
+
from belote.game import TrickCard
|
|
202
202
|
from belote.scoring import trick_card_points
|
|
203
203
|
|
|
204
204
|
print(f"Benchmarking trick_card_points() over {iterations} iterations...")
|
|
@@ -228,7 +228,7 @@ def benchmark_ai_legality_filter(iterations: int = 500) -> float:
|
|
|
228
228
|
AI calls `legal_cards` once per decision; an unrepresentative cold-cache
|
|
229
229
|
benchmark above would over-attribute AI time to legality checks.
|
|
230
230
|
"""
|
|
231
|
-
from belote.game import legal_cards
|
|
231
|
+
from belote.game import legal_cards
|
|
232
232
|
print(f"Benchmarking AI legality filter over {iterations} iterations...")
|
|
233
233
|
|
|
234
234
|
state = new_game()
|