summaryrefslogtreecommitdiffstats
path: root/src/client/games/slots.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/client/games/slots.rs125
1 files changed, 56 insertions, 69 deletions
diff --git a/src/client/games/slots.rs b/src/client/games/slots.rs
index 5bffc4c..3aad319 100644
--- a/src/client/games/slots.rs
+++ b/src/client/games/slots.rs
@@ -1,4 +1,5 @@
1use super::traits::{Game, GamePhase, GameState}; 1use super::traits::{Game, GameState};
2use crate::client::view::{SlotsPhase, SlotsView};
2use crate::slots::{LINE_NAMES, REELS, ROWS, paytable_lines}; 3use crate::slots::{LINE_NAMES, REELS, ROWS, paytable_lines};
3 4
4/// Height of the game board. It must never depend on the spin result. 5/// Height of the game board. It must never depend on the spin result.
@@ -28,105 +29,91 @@ impl SlotsGame {
28 } 29 }
29 } 30 }
30 31
31 fn highlighted_cells(state: &GameState) -> Vec<usize> { 32 fn render_grid(reels: &[char], winning: &[usize]) -> [String; ROWS] {
32 state
33 .data
34 .get("win_cells")
35 .map(|raw| {
36 raw.split(',')
37 .filter_map(|value| value.trim().parse::<usize>().ok())
38 .collect()
39 })
40 .unwrap_or_default()
41 }
42
43 fn render_grid(&self, reels: &str, winning: &[usize]) -> [String; ROWS] {
44 let chars: Vec<char> = reels.chars().collect();
45 std::array::from_fn(|row| { 33 std::array::from_fn(|row| {
46 (0..REELS) 34 (0..REELS)
47 .map(|col| Self::cell(&chars, row, col, winning)) 35 .map(|col| Self::cell(reels, row, col, winning))
48 .collect::<Vec<_>>() 36 .collect::<Vec<_>>()
49 .join(" ") 37 .join(" ")
50 }) 38 })
51 } 39 }
52 40
53 fn render_status(&self, state: &GameState) -> String { 41 fn render_status(view: &SlotsView) -> String {
54 if let Some(error) = state.data.get("error") { 42 if let Some(error) = &view.error {
55 return format!("ERROR: {error}"); 43 return format!("ERROR: {error}");
56 } 44 }
57 if state.phase == GamePhase::Playing { 45
58 return "Spinning...".to_string(); 46 match view.phase {
59 } 47 SlotsPhase::Spinning => "Spinning...".to_string(),
60 if state.phase == GamePhase::Finished { 48 SlotsPhase::Result => {
61 let winnings = state 49 let winnings = view.winnings.unwrap_or(0);
62 .data 50 if winnings == 0 {
63 .get("winnings") 51 return "No win.".to_string();
64 .map(String::as_str) 52 }
65 .unwrap_or("0"); 53 match view.win_type.as_str() {
66 if winnings != "0" { 54 "mega" => format!("MEGA WIN! {winnings} Kr"),
67 return match state.data.get("win_type").map(String::as_str) { 55 "big" => format!("BIG WIN! {winnings} Kr"),
68 Some("mega") => format!("MEGA WIN! {winnings} Kr"),
69 Some("big") => format!("BIG WIN! {winnings} Kr"),
70 _ => format!("Won {winnings} Kr"), 56 _ => format!("Won {winnings} Kr"),
71 }; 57 }
72 } 58 }
73 return "No win.".to_string(); 59 SlotsPhase::Ready => "Press [SPACE] to spin!".to_string(),
74 } 60 }
75 "Press [SPACE] to spin!".to_string()
76 } 61 }
77 62
78 fn render_win_lines(&self, state: &GameState) -> String { 63 fn render_win_lines(view: &SlotsView) -> String {
79 if state.phase != GamePhase::Finished { 64 if view.phase != SlotsPhase::Result || view.win_lines.is_empty() {
80 return String::new(); 65 return String::new();
81 } 66 }
82 match state.data.get("win_lines").map(String::as_str) {
83 Some(lines) if !lines.is_empty() && lines != "-" => {
84 let named: Vec<String> = lines
85 .split(',')
86 .filter_map(|raw| raw.trim().parse::<usize>().ok())
87 .map(|line| {
88 let name = line
89 .checked_sub(1)
90 .and_then(|index| LINE_NAMES.get(index))
91 .copied();
92 match name {
93 Some(name) => format!("{line} ({name})"),
94 None => line.to_string(),
95 }
96 })
97 .collect();
98 format!("Winning lines: {}", named.join(", "))
99 }
100 _ => String::new(),
101 }
102 }
103}
104 67
105impl Game for SlotsGame { 68 let named: Vec<String> = view
106 fn get_name(&self) -> &'static str { 69 .win_lines
107 "Slots" 70 .iter()
71 .map(|line| {
72 let name = line
73 .checked_sub(1)
74 .and_then(|index| LINE_NAMES.get(index))
75 .copied();
76 match name {
77 Some(name) => format!("{line} ({name})"),
78 None => line.to_string(),
79 }
80 })
81 .collect();
82 format!("Winning lines: {}", named.join(", "))
108 } 83 }
109 84
110 fn render(&self, state: &GameState) -> String { 85 /// Render a slots view to terminal lines. The GUI paints the same view.
111 let reels = state.data.get("reels").cloned().unwrap_or_default(); 86 pub fn render_view(view: &SlotsView) -> String {
112 let balance = state.data.get("balance").map(String::as_str).unwrap_or("?"); 87 let balance = view
113 let winning = Self::highlighted_cells(state); 88 .balance
114 let grid = self.render_grid(&reels, &winning); 89 .map(|balance| balance.to_string())
90 .unwrap_or_else(|| "?".to_string());
91 let grid = Self::render_grid(&view.reels, &view.win_cells);
115 let paytable = paytable_lines(); 92 let paytable = paytable_lines();
116 93
117 format!( 94 format!(
118 "=== SLOTS ===\r\nBet: {} Kr Balance: {} Kr\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n[+/-] Bet [SPACE] Spin [Q] Quit", 95 "=== SLOTS ===\r\nBet: {} Kr Balance: {} Kr\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n[+/-] Bet [SPACE] Spin [Q] Quit",
119 state.bet_amount, 96 view.bet,
120 balance, 97 balance,
121 grid[0], 98 grid[0],
122 grid[1], 99 grid[1],
123 grid[2], 100 grid[2],
124 paytable[0], 101 paytable[0],
125 paytable[1], 102 paytable[1],
126 self.render_status(state), 103 Self::render_status(view),
127 self.render_win_lines(state) 104 Self::render_win_lines(view)
128 ) 105 )
129 } 106 }
107}
108
109impl Game for SlotsGame {
110 fn get_name(&self) -> &'static str {
111 "Slots"
112 }
113
114 fn render(&self, state: &GameState) -> String {
115 Self::render_view(&SlotsView::from_state(state))
116 }
130 117
131 fn render_lines(&self, _state: &GameState) -> usize { 118 fn render_lines(&self, _state: &GameState) -> usize {
132 SLOT_BOARD_LINES 119 SLOT_BOARD_LINES