From 9951e2d5afbcabb4bd8016b6d160653c1ec5ed8e Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 28 Oct 2026 23:45:43 +0100 Subject: v1.3.0: simplify payouts to bet x symbol x shape --- src/client/games/slots.rs | 33 ++++++++++++++++++++--------- src/server/games/slots.rs | 32 ++++++++++++++++------------ src/slots.rs | 53 ++++++++++++++++++++++++++--------------------- 3 files changed, 71 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/client/games/slots.rs b/src/client/games/slots.rs index a417e7d..ad9ef5c 100644 --- a/src/client/games/slots.rs +++ b/src/client/games/slots.rs @@ -1,5 +1,5 @@ use super::traits::{Game, GamePhase, GameState}; -use crate::slots::{paytable_lines, REELS, ROWS}; +use crate::slots::{paytable_lines, LINE_NAMES, REELS, ROWS}; /// Height of the game board. It must never depend on the spin result. pub const SLOT_BOARD_LINES: usize = 10; @@ -81,7 +81,21 @@ impl SlotsGame { } match state.data.get("win_lines").map(String::as_str) { Some(lines) if !lines.is_empty() && lines != "-" => { - format!("Winning lines: {lines}") + let named: Vec = lines + .split(',') + .filter_map(|raw| raw.trim().parse::().ok()) + .map(|line| { + let name = line + .checked_sub(1) + .and_then(|index| LINE_NAMES.get(index)) + .copied(); + match name { + Some(name) => format!("{line} ({name})"), + None => line.to_string(), + } + }) + .collect(); + format!("Winning lines: {}", named.join(", ")) } _ => String::new(), } @@ -126,15 +140,14 @@ impl Game for SlotsGame { "SLOTS GAME\n\ =========\n\ 5 reels x 3 rows, 5 paylines.\n\ - Match 3, 4 or 5 symbols from the left on a payline.\n\ + A win needs 3, 4 or 5 matching symbols from reel 1 on a payline.\n\ \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\ + Payout = total bet x symbol x shape\n\ + Symbols: C=1 L=2 O=3 G=4 D=6 S=10\n\ + Shapes: 3 of a kind=1, 4 of a kind=3, 5 of a kind=14\n\ + \n\ + Lines: 1 top, 2 middle, 3 bottom, 4 V, 5 inverted V\n\ + Multiple winning lines are added together.\n\ \n\ Symbols: C=Cherry, L=Lemon, O=Orange, G=Grapes, D=Diamond, S=Seven\n\ \n\ diff --git a/src/server/games/slots.rs b/src/server/games/slots.rs index e9f58b1..298cd4c 100644 --- a/src/server/games/slots.rs +++ b/src/server/games/slots.rs @@ -1,5 +1,5 @@ use super::traits::{Game, GameError, GamePhase, GameResult, GameState}; -use crate::slots::PAYTABLE; +use crate::slots::{SHAPE_MULTIPLIERS, SYMBOL_MULTIPLIERS}; use rand::Rng; use std::collections::HashMap; @@ -27,10 +27,18 @@ pub struct SlotsGame { impl SlotsGame { pub fn new() -> Self { - let payouts = PAYTABLE + // payout = bet * symbol multiplier * shape multiplier + let payouts = SYMBOL_MULTIPLIERS .iter() - .map(|&(symbol, three, four, five)| { - (symbol, Payout { three, four, five }) + .map(|&(symbol, symbol_multiplier)| { + ( + symbol, + Payout { + three: symbol_multiplier * SHAPE_MULTIPLIERS[0].1, + four: symbol_multiplier * SHAPE_MULTIPLIERS[1].1, + five: symbol_multiplier * SHAPE_MULTIPLIERS[2].1, + }, + ) }) .collect(); @@ -116,17 +124,15 @@ impl SlotsGame { let p4 = 5.0 / 7776.0; let p5 = 1.0 / 7776.0; - let per_line: f64 = self - .payouts - .values() - .map(|payout| { - p3 * payout.three as f64 - + p4 * payout.four as f64 - + p5 * payout.five as f64 - }) + let symbol_sum: f64 = SYMBOL_MULTIPLIERS + .iter() + .map(|(_, multiplier)| *multiplier as f64) .sum(); + let shape_ev = p3 * SHAPE_MULTIPLIERS[0].1 as f64 + + p4 * SHAPE_MULTIPLIERS[1].1 as f64 + + p5 * SHAPE_MULTIPLIERS[2].1 as f64; - per_line * PAYLINES.len() as f64 + symbol_sum * shape_ev * PAYLINES.len() as f64 } } diff --git a/src/slots.rs b/src/slots.rs index a3b4ef4..9e41f05 100644 --- a/src/slots.rs +++ b/src/slots.rs @@ -13,33 +13,38 @@ pub const PAYLINES: [[usize; REELS]; 5] = [ [2, 1, 0, 1, 2], // line 5: inverted V ]; -/// (symbol, 3-of-a-kind, 4-of-a-kind, 5-of-a-kind) multipliers of total bet. -pub const PAYTABLE: [(char, u64, u64, u64); 6] = [ - ('C', 2, 5, 15), - ('L', 3, 7, 20), - ('O', 3, 8, 25), - ('G', 4, 12, 40), - ('D', 6, 18, 70), - ('S', 9, 30, 150), +pub const LINE_NAMES: [&str; 5] = ["top", "middle", "bottom", "V", "inverted V"]; + +/// Symbol value multipliers. +pub const SYMBOL_MULTIPLIERS: [(char, u64); 6] = [ + ('C', 1), + ('L', 2), + ('O', 3), + ('G', 4), + ('D', 6), + ('S', 10), ]; -/// Paytable rendered under the reels, split so it fits a narrow terminal. +/// (match length, shape multiplier): 3, 4 or 5 of a kind. +pub const SHAPE_MULTIPLIERS: [(usize, u64); 3] = [(3, 1), (4, 3), (5, 14)]; + +/// Paytable rendered under the reels. +/// +/// `payout = bet * symbol_multiplier * shape_multiplier`. pub fn paytable_lines() -> Vec { - let entries: Vec = PAYTABLE + let symbols = SYMBOL_MULTIPLIERS + .iter() + .map(|(symbol, multiplier)| format!("{symbol}{multiplier}")) + .collect::>() + .join(" "); + let shapes = SHAPE_MULTIPLIERS .iter() - .map(|(symbol, three, four, five)| format!("{symbol} {three}/{four}/{five}")) - .collect(); + .map(|(length, multiplier)| format!("{length}:{multiplier}")) + .collect::>() + .join(" "); - entries - .chunks(3) - .enumerate() - .map(|(index, chunk)| { - let prefix = if index == 0 { - "Pays 3/4/5 x bet: " - } else { - "" - }; - format!("{}{}", prefix, chunk.join(" ")) - }) - .collect() + vec![ + "Pays bet x symbol x shape".to_string(), + format!("Symbol {symbols} Shape {shapes}"), + ] } -- cgit v1.2.3