diff options
Diffstat (limited to 'src/bin/client/repl.rs')
| -rw-r--r-- | src/bin/client/repl.rs | 123 |
1 files changed, 100 insertions, 23 deletions
diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs index f85af47..7fa462a 100644 --- a/src/bin/client/repl.rs +++ b/src/bin/client/repl.rs | |||
| @@ -102,29 +102,76 @@ impl ClientState { | |||
| 102 | self.apply_prompt(); | 102 | self.apply_prompt(); |
| 103 | 103 | ||
| 104 | loop { | 104 | loop { |
| 105 | let event = read().unwrap(); | 105 | let mut quit = false; |
| 106 | match event { | 106 | |
| 107 | Event::Key(key) => { | 107 | if crossterm::event::poll(std::time::Duration::from_millis(25)).unwrap_or(false) { |
| 108 | if key.kind != KeyEventKind::Press { | 108 | match read().unwrap() { |
| 109 | continue; | 109 | Event::Key(key) => { |
| 110 | } | 110 | if key.kind == KeyEventKind::Press { |
| 111 | let Some(key) = map_key(key) else { | 111 | if let Some(key) = map_key(key) { |
| 112 | continue; | 112 | let effects = self.core.handle_key(key); |
| 113 | }; | 113 | quit = self.apply_effects(effects); |
| 114 | let effects = self.core.handle_key(key); | 114 | self.apply_prompt(); |
| 115 | if self.apply_effects(effects) { | 115 | } |
| 116 | break; | 116 | } |
| 117 | } | 117 | } |
| 118 | self.apply_prompt(); | 118 | Event::Resize(_, _) => self.apply_prompt(), |
| 119 | _ => {} | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | if !quit && self.core.expects_push() { | ||
| 124 | quit = self.pump_async(); | ||
| 125 | } | ||
| 126 | |||
| 127 | if !quit { | ||
| 128 | let effects = self.core.tick(); | ||
| 129 | if !effects.is_empty() { | ||
| 130 | quit = self.apply_effects(effects); | ||
| 119 | } | 131 | } |
| 120 | Event::Resize(_, _) => self.apply_prompt(), | 132 | } |
| 121 | _ => {} | 133 | |
| 134 | if quit { | ||
| 135 | break; | ||
| 122 | } | 136 | } |
| 123 | } | 137 | } |
| 124 | 138 | ||
| 125 | restore_terminal(&self.cleanup_done); | 139 | restore_terminal(&self.cleanup_done); |
| 126 | } | 140 | } |
| 127 | 141 | ||
| 142 | /// Read any server-initiated lines that are already waiting and apply them. | ||
| 143 | /// Returns true when an effect asked the program to quit. | ||
| 144 | fn pump_async(&mut self) -> bool { | ||
| 145 | loop { | ||
| 146 | let Some(mut stream) = self.stream.take() else { | ||
| 147 | return false; | ||
| 148 | }; | ||
| 149 | let line = match stream.read_available_line() { | ||
| 150 | Ok(line) => { | ||
| 151 | self.stream = Some(stream); | ||
| 152 | line | ||
| 153 | } | ||
| 154 | Err(_) => { | ||
| 155 | self.core.set_connected(false); | ||
| 156 | self.stream = None; | ||
| 157 | return false; | ||
| 158 | } | ||
| 159 | }; | ||
| 160 | |||
| 161 | let Some(line) = line else { | ||
| 162 | return false; | ||
| 163 | }; | ||
| 164 | |||
| 165 | let effects = match self.core.handle_async_line(&line) { | ||
| 166 | Some(effects) => effects, | ||
| 167 | None => self.core.handle_reply(&line), | ||
| 168 | }; | ||
| 169 | if self.apply_effects(effects) { | ||
| 170 | return true; | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 128 | fn apply_effects(&mut self, effects: Vec<Effect>) -> bool { | 175 | fn apply_effects(&mut self, effects: Vec<Effect>) -> bool { |
| 129 | for effect in effects { | 176 | for effect in effects { |
| 130 | match effect { | 177 | match effect { |
| @@ -250,6 +297,9 @@ impl ClientState { | |||
| 250 | let roulette = lines | 297 | let roulette = lines |
| 251 | .first() | 298 | .first() |
| 252 | .is_some_and(|first| first.contains("ROULETTE")); | 299 | .is_some_and(|first| first.contains("ROULETTE")); |
| 300 | let crash = lines | ||
| 301 | .first() | ||
| 302 | .is_some_and(|first| first.contains("CRASH")); | ||
| 253 | let rendered: Vec<String> = lines | 303 | let rendered: Vec<String> = lines |
| 254 | .iter() | 304 | .iter() |
| 255 | .map(|line| { | 305 | .map(|line| { |
| @@ -263,6 +313,18 @@ impl ClientState { | |||
| 263 | } else { | 313 | } else { |
| 264 | style_roulette_numbers(line) | 314 | style_roulette_numbers(line) |
| 265 | } | 315 | } |
| 316 | } else if crash { | ||
| 317 | if line.contains("CRASHED") { | ||
| 318 | line.as_str().bold().red().to_string() | ||
| 319 | } else if line.starts_with("Cashed out") { | ||
| 320 | line.as_str().bold().green().to_string() | ||
| 321 | } else if line.starts_with("Multiplier:") { | ||
| 322 | line.as_str().yellow().to_string() | ||
| 323 | } else if line.starts_with("ERROR:") { | ||
| 324 | line.as_str().red().to_string() | ||
| 325 | } else { | ||
| 326 | line.clone() | ||
| 327 | } | ||
| 266 | } else if line.contains('{') { | 328 | } else if line.contains('{') { |
| 267 | style_winning_cells(line) | 329 | style_winning_cells(line) |
| 268 | } else if line.contains("WIN!") || line.starts_with("Won ") { | 330 | } else if line.contains("WIN!") || line.starts_with("Won ") { |
| @@ -361,22 +423,37 @@ impl ClientState { | |||
| 361 | return "ERROR Not connected".to_string(); | 423 | return "ERROR Not connected".to_string(); |
| 362 | }; | 424 | }; |
| 363 | 425 | ||
| 426 | // A crash event can be pushed while a request is in flight. Route those | ||
| 427 | // lines as they arrive and keep waiting for the actual reply. | ||
| 428 | let mut pushed: Vec<String> = Vec::new(); | ||
| 364 | let result = (|| -> std::io::Result<String> { | 429 | let result = (|| -> std::io::Result<String> { |
| 365 | stream.write_line(line)?; | 430 | stream.write_line(line)?; |
| 366 | let mut response = String::new(); | 431 | loop { |
| 367 | let read = stream.read_line(&mut response)?; | 432 | let mut response = String::new(); |
| 368 | if read == 0 { | 433 | let read = stream.read_line(&mut response)?; |
| 369 | return Err(std::io::Error::new( | 434 | if read == 0 { |
| 370 | std::io::ErrorKind::UnexpectedEof, | 435 | return Err(std::io::Error::new( |
| 371 | "connection closed", | 436 | std::io::ErrorKind::UnexpectedEof, |
| 372 | )); | 437 | "connection closed", |
| 438 | )); | ||
| 439 | } | ||
| 440 | let response = response.trim_end().to_string(); | ||
| 441 | if self.core.is_async(&response) { | ||
| 442 | pushed.push(response); | ||
| 443 | continue; | ||
| 444 | } | ||
| 445 | return Ok(response); | ||
| 373 | } | 446 | } |
| 374 | Ok(response) | ||
| 375 | })(); | 447 | })(); |
| 376 | 448 | ||
| 377 | match result { | 449 | match result { |
| 378 | Ok(response) => { | 450 | Ok(response) => { |
| 379 | self.stream = Some(stream); | 451 | self.stream = Some(stream); |
| 452 | for line in pushed { | ||
| 453 | if let Some(effects) = self.core.handle_async_line(&line) { | ||
| 454 | self.apply_effects(effects); | ||
| 455 | } | ||
| 456 | } | ||
| 380 | response | 457 | response |
| 381 | } | 458 | } |
| 382 | Err(error) => { | 459 | Err(error) => { |
