diff options
Diffstat (limited to 'src/client/games/roulette.rs')
| -rw-r--r-- | src/client/games/roulette.rs | 141 |
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)] |
| 37 | pub enum Cursor { | 44 | pub 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. | ||
| 53 | const FELT_X_MAX: usize = 2 * NUM_COLUMNS - 1; | ||
| 54 | const FELT_Y_MAX: usize = 2 * 3 - 1; | ||
| 55 | |||
| 45 | impl Default for Cursor { | 56 | impl 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). |
| 150 | fn lattice_target(x: usize, y: usize) -> Option<BetTarget> { | 165 | fn 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. | ||
| 173 | fn 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 | } |
