diff options
Diffstat (limited to 'src/bin/client/repl.rs')
| -rw-r--r-- | src/bin/client/repl.rs | 990 |
1 files changed, 670 insertions, 320 deletions
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 @@ | |||
| 1 | use std::io::{BufRead, BufReader, Write, stdout}; | ||
| 2 | use std::net::TcpStream; | ||
| 3 | use rusqlite::Connection; | 1 | use rusqlite::Connection; |
| 2 | use std::io::{stdout, BufRead, BufReader, Write}; | ||
| 3 | use std::sync::{Arc, Mutex}; | ||
| 4 | use std::time::Instant; | 4 | use std::time::Instant; |
| 5 | 5 | ||
| 6 | use crate::connection; | 6 | use crate::connection::TlsStream; |
| 7 | use crate::database; | 7 | use crate::database; |
| 8 | use crate::games::manager::GameManager; | ||
| 8 | 9 | ||
| 9 | use crossterm::{event::{read, Event, KeyCode, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, cursor::{MoveToColumn, Show, Hide}, execute}; | 10 | use crossterm::{ |
| 11 | cursor::{Hide, MoveToColumn, Show}, | ||
| 12 | event::{read, Event, KeyCode, KeyModifiers}, | ||
| 13 | execute, | ||
| 14 | terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, | ||
| 15 | }; | ||
| 10 | 16 | ||
| 11 | use sha2::{Sha256, Digest}; | 17 | use sha2::{Digest, Sha256}; |
| 12 | 18 | ||
| 13 | pub struct ClientState { | 19 | pub struct ClientState { |
| 14 | pub stream: Option<TcpStream>, | 20 | pub stream: Option<TlsStream>, |
| 15 | pub conn: Connection, | 21 | pub conn: Connection, |
| 16 | pub addr: Option<String>, | 22 | pub addr: Option<String>, |
| 17 | pub default_port: u16, | 23 | pub default_port: u16, |
| 18 | pub history: Vec<String>, | 24 | pub history: Vec<String>, |
| 19 | pub history_index: usize, | 25 | pub history_index: usize, |
| 20 | pub logged_in_user: Option<String>, | 26 | pub logged_in_user: Option<String>, |
| 27 | pub game_manager: GameManager, | ||
| 28 | cleanup_done: Arc<Mutex<bool>>, | ||
| 29 | in_game_mode: bool, | ||
| 21 | } | 30 | } |
| 22 | 31 | ||
| 23 | impl ClientState { | 32 | impl ClientState { |
| @@ -37,12 +46,86 @@ impl ClientState { | |||
| 37 | history: vec![], | 46 | history: vec![], |
| 38 | history_index: 0, | 47 | history_index: 0, |
| 39 | logged_in_user: None, | 48 | logged_in_user: None, |
| 49 | game_manager: GameManager::new(), | ||
| 50 | cleanup_done: Arc::new(Mutex::new(false)), | ||
| 51 | in_game_mode: false, | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | pub fn enter_game_mode(&mut self) { | ||
| 56 | self.in_game_mode = true; | ||
| 57 | } | ||
| 58 | |||
| 59 | pub fn exit_game_mode(&mut self) { | ||
| 60 | self.in_game_mode = false; | ||
| 61 | self.game_manager.end_game(); | ||
| 62 | } | ||
| 63 | |||
| 64 | pub fn handle_game_key(&mut self, key_code: KeyCode) -> bool { | ||
| 65 | if !self.game_manager.is_in_game() { | ||
| 66 | return false; | ||
| 67 | } | ||
| 68 | |||
| 69 | match key_code { | ||
| 70 | KeyCode::Char('q') => { | ||
| 71 | self.exit_game_mode(); | ||
| 72 | println!("\nGame exited."); | ||
| 73 | return true; | ||
| 74 | } | ||
| 75 | KeyCode::Char(' ') => { | ||
| 76 | let should_start = !self.game_manager.is_game_started(); | ||
| 77 | if should_start { | ||
| 78 | if let Some(mut stream) = self.stream.take() { | ||
| 79 | match self.game_manager.start_game(&mut stream) { | ||
| 80 | Ok(()) => { | ||
| 81 | self.play_spin_animation(&mut stream); | ||
| 82 | } | ||
| 83 | Err(e) => println!("\nError starting game: {:?}", e), | ||
| 84 | } | ||
| 85 | self.stream = Some(stream); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | return true; | ||
| 89 | } | ||
| 90 | KeyCode::Char('+') => { | ||
| 91 | // Increase bet | ||
| 92 | match self.game_manager.adjust_bet(10) { | ||
| 93 | Ok(_new_bet) => { | ||
| 94 | if let Some(state) = self.game_manager.get_current_state() { | ||
| 95 | if let Some(game) = self.game_manager.get_current_game() { | ||
| 96 | let lines = | ||
| 97 | self.game_manager.get_current_render_lines().unwrap_or(8); | ||
| 98 | redraw_game_lines(&game.render(state), lines); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
| 102 | Err(e) => println!("\nError: {:?}", e), | ||
| 103 | } | ||
| 104 | return true; | ||
| 105 | } | ||
| 106 | KeyCode::Char('-') => { | ||
| 107 | // Decrease bet | ||
| 108 | match self.game_manager.adjust_bet(-10) { | ||
| 109 | Ok(_new_bet) => { | ||
| 110 | if let Some(state) = self.game_manager.get_current_state() { | ||
| 111 | if let Some(game) = self.game_manager.get_current_game() { | ||
| 112 | let lines = | ||
| 113 | self.game_manager.get_current_render_lines().unwrap_or(8); | ||
| 114 | redraw_game_lines(&game.render(state), lines); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | ||
| 118 | Err(e) => println!("\nError: {:?}", e), | ||
| 119 | } | ||
| 120 | return true; | ||
| 121 | } | ||
| 122 | _ => return false, | ||
| 40 | } | 123 | } |
| 41 | } | 124 | } |
| 42 | 125 | ||
| 43 | pub fn connect_to(&mut self, addr: &str) { | 126 | pub fn connect_to(&mut self, addr: &str) { |
| 44 | self.addr = Some(addr.to_string()); | 127 | self.addr = Some(addr.to_string()); |
| 45 | match connection::connect(addr, &self.conn) { | 128 | match crate::connection::connect(addr, &self.conn) { |
| 46 | Ok((s, authed_user)) => { | 129 | Ok((s, authed_user)) => { |
| 47 | self.stream = Some(s); | 130 | self.stream = Some(s); |
| 48 | self.logged_in_user = authed_user; | 131 | self.logged_in_user = authed_user; |
| @@ -53,6 +136,24 @@ impl ClientState { | |||
| 53 | } | 136 | } |
| 54 | 137 | ||
| 55 | pub fn run(&mut self) { | 138 | pub fn run(&mut self) { |
| 139 | // Set up cleanup handlers | ||
| 140 | let cleanup_done = self.cleanup_done.clone(); | ||
| 141 | |||
| 142 | // Set up panic handler | ||
| 143 | let panic_cleanup = cleanup_done.clone(); | ||
| 144 | std::panic::set_hook(Box::new(move |_| { | ||
| 145 | restore_terminal(&panic_cleanup); | ||
| 146 | })); | ||
| 147 | |||
| 148 | // Set up Ctrl+C handler | ||
| 149 | let ctrlc_cleanup = cleanup_done.clone(); | ||
| 150 | if let Err(e) = ctrlc::set_handler(move || { | ||
| 151 | restore_terminal(&ctrlc_cleanup); | ||
| 152 | std::process::exit(0); | ||
| 153 | }) { | ||
| 154 | eprintln!("Warning: Could not set Ctrl+C handler: {}", e); | ||
| 155 | } | ||
| 156 | |||
| 56 | enable_raw_mode().unwrap(); | 157 | enable_raw_mode().unwrap(); |
| 57 | 'outer: loop { | 158 | 'outer: loop { |
| 58 | let mut current_input = String::new(); | 159 | let mut current_input = String::new(); |
| @@ -64,308 +165,446 @@ impl ClientState { | |||
| 64 | Event::Key(key) => { | 165 | Event::Key(key) => { |
| 65 | // Only handle key press events, not release events | 166 | // Only handle key press events, not release events |
| 66 | if key.kind == crossterm::event::KeyEventKind::Press { | 167 | if key.kind == crossterm::event::KeyEventKind::Press { |
| 67 | match key.code { | 168 | // Handle game mode keys first |
| 68 | KeyCode::Up => { | 169 | if self.game_manager.is_in_game() && self.handle_game_key(key.code) { |
| 69 | if self.history_index > 0 { | 170 | // Game key was handled, continue loop |
| 70 | self.history_index -= 1; | 171 | if !self.game_manager.is_in_game() { |
| 71 | current_input = self.history[self.history_index].clone(); | 172 | // Game ended, redraw command prompt |
| 72 | redraw(¤t_input); | 173 | redraw(""); |
| 73 | } | ||
| 74 | } | ||
| 75 | KeyCode::Down => { | ||
| 76 | if self.history_index < self.history.len() { | ||
| 77 | self.history_index += 1; | ||
| 78 | current_input = if self.history_index == self.history.len() { "".to_string() } else { self.history[self.history_index].clone() }; | ||
| 79 | redraw(¤t_input); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | KeyCode::Enter => { | ||
| 83 | let line = current_input.trim().to_string(); | ||
| 84 | if !line.is_empty() { | ||
| 85 | self.history.push(line.clone()); | ||
| 86 | } | ||
| 87 | // Process command | ||
| 88 | let parts: Vec<&str> = line.splitn(2, ' ').collect(); | ||
| 89 | match parts[0] { | ||
| 90 | "connect" => { | ||
| 91 | let addr_str = if parts.len() < 2 { | ||
| 92 | if let Some(h) = &self.addr { | ||
| 93 | h.clone() | ||
| 94 | } else { | ||
| 95 | println!("\nNo host specified and no previous connection. Usage: connect <ip>:<port>"); | ||
| 96 | continue 'outer; | ||
| 97 | } | ||
| 98 | } else { | ||
| 99 | let mut a = parts[1].to_string(); | ||
| 100 | if !a.contains(':') { | ||
| 101 | a = format!("{}:{}", a, self.default_port); | ||
| 102 | } | ||
| 103 | a | ||
| 104 | }; | ||
| 105 | self.connect_to(&addr_str); | ||
| 106 | } | 174 | } |
| 107 | "send" => { | 175 | continue; |
| 108 | if self.stream.is_none() { | 176 | } |
| 109 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); | 177 | |
| 110 | continue 'outer; | 178 | // Handle normal command mode keys |
| 111 | } | 179 | match key.code { |
| 112 | if parts.len() < 2 { | 180 | KeyCode::Up => { |
| 113 | println!("\nUsage: send <message>"); | 181 | if self.history_index > 0 { |
| 114 | continue 'outer; | 182 | self.history_index -= 1; |
| 115 | } | 183 | current_input = self.history[self.history_index].clone(); |
| 116 | let msg = parts[1]; | 184 | redraw(¤t_input); |
| 117 | let s = self.stream.as_mut().unwrap(); | ||
| 118 | let writer_stream = s.try_clone().unwrap(); | ||
| 119 | let mut writer = writer_stream; | ||
| 120 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 121 | let mut response_reader = BufReader::new(response_reader_stream); | ||
| 122 | if let Err(_) = writeln!(writer, "{}", msg) { | ||
| 123 | self.stream = None; | ||
| 124 | println!("\nConnection lost"); | ||
| 125 | continue 'outer; | ||
| 126 | } | ||
| 127 | let mut response = String::new(); | ||
| 128 | if let Err(_) = response_reader.read_line(&mut response) { | ||
| 129 | self.stream = None; | ||
| 130 | println!("\nConnection lost"); | ||
| 131 | } else { | ||
| 132 | println!("\nEcho: {}", response.trim()); | ||
| 133 | } | 185 | } |
| 134 | } | 186 | } |
| 135 | "disconnect" => { | 187 | KeyCode::Down => { |
| 136 | self.stream = None; | 188 | if self.history_index < self.history.len() { |
| 137 | self.logged_in_user = None; | 189 | self.history_index += 1; |
| 138 | println!("\nDisconnected"); | 190 | current_input = if self.history_index == self.history.len() |
| 139 | } | 191 | { |
| 140 | "ping" => { | 192 | "".to_string() |
| 141 | if self.stream.is_none() { | ||
| 142 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); | ||
| 143 | continue 'outer; | ||
| 144 | } | ||
| 145 | let s = self.stream.as_mut().unwrap(); | ||
| 146 | let writer_stream = s.try_clone().unwrap(); | ||
| 147 | let mut writer = writer_stream; | ||
| 148 | let start = Instant::now(); | ||
| 149 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 150 | let mut response_reader = BufReader::new(response_reader_stream); | ||
| 151 | if let Err(_) = writeln!(writer, "PING") { | ||
| 152 | self.stream = None; | ||
| 153 | println!("\nConnection lost"); | ||
| 154 | continue 'outer; | ||
| 155 | } | ||
| 156 | let mut response = String::new(); | ||
| 157 | if let Err(_) = response_reader.read_line(&mut response) { | ||
| 158 | self.stream = None; | ||
| 159 | println!("\nConnection lost"); | ||
| 160 | } else { | ||
| 161 | if response.trim() == "PONG" { | ||
| 162 | let elapsed = start.elapsed(); | ||
| 163 | println!("\nPong ({:?})", elapsed); | ||
| 164 | } else { | 193 | } else { |
| 165 | self.stream = None; | 194 | self.history[self.history_index].clone() |
| 166 | println!("\nConnection lost"); | 195 | }; |
| 167 | } | 196 | redraw(¤t_input); |
| 168 | } | 197 | } |
| 169 | } | 198 | } |
| 170 | "register" => { | 199 | KeyCode::Enter => { |
| 171 | if self.stream.is_none() { | 200 | let line = current_input.trim().to_string(); |
| 172 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); | 201 | if !line.is_empty() { |
| 173 | continue 'outer; | 202 | self.history.push(line.clone()); |
| 174 | } | ||
| 175 | let username = read_line("\nUsername: "); | ||
| 176 | if username.is_empty() { | ||
| 177 | continue 'outer; | ||
| 178 | } | 203 | } |
| 179 | let password = read_password("Password: "); | 204 | let line_trimmed = current_input.trim().to_string(); |
| 180 | if password.is_empty() { | 205 | if !line_trimmed.is_empty() { |
| 181 | continue 'outer; | 206 | self.history.push(line_trimmed.clone()); |
| 182 | } | 207 | } |
| 183 | let hashed = format!("{:x}", Sha256::digest(password.as_bytes())); | 208 | |
| 184 | let msg = format!("REGISTER {} {}", username, hashed); | 209 | // Process normal commands |
| 185 | let s = self.stream.as_mut().unwrap(); | 210 | let parts: Vec<&str> = line_trimmed.splitn(3, ' ').collect(); |
| 186 | let writer_stream = s.try_clone().unwrap(); | 211 | match parts[0] { |
| 187 | let mut writer = writer_stream; | 212 | "play" => { |
| 188 | let response_reader_stream = s.try_clone().unwrap(); | 213 | if self.stream.is_none() { |
| 189 | let mut response_reader = BufReader::new(response_reader_stream); | 214 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); |
| 190 | if let Err(_) = writeln!(writer, "{}", msg) { | 215 | continue 'outer; |
| 191 | self.stream = None; | 216 | } |
| 192 | println!("\nConnection lost"); | 217 | if self.logged_in_user.is_none() { |
| 193 | continue 'outer; | 218 | println!("\nNot logged in. Use 'login' first."); |
| 194 | } | 219 | continue 'outer; |
| 195 | let mut response = String::new(); | 220 | } |
| 196 | if let Err(_) = response_reader.read_line(&mut response) { | 221 | if parts.len() < 2 { |
| 197 | self.stream = None; | 222 | let games = self.game_manager.get_available_games(); |
| 198 | println!("\nConnection lost"); | 223 | println!("\nAvailable games:"); |
| 199 | } else { | 224 | for (gt, name) in games { |
| 200 | let resp = response.trim(); | 225 | println!(" - {} ({})", gt, name); |
| 201 | if resp == format!("REGISTERED {}", username) { | 226 | } |
| 202 | println!("\nSuccessfully registered as {}", username); | 227 | println!("\nUsage: play <game>"); |
| 203 | } else if resp.starts_with("ERROR ") { | 228 | continue 'outer; |
| 204 | println!("\nRegistration failed: {}", &resp[6..]); | 229 | } |
| 205 | } else { | 230 | let game_type = parts[1]; |
| 206 | println!("\n{}", resp); | 231 | |
| 232 | if self.game_manager.is_in_game() { | ||
| 233 | println!("\nAlready in a game. Use 'quit' to exit current game."); | ||
| 234 | continue 'outer; | ||
| 235 | } | ||
| 236 | |||
| 237 | match self.game_manager.enter_game(game_type) { | ||
| 238 | Ok(()) => { | ||
| 239 | self.enter_game_mode(); | ||
| 240 | if let Some(game) = | ||
| 241 | self.game_manager.get_current_game() | ||
| 242 | { | ||
| 243 | if let Some(state) = | ||
| 244 | self.game_manager.get_current_state() | ||
| 245 | { | ||
| 246 | redraw_game(&game.render(state)); | ||
| 247 | } | ||
| 248 | } | ||
| 249 | } | ||
| 250 | Err(e) => { | ||
| 251 | println!("\nFailed to enter game: {:?}", e); | ||
| 252 | } | ||
| 253 | } | ||
| 207 | } | 254 | } |
| 208 | } | 255 | "games" => { |
| 209 | } | 256 | let games = self.game_manager.get_available_games(); |
| 210 | "login" => { | 257 | println!("\nAvailable games:"); |
| 211 | if self.stream.is_none() { | 258 | for (gt, name) in games { |
| 212 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); | 259 | println!(" - {} ({})", gt, name); |
| 213 | continue 'outer; | 260 | } |
| 214 | } | ||
| 215 | if self.logged_in_user.is_some() { | ||
| 216 | println!("\nAlready logged in as {}", self.logged_in_user.as_ref().unwrap()); | ||
| 217 | continue 'outer; | ||
| 218 | } | ||
| 219 | loop { | ||
| 220 | let username = read_line("\nUsername: "); | ||
| 221 | if username.is_empty() { | ||
| 222 | break; | ||
| 223 | } | 261 | } |
| 224 | let password = read_password("Password: "); | 262 | "connect" => { |
| 225 | if password.is_empty() { | 263 | let addr_str = if parts.len() < 2 { |
| 226 | break; | 264 | if let Some(h) = &self.addr { |
| 265 | h.clone() | ||
| 266 | } else { | ||
| 267 | println!("\nNo host specified and no previous connection. Usage: connect <ip>:<port>"); | ||
| 268 | continue 'outer; | ||
| 269 | } | ||
| 270 | } else { | ||
| 271 | let mut a = parts[1].to_string(); | ||
| 272 | if !a.contains(':') { | ||
| 273 | a = format!("{}:{}", a, self.default_port); | ||
| 274 | } | ||
| 275 | a | ||
| 276 | }; | ||
| 277 | self.connect_to(&addr_str); | ||
| 227 | } | 278 | } |
| 228 | let hashed = format!("{:x}", Sha256::digest(password.as_bytes())); | 279 | "send" => { |
| 229 | let msg = format!("AUTH {} {}", username, hashed); | 280 | if self.stream.is_none() { |
| 230 | let s = self.stream.as_mut().unwrap(); | 281 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); |
| 231 | let writer_stream = s.try_clone().unwrap(); | 282 | continue 'outer; |
| 232 | let mut writer = writer_stream; | 283 | } |
| 233 | let response_reader_stream = s.try_clone().unwrap(); | 284 | if parts.len() < 2 { |
| 234 | let mut response_reader = BufReader::new(response_reader_stream); | 285 | println!("\nUsage: send <message>"); |
| 235 | if let Err(_) = writeln!(writer, "{}", msg) { | 286 | continue 'outer; |
| 236 | self.stream = None; | 287 | } |
| 237 | println!("\nConnection lost"); | 288 | let msg = parts[1]; |
| 238 | break; | 289 | let s = self.stream.as_mut().unwrap(); |
| 290 | let writer_stream = s.try_clone().unwrap(); | ||
| 291 | let mut writer = writer_stream; | ||
| 292 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 293 | let mut response_reader = | ||
| 294 | BufReader::new(response_reader_stream); | ||
| 295 | if let Err(_) = writeln!(writer, "{}", msg) { | ||
| 296 | self.stream = None; | ||
| 297 | println!("\nConnection lost"); | ||
| 298 | continue 'outer; | ||
| 299 | } | ||
| 300 | let mut response = String::new(); | ||
| 301 | if let Err(_) = response_reader.read_line(&mut response) | ||
| 302 | { | ||
| 303 | self.stream = None; | ||
| 304 | println!("\nConnection lost"); | ||
| 305 | } else { | ||
| 306 | println!("\nEcho: {}", response.trim()); | ||
| 307 | } | ||
| 239 | } | 308 | } |
| 240 | let mut response = String::new(); | 309 | "disconnect" => { |
| 241 | if let Err(_) = response_reader.read_line(&mut response) { | ||
| 242 | self.stream = None; | 310 | self.stream = None; |
| 243 | println!("\nConnection lost"); | 311 | self.logged_in_user = None; |
| 244 | break; | 312 | println!("\nDisconnected"); |
| 245 | } else { | 313 | } |
| 246 | let resp = response.trim(); | 314 | "ping" => { |
| 247 | if resp.starts_with("OK") { | 315 | if self.stream.is_none() { |
| 248 | // Save credentials | 316 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); |
| 249 | if let Some(addr) = &self.addr { | 317 | continue 'outer; |
| 250 | if let Err(e) = database::save_credentials(&self.conn, addr, &username, &hashed) { | 318 | } |
| 251 | println!("Failed to save credentials: {}", e); | 319 | let s = self.stream.as_mut().unwrap(); |
| 320 | let writer_stream = s.try_clone().unwrap(); | ||
| 321 | let mut writer = writer_stream; | ||
| 322 | let start = Instant::now(); | ||
| 323 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 324 | let mut response_reader = | ||
| 325 | BufReader::new(response_reader_stream); | ||
| 326 | if let Err(_) = writeln!(writer, "PING") { | ||
| 327 | self.stream = None; | ||
| 328 | println!("\nConnection lost"); | ||
| 329 | continue 'outer; | ||
| 330 | } | ||
| 331 | let mut response = String::new(); | ||
| 332 | if let Err(_) = response_reader.read_line(&mut response) | ||
| 333 | { | ||
| 334 | self.stream = None; | ||
| 335 | println!("\nConnection lost"); | ||
| 336 | } else { | ||
| 337 | if response.trim() == "PONG" { | ||
| 338 | let elapsed = start.elapsed(); | ||
| 339 | println!("\nPong ({:?})", elapsed); | ||
| 340 | } else { | ||
| 341 | self.stream = None; | ||
| 342 | println!("\nConnection lost"); | ||
| 343 | } | ||
| 344 | } | ||
| 345 | } | ||
| 346 | "register" => { | ||
| 347 | if self.stream.is_none() { | ||
| 348 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); | ||
| 349 | continue 'outer; | ||
| 350 | } | ||
| 351 | let username = read_line("\nUsername: "); | ||
| 352 | if username.is_empty() { | ||
| 353 | continue 'outer; | ||
| 354 | } | ||
| 355 | let password = read_password("Password: "); | ||
| 356 | if password.is_empty() { | ||
| 357 | continue 'outer; | ||
| 358 | } | ||
| 359 | let hashed = format!( | ||
| 360 | "{:x}", | ||
| 361 | Sha256::digest(password.as_bytes()) | ||
| 362 | ); | ||
| 363 | let msg = format!("REGISTER {} {}", username, hashed); | ||
| 364 | let s = self.stream.as_mut().unwrap(); | ||
| 365 | let writer_stream = s.try_clone().unwrap(); | ||
| 366 | let mut writer = writer_stream; | ||
| 367 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 368 | let mut response_reader = | ||
| 369 | BufReader::new(response_reader_stream); | ||
| 370 | if let Err(_) = writeln!(writer, "{}", msg) { | ||
| 371 | self.stream = None; | ||
| 372 | println!("\nConnection lost"); | ||
| 373 | continue 'outer; | ||
| 374 | } | ||
| 375 | let mut response = String::new(); | ||
| 376 | if let Err(_) = response_reader.read_line(&mut response) | ||
| 377 | { | ||
| 378 | self.stream = None; | ||
| 379 | println!("\nConnection lost"); | ||
| 380 | } else { | ||
| 381 | let resp = response.trim(); | ||
| 382 | if resp == format!("REGISTERED {}", username) { | ||
| 383 | println!( | ||
| 384 | "\nSuccessfully registered as {}", | ||
| 385 | username | ||
| 386 | ); | ||
| 387 | } else if resp.starts_with("ERROR ") { | ||
| 388 | println!( | ||
| 389 | "\nRegistration failed: {}", | ||
| 390 | &resp[6..] | ||
| 391 | ); | ||
| 392 | } else { | ||
| 393 | println!("\n{}", resp); | ||
| 394 | } | ||
| 395 | } | ||
| 396 | } | ||
| 397 | "login" => { | ||
| 398 | if self.stream.is_none() { | ||
| 399 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); | ||
| 400 | continue 'outer; | ||
| 401 | } | ||
| 402 | if self.logged_in_user.is_some() { | ||
| 403 | println!( | ||
| 404 | "\nAlready logged in as {}", | ||
| 405 | self.logged_in_user.as_ref().unwrap() | ||
| 406 | ); | ||
| 407 | continue 'outer; | ||
| 408 | } | ||
| 409 | loop { | ||
| 410 | let username = read_line("\nUsername: "); | ||
| 411 | if username.is_empty() { | ||
| 412 | break; | ||
| 413 | } | ||
| 414 | let password = read_password("Password: "); | ||
| 415 | if password.is_empty() { | ||
| 416 | break; | ||
| 417 | } | ||
| 418 | let hashed = format!( | ||
| 419 | "{:x}", | ||
| 420 | Sha256::digest(password.as_bytes()) | ||
| 421 | ); | ||
| 422 | let msg = format!("AUTH {} {}", username, hashed); | ||
| 423 | let s = self.stream.as_mut().unwrap(); | ||
| 424 | let writer_stream = s.try_clone().unwrap(); | ||
| 425 | let mut writer = writer_stream; | ||
| 426 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 427 | let mut response_reader = | ||
| 428 | BufReader::new(response_reader_stream); | ||
| 429 | if let Err(_) = writeln!(writer, "{}", msg) { | ||
| 430 | self.stream = None; | ||
| 431 | println!("\nConnection lost"); | ||
| 432 | break; | ||
| 433 | } | ||
| 434 | let mut response = String::new(); | ||
| 435 | if let Err(_) = | ||
| 436 | response_reader.read_line(&mut response) | ||
| 437 | { | ||
| 438 | self.stream = None; | ||
| 439 | println!("\nConnection lost"); | ||
| 440 | break; | ||
| 441 | } else { | ||
| 442 | let resp = response.trim(); | ||
| 443 | if resp.starts_with("OK") { | ||
| 444 | // Save credentials | ||
| 445 | if let Some(addr) = &self.addr { | ||
| 446 | if let Err(e) = | ||
| 447 | database::save_credentials( | ||
| 448 | &self.conn, addr, &username, | ||
| 449 | &hashed, | ||
| 450 | ) | ||
| 451 | { | ||
| 452 | println!("Failed to save credentials: {}", e); | ||
| 453 | } | ||
| 454 | } | ||
| 455 | self.logged_in_user = | ||
| 456 | Some(username.clone()); | ||
| 457 | println!("\nLogged in as {}", username); | ||
| 458 | break; | ||
| 459 | } else if resp.starts_with("ERROR ") { | ||
| 460 | println!( | ||
| 461 | "\nAuthentication failed: {}", | ||
| 462 | &resp[6..] | ||
| 463 | ); | ||
| 464 | println!( | ||
| 465 | "Try again or press Enter to cancel." | ||
| 466 | ); | ||
| 467 | } else { | ||
| 468 | println!("\n{}", resp); | ||
| 252 | } | 469 | } |
| 253 | } | 470 | } |
| 254 | self.logged_in_user = Some(username.clone()); | 471 | } |
| 255 | println!("\nLogged in as {}", username); | 472 | } |
| 256 | break; | 473 | "setcred" => { |
| 257 | } else if resp.starts_with("ERROR ") { | 474 | if self.addr.is_none() { |
| 258 | println!("\nAuthentication failed: {}", &resp[6..]); | 475 | println!("\nNo host connected. Use 'connect <ip>:<port>' first."); |
| 259 | println!("Try again or press Enter to cancel."); | 476 | continue 'outer; |
| 477 | } | ||
| 478 | let username = read_line("\nUsername: "); | ||
| 479 | if username.is_empty() { | ||
| 480 | continue 'outer; | ||
| 481 | } | ||
| 482 | let password = read_password("Password: "); | ||
| 483 | if password.is_empty() { | ||
| 484 | continue 'outer; | ||
| 485 | } | ||
| 486 | let hashed = format!( | ||
| 487 | "{:x}", | ||
| 488 | Sha256::digest(password.as_bytes()) | ||
| 489 | ); | ||
| 490 | if let Some(addr) = &self.addr { | ||
| 491 | if let Err(e) = database::save_credentials( | ||
| 492 | &self.conn, addr, &username, &hashed, | ||
| 493 | ) { | ||
| 494 | println!("Failed to save credentials: {}", e); | ||
| 495 | } else { | ||
| 496 | println!("Credentials saved for {}", addr); | ||
| 497 | } | ||
| 498 | } | ||
| 499 | } | ||
| 500 | "logout" => { | ||
| 501 | self.logged_in_user = None; | ||
| 502 | println!("\nLogged out"); | ||
| 503 | } | ||
| 504 | "info" => { | ||
| 505 | if self.stream.is_none() { | ||
| 506 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); | ||
| 507 | continue 'outer; | ||
| 508 | } | ||
| 509 | let s = self.stream.as_mut().unwrap(); | ||
| 510 | let writer_stream = s.try_clone().unwrap(); | ||
| 511 | let mut writer = writer_stream; | ||
| 512 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 513 | let mut response_reader = | ||
| 514 | BufReader::new(response_reader_stream); | ||
| 515 | if let Err(_) = writeln!(writer, "INFO") { | ||
| 516 | self.stream = None; | ||
| 517 | println!("\nConnection lost"); | ||
| 518 | continue 'outer; | ||
| 519 | } | ||
| 520 | let mut response = String::new(); | ||
| 521 | if let Err(_) = response_reader.read_line(&mut response) | ||
| 522 | { | ||
| 523 | self.stream = None; | ||
| 524 | println!("\nConnection lost"); | ||
| 260 | } else { | 525 | } else { |
| 261 | println!("\n{}", resp); | 526 | let resp = response.trim(); |
| 527 | if resp.starts_with("INFO ") { | ||
| 528 | let parts: Vec<&str> = | ||
| 529 | resp.split_whitespace().collect(); | ||
| 530 | if parts.len() == 4 { | ||
| 531 | println!("\nLogged in as {} (ID: {})\r\nBalance: {} PLN", parts[1], parts[2], parts[3]); | ||
| 532 | } else { | ||
| 533 | println!("\n{}", resp); | ||
| 534 | } | ||
| 535 | } else if resp.starts_with("ERROR ") { | ||
| 536 | println!("\nError: {}", &resp[6..]); | ||
| 537 | } else { | ||
| 538 | println!("\n{}", resp); | ||
| 539 | } | ||
| 262 | } | 540 | } |
| 263 | } | 541 | } |
| 264 | } | 542 | "help" => { |
| 265 | } | 543 | if self.game_manager.is_in_game() { |
| 266 | "setcred" => { | 544 | if let Some(game) = |
| 267 | if self.addr.is_none() { | 545 | self.game_manager.get_current_game() |
| 268 | println!("\nNo host connected. Use 'connect <ip>:<port>' first."); | 546 | { |
| 269 | continue 'outer; | 547 | println!("\n{}", game.get_help()); |
| 270 | } | 548 | } |
| 271 | let username = read_line("\nUsername: "); | 549 | } else { |
| 272 | if username.is_empty() { | 550 | help(); |
| 273 | continue 'outer; | 551 | } |
| 274 | } | ||
| 275 | let password = read_password("Password: "); | ||
| 276 | if password.is_empty() { | ||
| 277 | continue 'outer; | ||
| 278 | } | ||
| 279 | let hashed = format!("{:x}", Sha256::digest(password.as_bytes())); | ||
| 280 | if let Some(addr) = &self.addr { | ||
| 281 | if let Err(e) = database::save_credentials(&self.conn, addr, &username, &hashed) { | ||
| 282 | println!("Failed to save credentials: {}", e); | ||
| 283 | } else { | ||
| 284 | println!("Credentials saved for {}", addr); | ||
| 285 | } | 552 | } |
| 553 | "quit" => { | ||
| 554 | if self.game_manager.is_in_game() { | ||
| 555 | self.game_manager.end_game(); | ||
| 556 | println!("\nGame exited."); | ||
| 557 | } else { | ||
| 558 | restore_terminal(&self.cleanup_done); | ||
| 559 | break 'outer; | ||
| 560 | } | ||
| 561 | } | ||
| 562 | _ => help(), | ||
| 286 | } | 563 | } |
| 564 | break; | ||
| 287 | } | 565 | } |
| 288 | "logout" => { | 566 | KeyCode::Backspace => { |
| 289 | self.logged_in_user = None; | 567 | if !current_input.is_empty() { |
| 290 | println!("\nLogged out"); | 568 | current_input.pop(); |
| 291 | } | 569 | execute!( |
| 292 | "info" => { | 570 | stdout(), |
| 293 | if self.stream.is_none() { | 571 | MoveToColumn(2), |
| 294 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); | 572 | Clear(ClearType::UntilNewLine) |
| 295 | continue 'outer; | 573 | ) |
| 296 | } | 574 | .unwrap(); |
| 297 | let s = self.stream.as_mut().unwrap(); | 575 | print!("{}", current_input); |
| 298 | let writer_stream = s.try_clone().unwrap(); | 576 | stdout().flush().unwrap(); |
| 299 | let mut writer = writer_stream; | ||
| 300 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 301 | let mut response_reader = BufReader::new(response_reader_stream); | ||
| 302 | if let Err(_) = writeln!(writer, "INFO") { | ||
| 303 | self.stream = None; | ||
| 304 | println!("\nConnection lost"); | ||
| 305 | continue 'outer; | ||
| 306 | } | 577 | } |
| 307 | let mut response = String::new(); | 578 | } |
| 308 | if let Err(_) = response_reader.read_line(&mut response) { | 579 | KeyCode::Char(c) => { |
| 309 | self.stream = None; | 580 | if key.modifiers.contains(KeyModifiers::CONTROL) && c == 'c' { |
| 310 | println!("\nConnection lost"); | 581 | restore_terminal(&self.cleanup_done); |
| 582 | std::process::exit(0); | ||
| 583 | } else if key.modifiers.contains(KeyModifiers::CONTROL) | ||
| 584 | && c == 'd' | ||
| 585 | { | ||
| 586 | restore_terminal(&self.cleanup_done); | ||
| 587 | std::process::exit(0); | ||
| 588 | } else if c == '\t' { | ||
| 589 | self.handle_tab_completion(&mut current_input); | ||
| 311 | } else { | 590 | } else { |
| 312 | let resp = response.trim(); | 591 | current_input.push(c); |
| 313 | if resp.starts_with("INFO ") { | 592 | print!("{}", c); |
| 314 | let parts: Vec<&str> = resp.split_whitespace().collect(); | 593 | stdout().flush().unwrap(); |
| 315 | if parts.len() == 4 { | ||
| 316 | println!("\nLogged in as {} (ID: {})\r\nBalance: {} PLN", parts[1], parts[2], parts[3]); | ||
| 317 | } else { | ||
| 318 | println!("\n{}", resp); | ||
| 319 | } | ||
| 320 | } else if resp.starts_with("ERROR ") { | ||
| 321 | println!("\nError: {}", &resp[6..]); | ||
| 322 | } else { | ||
| 323 | println!("\n{}", resp); | ||
| 324 | } | ||
| 325 | } | 594 | } |
| 326 | } | 595 | } |
| 327 | "help" => { | 596 | KeyCode::Tab => { |
| 328 | help(); | 597 | self.handle_tab_completion(&mut current_input); |
| 329 | } | 598 | } |
| 330 | "quit" => break 'outer, | 599 | _ => {} |
| 331 | _ => help(), | ||
| 332 | } | ||
| 333 | break; | ||
| 334 | } | ||
| 335 | KeyCode::Backspace => { | ||
| 336 | if !current_input.is_empty() { | ||
| 337 | current_input.pop(); | ||
| 338 | execute!(stdout(), MoveToColumn(2), | ||
| 339 | Clear(ClearType::UntilNewLine)).unwrap(); | ||
| 340 | print!("{}", current_input); | ||
| 341 | stdout().flush().unwrap(); | ||
| 342 | } | 600 | } |
| 343 | } | 601 | } |
| 344 | KeyCode::Char(c) => { | ||
| 345 | if key.modifiers.contains(KeyModifiers::CONTROL) && c == 'c' { | ||
| 346 | execute!(stdout(), Show).unwrap(); | ||
| 347 | return; | ||
| 348 | } else if c == '\t' { | ||
| 349 | self.handle_tab_completion(&mut current_input); | ||
| 350 | } else { | ||
| 351 | current_input.push(c); | ||
| 352 | print!("{}", c); | ||
| 353 | stdout().flush().unwrap(); | ||
| 354 | } | ||
| 355 | } | ||
| 356 | KeyCode::Tab => { | ||
| 357 | self.handle_tab_completion(&mut current_input); | ||
| 358 | } | ||
| 359 | _ => {} | ||
| 360 | } | ||
| 361 | } | 602 | } |
| 362 | } | ||
| 363 | _ => {} | 603 | _ => {} |
| 364 | } | 604 | } |
| 365 | } | 605 | } |
| 366 | } | 606 | } |
| 367 | disable_raw_mode().unwrap(); | 607 | restore_terminal(&self.cleanup_done); |
| 368 | execute!(stdout(), Show).unwrap(); | ||
| 369 | } | 608 | } |
| 370 | 609 | ||
| 371 | fn handle_tab_completion(&mut self, current_input: &mut String) { | 610 | fn handle_tab_completion(&mut self, current_input: &mut String) { |
| @@ -387,6 +626,77 @@ impl ClientState { | |||
| 387 | } | 626 | } |
| 388 | // else do nothing | 627 | // else do nothing |
| 389 | } | 628 | } |
| 629 | |||
| 630 | fn play_spin_animation(&mut self, stream: &mut TlsStream) { | ||
| 631 | use std::io::Write; | ||
| 632 | use std::thread; | ||
| 633 | use std::time::Duration; | ||
| 634 | |||
| 635 | let symbols = ['C', 'L', 'O', 'G', 'D', 'S']; | ||
| 636 | let frames = 20; | ||
| 637 | let delay = Duration::from_millis(50); | ||
| 638 | |||
| 639 | for _ in 0..frames { | ||
| 640 | let reels: String = (0..3) | ||
| 641 | .map(|_| symbols[rand::random::<usize>() % symbols.len()]) | ||
| 642 | .collect(); | ||
| 643 | |||
| 644 | execute!(stdout(), Clear(ClearType::CurrentLine), MoveToColumn(0)).unwrap(); | ||
| 645 | print!( | ||
| 646 | "[ {} ] [ {} ] [ {} ]", | ||
| 647 | reels.chars().nth(0).unwrap(), | ||
| 648 | reels.chars().nth(1).unwrap(), | ||
| 649 | reels.chars().nth(2).unwrap() | ||
| 650 | ); | ||
| 651 | stdout().flush().unwrap(); | ||
| 652 | |||
| 653 | thread::sleep(delay); | ||
| 654 | } | ||
| 655 | |||
| 656 | let mut response_str = String::new(); | ||
| 657 | let mut reader = std::io::BufReader::new(stream.try_clone().unwrap()); | ||
| 658 | if reader.read_line(&mut response_str).is_ok() { | ||
| 659 | let response = response_str.trim(); | ||
| 660 | |||
| 661 | if response.starts_with("GAME_RESULT") { | ||
| 662 | let parts: Vec<&str> = response.split_whitespace().collect(); | ||
| 663 | if parts.len() >= 7 { | ||
| 664 | let reels = parts[5]; | ||
| 665 | let win_type = parts[6]; | ||
| 666 | |||
| 667 | execute!(stdout(), Clear(ClearType::CurrentLine), MoveToColumn(0)).unwrap(); | ||
| 668 | print!( | ||
| 669 | "[ {} ] [ {} ] [ {} ]", | ||
| 670 | reels.chars().nth(0).unwrap_or('?'), | ||
| 671 | reels.chars().nth(1).unwrap_or('?'), | ||
| 672 | reels.chars().nth(2).unwrap_or('?') | ||
| 673 | ); | ||
| 674 | println!(); | ||
| 675 | |||
| 676 | if win_type == "jackpot" { | ||
| 677 | println!("\nđ JACKPOT! đ"); | ||
| 678 | } else if win_type == "two" { | ||
| 679 | println!("\n⨠Two of a kind! â¨"); | ||
| 680 | } else { | ||
| 681 | println!("\nNo win."); | ||
| 682 | } | ||
| 683 | |||
| 684 | println!("Winnings: {} PLN", parts[2]); | ||
| 685 | println!("New Balance: {} PLN", parts[4]); | ||
| 686 | stdout().flush().unwrap(); | ||
| 687 | } | ||
| 688 | } | ||
| 689 | } | ||
| 690 | } | ||
| 691 | } | ||
| 692 | |||
| 693 | fn restore_terminal(cleanup_done: &Arc<Mutex<bool>>) { | ||
| 694 | let mut done = cleanup_done.lock().unwrap(); | ||
| 695 | if !*done { | ||
| 696 | let _ = disable_raw_mode(); | ||
| 697 | let _ = execute!(stdout(), Show); | ||
| 698 | *done = true; | ||
| 699 | } | ||
| 390 | } | 700 | } |
| 391 | 701 | ||
| 392 | fn get_commands() -> Vec<String> { | 702 | fn get_commands() -> Vec<String> { |
| @@ -400,13 +710,15 @@ fn get_commands() -> Vec<String> { | |||
| 400 | "setcred".to_string(), | 710 | "setcred".to_string(), |
| 401 | "logout".to_string(), | 711 | "logout".to_string(), |
| 402 | "info".to_string(), | 712 | "info".to_string(), |
| 713 | "play".to_string(), | ||
| 403 | "help".to_string(), | 714 | "help".to_string(), |
| 404 | "quit".to_string(), | 715 | "quit".to_string(), |
| 405 | ] | 716 | ] |
| 406 | } | 717 | } |
| 407 | 718 | ||
| 408 | fn help() { | 719 | fn help() { |
| 409 | println!("\nAvailable commands:\r | 720 | println!( |
| 721 | "\nAvailable commands:\r | ||
| 410 | connect <ip>:<port> - connect to the server\r | 722 | connect <ip>:<port> - connect to the server\r |
| 411 | send <message> - send a message to the server\r | 723 | send <message> - send a message to the server\r |
| 412 | disconnect - disconnect from the server\r | 724 | disconnect - disconnect from the server\r |
| @@ -416,8 +728,38 @@ fn help() { | |||
| 416 | setcred - set credentials for a host\r | 728 | setcred - set credentials for a host\r |
| 417 | logout - logout from the server\r | 729 | logout - logout from the server\r |
| 418 | info - show login info\r | 730 | info - show login info\r |
| 731 | play <game> [bet] - start a game (e.g., play slots 50)\r | ||
| 419 | help - show this message\r | 732 | help - show this message\r |
| 420 | quit - quit the program"); | 733 | quit - quit the program" |
| 734 | ); | ||
| 735 | } | ||
| 736 | |||
| 737 | fn redraw_game(game_output: &str) { | ||
| 738 | execute!( | ||
| 739 | stdout(), | ||
| 740 | Clear(ClearType::CurrentLine), | ||
| 741 | Clear(ClearType::FromCursorDown), | ||
| 742 | MoveToColumn(0) | ||
| 743 | ) | ||
| 744 | .unwrap(); | ||
| 745 | println!("{}", game_output); | ||
| 746 | stdout().flush().unwrap(); | ||
| 747 | } | ||
| 748 | |||
| 749 | fn redraw_game_lines(game_output: &str, lines: usize) { | ||
| 750 | use crossterm::cursor::MoveUp; | ||
| 751 | if lines > 0 { | ||
| 752 | execute!(stdout(), MoveUp(lines as u16)).unwrap(); | ||
| 753 | } | ||
| 754 | execute!( | ||
| 755 | stdout(), | ||
| 756 | Clear(ClearType::CurrentLine), | ||
| 757 | Clear(ClearType::FromCursorDown), | ||
| 758 | MoveToColumn(0) | ||
| 759 | ) | ||
| 760 | .unwrap(); | ||
| 761 | println!("{}", game_output); | ||
| 762 | stdout().flush().unwrap(); | ||
| 421 | } | 763 | } |
| 422 | 764 | ||
| 423 | fn redraw(current_input: &str) { | 765 | fn redraw(current_input: &str) { |
| @@ -431,36 +773,40 @@ fn read_line(prompt: &str) -> String { | |||
| 431 | execute!(stdout(), Show).unwrap(); | 773 | execute!(stdout(), Show).unwrap(); |
| 432 | printsame!("{}", prompt); | 774 | printsame!("{}", prompt); |
| 433 | stdout().flush().unwrap(); | 775 | stdout().flush().unwrap(); |
| 434 | 776 | ||
| 435 | let mut input = String::new(); | 777 | let mut input = String::new(); |
| 436 | let prompt_len = prompt.len(); | 778 | let prompt_len = prompt.len(); |
| 437 | 779 | ||
| 438 | loop { | 780 | loop { |
| 439 | match read().unwrap() { | 781 | match read().unwrap() { |
| 440 | Event::Key(key) => { | 782 | Event::Key(key) => { |
| 441 | if key.kind == crossterm::event::KeyEventKind::Press { | 783 | if key.kind == crossterm::event::KeyEventKind::Press { |
| 442 | match key.code { | 784 | match key.code { |
| 443 | KeyCode::Enter => { | 785 | KeyCode::Enter => { |
| 444 | println!(); | 786 | println!(); |
| 445 | break; | 787 | break; |
| 446 | } | 788 | } |
| 447 | KeyCode::Backspace => { | 789 | KeyCode::Backspace => { |
| 448 | if !input.is_empty() { | 790 | if !input.is_empty() { |
| 449 | input.pop(); | 791 | input.pop(); |
| 450 | // Move cursor back and clear from current position to end of line | 792 | // Move cursor back and clear from current position to end of line |
| 451 | execute!(stdout(), MoveToColumn((prompt_len + input.len() + 1) as u16), | 793 | execute!( |
| 452 | Clear(ClearType::UntilNewLine)).unwrap(); | 794 | stdout(), |
| 453 | stdout().flush().unwrap(); | 795 | MoveToColumn((prompt_len + input.len() + 1) as u16), |
| 796 | Clear(ClearType::UntilNewLine) | ||
| 797 | ) | ||
| 798 | .unwrap(); | ||
| 799 | stdout().flush().unwrap(); | ||
| 800 | } | ||
| 801 | } | ||
| 802 | KeyCode::Char(c) => { | ||
| 803 | input.push(c); | ||
| 804 | print!("{}", c); | ||
| 805 | stdout().flush().unwrap(); | ||
| 806 | } | ||
| 807 | _ => {} | ||
| 454 | } | 808 | } |
| 455 | } | 809 | } |
| 456 | KeyCode::Char(c) => { | ||
| 457 | input.push(c); | ||
| 458 | print!("{}", c); | ||
| 459 | stdout().flush().unwrap(); | ||
| 460 | } | ||
| 461 | _ => {} | ||
| 462 | } | ||
| 463 | } | ||
| 464 | } | 810 | } |
| 465 | _ => {} | 811 | _ => {} |
| 466 | } | 812 | } |
| @@ -472,36 +818,40 @@ fn read_password(prompt: &str) -> String { | |||
| 472 | execute!(stdout(), Show).unwrap(); | 818 | execute!(stdout(), Show).unwrap(); |
| 473 | print!("{}", prompt); | 819 | print!("{}", prompt); |
| 474 | stdout().flush().unwrap(); | 820 | stdout().flush().unwrap(); |
| 475 | 821 | ||
| 476 | let mut input = String::new(); | 822 | let mut input = String::new(); |
| 477 | let prompt_len = prompt.len(); | 823 | let prompt_len = prompt.len(); |
| 478 | 824 | ||
| 479 | loop { | 825 | loop { |
| 480 | match read().unwrap() { | 826 | match read().unwrap() { |
| 481 | Event::Key(key) => { | 827 | Event::Key(key) => { |
| 482 | if key.kind == crossterm::event::KeyEventKind::Press { | 828 | if key.kind == crossterm::event::KeyEventKind::Press { |
| 483 | match key.code { | 829 | match key.code { |
| 484 | KeyCode::Enter => { | 830 | KeyCode::Enter => { |
| 485 | println!(); | 831 | println!(); |
| 486 | break; | 832 | break; |
| 487 | } | 833 | } |
| 488 | KeyCode::Backspace => { | 834 | KeyCode::Backspace => { |
| 489 | if !input.is_empty() { | 835 | if !input.is_empty() { |
| 490 | input.pop(); | 836 | input.pop(); |
| 491 | // Move cursor back and clear from current position to end of line | 837 | // Move cursor back and clear from current position to end of line |
| 492 | execute!(stdout(), MoveToColumn((prompt_len + input.len() + 1) as u16), | 838 | execute!( |
| 493 | Clear(ClearType::UntilNewLine)).unwrap(); | 839 | stdout(), |
| 494 | stdout().flush().unwrap(); | 840 | MoveToColumn((prompt_len + input.len() + 1) as u16), |
| 841 | Clear(ClearType::UntilNewLine) | ||
| 842 | ) | ||
| 843 | .unwrap(); | ||
| 844 | stdout().flush().unwrap(); | ||
| 845 | } | ||
| 846 | } | ||
| 847 | KeyCode::Char(c) => { | ||
| 848 | input.push(c); | ||
| 849 | print!("*"); | ||
| 850 | stdout().flush().unwrap(); | ||
| 851 | } | ||
| 852 | _ => {} | ||
| 495 | } | 853 | } |
| 496 | } | 854 | } |
| 497 | KeyCode::Char(c) => { | ||
| 498 | input.push(c); | ||
| 499 | print!("*"); | ||
| 500 | stdout().flush().unwrap(); | ||
| 501 | } | ||
| 502 | _ => {} | ||
| 503 | } | ||
| 504 | } | ||
| 505 | } | 855 | } |
| 506 | _ => {} | 856 | _ => {} |
| 507 | } | 857 | } |
