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/repl.rs | 990 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 670 insertions(+), 320 deletions(-) (limited to 'src/bin/client/repl.rs') diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs index a0e5b5e..194860b 100644 --- a/src/bin/client/repl.rs +++ b/src/bin/client/repl.rs @@ -1,23 +1,32 @@ -use std::io::{BufRead, BufReader, Write, stdout}; -use std::net::TcpStream; use rusqlite::Connection; +use std::io::{stdout, BufRead, BufReader, Write}; +use std::sync::{Arc, Mutex}; use std::time::Instant; -use crate::connection; +use crate::connection::TlsStream; use crate::database; +use crate::games::manager::GameManager; -use crossterm::{event::{read, Event, KeyCode, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, cursor::{MoveToColumn, Show, Hide}, execute}; +use crossterm::{ + cursor::{Hide, MoveToColumn, Show}, + event::{read, Event, KeyCode, KeyModifiers}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, +}; -use sha2::{Sha256, Digest}; +use sha2::{Digest, Sha256}; pub struct ClientState { - pub stream: Option, + pub stream: Option, pub conn: Connection, pub addr: Option, pub default_port: u16, pub history: Vec, pub history_index: usize, pub logged_in_user: Option, + pub game_manager: GameManager, + cleanup_done: Arc>, + in_game_mode: bool, } impl ClientState { @@ -37,12 +46,86 @@ impl ClientState { history: vec![], history_index: 0, logged_in_user: None, + game_manager: GameManager::new(), + cleanup_done: Arc::new(Mutex::new(false)), + in_game_mode: false, + } + } + + pub fn enter_game_mode(&mut self) { + self.in_game_mode = true; + } + + pub fn exit_game_mode(&mut self) { + self.in_game_mode = false; + self.game_manager.end_game(); + } + + pub fn handle_game_key(&mut self, key_code: KeyCode) -> bool { + if !self.game_manager.is_in_game() { + return false; + } + + match key_code { + KeyCode::Char('q') => { + self.exit_game_mode(); + println!("\nGame exited."); + return true; + } + KeyCode::Char(' ') => { + let should_start = !self.game_manager.is_game_started(); + if should_start { + if let Some(mut stream) = self.stream.take() { + match self.game_manager.start_game(&mut stream) { + Ok(()) => { + self.play_spin_animation(&mut stream); + } + Err(e) => println!("\nError starting game: {:?}", e), + } + self.stream = Some(stream); + } + } + return true; + } + KeyCode::Char('+') => { + // Increase bet + match self.game_manager.adjust_bet(10) { + Ok(_new_bet) => { + if let Some(state) = self.game_manager.get_current_state() { + if let Some(game) = self.game_manager.get_current_game() { + let lines = + self.game_manager.get_current_render_lines().unwrap_or(8); + redraw_game_lines(&game.render(state), lines); + } + } + } + Err(e) => println!("\nError: {:?}", e), + } + return true; + } + KeyCode::Char('-') => { + // Decrease bet + match self.game_manager.adjust_bet(-10) { + Ok(_new_bet) => { + if let Some(state) = self.game_manager.get_current_state() { + if let Some(game) = self.game_manager.get_current_game() { + let lines = + self.game_manager.get_current_render_lines().unwrap_or(8); + redraw_game_lines(&game.render(state), lines); + } + } + } + Err(e) => println!("\nError: {:?}", e), + } + return true; + } + _ => return false, } } pub fn connect_to(&mut self, addr: &str) { self.addr = Some(addr.to_string()); - match connection::connect(addr, &self.conn) { + match crate::connection::connect(addr, &self.conn) { Ok((s, authed_user)) => { self.stream = Some(s); self.logged_in_user = authed_user; @@ -53,6 +136,24 @@ impl ClientState { } pub fn run(&mut self) { + // Set up cleanup handlers + let cleanup_done = self.cleanup_done.clone(); + + // Set up panic handler + let panic_cleanup = cleanup_done.clone(); + std::panic::set_hook(Box::new(move |_| { + restore_terminal(&panic_cleanup); + })); + + // Set up Ctrl+C handler + let ctrlc_cleanup = cleanup_done.clone(); + if let Err(e) = ctrlc::set_handler(move || { + restore_terminal(&ctrlc_cleanup); + std::process::exit(0); + }) { + eprintln!("Warning: Could not set Ctrl+C handler: {}", e); + } + enable_raw_mode().unwrap(); 'outer: loop { let mut current_input = String::new(); @@ -64,308 +165,446 @@ impl ClientState { Event::Key(key) => { // Only handle key press events, not release events if key.kind == crossterm::event::KeyEventKind::Press { - match key.code { - KeyCode::Up => { - if self.history_index > 0 { - self.history_index -= 1; - current_input = self.history[self.history_index].clone(); - redraw(¤t_input); - } - } - KeyCode::Down => { - if self.history_index < self.history.len() { - self.history_index += 1; - current_input = if self.history_index == self.history.len() { "".to_string() } else { self.history[self.history_index].clone() }; - redraw(¤t_input); - } - } - KeyCode::Enter => { - let line = current_input.trim().to_string(); - if !line.is_empty() { - self.history.push(line.clone()); - } - // Process command - let parts: Vec<&str> = line.splitn(2, ' ').collect(); - match parts[0] { - "connect" => { - let addr_str = if parts.len() < 2 { - if let Some(h) = &self.addr { - h.clone() - } else { - println!("\nNo host specified and no previous connection. Usage: connect :"); - continue 'outer; - } - } else { - let mut a = parts[1].to_string(); - if !a.contains(':') { - a = format!("{}:{}", a, self.default_port); - } - a - }; - self.connect_to(&addr_str); + // Handle game mode keys first + if self.game_manager.is_in_game() && self.handle_game_key(key.code) { + // Game key was handled, continue loop + if !self.game_manager.is_in_game() { + // Game ended, redraw command prompt + redraw(""); } - "send" => { - if self.stream.is_none() { - println!("\nNot connected. Use 'connect :' first."); - continue 'outer; - } - if parts.len() < 2 { - println!("\nUsage: send "); - continue 'outer; - } - let msg = parts[1]; - let s = self.stream.as_mut().unwrap(); - let writer_stream = s.try_clone().unwrap(); - let mut writer = writer_stream; - let response_reader_stream = s.try_clone().unwrap(); - let mut response_reader = BufReader::new(response_reader_stream); - if let Err(_) = writeln!(writer, "{}", msg) { - self.stream = None; - println!("\nConnection lost"); - continue 'outer; - } - let mut response = String::new(); - if let Err(_) = response_reader.read_line(&mut response) { - self.stream = None; - println!("\nConnection lost"); - } else { - println!("\nEcho: {}", response.trim()); + continue; + } + + // Handle normal command mode keys + match key.code { + KeyCode::Up => { + if self.history_index > 0 { + self.history_index -= 1; + current_input = self.history[self.history_index].clone(); + redraw(¤t_input); } } - "disconnect" => { - self.stream = None; - self.logged_in_user = None; - println!("\nDisconnected"); - } - "ping" => { - if self.stream.is_none() { - println!("\nNot connected. Use 'connect :' first."); - continue 'outer; - } - let s = self.stream.as_mut().unwrap(); - let writer_stream = s.try_clone().unwrap(); - let mut writer = writer_stream; - let start = Instant::now(); - let response_reader_stream = s.try_clone().unwrap(); - let mut response_reader = BufReader::new(response_reader_stream); - if let Err(_) = writeln!(writer, "PING") { - self.stream = None; - println!("\nConnection lost"); - continue 'outer; - } - let mut response = String::new(); - if let Err(_) = response_reader.read_line(&mut response) { - self.stream = None; - println!("\nConnection lost"); - } else { - if response.trim() == "PONG" { - let elapsed = start.elapsed(); - println!("\nPong ({:?})", elapsed); + KeyCode::Down => { + if self.history_index < self.history.len() { + self.history_index += 1; + current_input = if self.history_index == self.history.len() + { + "".to_string() } else { - self.stream = None; - println!("\nConnection lost"); - } + self.history[self.history_index].clone() + }; + redraw(¤t_input); } } - "register" => { - if self.stream.is_none() { - println!("\nNot connected. Use 'connect :' first."); - continue 'outer; - } - let username = read_line("\nUsername: "); - if username.is_empty() { - continue 'outer; + KeyCode::Enter => { + let line = current_input.trim().to_string(); + if !line.is_empty() { + self.history.push(line.clone()); } - let password = read_password("Password: "); - if password.is_empty() { - continue 'outer; + let line_trimmed = current_input.trim().to_string(); + if !line_trimmed.is_empty() { + self.history.push(line_trimmed.clone()); } - let hashed = format!("{:x}", Sha256::digest(password.as_bytes())); - let msg = format!("REGISTER {} {}", username, hashed); - let s = self.stream.as_mut().unwrap(); - let writer_stream = s.try_clone().unwrap(); - let mut writer = writer_stream; - let response_reader_stream = s.try_clone().unwrap(); - let mut response_reader = BufReader::new(response_reader_stream); - if let Err(_) = writeln!(writer, "{}", msg) { - self.stream = None; - println!("\nConnection lost"); - continue 'outer; - } - let mut response = String::new(); - if let Err(_) = response_reader.read_line(&mut response) { - self.stream = None; - println!("\nConnection lost"); - } else { - let resp = response.trim(); - if resp == format!("REGISTERED {}", username) { - println!("\nSuccessfully registered as {}", username); - } else if resp.starts_with("ERROR ") { - println!("\nRegistration failed: {}", &resp[6..]); - } else { - println!("\n{}", resp); + + // Process normal commands + let parts: Vec<&str> = line_trimmed.splitn(3, ' ').collect(); + match parts[0] { + "play" => { + if self.stream.is_none() { + println!("\nNot connected. Use 'connect :' first."); + continue 'outer; + } + if self.logged_in_user.is_none() { + println!("\nNot logged in. Use 'login' first."); + continue 'outer; + } + if parts.len() < 2 { + let games = self.game_manager.get_available_games(); + println!("\nAvailable games:"); + for (gt, name) in games { + println!(" - {} ({})", gt, name); + } + println!("\nUsage: play "); + continue 'outer; + } + let game_type = parts[1]; + + if self.game_manager.is_in_game() { + println!("\nAlready in a game. Use 'quit' to exit current game."); + continue 'outer; + } + + match self.game_manager.enter_game(game_type) { + Ok(()) => { + self.enter_game_mode(); + if let Some(game) = + self.game_manager.get_current_game() + { + if let Some(state) = + self.game_manager.get_current_state() + { + redraw_game(&game.render(state)); + } + } + } + Err(e) => { + println!("\nFailed to enter game: {:?}", e); + } + } } - } - } - "login" => { - if self.stream.is_none() { - println!("\nNot connected. Use 'connect :' first."); - continue 'outer; - } - if self.logged_in_user.is_some() { - println!("\nAlready logged in as {}", self.logged_in_user.as_ref().unwrap()); - continue 'outer; - } - loop { - let username = read_line("\nUsername: "); - if username.is_empty() { - break; + "games" => { + let games = self.game_manager.get_available_games(); + println!("\nAvailable games:"); + for (gt, name) in games { + println!(" - {} ({})", gt, name); + } } - let password = read_password("Password: "); - if password.is_empty() { - break; + "connect" => { + let addr_str = if parts.len() < 2 { + if let Some(h) = &self.addr { + h.clone() + } else { + println!("\nNo host specified and no previous connection. Usage: connect :"); + continue 'outer; + } + } else { + let mut a = parts[1].to_string(); + if !a.contains(':') { + a = format!("{}:{}", a, self.default_port); + } + a + }; + self.connect_to(&addr_str); } - let hashed = format!("{:x}", Sha256::digest(password.as_bytes())); - let msg = format!("AUTH {} {}", username, hashed); - let s = self.stream.as_mut().unwrap(); - let writer_stream = s.try_clone().unwrap(); - let mut writer = writer_stream; - let response_reader_stream = s.try_clone().unwrap(); - let mut response_reader = BufReader::new(response_reader_stream); - if let Err(_) = writeln!(writer, "{}", msg) { - self.stream = None; - println!("\nConnection lost"); - break; + "send" => { + if self.stream.is_none() { + println!("\nNot connected. Use 'connect :' first."); + continue 'outer; + } + if parts.len() < 2 { + println!("\nUsage: send "); + continue 'outer; + } + let msg = parts[1]; + let s = self.stream.as_mut().unwrap(); + let writer_stream = s.try_clone().unwrap(); + let mut writer = writer_stream; + let response_reader_stream = s.try_clone().unwrap(); + let mut response_reader = + BufReader::new(response_reader_stream); + if let Err(_) = writeln!(writer, "{}", msg) { + self.stream = None; + println!("\nConnection lost"); + continue 'outer; + } + let mut response = String::new(); + if let Err(_) = response_reader.read_line(&mut response) + { + self.stream = None; + println!("\nConnection lost"); + } else { + println!("\nEcho: {}", response.trim()); + } } - let mut response = String::new(); - if let Err(_) = response_reader.read_line(&mut response) { + "disconnect" => { self.stream = None; - println!("\nConnection lost"); - break; - } else { - let resp = response.trim(); - if resp.starts_with("OK") { - // Save credentials - if let Some(addr) = &self.addr { - if let Err(e) = database::save_credentials(&self.conn, addr, &username, &hashed) { - println!("Failed to save credentials: {}", e); + self.logged_in_user = None; + println!("\nDisconnected"); + } + "ping" => { + if self.stream.is_none() { + println!("\nNot connected. Use 'connect :' first."); + continue 'outer; + } + let s = self.stream.as_mut().unwrap(); + let writer_stream = s.try_clone().unwrap(); + let mut writer = writer_stream; + let start = Instant::now(); + let response_reader_stream = s.try_clone().unwrap(); + let mut response_reader = + BufReader::new(response_reader_stream); + if let Err(_) = writeln!(writer, "PING") { + self.stream = None; + println!("\nConnection lost"); + continue 'outer; + } + let mut response = String::new(); + if let Err(_) = response_reader.read_line(&mut response) + { + self.stream = None; + println!("\nConnection lost"); + } else { + if response.trim() == "PONG" { + let elapsed = start.elapsed(); + println!("\nPong ({:?})", elapsed); + } else { + self.stream = None; + println!("\nConnection lost"); + } + } + } + "register" => { + if self.stream.is_none() { + println!("\nNot connected. Use 'connect :' first."); + continue 'outer; + } + let username = read_line("\nUsername: "); + if username.is_empty() { + continue 'outer; + } + let password = read_password("Password: "); + if password.is_empty() { + continue 'outer; + } + let hashed = format!( + "{:x}", + Sha256::digest(password.as_bytes()) + ); + let msg = format!("REGISTER {} {}", username, hashed); + let s = self.stream.as_mut().unwrap(); + let writer_stream = s.try_clone().unwrap(); + let mut writer = writer_stream; + let response_reader_stream = s.try_clone().unwrap(); + let mut response_reader = + BufReader::new(response_reader_stream); + if let Err(_) = writeln!(writer, "{}", msg) { + self.stream = None; + println!("\nConnection lost"); + continue 'outer; + } + let mut response = String::new(); + if let Err(_) = response_reader.read_line(&mut response) + { + self.stream = None; + println!("\nConnection lost"); + } else { + let resp = response.trim(); + if resp == format!("REGISTERED {}", username) { + println!( + "\nSuccessfully registered as {}", + username + ); + } else if resp.starts_with("ERROR ") { + println!( + "\nRegistration failed: {}", + &resp[6..] + ); + } else { + println!("\n{}", resp); + } + } + } + "login" => { + if self.stream.is_none() { + println!("\nNot connected. Use 'connect :' first."); + continue 'outer; + } + if self.logged_in_user.is_some() { + println!( + "\nAlready logged in as {}", + self.logged_in_user.as_ref().unwrap() + ); + continue 'outer; + } + loop { + let username = read_line("\nUsername: "); + if username.is_empty() { + break; + } + let password = read_password("Password: "); + if password.is_empty() { + break; + } + let hashed = format!( + "{:x}", + Sha256::digest(password.as_bytes()) + ); + let msg = format!("AUTH {} {}", username, hashed); + let s = self.stream.as_mut().unwrap(); + let writer_stream = s.try_clone().unwrap(); + let mut writer = writer_stream; + let response_reader_stream = s.try_clone().unwrap(); + let mut response_reader = + BufReader::new(response_reader_stream); + if let Err(_) = writeln!(writer, "{}", msg) { + self.stream = None; + println!("\nConnection lost"); + break; + } + let mut response = String::new(); + if let Err(_) = + response_reader.read_line(&mut response) + { + self.stream = None; + println!("\nConnection lost"); + break; + } else { + let resp = response.trim(); + if resp.starts_with("OK") { + // Save credentials + if let Some(addr) = &self.addr { + if let Err(e) = + database::save_credentials( + &self.conn, addr, &username, + &hashed, + ) + { + println!("Failed to save credentials: {}", e); + } + } + self.logged_in_user = + Some(username.clone()); + println!("\nLogged in as {}", username); + break; + } else if resp.starts_with("ERROR ") { + println!( + "\nAuthentication failed: {}", + &resp[6..] + ); + println!( + "Try again or press Enter to cancel." + ); + } else { + println!("\n{}", resp); } } - self.logged_in_user = Some(username.clone()); - println!("\nLogged in as {}", username); - break; - } else if resp.starts_with("ERROR ") { - println!("\nAuthentication failed: {}", &resp[6..]); - println!("Try again or press Enter to cancel."); + } + } + "setcred" => { + if self.addr.is_none() { + println!("\nNo host connected. Use 'connect :' first."); + continue 'outer; + } + let username = read_line("\nUsername: "); + if username.is_empty() { + continue 'outer; + } + let password = read_password("Password: "); + if password.is_empty() { + continue 'outer; + } + let hashed = format!( + "{:x}", + Sha256::digest(password.as_bytes()) + ); + if let Some(addr) = &self.addr { + if let Err(e) = database::save_credentials( + &self.conn, addr, &username, &hashed, + ) { + println!("Failed to save credentials: {}", e); + } else { + println!("Credentials saved for {}", addr); + } + } + } + "logout" => { + self.logged_in_user = None; + println!("\nLogged out"); + } + "info" => { + if self.stream.is_none() { + println!("\nNot connected. Use 'connect :' first."); + continue 'outer; + } + let s = self.stream.as_mut().unwrap(); + let writer_stream = s.try_clone().unwrap(); + let mut writer = writer_stream; + let response_reader_stream = s.try_clone().unwrap(); + let mut response_reader = + BufReader::new(response_reader_stream); + if let Err(_) = writeln!(writer, "INFO") { + self.stream = None; + println!("\nConnection lost"); + continue 'outer; + } + let mut response = String::new(); + if let Err(_) = response_reader.read_line(&mut response) + { + self.stream = None; + println!("\nConnection lost"); } else { - println!("\n{}", resp); + let resp = response.trim(); + if resp.starts_with("INFO ") { + let parts: Vec<&str> = + resp.split_whitespace().collect(); + if parts.len() == 4 { + println!("\nLogged in as {} (ID: {})\r\nBalance: {} PLN", parts[1], parts[2], parts[3]); + } else { + println!("\n{}", resp); + } + } else if resp.starts_with("ERROR ") { + println!("\nError: {}", &resp[6..]); + } else { + println!("\n{}", resp); + } } } - } - } - "setcred" => { - if self.addr.is_none() { - println!("\nNo host connected. Use 'connect :' first."); - continue 'outer; - } - let username = read_line("\nUsername: "); - if username.is_empty() { - continue 'outer; - } - let password = read_password("Password: "); - if password.is_empty() { - continue 'outer; - } - let hashed = format!("{:x}", Sha256::digest(password.as_bytes())); - if let Some(addr) = &self.addr { - if let Err(e) = database::save_credentials(&self.conn, addr, &username, &hashed) { - println!("Failed to save credentials: {}", e); - } else { - println!("Credentials saved for {}", addr); + "help" => { + if self.game_manager.is_in_game() { + if let Some(game) = + self.game_manager.get_current_game() + { + println!("\n{}", game.get_help()); + } + } else { + help(); + } } + "quit" => { + if self.game_manager.is_in_game() { + self.game_manager.end_game(); + println!("\nGame exited."); + } else { + restore_terminal(&self.cleanup_done); + break 'outer; + } + } + _ => help(), } + break; } - "logout" => { - self.logged_in_user = None; - println!("\nLogged out"); - } - "info" => { - if self.stream.is_none() { - println!("\nNot connected. Use 'connect :' first."); - continue 'outer; - } - let s = self.stream.as_mut().unwrap(); - let writer_stream = s.try_clone().unwrap(); - let mut writer = writer_stream; - let response_reader_stream = s.try_clone().unwrap(); - let mut response_reader = BufReader::new(response_reader_stream); - if let Err(_) = writeln!(writer, "INFO") { - self.stream = None; - println!("\nConnection lost"); - continue 'outer; + KeyCode::Backspace => { + if !current_input.is_empty() { + current_input.pop(); + execute!( + stdout(), + MoveToColumn(2), + Clear(ClearType::UntilNewLine) + ) + .unwrap(); + print!("{}", current_input); + stdout().flush().unwrap(); } - let mut response = String::new(); - if let Err(_) = response_reader.read_line(&mut response) { - self.stream = None; - println!("\nConnection lost"); + } + KeyCode::Char(c) => { + if key.modifiers.contains(KeyModifiers::CONTROL) && c == 'c' { + restore_terminal(&self.cleanup_done); + std::process::exit(0); + } else if key.modifiers.contains(KeyModifiers::CONTROL) + && c == 'd' + { + restore_terminal(&self.cleanup_done); + std::process::exit(0); + } else if c == '\t' { + self.handle_tab_completion(&mut current_input); } else { - let resp = response.trim(); - if resp.starts_with("INFO ") { - let parts: Vec<&str> = resp.split_whitespace().collect(); - if parts.len() == 4 { - println!("\nLogged in as {} (ID: {})\r\nBalance: {} PLN", parts[1], parts[2], parts[3]); - } else { - println!("\n{}", resp); - } - } else if resp.starts_with("ERROR ") { - println!("\nError: {}", &resp[6..]); - } else { - println!("\n{}", resp); - } + current_input.push(c); + print!("{}", c); + stdout().flush().unwrap(); } } - "help" => { - help(); + KeyCode::Tab => { + self.handle_tab_completion(&mut current_input); } - "quit" => break 'outer, - _ => help(), - } - break; - } - KeyCode::Backspace => { - if !current_input.is_empty() { - current_input.pop(); - execute!(stdout(), MoveToColumn(2), - Clear(ClearType::UntilNewLine)).unwrap(); - print!("{}", current_input); - stdout().flush().unwrap(); + _ => {} } } - KeyCode::Char(c) => { - if key.modifiers.contains(KeyModifiers::CONTROL) && c == 'c' { - execute!(stdout(), Show).unwrap(); - return; - } else if c == '\t' { - self.handle_tab_completion(&mut current_input); - } else { - current_input.push(c); - print!("{}", c); - stdout().flush().unwrap(); - } - } - KeyCode::Tab => { - self.handle_tab_completion(&mut current_input); - } - _ => {} - } } - } _ => {} } } } - disable_raw_mode().unwrap(); - execute!(stdout(), Show).unwrap(); + restore_terminal(&self.cleanup_done); } fn handle_tab_completion(&mut self, current_input: &mut String) { @@ -387,6 +626,77 @@ impl ClientState { } // else do nothing } + + fn play_spin_animation(&mut self, stream: &mut TlsStream) { + use std::io::Write; + use std::thread; + use std::time::Duration; + + let symbols = ['C', 'L', 'O', 'G', 'D', 'S']; + let frames = 20; + let delay = Duration::from_millis(50); + + for _ in 0..frames { + let reels: String = (0..3) + .map(|_| symbols[rand::random::() % symbols.len()]) + .collect(); + + execute!(stdout(), Clear(ClearType::CurrentLine), MoveToColumn(0)).unwrap(); + print!( + "[ {} ] [ {} ] [ {} ]", + reels.chars().nth(0).unwrap(), + reels.chars().nth(1).unwrap(), + reels.chars().nth(2).unwrap() + ); + stdout().flush().unwrap(); + + thread::sleep(delay); + } + + let mut response_str = String::new(); + let mut reader = std::io::BufReader::new(stream.try_clone().unwrap()); + if reader.read_line(&mut response_str).is_ok() { + let response = response_str.trim(); + + if response.starts_with("GAME_RESULT") { + let parts: Vec<&str> = response.split_whitespace().collect(); + if parts.len() >= 7 { + let reels = parts[5]; + let win_type = parts[6]; + + execute!(stdout(), Clear(ClearType::CurrentLine), MoveToColumn(0)).unwrap(); + print!( + "[ {} ] [ {} ] [ {} ]", + reels.chars().nth(0).unwrap_or('?'), + reels.chars().nth(1).unwrap_or('?'), + reels.chars().nth(2).unwrap_or('?') + ); + println!(); + + if win_type == "jackpot" { + println!("\n🎉 JACKPOT! 🎉"); + } else if win_type == "two" { + println!("\n✨ Two of a kind! ✨"); + } else { + println!("\nNo win."); + } + + println!("Winnings: {} PLN", parts[2]); + println!("New Balance: {} PLN", parts[4]); + stdout().flush().unwrap(); + } + } + } + } +} + +fn restore_terminal(cleanup_done: &Arc>) { + let mut done = cleanup_done.lock().unwrap(); + if !*done { + let _ = disable_raw_mode(); + let _ = execute!(stdout(), Show); + *done = true; + } } fn get_commands() -> Vec { @@ -400,13 +710,15 @@ fn get_commands() -> Vec { "setcred".to_string(), "logout".to_string(), "info".to_string(), + "play".to_string(), "help".to_string(), "quit".to_string(), ] } fn help() { - println!("\nAvailable commands:\r + println!( + "\nAvailable commands:\r connect : - connect to the server\r send - send a message to the server\r disconnect - disconnect from the server\r @@ -416,8 +728,38 @@ fn help() { setcred - set credentials for a host\r logout - logout from the server\r info - show login info\r + play [bet] - start a game (e.g., play slots 50)\r help - show this message\r - quit - quit the program"); + quit - quit the program" + ); +} + +fn redraw_game(game_output: &str) { + execute!( + stdout(), + Clear(ClearType::CurrentLine), + Clear(ClearType::FromCursorDown), + MoveToColumn(0) + ) + .unwrap(); + println!("{}", game_output); + stdout().flush().unwrap(); +} + +fn redraw_game_lines(game_output: &str, lines: usize) { + use crossterm::cursor::MoveUp; + if lines > 0 { + execute!(stdout(), MoveUp(lines as u16)).unwrap(); + } + execute!( + stdout(), + Clear(ClearType::CurrentLine), + Clear(ClearType::FromCursorDown), + MoveToColumn(0) + ) + .unwrap(); + println!("{}", game_output); + stdout().flush().unwrap(); } fn redraw(current_input: &str) { @@ -431,36 +773,40 @@ fn read_line(prompt: &str) -> String { execute!(stdout(), Show).unwrap(); printsame!("{}", prompt); stdout().flush().unwrap(); - + let mut input = String::new(); let prompt_len = prompt.len(); - + loop { match read().unwrap() { Event::Key(key) => { if key.kind == crossterm::event::KeyEventKind::Press { match key.code { - KeyCode::Enter => { - println!(); - break; - } - KeyCode::Backspace => { - if !input.is_empty() { - input.pop(); - // Move cursor back and clear from current position to end of line - execute!(stdout(), MoveToColumn((prompt_len + input.len() + 1) as u16), - Clear(ClearType::UntilNewLine)).unwrap(); - stdout().flush().unwrap(); + KeyCode::Enter => { + println!(); + break; + } + KeyCode::Backspace => { + if !input.is_empty() { + input.pop(); + // Move cursor back and clear from current position to end of line + execute!( + stdout(), + MoveToColumn((prompt_len + input.len() + 1) as u16), + Clear(ClearType::UntilNewLine) + ) + .unwrap(); + stdout().flush().unwrap(); + } + } + KeyCode::Char(c) => { + input.push(c); + print!("{}", c); + stdout().flush().unwrap(); + } + _ => {} } } - KeyCode::Char(c) => { - input.push(c); - print!("{}", c); - stdout().flush().unwrap(); - } - _ => {} - } - } } _ => {} } @@ -472,36 +818,40 @@ fn read_password(prompt: &str) -> String { execute!(stdout(), Show).unwrap(); print!("{}", prompt); stdout().flush().unwrap(); - + let mut input = String::new(); let prompt_len = prompt.len(); - + loop { match read().unwrap() { Event::Key(key) => { if key.kind == crossterm::event::KeyEventKind::Press { match key.code { - KeyCode::Enter => { - println!(); - break; - } - KeyCode::Backspace => { - if !input.is_empty() { - input.pop(); - // Move cursor back and clear from current position to end of line - execute!(stdout(), MoveToColumn((prompt_len + input.len() + 1) as u16), - Clear(ClearType::UntilNewLine)).unwrap(); - stdout().flush().unwrap(); + KeyCode::Enter => { + println!(); + break; + } + KeyCode::Backspace => { + if !input.is_empty() { + input.pop(); + // Move cursor back and clear from current position to end of line + execute!( + stdout(), + MoveToColumn((prompt_len + input.len() + 1) as u16), + Clear(ClearType::UntilNewLine) + ) + .unwrap(); + stdout().flush().unwrap(); + } + } + KeyCode::Char(c) => { + input.push(c); + print!("*"); + stdout().flush().unwrap(); + } + _ => {} } } - KeyCode::Char(c) => { - input.push(c); - print!("*"); - stdout().flush().unwrap(); - } - _ => {} - } - } } _ => {} } -- cgit v1.2.3