1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
use super::traits::{Game, GamePhase, GameState};
/// 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;
impl SlotsGame {
pub fn new() -> Self {
Self
}
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<char> = reels.chars().collect();
std::array::from_fn(|row| {
(0..REELS)
.map(|col| format!("[ {} ]", Self::cell(&chars, row, col)))
.collect::<Vec<_>>()
.join(" ")
})
}
fn render_status(&self, state: &GameState) -> String {
if let Some(error) = state.data.get("error") {
return format!("ERROR: {error}");
}
if state.phase == GamePhase::Playing {
return "Spinning...".to_string();
}
if state.phase == GamePhase::Finished {
let winnings = state
.data
.get("winnings")
.map(String::as_str)
.unwrap_or("0");
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 {
fn get_name(&self) -> &'static str {
"Slots"
}
fn render(&self, state: &GameState) -> String {
let reels = state.data.get("reels").cloned().unwrap_or_default();
let balance = state
.data
.get("balance")
.map(String::as_str)
.unwrap_or("?");
let grid = self.render_grid(&reels);
format!(
"=== 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,
grid[0],
grid[1],
grid[2],
self.render_status(state),
self.render_win_lines(state)
)
}
fn render_lines(&self, _state: &GameState) -> usize {
SLOT_BOARD_LINES
}
fn get_help(&self) -> String {
"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\
\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 total bet\n\
Q - Exit the game"
.to_string()
}
}
|