From 810e212b4b255914cfa73e85b52ec23db281c0e3 Mon Sep 17 00:00:00 2001 From: salmonik Date: Thu, 29 Oct 2026 23:06:22 +0100 Subject: v3.1.0: warn on shrink, but let a live round finish --- src/bin/client/repl.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/client/core.rs | 22 +++++++++++++++++++ src/client/ui.rs | 10 +++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs index 2cf121d..bf02c8d 100644 --- a/src/bin/client/repl.rs +++ b/src/bin/client/repl.rs @@ -17,7 +17,7 @@ use zagan_bet::client::core::{ClientCore, Effect, InteractiveCommand, Key}; use zagan_bet::client::database; use zagan_bet::client::ui::{ display_width, format_pong, host_lines, input_cursor_column, lines_to_move_up, - visual_line_count, + terminal_size_warning, visual_line_count, MIN_COLUMNS, MIN_ROWS, }; use zagan_bet::protocol::io_failure_message; use zagan_bet::roulette::{WHEEL, color as roulette_color}; @@ -36,6 +36,10 @@ pub struct ClientState { /// Text last drawn after the `> ` prompt, used to keep the command echo /// in the transcript before printing command output. prompt_text: String, + /// Last known terminal size, updated from resize events. + terminal_size: (u16, u16), + /// Size we already warned about, so a resize does not spam the transcript. + size_warned: Option<(u16, u16)>, } impl ClientState { @@ -60,6 +64,8 @@ impl ClientState { cleanup_done: Arc::new(Mutex::new(false)), game_lines: 0, prompt_text: String::new(), + terminal_size: crossterm::terminal::size().unwrap_or((MIN_COLUMNS, MIN_ROWS)), + size_warned: None, } } @@ -114,7 +120,15 @@ impl ClientState { } } } - Event::Resize(_, _) => self.apply_prompt(), + Event::Resize(columns, rows) => { + self.terminal_size = (columns, rows); + if self.core.is_in_game() { + let effects = self.core.refresh_board(); + self.apply_effects(effects); + } else { + self.apply_prompt(); + } + } _ => {} } } @@ -130,6 +144,10 @@ impl ClientState { } } + if !quit { + quit = self.warn_about_size(); + } + if quit { break; } @@ -292,7 +310,42 @@ impl ClientState { self.prompt_text.clear(); } + /// Warn once per size when the terminal is too small and no round is live. + /// Inside a game the warning is appended to the board instead, and during a + /// live round it is suppressed until the round settles. + fn warn_about_size(&mut self) -> bool { + let size = self.terminal_size; + let Some(message) = terminal_size_warning(size.0, size.1) else { + self.size_warned = None; + return false; + }; + if self.core.is_in_game() || self.size_warned == Some(size) { + return false; + } + self.size_warned = Some(size); + self.apply_effects(vec![Effect::Print(vec![message])]); + false + } + fn draw_region(&mut self, lines: &[String]) { + let warning = if self.core.is_live_game() { + None + } else { + terminal_size_warning(self.terminal_size.0, self.terminal_size.1) + }; + let owned; + let lines: &[String] = match warning { + Some(warning) => { + owned = { + let mut lines = lines.to_vec(); + lines.push(warning); + lines + }; + &owned + } + None => lines, + }; + if self.game_lines == 0 { self.commit_prompt_line(); } else { @@ -308,6 +361,8 @@ impl ClientState { .map(|line| { if line.starts_with("===") { line.as_str().bold().cyan().to_string() + } else if line.starts_with("WARNING:") { + line.as_str().bold().red().to_string() } else if roulette { if line.starts_with("Won ") { line.as_str().bold().green().to_string() diff --git a/src/client/core.rs b/src/client/core.rs index 8411d47..95fa050 100644 --- a/src/client/core.rs +++ b/src/client/core.rs @@ -138,6 +138,28 @@ impl ClientCore { self.game.is_in_game() } + /// True while a round is running and leaving would forfeit a reserved bet. + /// The terminal may shrink during one of these without interrupting it. + pub fn is_live_game(&self) -> bool { + match self + .game + .get_current_state() + .map(|state| state.game_type.as_str()) + { + Some("blackjack") => self + .game + .blackjack_state() + .is_some_and(|hand| hand.phase == "player"), + Some("crash") => self.crash_started.is_some(), + _ => false, + } + } + + /// Redraw the current board, for terminal resizes. + pub fn refresh_board(&self) -> Vec { + self.board_effects() + } + pub fn prompt_effect(&self) -> Option { if self.is_in_game() { return None; diff --git a/src/client/ui.rs b/src/client/ui.rs index 6161166..ca001c2 100644 --- a/src/client/ui.rs +++ b/src/client/ui.rs @@ -261,6 +261,16 @@ pub fn terminal_too_small(columns: u16, rows: u16) -> Option { )) } +/// One-line warning for a terminal that shrank while the client is running. +pub fn terminal_size_warning(columns: u16, rows: u16) -> Option { + if columns >= MIN_COLUMNS && rows >= MIN_ROWS { + return None; + } + Some(format!( + "WARNING: terminal is {columns}x{rows}; zagan.bet needs at least {MIN_COLUMNS}x{MIN_ROWS}. Resize to fit." + )) +} + /// Render remembered hosts. /// /// Two addresses that present the same certificate key are the same server, so -- cgit v1.2.3