summaryrefslogtreecommitdiffstats
path: root/src/bin/client/repl.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-29 20:43:09 +0100
committersalmonik <salmonik@tuta.io>2026-10-29 20:43:09 +0100
commit6e2787d792de64c003eff03b62a2df798ef851c0 (patch)
tree0e9bf97b2f71df5fb9eb00d5195d32b8326886ff /src/bin/client/repl.rs
parentv2.2.1: forfeit blackjack bets when leaving or disconnecting (diff)
downloadzagan-bet-6e2787d792de64c003eff03b62a2df798ef851c0.tar.gz
zagan-bet-6e2787d792de64c003eff03b62a2df798ef851c0.zip
v2.3.0: add crash with local multiplier and pushed crash event
Diffstat (limited to 'src/bin/client/repl.rs')
-rw-r--r--src/bin/client/repl.rs123
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) => {