summaryrefslogtreecommitdiffstats
path: root/src/bin/client/repl.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/bin/client/repl.rs990
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 @@
1use std::io::{BufRead, BufReader, Write, stdout};
2use std::net::TcpStream;
3use rusqlite::Connection; 1use rusqlite::Connection;
2use std::io::{stdout, BufRead, BufReader, Write};
3use std::sync::{Arc, Mutex};
4use std::time::Instant; 4use std::time::Instant;
5 5
6use crate::connection; 6use crate::connection::TlsStream;
7use crate::database; 7use crate::database;
8use crate::games::manager::GameManager;
8 9
9use crossterm::{event::{read, Event, KeyCode, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, cursor::{MoveToColumn, Show, Hide}, execute}; 10use 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
11use sha2::{Sha256, Digest}; 17use sha2::{Digest, Sha256};
12 18
13pub struct ClientState { 19pub 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
23impl ClientState { 32impl 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(&current_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(&current_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(&current_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(&current_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
693fn 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
392fn get_commands() -> Vec<String> { 702fn 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
408fn help() { 719fn 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
737fn 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
749fn 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
423fn redraw(current_input: &str) { 765fn 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 }