summaryrefslogtreecommitdiffstats
path: root/src/client/games/roulette.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/games/roulette.rs')
-rw-r--r--src/client/games/roulette.rs141
1 files changed, 103 insertions, 38 deletions
diff --git a/src/client/games/roulette.rs b/src/client/games/roulette.rs
index 96f8791..b3a8d9c 100644
--- a/src/client/games/roulette.rs
+++ b/src/client/games/roulette.rs
@@ -30,9 +30,16 @@ pub enum Direction {
30 30
31/// Where the betting cursor is. 31/// Where the betting cursor is.
32/// 32///
33/// Over the numbers the cursor sits on a *lattice point*, the way a chip sits 33/// Over the numbers the cursor walks a grid twice as fine as the felt itself:
34/// between numbers on a real felt: an inner point covers four numbers (corner), 34/// odd coordinates are the centre of a number cell and even coordinates are the
35/// an edge point covers two (split), and an outer corner is a straight number. 35/// line between cells, exactly where a chip sits on a real felt.
36///
37/// ```text
38/// odd,odd the number straight
39/// even,odd line between columns split of the two numbers
40/// odd,even line between rows split of the two numbers
41/// even,even corner of four cells corner
42/// ```
36#[derive(Debug, Clone, Copy, PartialEq, Eq)] 43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum Cursor { 44pub enum Cursor {
38 Zero, 45 Zero,
@@ -42,10 +49,14 @@ pub enum Cursor {
42 Outside(usize), 49 Outside(usize),
43} 50}
44 51
52/// Fine-grid bounds: 12 columns and 3 rows of cells, plus the lines between.
53const FELT_X_MAX: usize = 2 * NUM_COLUMNS - 1;
54const FELT_Y_MAX: usize = 2 * 3 - 1;
55
45impl Default for Cursor { 56impl Default for Cursor {
46 fn default() -> Self { 57 fn default() -> Self {
47 // Bottom-left number (1). 58 // Centre of the bottom-left number (1).
48 Cursor::Lattice { x: 0, y: 3 } 59 Cursor::Lattice { x: 1, y: 5 }
49 } 60 }
50} 61}
51 62
@@ -55,7 +66,9 @@ impl Cursor {
55 match parts.as_slice() { 66 match parts.as_slice() {
56 ["zero"] => Cursor::Zero, 67 ["zero"] => Cursor::Zero,
57 ["lattice", x, y] => match (x.parse::<usize>(), y.parse::<usize>()) { 68 ["lattice", x, y] => match (x.parse::<usize>(), y.parse::<usize>()) {
58 (Ok(x), Ok(y)) if x <= NUM_COLUMNS && y <= 3 => Cursor::Lattice { x, y }, 69 (Ok(x), Ok(y)) if (1..=FELT_X_MAX).contains(&x) && (1..=FELT_Y_MAX).contains(&y) => {
70 Cursor::Lattice { x, y }
71 }
59 _ => Cursor::default(), 72 _ => Cursor::default(),
60 }, 73 },
61 ["dozen", index] => parse_index(index).map(Cursor::Dozen).unwrap_or_default(), 74 ["dozen", index] => parse_index(index).map(Cursor::Dozen).unwrap_or_default(),
@@ -81,7 +94,7 @@ impl Cursor {
81 pub fn target(&self) -> Option<BetTarget> { 94 pub fn target(&self) -> Option<BetTarget> {
82 match self { 95 match self {
83 Cursor::Zero => Some(BetTarget::Straight { number: 0 }), 96 Cursor::Zero => Some(BetTarget::Straight { number: 0 }),
84 Cursor::Lattice { x, y } => lattice_target(*x, *y), 97 Cursor::Lattice { x, y } => felt_target(*x, *y),
85 Cursor::Dozen(index) => Some(BetTarget::Dozen { 98 Cursor::Dozen(index) => Some(BetTarget::Dozen {
86 index: *index as u8, 99 index: *index as u8,
87 }), 100 }),
@@ -103,27 +116,29 @@ impl Cursor {
103 116
104 pub fn moved(&self, direction: Direction) -> Cursor { 117 pub fn moved(&self, direction: Direction) -> Cursor {
105 match (*self, direction) { 118 match (*self, direction) {
106 (Cursor::Zero, Direction::Down) => Cursor::Lattice { x: 0, y: 0 }, 119 (Cursor::Zero, Direction::Down) => Cursor::Lattice { x: 1, y: 1 },
107 (Cursor::Zero, _) => Cursor::Zero, 120 (Cursor::Zero, _) => Cursor::Zero,
108 121
109 (Cursor::Lattice { x, y }, Direction::Left) => Cursor::Lattice { 122 (Cursor::Lattice { x, y }, Direction::Left) => Cursor::Lattice {
110 x: x.saturating_sub(1), 123 x: if x > 1 { x - 1 } else { x },
111 y, 124 y,
112 }, 125 },
113 (Cursor::Lattice { x, y }, Direction::Right) => Cursor::Lattice { 126 (Cursor::Lattice { x, y }, Direction::Right) => Cursor::Lattice {
114 x: (x + 1).min(NUM_COLUMNS), 127 x: (x + 1).min(FELT_X_MAX),
115 y, 128 y,
116 }, 129 },
117 (Cursor::Lattice { x: _, y: 0 }, Direction::Up) => Cursor::Zero, 130 (Cursor::Lattice { x: _, y: 1 }, Direction::Up) => Cursor::Zero,
118 (Cursor::Lattice { x, y }, Direction::Up) => Cursor::Lattice { x, y: y - 1 }, 131 (Cursor::Lattice { x, y }, Direction::Up) => Cursor::Lattice { x, y: y - 1 },
119 (Cursor::Lattice { x, y: 3 }, Direction::Down) => Cursor::Dozen((x / 4).min(2)), 132 (Cursor::Lattice { x, y: FELT_Y_MAX }, Direction::Down) => {
133 Cursor::Dozen((x.saturating_sub(1) / 8).min(2))
134 }
120 (Cursor::Lattice { x, y }, Direction::Down) => Cursor::Lattice { x, y: y + 1 }, 135 (Cursor::Lattice { x, y }, Direction::Down) => Cursor::Lattice { x, y: y + 1 },
121 136
122 (Cursor::Dozen(index), Direction::Left) => Cursor::Dozen(index.saturating_sub(1)), 137 (Cursor::Dozen(index), Direction::Left) => Cursor::Dozen(index.saturating_sub(1)),
123 (Cursor::Dozen(index), Direction::Right) => Cursor::Dozen((index + 1).min(2)), 138 (Cursor::Dozen(index), Direction::Right) => Cursor::Dozen((index + 1).min(2)),
124 (Cursor::Dozen(index), Direction::Up) => Cursor::Lattice { 139 (Cursor::Dozen(index), Direction::Up) => Cursor::Lattice {
125 x: 4 * index + 1, 140 x: 8 * index + 1,
126 y: 3, 141 y: FELT_Y_MAX,
127 }, 142 },
128 (Cursor::Dozen(index), Direction::Down) => Cursor::Column(index), 143 (Cursor::Dozen(index), Direction::Down) => Cursor::Column(index),
129 144
@@ -146,30 +161,69 @@ fn parse_index(raw: &str) -> Option<usize> {
146 raw.parse::<usize>().ok().filter(|index| *index < 3) 161 raw.parse::<usize>().ok().filter(|index| *index < 3)
147} 162}
148 163
149/// Numbers under a lattice point: 1 (outer corner), 2 (edge) or 4 (inner). 164/// Number at a felt cell (column 0-11, row 0-2 counted from the top).
150fn lattice_target(x: usize, y: usize) -> Option<BetTarget> { 165fn cell_number(column: usize, row: usize) -> Option<u8> {
151 let cell = |column: isize, row: isize| -> Option<u8> { 166 if column >= NUM_COLUMNS || row >= 3 {
152 if !(0..NUM_COLUMNS as isize).contains(&column) || !(0..3isize).contains(&row) { 167 return None;
153 return None; 168 }
169 Some((3 * column + (3 - row)) as u8)
170}
171
172/// Bet under a point of the fine grid.
173fn felt_target(x: usize, y: usize) -> Option<BetTarget> {
174 if !(1..=FELT_X_MAX).contains(&x) || !(1..=FELT_Y_MAX).contains(&y) {
175 return None;
176 }
177
178 let column = (x - 1) / 2;
179 let row = (y - 1) / 2;
180
181 match (x % 2, y % 2) {
182 // Centre of a cell: straight.
183 (1, 1) => cell_number(column, row).map(|number| BetTarget::Straight { number }),
184 // Vertical line: the two cells left and right of it.
185 (0, 1) => {
186 let left = cell_number(x / 2 - 1, row);
187 let right = cell_number(x / 2, row);
188 match (left, right) {
189 (Some(a), Some(b)) => Some(BetTarget::Split {
190 a: a.min(b),
191 b: a.max(b),
192 }),
193 _ => None,
194 }
195 }
196 // Horizontal line: the two cells above and below it.
197 (1, 0) => {
198 let above = cell_number(column, y / 2 - 1);
199 let below = cell_number(column, y / 2);
200 match (above, below) {
201 (Some(a), Some(b)) => Some(BetTarget::Split {
202 a: a.min(b),
203 b: a.max(b),
204 }),
205 _ => None,
206 }
207 }
208 // Crossing: the four cells around it.
209 _ => {
210 let mut numbers: Vec<u8> = [
211 (x / 2 - 1, y / 2 - 1),
212 (x / 2, y / 2 - 1),
213 (x / 2 - 1, y / 2),
214 (x / 2, y / 2),
215 ]
216 .into_iter()
217 .filter_map(|(column, row)| cell_number(column, row))
218 .collect();
219 numbers.sort_unstable();
220 match numbers.as_slice() {
221 [a, b, c, d] => Some(BetTarget::Corner {
222 numbers: [*a, *b, *c, *d],
223 }),
224 _ => None,
225 }
154 } 226 }
155 Some((3 * column + (3 - row)) as u8)
156 };
157
158 let x = x as isize;
159 let y = y as isize;
160 let mut numbers: Vec<u8> = [(x - 1, y - 1), (x, y - 1), (x - 1, y), (x, y)]
161 .into_iter()
162 .filter_map(|(column, row)| cell(column, row))
163 .collect();
164 numbers.sort_unstable();
165
166 match numbers.as_slice() {
167 [number] => Some(BetTarget::Straight { number: *number }),
168 [a, b] => Some(BetTarget::Split { a: *a, b: *b }),
169 [a, b, c, d] => Some(BetTarget::Corner {
170 numbers: [*a, *b, *c, *d],
171 }),
172 _ => None,
173 } 227 }
174} 228}
175 229
@@ -317,7 +371,18 @@ impl RouletteGame {
317 } 371 }
318 } 372 }
319 Some("spinning") => "Spinning...".to_string(), 373 Some("spinning") => "Spinning...".to_string(),
320 _ => "Arrows move, ENTER places the chip, SPACE spins.".to_string(), 374 _ => {
375 let chip = state.data.get("chip").map(String::as_str).unwrap_or("10");
376 match Self::cursor(state).target() {
377 Some(target) => format!(
378 "Bet: {} · pays {} · ENTER places {} Kr",
379 target.label(),
380 target.return_label(),
381 chip
382 ),
383 None => "Arrows move, ENTER places the chip, SPACE spins.".to_string(),
384 }
385 }
321 } 386 }
322 } 387 }
323} 388}