belote-cli 2.9.0__tar.gz → 2.9.5__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.9.0 → belote_cli-2.9.5}/CHANGELOG.md +75 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/PKG-INFO +33 -25
- {belote_cli-2.9.0 → belote_cli-2.9.5}/README.md +32 -24
- {belote_cli-2.9.0 → belote_cli-2.9.5}/pyproject.toml +1 -1
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/__init__.py +1 -1
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/game.py +8 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/gameflow.py +10 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/input.py +12 -8
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/main.py +2 -2
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/scoring.py +56 -8
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/ui/announce.py +15 -2
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/ui/menu.py +44 -34
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/ui/prompts.py +121 -95
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/ui/render.py +296 -68
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/test_extended.py +44 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/test_layout.py +2 -2
- {belote_cli-2.9.0 → belote_cli-2.9.5}/.claude/settings.local.json +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/.gitignore +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/.python-version +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/DEVELOPMENT.md +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/GRIMAUD Standard Playing-Cards-1898.png +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/LICENSE +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/scripts/benchmark.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/ai.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/ansi.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/core/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/core/economy.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/core/run_state.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/core/scoring.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/engine/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/engine/event_bus.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/engine/modifier_patch.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/engine/round_driver.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/base.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/jokers/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/jokers/annonces.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/jokers/coinche.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/jokers/contract.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/jokers/corrupted.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/jokers/economy.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/jokers/hand_comp.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/jokers/trick_timing.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/partner_jokers/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/partner_jokers/passive.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/partner_jokers/risky.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/partner_jokers/shaper.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/planets.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/registry.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/tarots.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/items/vouchers.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/main.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/partner/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/partner/partner_state.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/partner/personality.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/partner/trust.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/progression/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/progression/save.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/progression/unlocks.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/run/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/run/ante.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/run/ante_themes.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/run/boss.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/run/decks.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/run/shop.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/ui/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/ui/announce.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/ui/collection.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/ui/hud.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/ui/menu.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/ui/rules.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/ui/shop.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/belatro/ui/trust_bar.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/config.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/context.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/deck.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/rules.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/stats.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/themes.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/ui/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/src/belote/ui/layout.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/__init__.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_belatro.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_boss_modifiers_integration.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_collection_logic.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_contract_unlocks.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_dead_flag_fixes.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_deck_variants.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_partner_trust.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_phase0_coverage.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_phase1_plumbing.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_phase2_content.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_phase3_meta.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_progression.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/belatro/test_round_driver.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/test_ai.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/test_belote.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/test_game_logic.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/test_gameflow.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/test_new_coverage.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/test_official_rules.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/test_properties.py +0 -0
- {belote_cli-2.9.0 → belote_cli-2.9.5}/tests/test_undo.py +0 -0
|
@@ -5,6 +5,81 @@ 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.5] - 2026-05-07
|
|
9
|
+
|
|
10
|
+
In-game keyboard shortcuts cleaned up, the trick mat now anchors every played card inside a visible per-seat slot, the round history overlay carries the full per-round picture, and the cards have been redrawn in a GRIMAUD-1898 style with both-corner indices and patterned pip layouts.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`src/belote/input.py`** — `Key.THEME` was defined but never mapped to a keystroke; the help text falsely advertised `Shift+T` (which terminals can't generally distinguish in raw mode). Theme cycling during gameplay was unreachable. New mapping: `t/T → Key.THEME`, `h/H → Key.HIST`, `?` → `Key.HELP`, `o/O → Key.SORT`. Both the Unix and Windows readers updated.
|
|
15
|
+
- **`src/belote/input.py`** — `s` was previously stolen by `Key.SORT`, so pressing `S` in round-2 bidding triggered a sort instead of the **Sans Atout** quick-bid the help text promised. Sort now lives on `O` (matching what the help screen always claimed) and `s` falls through to `Key.CHAR` so SA bidding works.
|
|
16
|
+
- **`src/belote/gameflow.py::run_play`** — when the user had already pressed any key earlier in the round (which sets `skip_anims`), the post-trick pause was skipped entirely and the 4th card vanished before it could be read. New `MIN_TRICK_DWELL = 0.5s` non-skippable hold runs after every completed trick (even on the `instant` speed preset) so the player always sees all four cards before the mat clears.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`src/belote/ui/render.py`** — visible **slot frames** drawn around each compass position on the trick mat. Implemented via three new helpers (`_slot_anchors`, `_slot_frame_row`, `_felt_pad_ns`, `_we_row`) that paint thin `─`/`│` borders on the felt cells immediately surrounding each card slot, in the felt-placeholder dim colour. Total mat dimensions (`6 + 3*card_h`) are unchanged, so `_calculate_base_row` and `patch_trick_card` continue to work without any coordinate adjustments — patched cards land exactly inside the existing frame.
|
|
21
|
+
- **`src/belote/game.py::RoundScore`** — eight new optional fields (`contract`, `trump`, `taker_seat`, `tricks_ns`, `tricks_ew`, `last_trick_winner`, `decl_summary_ns`, `decl_summary_ew`) populated from `state` and `state.completed_tricks` at scoring time. All fields default so existing test fixtures and any historical `RoundScore` constructions remain valid.
|
|
22
|
+
- **`src/belote/scoring.py::apply_round_score`** — now computes per-team trick counts via `trick_winner_seat`/`team_of`, builds short declaration labels (`"100♥"`, `"Belote"`, `"Carré-J"`, …) gated on the team's `*_decl_pts > 0` so only the *scored* declarations appear, and threads everything into `RoundScore`. New helper `_decl_short_label` covers belote/rebelote/sequence/carre.
|
|
23
|
+
- **`src/belote/ui/prompts.py::show_history`** — rewritten as an 8-column table (`RD | TAKER | CONTRACT | TRICKS | DECLARATIONS | NS | EW | STATUS`) for terminals ≥78 cols, with a 2-line-per-round fallback for narrower terminals. Status colouring: gold `CAPOT`, red `CHUTE`, dim `LITIGE`. Existing scrolling, view-height clamp, and exit-on-any-key behaviour preserved.
|
|
24
|
+
- **`src/belote/ui/render.py::_card_face_internal`** — full GRIMAUD-1898-inspired redraw:
|
|
25
|
+
- Both corners now carry a 3-cell `rank+suit` index (`A♠` top-left, `♠A` bottom-right). The index padding scales with `inner_w`.
|
|
26
|
+
- Pip cards (7-10) at `card_h ≥ 7` get a recognisable pip arrangement instead of a single centred suit symbol.
|
|
27
|
+
- Court cards J/Q/K each get a distinct multi-row motif (sword, jewelled headdress, crown).
|
|
28
|
+
- Aces get a decorative `╭─◆─╮` / `╰─◆─╯` wreath around the central suit.
|
|
29
|
+
- Compact 6×5 layout keeps the single inner row but still benefits from both-corner indices.
|
|
30
|
+
- All variants honour the active theme (`face_card_bg`, `card_face_bg`, `highlight_bg`, `red_fg`/`black_fg`) and the `DIM` prefix for illegal cards. ASCII fallback paths preserved.
|
|
31
|
+
- **`tests/test_extended.py::test_round_score_history_extra_fields`** — pins the new `RoundScore` fields end-to-end through `apply_round_score`.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **`src/belote/ui/menu.py`** — main-menu loop now handles `Key.THEME` (cycles forward through `THEMES`), so the new in-game `T` shortcut works at the menu too.
|
|
36
|
+
- **`src/belote/main.py`**, **`src/belote/ui/render.py`** — game-over hint and HUD compact hint updated to advertise the new `[H] History` / `[T] Theme` shortcuts.
|
|
37
|
+
- **`src/belote/ui/prompts.py::show_help`** — help-screen text rewritten to match the new bindings.
|
|
38
|
+
- **`README.md`** — Controls section rewritten; theme section now lists all six themes by name.
|
|
39
|
+
- **`tests/test_layout.py::test_hud_compact_omits_help_hints_and_theme`** — assertion updated for the new compact-HUD hint substring.
|
|
40
|
+
|
|
41
|
+
### Notes
|
|
42
|
+
|
|
43
|
+
436/436 tests pass. No gameplay, scoring, or AI-decision changes — all updates are UX (keys, slot framing, dwell, history depth, card glyphs).
|
|
44
|
+
|
|
45
|
+
## [2.9.2] - 2026-05-07
|
|
46
|
+
|
|
47
|
+
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.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- **`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.
|
|
52
|
+
- **`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.
|
|
53
|
+
- **`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`).
|
|
54
|
+
- **`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.
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- **`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.
|
|
59
|
+
|
|
60
|
+
### Notes
|
|
61
|
+
|
|
62
|
+
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.
|
|
63
|
+
|
|
64
|
+
## [2.9.1] - 2026-05-06
|
|
65
|
+
|
|
66
|
+
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.
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- **`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.
|
|
71
|
+
- **`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.
|
|
72
|
+
- **`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.
|
|
73
|
+
- **`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.
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- **`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.
|
|
78
|
+
|
|
79
|
+
### Notes
|
|
80
|
+
|
|
81
|
+
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.
|
|
82
|
+
|
|
8
83
|
## [2.9.0] - 2026-05-06
|
|
9
84
|
|
|
10
85
|
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`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: belote-cli
|
|
3
|
-
Version: 2.9.
|
|
3
|
+
Version: 2.9.5
|
|
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
|
.-' \ / '-.
|
|
@@ -164,12 +170,12 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
164
170
|
## Controls
|
|
165
171
|
|
|
166
172
|
**General:**
|
|
167
|
-
-
|
|
173
|
+
- `?`: Show keyboard shortcut help
|
|
168
174
|
- `M`: Toggle sound effects on/off
|
|
169
|
-
- `I`: Toggle BelAtro score overlay (per-trick breakdown popup)
|
|
175
|
+
- `I` or `V`: Toggle BelAtro score overlay (per-trick breakdown popup)
|
|
170
176
|
- `Q`: Quit to main menu or exit
|
|
171
|
-
- `
|
|
172
|
-
- `T`:
|
|
177
|
+
- `H`: View Game History (round-by-round, with contract / taker / tricks / declarations)
|
|
178
|
+
- `T`: Cycle UI Theme
|
|
173
179
|
|
|
174
180
|
**Classic Belote:**
|
|
175
181
|
- `↑` `↓`: Navigate options
|
|
@@ -177,7 +183,6 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
177
183
|
- `Enter`: Select option / Enter submenu
|
|
178
184
|
|
|
179
185
|
**BelAtro (Roguelite):**
|
|
180
|
-
- `S`: View current Run State and Jokers
|
|
181
186
|
- `1`-`5`: Inspect specific Jokers in the Shop
|
|
182
187
|
- `U`: Use a consumable (Tarot/Planet) during gameplay
|
|
183
188
|
|
|
@@ -188,6 +193,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
188
193
|
- `O`: Sort hand by suit and rank
|
|
189
194
|
- `Z`: Undo last move
|
|
190
195
|
- `Space` or `Esc`: Skip animations
|
|
196
|
+
- During bidding round 2: `P` = Pass, `A` = Tout Atout, `S` = Sans Atout
|
|
191
197
|
|
|
192
198
|
## Features
|
|
193
199
|
|
|
@@ -198,7 +204,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
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.
|
|
200
206
|
- **Enhanced Hard AI**: Advanced void inference and 2-ply lookahead for critical tricks (Dix de Der).
|
|
201
|
-
- **Customizable Themes:** Switch between
|
|
207
|
+
- **Customizable Themes:** Switch between six color palettes (Classic Green, Dark Mode, Blue Velvet, Red Casino, Sepia Vintage, High Contrast) using the `T` key during gameplay.
|
|
202
208
|
- **Incremental Rendering:** High-performance cursor-based updates for zero-flicker gameplay even at high speeds.
|
|
203
209
|
- **Hand Sorting:** Strategic "play value" organization (honors grouped together) for better tactical awareness.
|
|
204
210
|
- **Main Menu:** Simple single-player entry point with configurable AI difficulty, Target Score, and Speed.
|
|
@@ -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
|
.-' \ / '-.
|
|
@@ -121,12 +127,12 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
121
127
|
## Controls
|
|
122
128
|
|
|
123
129
|
**General:**
|
|
124
|
-
-
|
|
130
|
+
- `?`: Show keyboard shortcut help
|
|
125
131
|
- `M`: Toggle sound effects on/off
|
|
126
|
-
- `I`: Toggle BelAtro score overlay (per-trick breakdown popup)
|
|
132
|
+
- `I` or `V`: Toggle BelAtro score overlay (per-trick breakdown popup)
|
|
127
133
|
- `Q`: Quit to main menu or exit
|
|
128
|
-
- `
|
|
129
|
-
- `T`:
|
|
134
|
+
- `H`: View Game History (round-by-round, with contract / taker / tricks / declarations)
|
|
135
|
+
- `T`: Cycle UI Theme
|
|
130
136
|
|
|
131
137
|
**Classic Belote:**
|
|
132
138
|
- `↑` `↓`: Navigate options
|
|
@@ -134,7 +140,6 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
134
140
|
- `Enter`: Select option / Enter submenu
|
|
135
141
|
|
|
136
142
|
**BelAtro (Roguelite):**
|
|
137
|
-
- `S`: View current Run State and Jokers
|
|
138
143
|
- `1`-`5`: Inspect specific Jokers in the Shop
|
|
139
144
|
- `U`: Use a consumable (Tarot/Planet) during gameplay
|
|
140
145
|
|
|
@@ -145,6 +150,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
145
150
|
- `O`: Sort hand by suit and rank
|
|
146
151
|
- `Z`: Undo last move
|
|
147
152
|
- `Space` or `Esc`: Skip animations
|
|
153
|
+
- During bidding round 2: `P` = Pass, `A` = Tout Atout, `S` = Sans Atout
|
|
148
154
|
|
|
149
155
|
## Features
|
|
150
156
|
|
|
@@ -155,7 +161,7 @@ belote --difficulty hard --target 500 --seed 123 --speed fast
|
|
|
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.
|
|
157
163
|
- **Enhanced Hard AI**: Advanced void inference and 2-ply lookahead for critical tricks (Dix de Der).
|
|
158
|
-
- **Customizable Themes:** Switch between
|
|
164
|
+
- **Customizable Themes:** Switch between six color palettes (Classic Green, Dark Mode, Blue Velvet, Red Casino, Sepia Vintage, High Contrast) using the `T` key during gameplay.
|
|
159
165
|
- **Incremental Rendering:** High-performance cursor-based updates for zero-flicker gameplay even at high speeds.
|
|
160
166
|
- **Hand Sorting:** Strategic "play value" organization (honors grouped together) for better tactical awareness.
|
|
161
167
|
- **Main Menu:** Simple single-player entry point with configurable AI difficulty, Target Score, and Speed.
|
|
@@ -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).
|
|
@@ -149,6 +149,14 @@ class RoundScore:
|
|
|
149
149
|
is_capot: bool
|
|
150
150
|
is_litige: bool = False
|
|
151
151
|
litige_points: int = 0
|
|
152
|
+
contract: str | None = None
|
|
153
|
+
trump: Suit | None = None
|
|
154
|
+
taker_seat: Seat | None = None
|
|
155
|
+
tricks_ns: int = 0
|
|
156
|
+
tricks_ew: int = 0
|
|
157
|
+
last_trick_winner: Seat | None = None
|
|
158
|
+
decl_summary_ns: tuple[str, ...] = ()
|
|
159
|
+
decl_summary_ew: tuple[str, ...] = ()
|
|
152
160
|
|
|
153
161
|
|
|
154
162
|
@dataclass(frozen=True, slots=True)
|
|
@@ -50,6 +50,12 @@ from .ui import (
|
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
# Minimum time the four cards stay on the mat before a trick clears. This
|
|
54
|
+
# applies even when the user has skipped earlier animations (so a fast-paced
|
|
55
|
+
# session still lets the player read every completed trick).
|
|
56
|
+
MIN_TRICK_DWELL: float = 0.5
|
|
57
|
+
|
|
58
|
+
|
|
53
59
|
def create_ai_players(diffs_map: dict[Seat, str]) -> dict[Seat, AIPlayer]:
|
|
54
60
|
"""Create AI players for seats not occupied by humans."""
|
|
55
61
|
ai_seats = {Seat.EAST, Seat.NORTH, Seat.WEST}
|
|
@@ -177,6 +183,10 @@ def run_play(
|
|
|
177
183
|
# 3. If this completes a trick, pause longer and show announcements
|
|
178
184
|
if len(display_state.current_trick) == 4:
|
|
179
185
|
play_sound("trick")
|
|
186
|
+
# Non-skippable minimum dwell so all four cards are always visible
|
|
187
|
+
# before the trick clears, even when the user has skipped earlier
|
|
188
|
+
# animations or is on the "instant" speed preset.
|
|
189
|
+
interruptible_sleep(MIN_TRICK_DWELL, None)
|
|
180
190
|
if len(current.completed_tricks) == 7: # This was the 8th trick
|
|
181
191
|
se_trump = current.boss_modifiers.seven_eight_trump
|
|
182
192
|
is_sa = current.contract == "sans_atout"
|
|
@@ -143,14 +143,16 @@ class _UnixKeyReader:
|
|
|
143
143
|
ch = chr(byte)
|
|
144
144
|
if ch.lower() == "q":
|
|
145
145
|
return KeyEvent(Key.QUIT)
|
|
146
|
-
if ch
|
|
146
|
+
if ch == "?":
|
|
147
147
|
return KeyEvent(Key.HELP)
|
|
148
|
-
if ch.lower() == "
|
|
148
|
+
if ch.lower() == "h":
|
|
149
|
+
return KeyEvent(Key.HIST)
|
|
150
|
+
if ch.lower() == "t":
|
|
151
|
+
return KeyEvent(Key.THEME)
|
|
152
|
+
if ch.lower() == "o":
|
|
149
153
|
return KeyEvent(Key.SORT)
|
|
150
154
|
if ch.lower() == "m":
|
|
151
155
|
return KeyEvent(Key.MUTE)
|
|
152
|
-
if ch.lower() == "t":
|
|
153
|
-
return KeyEvent(Key.HIST)
|
|
154
156
|
if ch.lower() == "i" or ch.lower() == "v":
|
|
155
157
|
return KeyEvent(Key.OVERLAY)
|
|
156
158
|
|
|
@@ -262,14 +264,16 @@ if os.name == "nt":
|
|
|
262
264
|
return KeyEvent(Key.SPACE)
|
|
263
265
|
if ch.lower() == b"q":
|
|
264
266
|
return KeyEvent(Key.QUIT)
|
|
265
|
-
if ch
|
|
267
|
+
if ch == b"?":
|
|
266
268
|
return KeyEvent(Key.HELP)
|
|
267
|
-
if ch.lower() == b"
|
|
269
|
+
if ch.lower() == b"h":
|
|
270
|
+
return KeyEvent(Key.HIST)
|
|
271
|
+
if ch.lower() == b"t":
|
|
272
|
+
return KeyEvent(Key.THEME)
|
|
273
|
+
if ch.lower() == b"o":
|
|
268
274
|
return KeyEvent(Key.SORT)
|
|
269
275
|
if ch.lower() == b"m":
|
|
270
276
|
return KeyEvent(Key.MUTE)
|
|
271
|
-
if ch.lower() == b"t":
|
|
272
|
-
return KeyEvent(Key.HIST)
|
|
273
277
|
if ch.lower() in (b"i", b"v"):
|
|
274
278
|
return KeyEvent(Key.OVERLAY)
|
|
275
279
|
|
|
@@ -240,7 +240,7 @@ def main() -> None:
|
|
|
240
240
|
# Wait for Enter/R/Q/T
|
|
241
241
|
sys.stdout.write(f"\n {BOLD}{gold_fg()}GAME OVER{RESET}")
|
|
242
242
|
sys.stdout.write(
|
|
243
|
-
f"\n {white_fg()}[Enter/Q] Menu [R] Rematch [
|
|
243
|
+
f"\n {white_fg()}[Enter/Q] Menu [R] Rematch [H] History{RESET} "
|
|
244
244
|
)
|
|
245
245
|
sys.stdout.flush()
|
|
246
246
|
|
|
@@ -254,7 +254,7 @@ def main() -> None:
|
|
|
254
254
|
show_final_screen(state)
|
|
255
255
|
sys.stdout.write(f"\n {BOLD}{gold_fg()}GAME OVER{RESET}")
|
|
256
256
|
sys.stdout.write(
|
|
257
|
-
f"\n {white_fg()}[Enter/Q] Menu [R] Rematch [
|
|
257
|
+
f"\n {white_fg()}[Enter/Q] Menu [R] Rematch [H] History{RESET} "
|
|
258
258
|
)
|
|
259
259
|
sys.stdout.flush()
|
|
260
260
|
if ev.key in (Key.ENTER, Key.QUIT):
|
|
@@ -719,6 +719,20 @@ def score_round(state: GameState) -> ScoringBreakdown:
|
|
|
719
719
|
)
|
|
720
720
|
|
|
721
721
|
|
|
722
|
+
def _decl_short_label(d: Declaration) -> str:
|
|
723
|
+
if d.kind == "belote":
|
|
724
|
+
return "Belote"
|
|
725
|
+
if d.kind == "rebelote":
|
|
726
|
+
return "Rebelote"
|
|
727
|
+
if d.kind == "carre" and isinstance(d.detail, Carre):
|
|
728
|
+
rank = _VALUE_TO_RANK.get(d.detail.rank)
|
|
729
|
+
return f"Carré-{rank.value}" if rank else "Carré"
|
|
730
|
+
if d.kind == "sequence" and isinstance(d.detail, Sequence):
|
|
731
|
+
pts = _SEQUENCE_POINTS.get(d.detail.length, 0)
|
|
732
|
+
return f"{pts}{d.detail.suit.symbol}"
|
|
733
|
+
return d.kind
|
|
734
|
+
|
|
735
|
+
|
|
722
736
|
def apply_round_score(state: GameState, breakdown: ScoringBreakdown) -> GameState:
|
|
723
737
|
"""Apply round scoring result to team scores and advance state."""
|
|
724
738
|
ns, ew = state.team_scores
|
|
@@ -731,6 +745,46 @@ def apply_round_score(state: GameState, breakdown: ScoringBreakdown) -> GameStat
|
|
|
731
745
|
|
|
732
746
|
new_scores = (ns, ew)
|
|
733
747
|
|
|
748
|
+
# Trick counts per team across the played round.
|
|
749
|
+
se_trump = state.boss_modifiers.seven_eight_trump
|
|
750
|
+
is_sa = state.contract == "sans_atout"
|
|
751
|
+
tricks_ns = 0
|
|
752
|
+
tricks_ew = 0
|
|
753
|
+
for trick in state.completed_tricks:
|
|
754
|
+
winner = trick_winner_seat(trick, state.trump, se_trump, is_sa)
|
|
755
|
+
if winner is None:
|
|
756
|
+
continue
|
|
757
|
+
if team_of(winner) == 0:
|
|
758
|
+
tricks_ns += 1
|
|
759
|
+
else:
|
|
760
|
+
tricks_ew += 1
|
|
761
|
+
|
|
762
|
+
# Declaration summaries — only show the team(s) that actually scored decls,
|
|
763
|
+
# which matches Belote's "best team takes all decls" rule.
|
|
764
|
+
ns_decl_total = breakdown.taker_declarations if breakdown.taker_team == 0 else breakdown.defender_declarations
|
|
765
|
+
ew_decl_total = breakdown.defender_declarations if breakdown.taker_team == 0 else breakdown.taker_declarations
|
|
766
|
+
ns_decls = tuple(
|
|
767
|
+
_decl_short_label(d) for d in state.declarations if team_of(d.seat) == 0
|
|
768
|
+
) if ns_decl_total > 0 else ()
|
|
769
|
+
ew_decls = tuple(
|
|
770
|
+
_decl_short_label(d) for d in state.declarations if team_of(d.seat) == 1
|
|
771
|
+
) if ew_decl_total > 0 else ()
|
|
772
|
+
|
|
773
|
+
common_kwargs = dict(
|
|
774
|
+
is_failed=breakdown.is_failed,
|
|
775
|
+
is_capot=breakdown.is_capot,
|
|
776
|
+
is_litige=breakdown.is_litige,
|
|
777
|
+
litige_points=breakdown.litige_points_awarded,
|
|
778
|
+
contract=state.contract,
|
|
779
|
+
trump=state.trump,
|
|
780
|
+
taker_seat=state.taker,
|
|
781
|
+
tricks_ns=tricks_ns,
|
|
782
|
+
tricks_ew=tricks_ew,
|
|
783
|
+
last_trick_winner=state.last_trick_winner,
|
|
784
|
+
decl_summary_ns=ns_decls,
|
|
785
|
+
decl_summary_ew=ew_decls,
|
|
786
|
+
)
|
|
787
|
+
|
|
734
788
|
# Create RoundScore for history
|
|
735
789
|
if breakdown.taker_team == 0:
|
|
736
790
|
round_score = RoundScore(
|
|
@@ -745,10 +799,7 @@ def apply_round_score(state: GameState, breakdown: ScoringBreakdown) -> GameStat
|
|
|
745
799
|
ew_rebelote=breakdown.defender_rebelote,
|
|
746
800
|
ns_total=breakdown.taker_total,
|
|
747
801
|
ew_total=breakdown.defender_total,
|
|
748
|
-
|
|
749
|
-
is_capot=breakdown.is_capot,
|
|
750
|
-
is_litige=breakdown.is_litige,
|
|
751
|
-
litige_points=breakdown.litige_points_awarded,
|
|
802
|
+
**common_kwargs,
|
|
752
803
|
)
|
|
753
804
|
else:
|
|
754
805
|
round_score = RoundScore(
|
|
@@ -763,10 +814,7 @@ def apply_round_score(state: GameState, breakdown: ScoringBreakdown) -> GameStat
|
|
|
763
814
|
ew_rebelote=breakdown.taker_rebelote,
|
|
764
815
|
ns_total=breakdown.defender_total,
|
|
765
816
|
ew_total=breakdown.taker_total,
|
|
766
|
-
|
|
767
|
-
is_capot=breakdown.is_capot,
|
|
768
|
-
is_litige=breakdown.is_litige,
|
|
769
|
-
litige_points=breakdown.litige_points_awarded,
|
|
817
|
+
**common_kwargs,
|
|
770
818
|
)
|
|
771
819
|
|
|
772
820
|
new_history = state.score_history + (round_score,)
|
|
@@ -12,9 +12,11 @@ from ..ansi import (
|
|
|
12
12
|
ansi_center,
|
|
13
13
|
banner_bg,
|
|
14
14
|
banner_fg,
|
|
15
|
+
clear_line,
|
|
15
16
|
clear_screen,
|
|
16
17
|
gold_fg,
|
|
17
18
|
hide_cursor,
|
|
19
|
+
move,
|
|
18
20
|
white_fg,
|
|
19
21
|
)
|
|
20
22
|
from ..context import AUDIO
|
|
@@ -33,8 +35,19 @@ def toggle_mute() -> bool:
|
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
def announce(message: str, duration: float = 2.0, reader: KeyReader | None = None) -> None:
|
|
36
|
-
"""Display a transient announcement banner.
|
|
37
|
-
|
|
38
|
+
"""Display a transient announcement banner.
|
|
39
|
+
|
|
40
|
+
Painted with absolute cursor positioning + clear_line so it never triggers
|
|
41
|
+
a scroll, even when the cursor is parked on the bottom row of the alt
|
|
42
|
+
screen. Writing \\r\\n at the bottom row scrolls the alt-screen on Konsole
|
|
43
|
+
and other strict emulators, which leaks the previous frame onto rows the
|
|
44
|
+
next render's blank padding doesn't repaint.
|
|
45
|
+
"""
|
|
46
|
+
term_w, term_h = get_term_size()
|
|
47
|
+
banner = ansi_center(
|
|
48
|
+
f"{banner_bg()}{banner_fg()} {BOLD} {message} {RESET}", term_w
|
|
49
|
+
)
|
|
50
|
+
sys.stdout.write(move(max(1, term_h - 1), 1) + clear_line() + banner)
|
|
38
51
|
sys.stdout.flush()
|
|
39
52
|
if reader and duration > 0:
|
|
40
53
|
interruptible_sleep(duration, reader)
|