diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/client/repl.rs | 8 | ||||
| -rw-r--r-- | src/client/games/slots.rs | 10 | ||||
| -rw-r--r-- | src/client/ui.rs | 17 | ||||
| -rw-r--r-- | src/slots.rs | 18 |
4 files changed, 44 insertions, 9 deletions
diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs index 58cb541..1ed3073 100644 --- a/src/bin/client/repl.rs +++ b/src/bin/client/repl.rs | |||
| @@ -17,6 +17,7 @@ use zagan_bet::client::core::{ClientCore, Effect, InteractiveCommand, Key}; | |||
| 17 | use zagan_bet::client::database; | 17 | use zagan_bet::client::database; |
| 18 | use zagan_bet::client::ui::{ | 18 | use zagan_bet::client::ui::{ |
| 19 | display_width, format_pong, host_lines, input_cursor_column, lines_to_move_up, | 19 | display_width, format_pong, host_lines, input_cursor_column, lines_to_move_up, |
| 20 | visual_line_count, | ||
| 20 | }; | 21 | }; |
| 21 | use zagan_bet::protocol::io_failure_message; | 22 | use zagan_bet::protocol::io_failure_message; |
| 22 | 23 | ||
| @@ -29,7 +30,7 @@ pub struct ClientState { | |||
| 29 | pub default_port: u16, | 30 | pub default_port: u16, |
| 30 | core: ClientCore, | 31 | core: ClientCore, |
| 31 | cleanup_done: Arc<Mutex<bool>>, | 32 | cleanup_done: Arc<Mutex<bool>>, |
| 32 | /// Number of terminal lines currently occupied by the game board. | 33 | /// Number of visual terminal lines currently occupied by the game board. |
| 33 | game_lines: usize, | 34 | game_lines: usize, |
| 34 | /// Text last drawn after the `> ` prompt, used to keep the command echo | 35 | /// Text last drawn after the `> ` prompt, used to keep the command echo |
| 35 | /// in the transcript before printing command output. | 36 | /// in the transcript before printing command output. |
| @@ -255,7 +256,10 @@ impl ClientState { | |||
| 255 | .collect(); | 256 | .collect(); |
| 256 | print!("{}", rendered.join("\r\n")); | 257 | print!("{}", rendered.join("\r\n")); |
| 257 | stdout().flush().unwrap(); | 258 | stdout().flush().unwrap(); |
| 258 | self.game_lines = lines.len().max(1); | 259 | let width = crossterm::terminal::size() |
| 260 | .map(|(columns, _)| columns as usize) | ||
| 261 | .unwrap_or(80); | ||
| 262 | self.game_lines = visual_line_count(lines, width).max(1); | ||
| 259 | } | 263 | } |
| 260 | 264 | ||
| 261 | fn clear_region(&mut self) { | 265 | fn clear_region(&mut self) { |
diff --git a/src/client/games/slots.rs b/src/client/games/slots.rs index 7e59919..a417e7d 100644 --- a/src/client/games/slots.rs +++ b/src/client/games/slots.rs | |||
| @@ -1,8 +1,8 @@ | |||
| 1 | use super::traits::{Game, GamePhase, GameState}; | 1 | use super::traits::{Game, GamePhase, GameState}; |
| 2 | use crate::slots::{paytable_line, REELS, ROWS}; | 2 | use crate::slots::{paytable_lines, REELS, ROWS}; |
| 3 | 3 | ||
| 4 | /// Height of the game board. It must never depend on the spin result. | 4 | /// Height of the game board. It must never depend on the spin result. |
| 5 | pub const SLOT_BOARD_LINES: usize = 9; | 5 | pub const SLOT_BOARD_LINES: usize = 10; |
| 6 | 6 | ||
| 7 | pub struct SlotsGame; | 7 | pub struct SlotsGame; |
| 8 | 8 | ||
| @@ -102,15 +102,17 @@ impl Game for SlotsGame { | |||
| 102 | .unwrap_or("?"); | 102 | .unwrap_or("?"); |
| 103 | let winning = Self::highlighted_cells(state); | 103 | let winning = Self::highlighted_cells(state); |
| 104 | let grid = self.render_grid(&reels, &winning); | 104 | let grid = self.render_grid(&reels, &winning); |
| 105 | let paytable = paytable_lines(); | ||
| 105 | 106 | ||
| 106 | format!( | 107 | format!( |
| 107 | "=== SLOTS ===\r\nBet: {} PLN Balance: {} PLN\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n[+/-] Bet [SPACE] Spin [Q] Quit", | 108 | "=== SLOTS ===\r\nBet: {} PLN Balance: {} PLN\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n[+/-] Bet [SPACE] Spin [Q] Quit", |
| 108 | state.bet_amount, | 109 | state.bet_amount, |
| 109 | balance, | 110 | balance, |
| 110 | grid[0], | 111 | grid[0], |
| 111 | grid[1], | 112 | grid[1], |
| 112 | grid[2], | 113 | grid[2], |
| 113 | paytable_line(), | 114 | paytable[0], |
| 115 | paytable[1], | ||
| 114 | self.render_status(state), | 116 | self.render_status(state), |
| 115 | self.render_win_lines(state) | 117 | self.render_win_lines(state) |
| 116 | ) | 118 | ) |
diff --git a/src/client/ui.rs b/src/client/ui.rs index 493f660..11cedc7 100644 --- a/src/client/ui.rs +++ b/src/client/ui.rs | |||
| @@ -257,6 +257,23 @@ pub fn input_cursor_column(prompt_len: usize, input_len: usize) -> u16 { | |||
| 257 | .min(u16::MAX as usize) as u16 | 257 | .min(u16::MAX as usize) as u16 |
| 258 | } | 258 | } |
| 259 | 259 | ||
| 260 | /// Number of terminal rows a block of logical lines occupies after wrapping. | ||
| 261 | /// Empty lines still occupy one row. | ||
| 262 | pub fn visual_line_count(lines: &[String], width: usize) -> usize { | ||
| 263 | let width = width.max(1); | ||
| 264 | lines | ||
| 265 | .iter() | ||
| 266 | .map(|line| { | ||
| 267 | let columns = display_width(line); | ||
| 268 | if columns == 0 { | ||
| 269 | 1 | ||
| 270 | } else { | ||
| 271 | columns.div_ceil(width) | ||
| 272 | } | ||
| 273 | }) | ||
| 274 | .sum() | ||
| 275 | } | ||
| 276 | |||
| 260 | /// Format a ping round-trip time with one decimal place. | 277 | /// Format a ping round-trip time with one decimal place. |
| 261 | pub fn format_pong(elapsed: std::time::Duration) -> String { | 278 | pub fn format_pong(elapsed: std::time::Duration) -> String { |
| 262 | format!("Pong ({:.1}ms)", elapsed.as_secs_f64() * 1000.0) | 279 | format!("Pong ({:.1}ms)", elapsed.as_secs_f64() * 1000.0) |
diff --git a/src/slots.rs b/src/slots.rs index 561c449..a3b4ef4 100644 --- a/src/slots.rs +++ b/src/slots.rs | |||
| @@ -23,11 +23,23 @@ pub const PAYTABLE: [(char, u64, u64, u64); 6] = [ | |||
| 23 | ('S', 9, 30, 150), | 23 | ('S', 9, 30, 150), |
| 24 | ]; | 24 | ]; |
| 25 | 25 | ||
| 26 | /// One-line paytable rendered under the reels. | 26 | /// Paytable rendered under the reels, split so it fits a narrow terminal. |
| 27 | pub fn paytable_line() -> String { | 27 | pub fn paytable_lines() -> Vec<String> { |
| 28 | let entries: Vec<String> = PAYTABLE | 28 | let entries: Vec<String> = PAYTABLE |
| 29 | .iter() | 29 | .iter() |
| 30 | .map(|(symbol, three, four, five)| format!("{symbol} {three}/{four}/{five}")) | 30 | .map(|(symbol, three, four, five)| format!("{symbol} {three}/{four}/{five}")) |
| 31 | .collect(); | 31 | .collect(); |
| 32 | format!("Pays 3/4/5 x bet: {}", entries.join(" ")) | 32 | |
| 33 | entries | ||
| 34 | .chunks(3) | ||
| 35 | .enumerate() | ||
| 36 | .map(|(index, chunk)| { | ||
| 37 | let prefix = if index == 0 { | ||
| 38 | "Pays 3/4/5 x bet: " | ||
| 39 | } else { | ||
| 40 | "" | ||
| 41 | }; | ||
| 42 | format!("{}{}", prefix, chunk.join(" ")) | ||
| 43 | }) | ||
| 44 | .collect() | ||
| 33 | } | 45 | } |
