belote-cli 4.9.7__tar.gz → 4.10.0__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.7 → belote_cli-4.10.0}/CHANGELOG.md +97 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/DEVELOPMENT.md +24 -2
- {belote_cli-4.9.7 → belote_cli-4.10.0}/PKG-INFO +27 -11
- {belote_cli-4.9.7 → belote_cli-4.10.0}/README.md +26 -10
- {belote_cli-4.9.7 → belote_cli-4.10.0}/pyproject.toml +1 -1
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/__init__.py +1 -1
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/a11y.py +11 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ai.py +14 -18
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ansi.py +5 -12
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/core/round_ledger.py +5 -5
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/core/run_state.py +3 -0
- belote_cli-4.10.0/src/belote/belatro/core/scoring.py +636 -0
- belote_cli-4.10.0/src/belote/belatro/core/state.py +46 -0
- belote_cli-4.10.0/src/belote/belatro/daily.py +224 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/engine/modifier_patch.py +11 -10
- belote_cli-4.10.0/src/belote/belatro/engine/round_driver.py +644 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ghost_run.py +3 -7
- belote_cli-4.10.0/src/belote/belatro/integration.py +75 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/trick_timing.py +4 -1
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/main.py +403 -335
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/partner/personality.py +2 -1
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/partner/trust.py +1 -9
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/progression/save.py +3 -6
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/run/boss.py +8 -5
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/run/shop.py +9 -3
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/run_summary.py +4 -6
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/announce.py +5 -56
- belote_cli-4.10.0/src/belote/belatro/ui/collection.py +155 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/hud.py +9 -18
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/rules.py +27 -31
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/config.py +6 -31
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/deck.py +17 -12
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/game.py +65 -96
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/gameflow.py +243 -257
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/input.py +27 -54
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/main.py +48 -25
- belote_cli-4.10.0/src/belote/paths.py +45 -0
- belote_cli-4.10.0/src/belote/round_engine.py +118 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/scoring.py +21 -23
- belote_cli-4.10.0/src/belote/settings.py +158 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/stats.py +18 -8
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/themes.py +17 -1
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/anim.py +6 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/announce.py +78 -18
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/menu.py +31 -92
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/prompts.py +37 -32
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/render.py +64 -90
- belote_cli-4.10.0/src/belote/ui/settings_menu.py +167 -0
- belote_cli-4.10.0/src/belote/ui/text.py +21 -0
- belote_cli-4.10.0/src/belote/variants.py +49 -0
- belote_cli-4.10.0/tests/belatro/_events.py +79 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_audit_round_fixes.py +35 -7
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_boss_contracts.py +91 -15
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_boss_modifiers_integration.py +33 -31
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_competition_malediction.py +9 -17
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_contract_unlocks.py +6 -5
- belote_cli-4.10.0/tests/belatro/test_daily.py +108 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_dead_flag_fixes.py +113 -43
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_deck_variants.py +3 -2
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_decks_4_5.py +10 -9
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_design_calls.py +3 -2
- belote_cli-4.10.0/tests/belatro/test_economy.py +109 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_event_bus.py +64 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_handler_registry.py +2 -1
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_heist.py +10 -8
- belote_cli-4.10.0/tests/belatro/test_jokers.py +520 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_jokers_4_5.py +4 -3
- belote_cli-4.10.0/tests/belatro/test_partner_jokers.py +690 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_partner_trust.py +124 -8
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_phase0_coverage.py +17 -15
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_phase1_plumbing.py +2 -2
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_phase2_content.py +5 -4
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_progression.py +113 -0
- belote_cli-4.10.0/tests/belatro/test_registry_integrity.py +131 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_round_driver.py +22 -22
- belote_cli-4.10.0/tests/belatro/test_run_state.py +185 -0
- belote_cli-4.10.0/tests/belatro/test_score_accumulator.py +117 -0
- belote_cli-4.10.0/tests/belatro/test_screens.py +187 -0
- belote_cli-4.10.0/tests/belatro/test_shop.py +257 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_slot_machine_tally.py +10 -8
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_a11y.py +6 -5
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_ai.py +13 -12
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_ai_deluge_voids.py +3 -7
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_alt_screen_scroll.py +3 -3
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_announce_stats.py +3 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_belote.py +8 -6
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_deluge_legal_cards.py +3 -2
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_g2_ai_signals.py +1 -1
- belote_cli-4.10.0/tests/test_gameflow_headless.py +110 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_input_eof.py +47 -2
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_invariants.py +8 -8
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_layout.py +3 -2
- belote_cli-4.10.0/tests/test_legal_bids.py +31 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_menu_target_and_exit.py +14 -18
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_official_rules.py +3 -2
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_properties.py +6 -4
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_render_diff.py +4 -10
- belote_cli-4.10.0/tests/test_repo_layout.py +46 -0
- belote_cli-4.10.0/tests/test_round_engine.py +109 -0
- belote_cli-4.10.0/tests/test_settings.py +117 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_stats_persistence.py +73 -1
- {belote_cli-4.9.7 → belote_cli-4.10.0}/uv.lock +1 -1
- belote_cli-4.9.7/src/belote/belatro/core/scoring.py +0 -689
- belote_cli-4.9.7/src/belote/belatro/engine/round_driver.py +0 -615
- belote_cli-4.9.7/src/belote/belatro/ui/collection.py +0 -153
- belote_cli-4.9.7/tests/belatro/__init__.py +0 -0
- belote_cli-4.9.7/tests/belatro/test_belatro.py +0 -2037
- belote_cli-4.9.7/tests/belatro/test_partner_jokers.py +0 -357
- belote_cli-4.9.7/tests/belatro/test_registry_integrity.py +0 -61
- {belote_cli-4.9.7 → belote_cli-4.10.0}/.gitignore +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/.python-version +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/LICENSE +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/scripts/benchmark.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/achievements.py +0 -0
- {belote_cli-4.9.7/src → belote_cli-4.10.0/src/belote/belatro}/__init__.py +0 -0
- {belote_cli-4.9.7/src/belote/belatro → belote_cli-4.10.0/src/belote/belatro/core}/__init__.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/core/economy.py +0 -0
- {belote_cli-4.9.7/src/belote/belatro/core → belote_cli-4.10.0/src/belote/belatro/engine}/__init__.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/engine/event_bus.py +0 -0
- {belote_cli-4.9.7/src/belote/belatro/engine → belote_cli-4.10.0/src/belote/belatro/items}/__init__.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/base.py +0 -0
- {belote_cli-4.9.7/src/belote/belatro/items → belote_cli-4.10.0/src/belote/belatro/items/jokers}/__init__.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/annonces.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/coinche.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/contract.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/corrupted.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/economy.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/jokers/hand_comp.py +0 -0
- {belote_cli-4.9.7/src/belote/belatro/items/jokers → belote_cli-4.10.0/src/belote/belatro/items/partner_jokers}/__init__.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/partner_jokers/passive.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/partner_jokers/risky.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/partner_jokers/shaper.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/planets.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/registry.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/tarots.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/items/vouchers.py +0 -0
- {belote_cli-4.9.7/src/belote/belatro/items/partner_jokers → belote_cli-4.10.0/src/belote/belatro/partner}/__init__.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/partner/partner_state.py +0 -0
- {belote_cli-4.9.7/src/belote/belatro/partner → belote_cli-4.10.0/src/belote/belatro/progression}/__init__.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/progression/unlocks.py +0 -0
- {belote_cli-4.9.7/src/belote/belatro/progression → belote_cli-4.10.0/src/belote/belatro/run}/__init__.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/run/ante.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/run/ante_themes.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/run/decks.py +0 -0
- {belote_cli-4.9.7/src/belote/belatro/run → belote_cli-4.10.0/src/belote/belatro/ui}/__init__.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/consumables.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/history.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/inventory.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/menu.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/shop.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/belatro/ui/trust_bar.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/context.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/replay.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/rules.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/__init__.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/fit_guard.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/src/belote/ui/layout.py +0 -0
- {belote_cli-4.9.7/src/belote/belatro/ui → belote_cli-4.10.0/tests}/__init__.py +0 -0
- {belote_cli-4.9.7/tests → belote_cli-4.10.0/tests/belatro}/__init__.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_collection_logic.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_consumables_ui.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_endless.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_ghost_run.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_history_overlay.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_hud_synergy.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_hud_toggle.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_inventory_overlay.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_joker_callouts.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_joker_contracts.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_phase3_meta.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_run_summary.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_shop_animations.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_shop_empty_pools.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/belatro/test_voucher_idempotency.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/conftest.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/perf_baselines.json +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_achievements.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_ai_sans_atout.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_anim_helpers.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_ansi_helpers.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_belote_stinger.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_benchmark_smoke.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_bidding_all_pass.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_declaration_tiebreak.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_extended.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_g1_coinche_classic.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_game_logic.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_gameflow.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_hand_auto_sort.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_history_overlay_cache.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_hud_fit.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_input_escape_seqs.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_input_wasd.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_new_coverage.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_no_color.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_perf_regression.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_render_felt_polish.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_render_golden.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_render_hand_lift.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_replay.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_scoring_totals.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_undo.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_wcwidth.py +0 -0
- {belote_cli-4.9.7 → belote_cli-4.10.0}/tests/test_winner_glow.py +0 -0
|
@@ -5,6 +5,103 @@ 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.10.0] - 2026-11-01
|
|
9
|
+
|
|
10
|
+
Verification pass over two external audit reports (every claim checked
|
|
11
|
+
against the code), then the full follow-up: BelAtro becomes a true expansion
|
|
12
|
+
of the core game, one shared round engine, the god functions split, and three
|
|
13
|
+
features (Settings, achievement toasts, Daily Challenge). Refactors were
|
|
14
|
+
verified with seeded whole-round fingerprints (BelAtro: 800 rounds across all
|
|
15
|
+
bosses/jokers/flags; classic: 450 rounds incl. heavy undo) — byte-identical
|
|
16
|
+
except the intended undo fix. Tests 1228 → 1286, coverage 79% → 83%.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **Settings screen** (main menu → Settings), persisted to `settings.json` in
|
|
20
|
+
the config dir: AI difficulty (per seat), target, speed, theme, animations,
|
|
21
|
+
fast redraw, screen-reader hints, replay analysis, and expansion toggles
|
|
22
|
+
(BelAtro ghost recording). `BELOTE_*` env vars still override while set; the
|
|
23
|
+
CLI flags now override the saved values for one session.
|
|
24
|
+
- **Achievement toasts:** unlocking an achievement pops a boxed toast (and a
|
|
25
|
+
screen-reader line) at the end of the round / game.
|
|
26
|
+
- **Daily Challenge:** one date-seeded BelAtro run per day, identical for
|
|
27
|
+
everyone (fixed deck, starter shop pool, no endless), with a share code and
|
|
28
|
+
a local best-result record (`daily.json`).
|
|
29
|
+
- **Screen-reader hints now announce declarations** (`a11y.announce_declaration`
|
|
30
|
+
existed but was never called).
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- **Classic: undo during play re-ran the coinche flow** — the player was
|
|
34
|
+
re-prompted to coinche and the AI defenders were silently re-polled
|
|
35
|
+
(shifting their RNG) every time an undo resumed the play phase.
|
|
36
|
+
- **BelAtro collection gallery** pinned the cursor to the top row (the list
|
|
37
|
+
was sliced from the selected item), hiding every item above it.
|
|
38
|
+
- **BelAtro rules screen** called `hide_cursor()` without writing it (no-op).
|
|
39
|
+
- **Le Divorce / La Trahison** could leave partner trust frozen at 0 if the
|
|
40
|
+
round aborted; the restore now runs in a `finally`.
|
|
41
|
+
- **BelAtro: Sans Atout bid from the normal prompt became a pass.**
|
|
42
|
+
`UICallbacks.prompt_bid` filtered UI sentinels with `isinstance(res, str)`,
|
|
43
|
+
which also matched `SANS_ATOUT_BID` (the plain string `"sans_atout"`).
|
|
44
|
+
- **L'Aristocrate ace payout ignored La Rupture / L'Anarchie.** Aces are now
|
|
45
|
+
credited via `compute_trick_winners` (new `_aces_won_by_team` helper) instead
|
|
46
|
+
of raw `trick_winner_seat` with the final trump.
|
|
47
|
+
- **mypy was blind to every absolute `belote.*` import.** A stray empty
|
|
48
|
+
`src/__init__.py` made mypy name modules `src.belote.*`; with
|
|
49
|
+
`ignore_missing_imports` all `from belote.… import` resolved to `Any`.
|
|
50
|
+
Removed it (guarded by `tests/test_repo_layout.py`) and fixed the 9 errors
|
|
51
|
+
it was hiding (incl. a possible `None.lower()` in the BelAtro rules screen).
|
|
52
|
+
- **Theme file ignored `XDG_CONFIG_HOME` / `%APPDATA%`** (hardcoded
|
|
53
|
+
`~/.config`, which also let the test suite write the real user config).
|
|
54
|
+
- **Main menu flicker:** the 300ms steam animation no longer clears the whole
|
|
55
|
+
screen each tick; it repaints in place.
|
|
56
|
+
- **Achievements are now visible** on the Statistics screen (they were
|
|
57
|
+
evaluated and saved but never shown).
|
|
58
|
+
|
|
59
|
+
### Changed — architecture
|
|
60
|
+
- **The core never imports BelAtro** (enforced by `tests/test_repo_layout.py`).
|
|
61
|
+
Boss/deck/voucher rule switches moved from BelAtro's `BossModifiers` +
|
|
62
|
+
`_joker_state` keys to a core `RuleVariants` (`belote/variants.py`,
|
|
63
|
+
`GameState.variants`). BelAtro's chips/mult/money/joker state moved off the
|
|
64
|
+
core `GameState` onto `belatro/core/state.py::BelatroState`, a subclass that
|
|
65
|
+
core transitions (now generic over `GameState` subclasses) carry along.
|
|
66
|
+
The stats screen, bid-hint line, I/V keys and main-menu modes are now
|
|
67
|
+
registration hooks wired from the entry point via `belatro/integration.py`.
|
|
68
|
+
- **One round engine** (`belote/round_engine.py`): classic `gameflow` and
|
|
69
|
+
BelAtro `round_driver` are two `RoundPolicy` implementations of
|
|
70
|
+
`play_round`. Coinche level now lives on `state.coinche_level` in both modes.
|
|
71
|
+
- **God functions split:** `process_event` (369 lines) → per-event handler
|
|
72
|
+
methods; `drive_round` (450) → a round context + phase functions;
|
|
73
|
+
`_play_blind` (344) → setup / boss / settlement helpers.
|
|
74
|
+
- **Tests:** the 2037-line `test_belatro.py` split by topic (economy, score
|
|
75
|
+
accumulator, run state, jokers, shop, …); 2 duplicate tests removed; new
|
|
76
|
+
coverage for the round engine, headless classic rounds, the collection /
|
|
77
|
+
rules / shop screens, settings and the daily challenge.
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
- AI / target / speed / theme moved from the main menu into Settings.
|
|
81
|
+
- Shared key-alias table for the Unix and Windows readers; one
|
|
82
|
+
`ThemeManager.cycle()`; table-driven card art (byte-identical output);
|
|
83
|
+
`wrap_words` text helper; `BELOTE_NO_DIFF` read once instead of per frame.
|
|
84
|
+
- One `legal_bids(state)` in `game.py` replaces four hand-rolled copies
|
|
85
|
+
(bid prompt, bid renderer, BelAtro joker hooks, partner legality gate).
|
|
86
|
+
- One `deck.is_trump_card()` replaces the inline "trump suit or Déluge 7/8"
|
|
87
|
+
predicate in `deck.py`, `game.py`, `ai.py` and `round_driver.py`
|
|
88
|
+
(`_card_beats` keeps its Tout-Atout-exclusive variant, now commented).
|
|
89
|
+
- New `belote/paths.py` owns data/config locations (was 5 copies; the BelAtro
|
|
90
|
+
ones fell back to the CWD on Windows without `%APPDATA%`).
|
|
91
|
+
- Dix de Der uses `GLOBAL_CONFIG.LAST_TRICK_BONUS` everywhere (3 hardcoded 10s).
|
|
92
|
+
- BelAtro history entries are built from typed `GameState`/`ScoringBreakdown`
|
|
93
|
+
instead of `object` + `getattr`.
|
|
94
|
+
|
|
95
|
+
### Removed
|
|
96
|
+
- `BelAtroAnnounce.score_popup` (slated for removal in 4.8) and its dead
|
|
97
|
+
overlay flag helpers.
|
|
98
|
+
- `ScoreAccumulator.partner_jokers_double` / `TrustTrack.partner_jokers_double`
|
|
99
|
+
(deprecated since 3.5.0; `partner_tier` replaces them).
|
|
100
|
+
- Dead code: `Seat.prev_seat`, `GameState.current_bidder`, `set_announced`,
|
|
101
|
+
`Config.ensure_stats_dir`, legacy `Config.CARD_*` / `MIN_TERM_*` /
|
|
102
|
+
`THEME_NAME`, `render.CARD_W/CARD_H/CARD_GAP/SIDE_COL_W`,
|
|
103
|
+
`ansi.save_cursor/restore_cursor/scroll_region`, unused HUD tables.
|
|
104
|
+
|
|
8
105
|
## [4.9.7] - 2026-11-01
|
|
9
106
|
|
|
10
107
|
Repo-wide audit: four parallel reviewers swept the classic engine, the
|
|
@@ -171,6 +171,26 @@ PYTHONPATH=src python scripts/benchmark.py
|
|
|
171
171
|
|
|
172
172
|
Use these as a regression-detection floor for future changes.
|
|
173
173
|
|
|
174
|
+
## Architecture: core game vs. BelAtro expansion
|
|
175
|
+
|
|
176
|
+
- **The core never imports BelAtro.** `tests/test_repo_layout.py` enforces
|
|
177
|
+
it; only the composition root `belote/main.py` wires the expansion in via
|
|
178
|
+
`belote/belatro/integration.py` (menu modes, stats section, settings
|
|
179
|
+
toggle) and BelAtro installs its run-scoped hooks itself (history
|
|
180
|
+
override, bid hint, extension keys I/V).
|
|
181
|
+
- **One round engine.** `belote/round_engine.py::play_round` owns the phase
|
|
182
|
+
order (bidding with all-pass handling → contract → tricks). Classic
|
|
183
|
+
(`gameflow.ClassicRound`) and BelAtro (`belatro/engine/round_driver.py::
|
|
184
|
+
_RoundContext`) are `RoundPolicy` implementations — change the round's
|
|
185
|
+
flow there, once.
|
|
186
|
+
- **Rule variants are core.** Boss / deck / voucher effects that change how
|
|
187
|
+
Belote is played live on `GameState.variants` (`belote/variants.py`);
|
|
188
|
+
bosses only switch variants on.
|
|
189
|
+
- **Expansion state rides on a subclass.** BelAtro's chips / mult / money /
|
|
190
|
+
joker state live on `belatro/core/state.py::BelatroState`. Core
|
|
191
|
+
transitions are generic over `GameState` subclasses and build states with
|
|
192
|
+
`replace()`, so the subclass survives every move.
|
|
193
|
+
|
|
174
194
|
## Accessibility
|
|
175
195
|
|
|
176
196
|
Set `BELOTE_A11Y=1` to emit one-line plain-text descriptions of card plays,
|
|
@@ -196,8 +216,10 @@ run won/lost).
|
|
|
196
216
|
|
|
197
217
|
## Optional Runtime Flags
|
|
198
218
|
|
|
199
|
-
|
|
200
|
-
|
|
219
|
+
Players toggle these from the in-game **Settings** screen (`belote/settings.py`
|
|
220
|
+
→ `settings.json`; `settings.apply()` pushes the values into the modules).
|
|
221
|
+
The environment variables below still work and *override* the saved setting
|
|
222
|
+
while set — handy for debugging and CI. Each is read once at startup.
|
|
201
223
|
|
|
202
224
|
- `BELOTE_REPLAY=1` — after every Classic round, print a one-line summary
|
|
203
225
|
of how often South's plays matched the Hard-AI's preferred line
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: belote-cli
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.10.0
|
|
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
|
|
@@ -113,14 +113,14 @@ belatro
|
|
|
113
113
|
.-'``'|-._ ) _.-|
|
|
114
114
|
/ .--.| `''---...........---''` |
|
|
115
115
|
/ / | BelAtro |
|
|
116
|
-
| | |
|
|
117
|
-
| | |
|
|
118
|
-
| | |
|
|
119
|
-
| | | Speed: < Normal > |
|
|
120
|
-
| | | Theme: < Classic Green > |
|
|
116
|
+
| | | Daily Challenge |
|
|
117
|
+
| | | > Start Game < |
|
|
118
|
+
| | | Settings |
|
|
121
119
|
| | | Rules & History |
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
| | | Statistics |
|
|
121
|
+
| | | Quit |
|
|
122
|
+
\ \ | |
|
|
123
|
+
`\ `\ | |
|
|
124
124
|
`\ `| |
|
|
125
125
|
_/ /\ /
|
|
126
126
|
(__/ \ /
|
|
@@ -166,10 +166,26 @@ belote
|
|
|
166
166
|
# Play the new Roguelite expansion
|
|
167
167
|
belatro
|
|
168
168
|
|
|
169
|
-
#
|
|
169
|
+
# One-off overrides of the saved settings (not persisted)
|
|
170
170
|
belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
+
**Settings** live in the main menu's *Settings* screen and are saved to
|
|
174
|
+
`settings.json` in your config directory (`$XDG_CONFIG_HOME/belote`, else
|
|
175
|
+
`~/.config/belote`; `%APPDATA%\belote` on Windows): AI difficulty (per seat
|
|
176
|
+
via Enter), target score, speed, theme, animations, fast redraw,
|
|
177
|
+
screen-reader hints, post-round replay analysis and BelAtro ghost-run
|
|
178
|
+
recording. The old `BELOTE_*` environment variables still work and override
|
|
179
|
+
the saved value while set.
|
|
180
|
+
|
|
181
|
+
**Daily Challenge** (main menu): one seeded BelAtro run per day, the same for
|
|
182
|
+
everyone (fixed deck, date-derived seed, starter shop pool, no endless).
|
|
183
|
+
The intro shows the day's number and a share code; your best result per day
|
|
184
|
+
is kept locally.
|
|
185
|
+
|
|
186
|
+
**Achievements** pop a toast the moment you unlock one and are listed on the
|
|
187
|
+
Statistics screen.
|
|
188
|
+
|
|
173
189
|
|
|
174
190
|
## Controls
|
|
175
191
|
|
|
@@ -219,7 +235,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
219
235
|
- **Polished Felt Mat (3.9.4):** The trick mat now has a subtle vignette at its edges, a faint deterministic braille pip-dot texture (fabric-weave feel without intrusive glyphs), and — at standard/spacious terminal sizes — a decorative `╔═══◆═══...═══◆═══╗` outer frame with corner ornaments.
|
|
220
236
|
- **Selection HUD (3.9.4):** Selecting a card in hand now paints a highlighted bar under it AND a centered `► A♠ — Trump ◄` readout below, color-coded by suit / trump / legality.
|
|
221
237
|
- **Hand Sorting:** Strategic "play value" organization (honors grouped together) for better tactical awareness.
|
|
222
|
-
- **Main Menu:** Simple single-player entry point
|
|
238
|
+
- **Main Menu:** Simple single-player entry point; AI difficulty, Target Score, Speed and Theme live in the Settings screen.
|
|
223
239
|
- **Undo/Redo:** Press `Z` to undo your last move during bidding or play.
|
|
224
240
|
- **Statistics:** unified global tracking of games played, win rates, best rounds, and BelAtro expansion milestones.
|
|
225
241
|
- **Responsive Layout (3 tiers):** Three preset layouts — **compact** (80×32, fits 1366×768), **standard** (96×38), **spacious** (120×48+). The game picks the largest preset that fits your terminal on every render, so resizing mid-game adapts automatically; cards, side columns, and HUD verbosity all scale with the preset. Vertical centering pads tall terminals so the game never clings to the top.
|
|
@@ -280,7 +296,7 @@ belote/
|
|
|
280
296
|
PYTHONPATH=src pytest
|
|
281
297
|
```
|
|
282
298
|
|
|
283
|
-
Currently **
|
|
299
|
+
Currently **1286 tests** passing with 100% coverage on game-logic modules (4.10.0).
|
|
284
300
|
|
|
285
301
|
## Technical Integrity
|
|
286
302
|
|
|
@@ -70,14 +70,14 @@ belatro
|
|
|
70
70
|
.-'``'|-._ ) _.-|
|
|
71
71
|
/ .--.| `''---...........---''` |
|
|
72
72
|
/ / | BelAtro |
|
|
73
|
-
| | |
|
|
74
|
-
| | |
|
|
75
|
-
| | |
|
|
76
|
-
| | | Speed: < Normal > |
|
|
77
|
-
| | | Theme: < Classic Green > |
|
|
73
|
+
| | | Daily Challenge |
|
|
74
|
+
| | | > Start Game < |
|
|
75
|
+
| | | Settings |
|
|
78
76
|
| | | Rules & History |
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
| | | Statistics |
|
|
78
|
+
| | | Quit |
|
|
79
|
+
\ \ | |
|
|
80
|
+
`\ `\ | |
|
|
81
81
|
`\ `| |
|
|
82
82
|
_/ /\ /
|
|
83
83
|
(__/ \ /
|
|
@@ -123,10 +123,26 @@ belote
|
|
|
123
123
|
# Play the new Roguelite expansion
|
|
124
124
|
belatro
|
|
125
125
|
|
|
126
|
-
#
|
|
126
|
+
# One-off overrides of the saved settings (not persisted)
|
|
127
127
|
belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
+
**Settings** live in the main menu's *Settings* screen and are saved to
|
|
131
|
+
`settings.json` in your config directory (`$XDG_CONFIG_HOME/belote`, else
|
|
132
|
+
`~/.config/belote`; `%APPDATA%\belote` on Windows): AI difficulty (per seat
|
|
133
|
+
via Enter), target score, speed, theme, animations, fast redraw,
|
|
134
|
+
screen-reader hints, post-round replay analysis and BelAtro ghost-run
|
|
135
|
+
recording. The old `BELOTE_*` environment variables still work and override
|
|
136
|
+
the saved value while set.
|
|
137
|
+
|
|
138
|
+
**Daily Challenge** (main menu): one seeded BelAtro run per day, the same for
|
|
139
|
+
everyone (fixed deck, date-derived seed, starter shop pool, no endless).
|
|
140
|
+
The intro shows the day's number and a share code; your best result per day
|
|
141
|
+
is kept locally.
|
|
142
|
+
|
|
143
|
+
**Achievements** pop a toast the moment you unlock one and are listed on the
|
|
144
|
+
Statistics screen.
|
|
145
|
+
|
|
130
146
|
|
|
131
147
|
## Controls
|
|
132
148
|
|
|
@@ -176,7 +192,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
176
192
|
- **Polished Felt Mat (3.9.4):** The trick mat now has a subtle vignette at its edges, a faint deterministic braille pip-dot texture (fabric-weave feel without intrusive glyphs), and — at standard/spacious terminal sizes — a decorative `╔═══◆═══...═══◆═══╗` outer frame with corner ornaments.
|
|
177
193
|
- **Selection HUD (3.9.4):** Selecting a card in hand now paints a highlighted bar under it AND a centered `► A♠ — Trump ◄` readout below, color-coded by suit / trump / legality.
|
|
178
194
|
- **Hand Sorting:** Strategic "play value" organization (honors grouped together) for better tactical awareness.
|
|
179
|
-
- **Main Menu:** Simple single-player entry point
|
|
195
|
+
- **Main Menu:** Simple single-player entry point; AI difficulty, Target Score, Speed and Theme live in the Settings screen.
|
|
180
196
|
- **Undo/Redo:** Press `Z` to undo your last move during bidding or play.
|
|
181
197
|
- **Statistics:** unified global tracking of games played, win rates, best rounds, and BelAtro expansion milestones.
|
|
182
198
|
- **Responsive Layout (3 tiers):** Three preset layouts — **compact** (80×32, fits 1366×768), **standard** (96×38), **spacious** (120×48+). The game picks the largest preset that fits your terminal on every render, so resizing mid-game adapts automatically; cards, side columns, and HUD verbosity all scale with the preset. Vertical centering pads tall terminals so the game never clings to the top.
|
|
@@ -237,7 +253,7 @@ belote/
|
|
|
237
253
|
PYTHONPATH=src pytest
|
|
238
254
|
```
|
|
239
255
|
|
|
240
|
-
Currently **
|
|
256
|
+
Currently **1286 tests** passing with 100% coverage on game-logic modules (4.10.0).
|
|
241
257
|
|
|
242
258
|
## Technical Integrity
|
|
243
259
|
|
|
@@ -55,6 +55,17 @@ def is_enabled() -> bool:
|
|
|
55
55
|
return _ENABLED
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
def set_enabled(on: bool) -> None:
|
|
59
|
+
"""Switch screen-reader hints on/off (the Settings screen)."""
|
|
60
|
+
global _ENABLED, _sink
|
|
61
|
+
if on == _ENABLED:
|
|
62
|
+
return
|
|
63
|
+
_ENABLED = on
|
|
64
|
+
if _sink is not None and _sink is not sys.stderr:
|
|
65
|
+
_sink.close()
|
|
66
|
+
_sink = None
|
|
67
|
+
|
|
68
|
+
|
|
58
69
|
def _refresh_enabled_from_env() -> None:
|
|
59
70
|
"""Re-read BELOTE_A11Y from the live environment. Public for tests."""
|
|
60
71
|
global _ENABLED, _sink
|
|
@@ -4,7 +4,7 @@ import random
|
|
|
4
4
|
from collections import Counter
|
|
5
5
|
from enum import Enum
|
|
6
6
|
|
|
7
|
-
from .deck import _NONTRUMP_ORDER, Card, Contract, Rank, Suit, trick_rank
|
|
7
|
+
from .deck import _NONTRUMP_ORDER, Card, Contract, Rank, Suit, is_trump_card, trick_rank
|
|
8
8
|
from .game import (
|
|
9
9
|
SANS_ATOUT_BID,
|
|
10
10
|
BidValue,
|
|
@@ -70,7 +70,7 @@ class AIPlayer:
|
|
|
70
70
|
# unseeded Random() for legacy callers that construct an AIPlayer
|
|
71
71
|
# directly (e.g. test fixtures).
|
|
72
72
|
self._rng = rng if rng is not None else random.Random()
|
|
73
|
-
# Set per decide_card() call from state.
|
|
73
|
+
# Set per decide_card() call from state.variants.seven_eight_trump.
|
|
74
74
|
# All ranking helpers in this class read it via self._se.
|
|
75
75
|
self._se = False
|
|
76
76
|
|
|
@@ -140,7 +140,7 @@ class AIPlayer:
|
|
|
140
140
|
# set at round start in current code, but the assumption is
|
|
141
141
|
# defensive — a future boss or voucher that toggles visibility
|
|
142
142
|
# mid-round would otherwise see stale cached partner cards.
|
|
143
|
-
hide = bool(state.
|
|
143
|
+
hide = bool(state.variants.hide_partner_hand)
|
|
144
144
|
partner_key: tuple[int, int, bool] = (completed_count, current_count, hide)
|
|
145
145
|
if self.memory.last_partner_hand_key != partner_key:
|
|
146
146
|
p = partner(self.seat)
|
|
@@ -199,9 +199,9 @@ class AIPlayer:
|
|
|
199
199
|
or None (pass). TA and SA are only considered in round 2; round 1 is
|
|
200
200
|
"take the up-card suit at the standard contract" only.
|
|
201
201
|
"""
|
|
202
|
-
self._se = state.
|
|
202
|
+
self._se = state.variants.seven_eight_trump
|
|
203
203
|
# Boss: La Solitude (Partner forced pass)
|
|
204
|
-
if state.
|
|
204
|
+
if state.variants.partner_forced_pass and self.seat == partner(Seat.SOUTH):
|
|
205
205
|
return None
|
|
206
206
|
|
|
207
207
|
hand = state.hand_of(self.seat)
|
|
@@ -329,7 +329,7 @@ class AIPlayer:
|
|
|
329
329
|
jack-heavy hand under Le Sauvage even though those jacks would score
|
|
330
330
|
zero in actual play.
|
|
331
331
|
"""
|
|
332
|
-
bm = state.
|
|
332
|
+
bm = state.variants
|
|
333
333
|
ta_pts = sum(card_points_with_modifiers(c, Suit.TOUT_ATOUT, bm) for c in hand)
|
|
334
334
|
# The Jack-count bonus is the AI's TA strength signal — drop it if
|
|
335
335
|
# jacks are suppressed.
|
|
@@ -364,17 +364,13 @@ class AIPlayer:
|
|
|
364
364
|
# see deck.py::trick_rank and ::card_points). Every ranking / point
|
|
365
365
|
# read in this method must thread `_se` through or the AI under-
|
|
366
366
|
# values trump cards that should beat them.
|
|
367
|
-
self._se = state.
|
|
367
|
+
self._se = state.variants.seven_eight_trump
|
|
368
368
|
|
|
369
369
|
# Boss: L'Agent Double (Partner sabotages on 3 random tricks)
|
|
370
|
-
if state.
|
|
370
|
+
if state.variants.agent_double_active and self.seat == partner(Seat.SOUTH):
|
|
371
371
|
current_trick = len(state.completed_tricks) + 1
|
|
372
|
-
raw_tricks = state._joker_state.get("agent_double_tricks", frozenset())
|
|
373
|
-
sabotage_tricks: frozenset[int] = (
|
|
374
|
-
raw_tricks if isinstance(raw_tricks, frozenset) else frozenset()
|
|
375
|
-
)
|
|
376
372
|
trump = state.trump
|
|
377
|
-
if current_trick in sabotage_tricks and trump is not None:
|
|
373
|
+
if current_trick in state.variants.sabotage_tricks and trump is not None:
|
|
378
374
|
return min(legal, key=lambda c: trick_rank(c, trump, self._se))
|
|
379
375
|
|
|
380
376
|
if not hand:
|
|
@@ -459,7 +455,7 @@ class AIPlayer:
|
|
|
459
455
|
# aces_zero, jacks_zero, tens_zero, ban_clubs). Pre-4.1.1 medium AI used
|
|
460
456
|
# raw card_points and would "preserve" a K of trump under kings_zero as
|
|
461
457
|
# if it still scored 20 — picking it as the highest-value card to keep.
|
|
462
|
-
bm = state.
|
|
458
|
+
bm = state.variants
|
|
463
459
|
|
|
464
460
|
# Update void inferences
|
|
465
461
|
self._update_voids(state)
|
|
@@ -588,7 +584,7 @@ class AIPlayer:
|
|
|
588
584
|
if c.suit in suit_cards:
|
|
589
585
|
suit_cards[c.suit].append(c)
|
|
590
586
|
|
|
591
|
-
bm = state.
|
|
587
|
+
bm = state.variants
|
|
592
588
|
# Honors are J/9/A; drop a rank from the count if it's zeroed by a
|
|
593
589
|
# boss flag (3.9.3 — honor-counting was previously boss-blind).
|
|
594
590
|
# 4.6.4: hoisted out of the per-suit loop. The closure was being
|
|
@@ -716,7 +712,7 @@ class AIPlayer:
|
|
|
716
712
|
if lead_suit_for_ranks is not None and c.suit != lead_suit_for_ranks:
|
|
717
713
|
if is_sa or trump is Suit.TOUT_ATOUT:
|
|
718
714
|
return -1
|
|
719
|
-
if
|
|
715
|
+
if not is_trump_card(c, trump, se):
|
|
720
716
|
return -1
|
|
721
717
|
return _NONTRUMP_ORDER[c.rank] if is_sa else trick_rank(c, trump, se)
|
|
722
718
|
|
|
@@ -852,7 +848,7 @@ class AIPlayer:
|
|
|
852
848
|
# (kings_zero / aces_zero / jacks_zero / tens_zero / ban_clubs).
|
|
853
849
|
# Propagates to _score_discarding_strategy / _score_winning_strategy
|
|
854
850
|
# via the `points` parameter and to _score_leading_strategy via `bm`.
|
|
855
|
-
bm = state.
|
|
851
|
+
bm = state.variants
|
|
856
852
|
points = card_points_with_modifiers(card, trump, bm)
|
|
857
853
|
# Small per-card tiebreaker: when win/loss heuristics are otherwise
|
|
858
854
|
# equal, slightly bias toward playing the higher-value card. The
|
|
@@ -1124,7 +1120,7 @@ class AIPlayer:
|
|
|
1124
1120
|
# Le Républicain (or any deck/voucher that sets the flag): 7s and 8s
|
|
1125
1121
|
# are wild and may be played on any suit, so an off-suit 7/8 doesn't
|
|
1126
1122
|
# prove void in lead suit.
|
|
1127
|
-
wild_active =
|
|
1123
|
+
wild_active = state.variants.wild_seven_eight
|
|
1128
1124
|
|
|
1129
1125
|
# 1. Process new completed tricks
|
|
1130
1126
|
trump = state.trump
|
|
@@ -236,6 +236,11 @@ def clear_screen() -> str:
|
|
|
236
236
|
return "\x1b[H\x1b[2J"
|
|
237
237
|
|
|
238
238
|
|
|
239
|
+
def clear_below() -> str:
|
|
240
|
+
"""Erase from the cursor to the end of the screen."""
|
|
241
|
+
return "\x1b[J"
|
|
242
|
+
|
|
243
|
+
|
|
239
244
|
def clear_line() -> str:
|
|
240
245
|
return "\x1b[2K"
|
|
241
246
|
|
|
@@ -260,18 +265,6 @@ def alt_screen_off() -> str:
|
|
|
260
265
|
return "\x1b[?1049l"
|
|
261
266
|
|
|
262
267
|
|
|
263
|
-
def save_cursor() -> str:
|
|
264
|
-
return "\x1b7"
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
def restore_cursor() -> str:
|
|
268
|
-
return "\x1b8"
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def scroll_region(top: int, bottom: int) -> str:
|
|
272
|
-
return f"\x1b[{top};{bottom}r"
|
|
273
|
-
|
|
274
|
-
|
|
275
268
|
# Palette accessors. Read precomputed escape strings from the active palette
|
|
276
269
|
# (rebuilt by `_refresh_theme_cache()` via theme_manager's change callback).
|
|
277
270
|
def felt_bg() -> str:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""4.6.2 — RoundLedger.
|
|
2
2
|
|
|
3
3
|
A mutable side-channel that lives for one BelAtro round and replaces the
|
|
4
|
-
per-event `dataclasses.replace(
|
|
4
|
+
per-event `dataclasses.replace(BelatroState, ...)` pattern in
|
|
5
5
|
`ScoreAccumulator.update_state`.
|
|
6
6
|
|
|
7
|
-
Frozen-
|
|
7
|
+
Frozen-BelatroState contract still holds at round boundaries: `seal_round()` does
|
|
8
8
|
the single `replace()` that produces the final canonical state at round end.
|
|
9
9
|
|
|
10
10
|
Why this design:
|
|
@@ -31,7 +31,7 @@ from contextlib import contextmanager
|
|
|
31
31
|
from dataclasses import dataclass, field, replace
|
|
32
32
|
from typing import Any
|
|
33
33
|
|
|
34
|
-
from
|
|
34
|
+
from .state import BelatroState
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
@dataclass(slots=True)
|
|
@@ -55,8 +55,8 @@ class RoundLedger:
|
|
|
55
55
|
# updates.
|
|
56
56
|
joker_state: dict[str, Any] = field(default_factory=dict)
|
|
57
57
|
|
|
58
|
-
def seal_round(self, state:
|
|
59
|
-
"""Produce the canonical sealed
|
|
58
|
+
def seal_round(self, state: BelatroState) -> BelatroState:
|
|
59
|
+
"""Produce the canonical sealed BelatroState for the end of the round.
|
|
60
60
|
|
|
61
61
|
Single `replace()` that stamps the ledger's chips/mult/money into the
|
|
62
62
|
frozen state. `joker_state` is already live on `state` (shared dict)
|
|
@@ -95,6 +95,9 @@ class BelAtroRun:
|
|
|
95
95
|
|
|
96
96
|
# ── Determinism ────────────────────────────────────────
|
|
97
97
|
seed: int | None = None
|
|
98
|
+
# ISO date when this is a Daily Challenge run (see belatro/daily.py):
|
|
99
|
+
# starter shop pool, no endless offer.
|
|
100
|
+
daily: str | None = None
|
|
98
101
|
_rng: Any = None
|
|
99
102
|
|
|
100
103
|
# ── Idempotency guard for voucher.apply() ──────────────
|