From 46202ee2e398ffd49febe6684f5c6a8648da600d Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 1 Nov 2026 17:58:03 +0100 Subject: v4.0.1: introduce Command/View and port roulette to the shared boundary --- src/client/core.rs | 109 +++++++++++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 49 deletions(-) (limited to 'src/client/core.rs') diff --git a/src/client/core.rs b/src/client/core.rs index 0b2e683..7e43fb7 100644 --- a/src/client/core.rs +++ b/src/client/core.rs @@ -4,13 +4,14 @@ use crate::client::games::response::{ parse_roulette_response, parse_spin_response, }; use crate::client::games::roulette::Direction; +use crate::client::view::{Command, RouletteView, View}; use crate::client::ui::{ command_names, completion_lines, display_width, help_lines, history_lines, input_cursor_column, parse_bet_arg, record_history, }; use crate::crash::{CrashState, multiplier_at}; use crate::history::MAX_HISTORY; -use crate::roulette::Bets; +use crate::roulette::{BetTarget, Bets}; use std::time::{Duration, Instant}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -41,13 +42,10 @@ pub enum InteractiveCommand { pub enum Effect { /// Append lines to the scrolling transcript. Print(Vec), - /// Replace the game board region in place. - ReplaceRegion(Vec), - /// Run the roulette wheel animation, then draw the final board lines. - AnimateRoulette { - winning: u8, - final_lines: Vec, - }, + /// Draw the current view in the board region. + Render(View), + /// Run the roulette wheel animation, then draw the final view. + AnimateRoulette { winning: u8, view: View }, /// Remove the game board region. ClearRegion, /// Redraw the command prompt. `text` is the input without the `> ` prefix. @@ -215,11 +213,8 @@ impl ClientCore { self.balance = Some(result.balance); let winning = result.number; self.game.apply_roulette_result(result); - let final_lines = self.board_lines().unwrap_or_default(); - vec![Effect::AnimateRoulette { - winning, - final_lines, - }] + let view = self.view(); + vec![Effect::AnimateRoulette { winning, view }] } Err(message) => { self.game.set_error(message); @@ -380,53 +375,62 @@ impl ClientCore { } fn handle_roulette_key(&mut self, key: Key) -> Vec { - let direction = match key { - Key::Up => Some(Direction::Up), - Key::Down => Some(Direction::Down), - Key::Left => Some(Direction::Left), - Key::Right => Some(Direction::Right), - _ => None, - }; - if let Some(direction) = direction { - let cursor = self.game.roulette_cursor().moved(direction); - self.game.set_roulette_cursor(&cursor); - return self.board_effects(); - } - match key { Key::CtrlC => vec![Effect::Quit], - Key::Char('q') => { - self.game.end_game(); - vec![ - Effect::ClearRegion, - Effect::Print(vec!["Game exited.".to_string()]), - ] + Key::Up => self.command(Command::MoveCursor(Direction::Up)), + Key::Down => self.command(Command::MoveCursor(Direction::Down)), + Key::Left => self.command(Command::MoveCursor(Direction::Left)), + Key::Right => self.command(Command::MoveCursor(Direction::Right)), + Key::Enter => self.command(Command::PlaceBet), + Key::Char(' ') => self.command(Command::Spin), + Key::Char('c') => self.command(Command::ClearBets), + Key::Char('+') | Key::Char('=') => self.command(Command::AdjustChip(10)), + Key::Char('-') => self.command(Command::AdjustChip(-10)), + Key::Char('q') => self.command(Command::Leave), + _ => Vec::new(), + } + } + + /// Apply a semantic command. Both frontends drive the core through this. + pub fn command(&mut self, command: Command) -> Vec { + match command { + Command::MoveCursor(direction) => { + let cursor = self.game.roulette_cursor().moved(direction); + self.game.set_roulette_cursor(&cursor); + self.board_effects() } - Key::Char(' ') => self.spin_roulette(), - Key::Char('c') => { + Command::PlaceBet => match self.game.roulette_cursor().target() { + Some(target) => self.place_roulette_chip_on(target), + None => Vec::new(), + }, + Command::PlaceBetAt(target) => self.place_roulette_chip_on(target), + Command::ClearBets => { self.game.set_roulette_bets(&Bets::default()); self.game.set_roulette_status("place"); self.board_effects() } - Key::Char('+') | Key::Char('=') => { - let chip = self.game.roulette_chip().saturating_add(10); - self.game.set_roulette_chip(chip); + Command::SetChip(chip) => { + self.game.set_roulette_chip(chip.max(10)); self.board_effects() } - Key::Char('-') => { - let chip = self.game.roulette_chip().saturating_sub(10).max(10); + Command::AdjustChip(delta) => { + let chip = (self.game.roulette_chip() as i64 + delta).max(10) as u64; self.game.set_roulette_chip(chip); self.board_effects() } - Key::Enter => self.place_roulette_chip(), - _ => Vec::new(), + Command::Spin => self.spin_roulette(), + Command::Leave => { + self.crash_started = None; + self.game.end_game(); + vec![ + Effect::ClearRegion, + Effect::Print(vec!["Game exited.".to_string()]), + ] + } } } - fn place_roulette_chip(&mut self) -> Vec { - let Some(target) = self.game.roulette_cursor().target() else { - return Vec::new(); - }; + fn place_roulette_chip_on(&mut self, target: BetTarget) -> Vec { let mut bets = self.game.roulette_bets(); bets.add(target, self.game.roulette_chip()); self.place_roulette_bets(bets) @@ -950,11 +954,18 @@ impl ClientCore { Some(game.render(state).lines().map(str::to_string).collect()) } + /// Snapshot of the current screen for a frontend to draw. + pub fn view(&self) -> View { + if let Some(state) = self.game.get_current_state() { + if state.game_type == "roulette" { + return View::Roulette(RouletteView::from_state(state)); + } + } + View::Text(self.board_lines().unwrap_or_default()) + } + fn board_effects(&self) -> Vec { - self.board_lines() - .map(Effect::ReplaceRegion) - .into_iter() - .collect() + vec![Effect::Render(self.view())] } fn prompt_effects(&self) -> Vec { -- cgit v1.2.3