diff options
Diffstat (limited to '')
| -rw-r--r-- | src/client/games/roulette.rs | 141 |
1 files changed, 58 insertions, 83 deletions
diff --git a/src/client/games/roulette.rs b/src/client/games/roulette.rs index b3a8d9c..ec9573c 100644 --- a/src/client/games/roulette.rs +++ b/src/client/games/roulette.rs | |||
| @@ -1,4 +1,5 @@ | |||
| 1 | use super::traits::{Game, GameState}; | 1 | use super::traits::{Game, GameState}; |
| 2 | use crate::client::view::{RoulettePhase, RouletteView}; | ||
| 2 | use crate::roulette::{BetTarget, Bets, Outside, is_even, is_odd}; | 3 | use crate::roulette::{BetTarget, Bets, Outside, is_even, is_odd}; |
| 3 | 4 | ||
| 4 | pub const ROULETTE_BOARD_LINES: usize = 12; | 5 | pub const ROULETTE_BOARD_LINES: usize = 12; |
| @@ -232,22 +233,6 @@ impl RouletteGame { | |||
| 232 | Self | 233 | Self |
| 233 | } | 234 | } |
| 234 | 235 | ||
| 235 | fn bets(state: &GameState) -> Bets { | ||
| 236 | state | ||
| 237 | .data | ||
| 238 | .get("bets") | ||
| 239 | .and_then(|raw| Bets::from_json(raw).ok()) | ||
| 240 | .unwrap_or_default() | ||
| 241 | } | ||
| 242 | |||
| 243 | fn cursor(state: &GameState) -> Cursor { | ||
| 244 | state | ||
| 245 | .data | ||
| 246 | .get("cursor") | ||
| 247 | .map(|raw| Cursor::parse(raw)) | ||
| 248 | .unwrap_or_default() | ||
| 249 | } | ||
| 250 | |||
| 251 | fn zero_cell(cursor: Cursor) -> String { | 236 | fn zero_cell(cursor: Cursor) -> String { |
| 252 | let cell = if cursor == Cursor::Zero { | 237 | let cell = if cursor == Cursor::Zero { |
| 253 | "[ 0 ]".to_string() | 238 | "[ 0 ]".to_string() |
| @@ -329,92 +314,82 @@ impl RouletteGame { | |||
| 329 | format!("Bets: {list} Total: {} Kr", bets.total()) | 314 | format!("Bets: {list} Total: {} Kr", bets.total()) |
| 330 | } | 315 | } |
| 331 | 316 | ||
| 332 | fn status_line(state: &GameState) -> String { | 317 | fn status_line(view: &RouletteView) -> String { |
| 333 | if let Some(error) = state.data.get("error") { | 318 | if let Some(error) = &view.error { |
| 334 | if !error.is_empty() { | 319 | return format!("ERROR: {error}"); |
| 335 | return format!("ERROR: {error}"); | ||
| 336 | } | ||
| 337 | } | 320 | } |
| 338 | 321 | ||
| 339 | match state.data.get("status").map(String::as_str) { | 322 | match view.phase { |
| 340 | Some("result") => { | 323 | RoulettePhase::Result => { |
| 341 | let number = state.data.get("number").map(String::as_str).unwrap_or("?"); | 324 | let number = view.number.unwrap_or(0); |
| 342 | let color = state | 325 | let color = view |
| 343 | .data | 326 | .color |
| 344 | .get("color") | 327 | .as_deref() |
| 345 | .map(|value| value.to_uppercase()) | 328 | .map(str::to_uppercase) |
| 346 | .unwrap_or_default(); | 329 | .unwrap_or_default(); |
| 347 | let parity = number | 330 | let parity = if is_even(number) { |
| 348 | .parse::<u8>() | 331 | " EVEN" |
| 349 | .ok() | 332 | } else if is_odd(number) { |
| 350 | .map(|number| { | 333 | " ODD" |
| 351 | if is_even(number) { | ||
| 352 | " EVEN" | ||
| 353 | } else if is_odd(number) { | ||
| 354 | " ODD" | ||
| 355 | } else { | ||
| 356 | "" | ||
| 357 | } | ||
| 358 | }) | ||
| 359 | .unwrap_or(""); | ||
| 360 | let winnings = state | ||
| 361 | .data | ||
| 362 | .get("winnings") | ||
| 363 | .map(String::as_str) | ||
| 364 | .unwrap_or("0"); | ||
| 365 | let stake = state.bet_amount; | ||
| 366 | if winnings == "0" { | ||
| 367 | format!("No win at {number} {color}{parity}") | ||
| 368 | } else { | 334 | } else { |
| 369 | let profit = winnings.parse::<u64>().unwrap_or(0).saturating_sub(stake); | 335 | "" |
| 370 | format!("Won {winnings} Kr at {number} {color}{parity} (+{profit})") | 336 | }; |
| 371 | } | 337 | match view.winnings.unwrap_or(0) { |
| 372 | } | 338 | 0 => format!("No win at {number} {color}{parity}"), |
| 373 | Some("spinning") => "Spinning...".to_string(), | 339 | winnings => { |
| 374 | _ => { | 340 | let profit = winnings.saturating_sub(view.bet_amount); |
| 375 | let chip = state.data.get("chip").map(String::as_str).unwrap_or("10"); | 341 | format!("Won {winnings} Kr at {number} {color}{parity} (+{profit})") |
| 376 | match Self::cursor(state).target() { | 342 | } |
| 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 | } | 343 | } |
| 385 | } | 344 | } |
| 345 | RoulettePhase::Spinning => "Spinning...".to_string(), | ||
| 346 | RoulettePhase::Placing => match view.cursor.target() { | ||
| 347 | Some(target) => format!( | ||
| 348 | "Bet: {} · pays {} · ENTER places {} Kr", | ||
| 349 | target.label(), | ||
| 350 | target.return_label(), | ||
| 351 | view.chip | ||
| 352 | ), | ||
| 353 | None => "Arrows move, ENTER places the chip, SPACE spins.".to_string(), | ||
| 354 | }, | ||
| 386 | } | 355 | } |
| 387 | } | 356 | } |
| 388 | } | ||
| 389 | |||
| 390 | impl Game for RouletteGame { | ||
| 391 | fn get_name(&self) -> &'static str { | ||
| 392 | "Roulette" | ||
| 393 | } | ||
| 394 | 357 | ||
| 395 | fn render(&self, state: &GameState) -> String { | 358 | /// Render a roulette view to terminal lines. The GUI draws the same view |
| 396 | let bets = Self::bets(state); | 359 | /// with a painter instead. |
| 397 | let chip = state.data.get("chip").map(String::as_str).unwrap_or("10"); | 360 | pub fn render_view(view: &RouletteView) -> String { |
| 398 | let balance = state.data.get("balance").map(String::as_str).unwrap_or("?"); | 361 | let covered = view.cursor.numbers(); |
| 399 | let cursor = Self::cursor(state); | 362 | let balance = view |
| 400 | let covered = cursor.numbers(); | 363 | .balance |
| 364 | .map(|balance| balance.to_string()) | ||
| 365 | .unwrap_or_else(|| "?".to_string()); | ||
| 401 | 366 | ||
| 402 | let lines = [ | 367 | let lines = [ |
| 403 | "=== ROULETTE ===".to_string(), | 368 | "=== ROULETTE ===".to_string(), |
| 404 | format!("Balance: {balance} Kr Chip: {chip} Kr"), | 369 | format!("Balance: {balance} Kr Chip: {} Kr", view.chip), |
| 405 | Self::zero_cell(cursor), | 370 | Self::zero_cell(view.cursor), |
| 406 | Self::number_row(0, &covered), | 371 | Self::number_row(0, &covered), |
| 407 | Self::number_row(1, &covered), | 372 | Self::number_row(1, &covered), |
| 408 | Self::number_row(2, &covered), | 373 | Self::number_row(2, &covered), |
| 409 | Self::dozens_row(cursor), | 374 | Self::dozens_row(view.cursor), |
| 410 | Self::columns_row(cursor), | 375 | Self::columns_row(view.cursor), |
| 411 | Self::outside_row(cursor), | 376 | Self::outside_row(view.cursor), |
| 412 | Self::bets_line(&bets), | 377 | Self::bets_line(&view.bets), |
| 413 | Self::status_line(state), | 378 | Self::status_line(view), |
| 414 | "[ARROWS] Move [ENTER] Bet [C]lear [+/-] Chip [SPACE] Spin [Q] Quit".to_string(), | 379 | "[ARROWS] Move [ENTER] Bet [C]lear [+/-] Chip [SPACE] Spin [Q] Quit".to_string(), |
| 415 | ]; | 380 | ]; |
| 416 | lines.join("\r\n") | 381 | lines.join("\r\n") |
| 417 | } | 382 | } |
| 383 | } | ||
| 384 | |||
| 385 | impl Game for RouletteGame { | ||
| 386 | fn get_name(&self) -> &'static str { | ||
| 387 | "Roulette" | ||
| 388 | } | ||
| 389 | |||
| 390 | fn render(&self, state: &GameState) -> String { | ||
| 391 | Self::render_view(&RouletteView::from_state(state)) | ||
| 392 | } | ||
| 418 | 393 | ||
| 419 | fn render_lines(&self, _state: &GameState) -> usize { | 394 | fn render_lines(&self, _state: &GameState) -> usize { |
| 420 | ROULETTE_BOARD_LINES | 395 | ROULETTE_BOARD_LINES |
