From fc59ef77cda14d97d854dfcaa848307cfa512b69 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 28 Oct 2026 23:13:24 +0100 Subject: v1.0.0: replace slots with 5x3 reels and five paylines --- src/client/games/manager.rs | 11 ++++++ src/client/games/response.rs | 13 ++++++- src/client/games/slots.rs | 87 +++++++++++++++++++++++++++++++------------- 3 files changed, 85 insertions(+), 26 deletions(-) (limited to 'src/client/games') diff --git a/src/client/games/manager.rs b/src/client/games/manager.rs index 71cd28c..8436142 100644 --- a/src/client/games/manager.rs +++ b/src/client/games/manager.rs @@ -75,6 +75,7 @@ impl GameManager { session.data.remove("error"); session.data.remove("reels"); session.data.remove("win_type"); + session.data.remove("win_lines"); session.data.remove("winnings"); Ok(()) } @@ -91,6 +92,15 @@ impl GameManager { session .data .insert("win_type".to_string(), result.win_type); + session.data.insert( + "win_lines".to_string(), + result + .win_lines + .iter() + .map(usize::to_string) + .collect::>() + .join(","), + ); session .data .insert("winnings".to_string(), result.winnings.to_string()); @@ -107,6 +117,7 @@ impl GameManager { session.phase = GamePhase::WaitingToStart; session.data.remove("reels"); session.data.remove("win_type"); + session.data.remove("win_lines"); session.data.remove("winnings"); session.data.insert("error".to_string(), message.into()); } diff --git a/src/client/games/response.rs b/src/client/games/response.rs index 6948805..de22a75 100644 --- a/src/client/games/response.rs +++ b/src/client/games/response.rs @@ -6,6 +6,7 @@ pub struct SpinResult { pub balance: i64, pub reels: String, pub win_type: String, + pub win_lines: Vec, } /// Parse one server reply to `PLAY_START`. @@ -45,10 +46,19 @@ pub fn parse_spin_response(response: &str) -> Result { .parse::() .map_err(|_| format!("Invalid balance from server: {}", parts[4]))?; - if parts[5].chars().count() != 3 { + if parts[5].chars().count() != 3 && parts[5].chars().count() != 15 { return Err(format!("Invalid reels from server: {}", parts[5])); } + let win_lines = parts + .get(7) + .map(|raw| { + raw.split(',') + .filter_map(|value| value.trim().parse::().ok()) + .collect() + }) + .unwrap_or_default(); + Ok(SpinResult { session_id: parts[1].to_string(), winnings, @@ -56,5 +66,6 @@ pub fn parse_spin_response(response: &str) -> Result { balance, reels: parts[5].to_string(), win_type: parts[6].to_string(), + win_lines, }) } diff --git a/src/client/games/slots.rs b/src/client/games/slots.rs index 5eed34d..39c43b2 100644 --- a/src/client/games/slots.rs +++ b/src/client/games/slots.rs @@ -1,8 +1,10 @@ use super::traits::{Game, GamePhase, GameState}; -/// Height of the game board. It must never depend on the spin result, or an -/// in-place redraw would move to the wrong line. -pub const SLOT_BOARD_LINES: usize = 7; +/// Height of the game board. It must never depend on the spin result. +pub const SLOT_BOARD_LINES: usize = 9; +pub const LINES: usize = 5; +pub const REELS: usize = 5; +pub const ROWS: usize = 3; pub struct SlotsGame; @@ -11,17 +13,27 @@ impl SlotsGame { Self } - fn render_reels(&self, reels: &str) -> String { - let mut chars = reels.chars(); - format!( - "[ {} ] [ {} ] [ {} ]", - chars.next().unwrap_or('?'), - chars.next().unwrap_or('?'), - chars.next().unwrap_or('?') - ) + fn cell(chars: &[char], row: usize, col: usize) -> char { + if chars.len() == REELS * ROWS { + chars.get(col * ROWS + row).copied().unwrap_or('?') + } else if chars.len() == 3 && row == 0 { + // Legacy 3-symbol server: degrade to the first row. + chars.get(col).copied().unwrap_or('?') + } else { + '?' + } + } + + fn render_grid(&self, reels: &str) -> [String; ROWS] { + let chars: Vec = reels.chars().collect(); + std::array::from_fn(|row| { + (0..REELS) + .map(|col| format!("[ {} ]", Self::cell(&chars, row, col))) + .collect::>() + .join(" ") + }) } - /// The fifth board line: current status, result, or error. fn render_status(&self, state: &GameState) -> String { if let Some(error) = state.data.get("error") { return format!("ERROR: {error}"); @@ -35,14 +47,29 @@ impl SlotsGame { .get("winnings") .map(String::as_str) .unwrap_or("0"); - return match state.data.get("win_type").map(String::as_str) { - Some("jackpot") => format!("JACKPOT! Winnings: {winnings} PLN"), - Some("two") => format!("Two of a kind! Winnings: {winnings} PLN"), - _ => "No win.".to_string(), - }; + if winnings != "0" { + return match state.data.get("win_type").map(String::as_str) { + Some("mega") => format!("MEGA WIN! {winnings} PLN"), + Some("big") => format!("BIG WIN! {winnings} PLN"), + _ => format!("Won {winnings} PLN"), + }; + } + return "No win.".to_string(); } "Press [SPACE] to spin!".to_string() } + + fn render_win_lines(&self, state: &GameState) -> String { + if state.phase != GamePhase::Finished { + return String::new(); + } + match state.data.get("win_lines").map(String::as_str) { + Some(lines) if !lines.is_empty() && lines != "-" => { + format!("Winning lines: {lines}") + } + _ => String::new(), + } + } } impl Game for SlotsGame { @@ -57,13 +84,18 @@ impl Game for SlotsGame { .get("balance") .map(String::as_str) .unwrap_or("?"); + let grid = self.render_grid(&reels); format!( - "=== SLOTS ===\r\nBet: {} PLN Balance: {} PLN\r\n{}\r\n\r\n{}\r\n\r\n[+/-] Adjust Bet [Q] Quit", + "=== SLOTS ===\r\nBet: {} PLN Lines: {} Balance: {} PLN\r\n{}\r\n{}\r\n{}\r\n\r\n{}\r\n{}\r\n[+/-] Bet [SPACE] Spin [Q] Quit", state.bet_amount, + LINES, balance, - self.render_reels(&reels), - self.render_status(state) + grid[0], + grid[1], + grid[2], + self.render_status(state), + self.render_win_lines(state) ) } @@ -74,17 +106,22 @@ impl Game for SlotsGame { fn get_help(&self) -> String { "SLOTS GAME\n\ =========\n\ - Match symbols to win!\n\ + 5 reels x 3 rows, 5 paylines.\n\ + Match 3, 4 or 5 symbols from the left on a payline.\n\ \n\ - Payouts:\n\ - Three of a kind: 2x-28x bet\n\ - Two of a kind: 1x-3x bet\n\ + Payouts (x total bet, 3/4/5 of a kind):\n\ + C: 2 / 5 / 15\n\ + L: 3 / 7 / 20\n\ + O: 3 / 8 / 25\n\ + G: 4 / 12 / 40\n\ + D: 6 / 18 / 70\n\ + S: 9 / 30 / 150\n\ \n\ Symbols: C=Cherry, L=Lemon, O=Orange, G=Grapes, D=Diamond, S=Seven\n\ \n\ Controls:\n\ SPACE - Spin the reels\n\ - +/- - Adjust bet amount\n\ + +/- - Adjust total bet\n\ Q - Exit the game" .to_string() } -- cgit v1.2.3