From 5d6b5049c2a34a3f8abe725d4a871ba84a0dd714 Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 24 Aug 2026 14:48:12 +0200 Subject: TLS transport and games framework on client and server --- src/bin/client/games/manager.rs | 127 ++++++++++++++++++++++++++++++++++++++++ src/bin/client/games/mod.rs | 3 + src/bin/client/games/slots.rs | 62 ++++++++++++++++++++ src/bin/client/games/traits.rs | 30 ++++++++++ 4 files changed, 222 insertions(+) create mode 100644 src/bin/client/games/manager.rs create mode 100644 src/bin/client/games/mod.rs create mode 100644 src/bin/client/games/slots.rs create mode 100644 src/bin/client/games/traits.rs (limited to 'src/bin/client/games') diff --git a/src/bin/client/games/manager.rs b/src/bin/client/games/manager.rs new file mode 100644 index 0000000..6959214 --- /dev/null +++ b/src/bin/client/games/manager.rs @@ -0,0 +1,127 @@ +use crate::games::slots::SlotsGame; +use crate::games::traits::{Game, GameError, GamePhase, GameState}; +use std::collections::HashMap; +use std::io::Write; + +pub struct GameManager { + current_session: Option, + pending_game_type: Option, + games: HashMap>, +} + +impl GameManager { + pub fn new() -> Self { + let mut games: HashMap> = HashMap::new(); + games.insert("slots".to_string(), Box::new(SlotsGame::new())); + + Self { + current_session: None, + pending_game_type: None, + games, + } + } + + pub fn get_available_games(&self) -> Vec<(&str, &str)> { + self.games + .iter() + .map(|(k, v)| (k.as_str(), v.get_name())) + .collect() + } + + pub fn enter_game(&mut self, game_type: &str) -> Result<(), GameError> { + if self.current_session.is_some() { + return Err(GameError::InvalidAction); + } + + if !self.games.contains_key(game_type) { + return Err(GameError::InvalidAction); + } + + self.pending_game_type = Some(game_type.to_string()); + + self.current_session = Some(GameState { + game_type: game_type.to_string(), + bet_amount: 10, + phase: GamePhase::WaitingToStart, + data: HashMap::new(), + }); + + Ok(()) + } + + pub fn start_game( + &mut self, + stream: &mut crate::connection::TlsStream, + ) -> Result<(), GameError> { + let game_type = self + .pending_game_type + .take() + .ok_or(GameError::InvalidAction)?; + + let state = self + .current_session + .as_mut() + .ok_or(GameError::InvalidAction)?; + + if state.phase != GamePhase::WaitingToStart { + return Err(GameError::InvalidAction); + } + + let bet_amount = state.bet_amount; + + let writer = stream; + writeln!(writer, "PLAY_START {} {}", game_type, bet_amount) + .map_err(|_| GameError::NetworkError)?; + + Ok(()) + } + + pub fn get_current_game(&self) -> Option<&dyn Game> { + self.current_session + .as_ref() + .and_then(|session| self.games.get(&session.game_type).map(|game| game.as_ref())) + } + + pub fn get_current_state(&self) -> Option<&GameState> { + self.current_session.as_ref() + } + + pub fn get_current_render_lines(&self) -> Option { + let session = self.current_session.as_ref()?; + let game = self.games.get(&session.game_type)?; + Some(game.render_lines(session)) + } + + pub fn is_in_game(&self) -> bool { + self.current_session.is_some() + } + + pub fn is_game_started(&self) -> bool { + self.current_session + .as_ref() + .map(|s| s.phase != GamePhase::WaitingToStart) + .unwrap_or(false) + } + + pub fn end_game(&mut self) { + self.current_session = None; + self.pending_game_type = None; + } + + pub fn adjust_bet(&mut self, delta: i64) -> Result { + if let Some(ref session) = self.current_session { + if session.phase != GamePhase::WaitingToStart { + return Err(GameError::InvalidAction); + } + } + + if let Some(ref mut session) = self.current_session { + let current_bet = session.bet_amount as i64; + let new_bet = (current_bet + delta).max(10) as u64; + session.bet_amount = new_bet; + Ok(new_bet) + } else { + Err(GameError::InvalidAction) + } + } +} diff --git a/src/bin/client/games/mod.rs b/src/bin/client/games/mod.rs new file mode 100644 index 0000000..aa74574 --- /dev/null +++ b/src/bin/client/games/mod.rs @@ -0,0 +1,3 @@ +pub mod manager; +pub mod slots; +pub mod traits; diff --git a/src/bin/client/games/slots.rs b/src/bin/client/games/slots.rs new file mode 100644 index 0000000..2f9d63e --- /dev/null +++ b/src/bin/client/games/slots.rs @@ -0,0 +1,62 @@ +use crate::games::traits::{Game, GameState}; + +pub struct SlotsGame; + +impl SlotsGame { + pub fn new() -> Self { + Self + } + + fn render_reels(&self, reels: &str) -> String { + if reels.is_empty() { + "[ ? ] [ ? ] [ ? ]".to_string() + } else { + let chars: Vec = reels.chars().collect(); + format!( + "[ {} ] [ {} ] [ {} ]\r", + chars.get(0).unwrap_or(&'?'), + chars.get(1).unwrap_or(&'?'), + chars.get(2).unwrap_or(&'?') + ) + } + } +} + +impl Game for SlotsGame { + fn get_name(&self) -> &'static str { + "Slots" + } + + fn render(&self, state: &GameState) -> String { + let reels = state.data.get("reels").cloned().unwrap_or_default(); + + let mut output = format!("\n=== SLOTS ===\r\n"); + output += &format!("Bet: {} PLN\r\n", state.bet_amount); + output += &format!("{}\r\n", self.render_reels(&reels)); + output += "\nPress [SPACE] to spin!\r\n"; + output += "\nControls: [+/-] Adjust Bet [Q] Quit\r\n"; + output + } + + fn render_lines(&self, state: &GameState) -> usize { + self.render(state).lines().count() + } + + fn get_help(&self) -> String { + "SLOTS GAME\n\ + =========\n\ + Match symbols to win!\n\ + \n\ + Payouts:\n\ + Three of a kind: 5x-100x bet\n\ + Two of a kind: 2x-20x bet\n\ + \n\ + Symbols: C=Cherry, L=Lemon, O=Orange, G=Grapes, D=Diamond, S=Seven\n\ + \n\ + Controls:\n\ + SPACE - Spin the reels\n\ + +/- - Adjust bet amount\n\ + Q - Exit the game" + .to_string() + } +} diff --git a/src/bin/client/games/traits.rs b/src/bin/client/games/traits.rs new file mode 100644 index 0000000..19f2638 --- /dev/null +++ b/src/bin/client/games/traits.rs @@ -0,0 +1,30 @@ +use std::collections::HashMap; + +#[derive(Debug, Clone)] +pub enum GameError { + InvalidAction, + NetworkError, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum GamePhase { + WaitingToStart, +} + +#[derive(Debug, Clone)] +pub struct GameState { + pub game_type: String, + pub bet_amount: u64, + pub phase: GamePhase, + pub data: HashMap, +} + +pub trait Game: Send + Sync { + fn get_name(&self) -> &'static str; + + fn render(&self, state: &GameState) -> String; + + fn render_lines(&self, state: &GameState) -> usize; + + fn get_help(&self) -> String; +} -- cgit v1.2.3