From ea5e7d336719571e51ef14478ab603e71126e3d8 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 28 Oct 2026 22:28:05 +0100 Subject: v0.2.2: split client/server logic into the library --- src/bin/client/connection.rs | 2 +- src/bin/client/database.rs | 118 ----------------------------------- src/bin/client/games/manager.rs | 134 ---------------------------------------- src/bin/client/games/mod.rs | 3 - src/bin/client/games/slots.rs | 61 ------------------ src/bin/client/games/traits.rs | 32 ---------- src/bin/client/main.rs | 2 - src/bin/client/mod.rs | 6 -- src/bin/client/repl.rs | 6 +- 9 files changed, 4 insertions(+), 360 deletions(-) delete mode 100644 src/bin/client/database.rs delete mode 100644 src/bin/client/games/manager.rs delete mode 100644 src/bin/client/games/mod.rs delete mode 100644 src/bin/client/games/slots.rs delete mode 100644 src/bin/client/games/traits.rs delete mode 100644 src/bin/client/mod.rs (limited to 'src/bin/client') diff --git a/src/bin/client/connection.rs b/src/bin/client/connection.rs index 6aff321..e54b646 100644 --- a/src/bin/client/connection.rs +++ b/src/bin/client/connection.rs @@ -1,4 +1,4 @@ -use crate::database; +use zagan_bet::client::database; use crate::tls::TlsConnector; use rusqlite::Connection; use std::io::{Read, Write}; diff --git a/src/bin/client/database.rs b/src/bin/client/database.rs deleted file mode 100644 index e1fa42d..0000000 --- a/src/bin/client/database.rs +++ /dev/null @@ -1,118 +0,0 @@ -use rusqlite::Connection; - -pub fn init_db(conn: &Connection) { - conn.execute( - "CREATE TABLE IF NOT EXISTS hosts ( - host TEXT PRIMARY KEY, - last_used DATETIME DEFAULT CURRENT_TIMESTAMP, - fingerprint TEXT - )", - [], - ) - .unwrap(); - // Migration for databases created before TOFU trust. - let _ = conn.execute("ALTER TABLE hosts ADD COLUMN fingerprint TEXT", []); - conn.execute( - "CREATE TABLE IF NOT EXISTS credentials ( - host TEXT PRIMARY KEY, - username TEXT NOT NULL, - password TEXT NOT NULL - )", - [], - ) - .unwrap(); -} - -pub fn get_last_host(conn: &Connection) -> Option { - let mut stmt = conn - .prepare("SELECT host FROM hosts ORDER BY last_used DESC LIMIT 1") - .unwrap(); - let mut rows = stmt.query([]).unwrap(); - if let Ok(Some(row)) = rows.next() { - row.get(0).ok() - } else { - None - } -} - -pub fn save_host(conn: &Connection, host: &str) { - conn.execute( - "INSERT INTO hosts (host) VALUES (?1) - ON CONFLICT(host) DO UPDATE SET last_used = CURRENT_TIMESTAMP", - [host], - ) - .unwrap(); -} - -pub fn save_credentials( - conn: &Connection, - host: &str, - username: &str, - password: &str, -) -> Result<(), Box> { - conn.execute( - "INSERT OR REPLACE INTO credentials (host, username, password) VALUES (?1, ?2, ?3)", - (host, username, password), - )?; - Ok(()) -} - -pub fn get_credentials(conn: &Connection, host: &str) -> Option<(String, String)> { - let mut stmt = conn - .prepare("SELECT username, password FROM credentials WHERE host = ?1") - .unwrap(); - let mut rows = stmt.query([host]).unwrap(); - if let Ok(Some(row)) = rows.next() { - let username: String = row.get(0).unwrap(); - let password: String = row.get(1).unwrap(); - Some((username, password)) - } else { - None - } -} - -/// SHA-256 SPKI fingerprint of a host's certificate, if trusted. -pub fn get_fingerprint(conn: &Connection, host: &str) -> Option { - let mut stmt = conn - .prepare("SELECT fingerprint FROM hosts WHERE host = ?1") - .ok()?; - let mut rows = stmt.query([host]).ok()?; - match rows.next() { - Ok(Some(row)) => row.get(0).ok(), - _ => None, - } -} - -pub fn save_fingerprint( - conn: &Connection, - host: &str, - fingerprint: &str, -) -> Result<(), rusqlite::Error> { - // Hosts may not exist yet (fingerprint can be saved before first - // successful connect completes save_host). - conn.execute("INSERT OR IGNORE INTO hosts (host) VALUES (?1)", [host])?; - conn.execute( - "UPDATE hosts SET fingerprint = ?2 WHERE host = ?1", - (host, fingerprint), - )?; - Ok(()) -} - -/// Remove all trust and credentials for a host ("forget this host"). -pub fn forget_host(conn: &Connection, host: &str) -> Result<(), rusqlite::Error> { - conn.execute("DELETE FROM hosts WHERE host = ?1", [host])?; - conn.execute("DELETE FROM credentials WHERE host = ?1", [host])?; - Ok(()) -} - -/// List all remembered hosts with their fingerprints. -pub fn get_all_hosts(conn: &Connection) -> Vec<(String, Option)> { - let mut stmt = conn - .prepare("SELECT host, fingerprint FROM hosts ORDER BY last_used DESC") - .unwrap(); - let rows = stmt.query_map([], |row| { - Ok((row.get(0)?, row.get(1)?)) - }) - .unwrap(); - rows.filter_map(|r| r.ok()).collect() -} diff --git a/src/bin/client/games/manager.rs b/src/bin/client/games/manager.rs deleted file mode 100644 index 0b5b4cb..0000000 --- a/src/bin/client/games/manager.rs +++ /dev/null @@ -1,134 +0,0 @@ -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 - .as_ref() - .ok_or(GameError::InvalidAction)?; - - let state = self - .current_session - .as_mut() - .ok_or(GameError::InvalidAction)?; - - if state.phase != GamePhase::WaitingToStart && state.phase != GamePhase::Finished { - 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 reset_phase_to_waiting(&mut self) { - if let Some(ref mut session) = self.current_session { - session.phase = GamePhase::WaitingToStart; - session.data.clear(); - } - } - - 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 deleted file mode 100644 index aa74574..0000000 --- a/src/bin/client/games/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index ff55274..0000000 --- a/src/bin/client/games/slots.rs +++ /dev/null @@ -1,61 +0,0 @@ -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(); - - format!( - "=== SLOTS ===\r\nBet: {} PLN\r\n{}\r\n\r\nPress [SPACE] to spin!\r\n\r\nControls: [+/-] Adjust Bet [Q] Quit", - state.bet_amount, - self.render_reels(&reels) - ) - } - - fn render_lines(&self, _state: &GameState) -> usize { - 6 - } - - fn get_help(&self) -> String { - "SLOTS GAME\n\ - =========\n\ - Match symbols to win!\n\ - \n\ - Payouts:\n\ - Three of a kind: 2x-28x bet\n\ - Two of a kind: 1x-3x 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 deleted file mode 100644 index aed0c24..0000000 --- a/src/bin/client/games/traits.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::collections::HashMap; - -#[derive(Debug, Clone)] -pub enum GameError { - InvalidAction, - NetworkError, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum GamePhase { - WaitingToStart, - Playing, - Finished, -} - -#[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; -} diff --git a/src/bin/client/main.rs b/src/bin/client/main.rs index 4e03381..1b701d8 100644 --- a/src/bin/client/main.rs +++ b/src/bin/client/main.rs @@ -1,8 +1,6 @@ #[macro_use] mod macros; mod connection; -mod database; -mod games; mod repl; mod tls; diff --git a/src/bin/client/mod.rs b/src/bin/client/mod.rs deleted file mode 100644 index 5921c41..0000000 --- a/src/bin/client/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod database; -mod connection; -mod games; -pub mod platform; -pub mod tls; -pub mod repl; diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs index a6f0331..24605e2 100644 --- a/src/bin/client/repl.rs +++ b/src/bin/client/repl.rs @@ -4,9 +4,9 @@ use std::sync::{Arc, Mutex}; use std::time::Instant; use crate::connection::TlsStream; -use crate::database; -use crate::games::manager::GameManager; -use crate::games::traits::GamePhase; +use zagan_bet::client::database; +use zagan_bet::client::games::manager::GameManager; +use zagan_bet::client::games::traits::GamePhase; use crossterm::{ cursor::{Hide, MoveToColumn, Show}, -- cgit v1.2.3