diff options
Diffstat (limited to 'src/client/core.rs')
| -rw-r--r-- | src/client/core.rs | 182 |
1 files changed, 135 insertions, 47 deletions
diff --git a/src/client/core.rs b/src/client/core.rs index 6d8294c..278dc00 100644 --- a/src/client/core.rs +++ b/src/client/core.rs | |||
| @@ -3,17 +3,16 @@ use crate::client::games::response::{ | |||
| 3 | parse_blackjack_response, parse_crash_response, parse_history_response, | 3 | parse_blackjack_response, parse_crash_response, parse_history_response, |
| 4 | parse_roulette_response, parse_spin_response, | 4 | parse_roulette_response, parse_spin_response, |
| 5 | }; | 5 | }; |
| 6 | use crate::client::ui::{game_lines, history_lines}; | ||
| 6 | use crate::client::view::{ | 7 | use crate::client::view::{ |
| 7 | BlackjackView, Command, CrashView, HistoryView, LobbyView, RouletteView, SlotsView, View, | 8 | BlackjackView, Command, CrashView, HistoryView, LobbyView, RouletteView, SlotsView, View, |
| 8 | }; | 9 | }; |
| 9 | use crate::client::ui::{game_lines, history_lines}; | ||
| 10 | use crate::crash::{CrashState, multiplier_at}; | 10 | use crate::crash::{CrashState, multiplier_at}; |
| 11 | use crate::history::{PlayRecord, MAX_HISTORY}; | 11 | use crate::history::{MAX_HISTORY, PlayRecord}; |
| 12 | use crate::roulette::{BetTarget, Bets}; | 12 | use crate::roulette::{BetTarget, Bets}; |
| 13 | use crate::version::{parse_pong, unix_time_ms}; | ||
| 13 | use std::time::{Duration, Instant}; | 14 | use std::time::{Duration, Instant}; |
| 14 | 15 | ||
| 15 | |||
| 16 | |||
| 17 | /// High-level actions produced by the client core. The terminal adapter is | 16 | /// High-level actions produced by the client core. The terminal adapter is |
| 18 | /// responsible for turning these into crossterm calls and network writes. | 17 | /// responsible for turning these into crossterm calls and network writes. |
| 19 | #[derive(Debug, Clone, PartialEq)] | 18 | #[derive(Debug, Clone, PartialEq)] |
| @@ -24,8 +23,8 @@ pub enum Effect { | |||
| 24 | Render(View), | 23 | Render(View), |
| 25 | /// Remove the game board region. | 24 | /// Remove the game board region. |
| 26 | ClearRegion, | 25 | ClearRegion, |
| 27 | /// Send one protocol line and feed the reply to `handle_reply`. | 26 | /// Send one enveloped request and route its reply back by `id`. |
| 28 | SendLine(String), | 27 | SendRequest { id: u64, payload: String }, |
| 29 | /// Drop the connection. | 28 | /// Drop the connection. |
| 30 | Disconnect, | 29 | Disconnect, |
| 31 | /// Leave the program. | 30 | /// Leave the program. |
| @@ -61,6 +60,10 @@ pub struct ClientCore { | |||
| 61 | connected: bool, | 60 | connected: bool, |
| 62 | logged_in: Option<String>, | 61 | logged_in: Option<String>, |
| 63 | pending: Option<Pending>, | 62 | pending: Option<Pending>, |
| 63 | /// Id of the request whose reply fills `pending`. | ||
| 64 | pending_id: Option<u64>, | ||
| 65 | /// Next correlation id allocated on this connection. | ||
| 66 | next_request_id: u64, | ||
| 64 | balance: Option<i64>, | 67 | balance: Option<i64>, |
| 65 | /// Account number from the last `INFO`, shown by both frontends. | 68 | /// Account number from the last `INFO`, shown by both frontends. |
| 66 | account: Option<String>, | 69 | account: Option<String>, |
| @@ -68,6 +71,13 @@ pub struct ClientCore { | |||
| 68 | fingerprint: Option<String>, | 71 | fingerprint: Option<String>, |
| 69 | /// Local anchor for the crash multiplier, set when a round starts. | 72 | /// Local anchor for the crash multiplier, set when a round starts. |
| 70 | crash_started: Option<Instant>, | 73 | crash_started: Option<Instant>, |
| 74 | /// Server-time anchor for the crash multiplier, when the server supplied | ||
| 75 | /// `started_at`. | ||
| 76 | crash_started_ms: Option<u64>, | ||
| 77 | /// Server clock minus local clock, in milliseconds, from `HELLO`/`PONG`. | ||
| 78 | server_offset_ms: i64, | ||
| 79 | /// True once a frontend has supplied a server timestamp. | ||
| 80 | clock_synced: bool, | ||
| 71 | /// App screen shown when no game is open. | 81 | /// App screen shown when no game is open. |
| 72 | screen: Screen, | 82 | screen: Screen, |
| 73 | /// Last plays fetched with `history`. | 83 | /// Last plays fetched with `history`. |
| @@ -83,11 +93,16 @@ impl ClientCore { | |||
| 83 | connected: false, | 93 | connected: false, |
| 84 | logged_in: None, | 94 | logged_in: None, |
| 85 | pending: None, | 95 | pending: None, |
| 96 | pending_id: None, | ||
| 97 | next_request_id: 1, | ||
| 86 | balance: None, | 98 | balance: None, |
| 87 | account: None, | 99 | account: None, |
| 88 | host: None, | 100 | host: None, |
| 89 | fingerprint: None, | 101 | fingerprint: None, |
| 90 | crash_started: None, | 102 | crash_started: None, |
| 103 | crash_started_ms: None, | ||
| 104 | server_offset_ms: 0, | ||
| 105 | clock_synced: false, | ||
| 91 | screen: Screen::Lobby, | 106 | screen: Screen::Lobby, |
| 92 | play_history: Vec::new(), | 107 | play_history: Vec::new(), |
| 93 | login_error: None, | 108 | login_error: None, |
| @@ -99,11 +114,19 @@ impl ClientCore { | |||
| 99 | if !connected { | 114 | if !connected { |
| 100 | self.logged_in = None; | 115 | self.logged_in = None; |
| 101 | self.pending = None; | 116 | self.pending = None; |
| 117 | self.pending_id = None; | ||
| 102 | self.crash_started = None; | 118 | self.crash_started = None; |
| 119 | self.crash_started_ms = None; | ||
| 120 | self.clock_synced = false; | ||
| 103 | self.screen = Screen::Lobby; | 121 | self.screen = Screen::Lobby; |
| 104 | } | 122 | } |
| 105 | } | 123 | } |
| 106 | 124 | ||
| 125 | /// The signed-in username, if any. | ||
| 126 | pub fn user(&self) -> Option<&str> { | ||
| 127 | self.logged_in.as_deref() | ||
| 128 | } | ||
| 129 | |||
| 107 | pub fn set_logged_in(&mut self, user: Option<String>) { | 130 | pub fn set_logged_in(&mut self, user: Option<String>) { |
| 108 | if user.is_some() { | 131 | if user.is_some() { |
| 109 | self.login_error = None; | 132 | self.login_error = None; |
| @@ -147,7 +170,44 @@ impl ClientCore { | |||
| 147 | self.board_effects() | 170 | self.board_effects() |
| 148 | } | 171 | } |
| 149 | 172 | ||
| 173 | /// Record the server's clock and the local time it was received. The | ||
| 174 | /// offset is for display; cash-out still settles on the server. | ||
| 175 | pub fn set_server_time(&mut self, server_ms: u64) { | ||
| 176 | self.server_offset_ms = server_ms as i64 - unix_time_ms() as i64; | ||
| 177 | self.clock_synced = true; | ||
| 178 | } | ||
| 179 | |||
| 180 | /// The game server's clock, as this client currently estimates it. | ||
| 181 | pub fn server_now_ms(&self) -> u64 { | ||
| 182 | (unix_time_ms() as i64 + self.server_offset_ms).max(0) as u64 | ||
| 183 | } | ||
| 184 | |||
| 185 | /// Allocate a correlation id for a request the adapter sends directly | ||
| 186 | /// (the terminal's login prompt bypasses `ClientCore`). | ||
| 187 | pub fn allocate_request_id(&mut self) -> u64 { | ||
| 188 | let id = self.next_request_id; | ||
| 189 | self.next_request_id = self.next_request_id.wrapping_add(1).max(1); | ||
| 190 | id | ||
| 191 | } | ||
| 192 | |||
| 193 | fn begin_request(&mut self, pending: Pending, payload: String) -> Effect { | ||
| 194 | let id = self.allocate_request_id(); | ||
| 195 | self.pending = Some(pending); | ||
| 196 | self.pending_id = Some(id); | ||
| 197 | Effect::SendRequest { id, payload } | ||
| 198 | } | ||
| 199 | |||
| 200 | /// Route a reply by correlation id. A reply we no longer expect (a late | ||
| 201 | /// response after a timeout) is ignored instead of corrupting the board. | ||
| 202 | pub fn handle_reply_id(&mut self, id: u64, reply: &str) -> Vec<Effect> { | ||
| 203 | if self.pending_id != Some(id) { | ||
| 204 | return Vec::new(); | ||
| 205 | } | ||
| 206 | self.handle_reply(reply) | ||
| 207 | } | ||
| 208 | |||
| 150 | pub fn handle_reply(&mut self, reply: &str) -> Vec<Effect> { | 209 | pub fn handle_reply(&mut self, reply: &str) -> Vec<Effect> { |
| 210 | self.pending_id = None; | ||
| 151 | match self.pending.take() { | 211 | match self.pending.take() { |
| 152 | Some(Pending::Spin) => match parse_spin_response(reply) { | 212 | Some(Pending::Spin) => match parse_spin_response(reply) { |
| 153 | Ok(result) => { | 213 | Ok(result) => { |
| @@ -265,7 +325,8 @@ impl ClientCore { | |||
| 265 | } | 325 | } |
| 266 | }, | 326 | }, |
| 267 | Some(Pending::Ping) => { | 327 | Some(Pending::Ping) => { |
| 268 | if reply.trim() == "PONG" { | 328 | if let Some(server_ms) = parse_pong(reply) { |
| 329 | self.set_server_time(server_ms); | ||
| 269 | print_line("Pong") | 330 | print_line("Pong") |
| 270 | } else { | 331 | } else { |
| 271 | self.connected = false; | 332 | self.connected = false; |
| @@ -305,10 +366,8 @@ impl ClientCore { | |||
| 305 | if self.game.begin_spin().is_err() { | 366 | if self.game.begin_spin().is_err() { |
| 306 | return Vec::new(); | 367 | return Vec::new(); |
| 307 | } | 368 | } |
| 308 | self.pending = Some(Pending::Spin); | ||
| 309 | |||
| 310 | let mut effects = self.board_effects(); | 369 | let mut effects = self.board_effects(); |
| 311 | effects.push(Effect::SendLine(request)); | 370 | effects.push(self.begin_request(Pending::Spin, request)); |
| 312 | effects | 371 | effects |
| 313 | } | 372 | } |
| 314 | 373 | ||
| @@ -322,24 +381,24 @@ impl ClientCore { | |||
| 322 | return print_line("Not connected. Use 'connect <ip>:<port>' first."); | 381 | return print_line("Not connected. Use 'connect <ip>:<port>' first."); |
| 323 | } | 382 | } |
| 324 | self.login_error = None; | 383 | self.login_error = None; |
| 325 | self.pending = Some(Pending::Login { user: user.clone() }); | 384 | vec![self.begin_request( |
| 326 | vec![Effect::SendLine(format!("AUTH {user} {credential}"))] | 385 | Pending::Login { user: user.clone() }, |
| 386 | format!("AUTH {user} {credential}"), | ||
| 387 | )] | ||
| 327 | } | 388 | } |
| 328 | Command::Register { user, credential } => { | 389 | Command::Register { user, credential } => { |
| 329 | if !self.connected { | 390 | if !self.connected { |
| 330 | return print_line("Not connected. Use 'connect <ip>:<port>' first."); | 391 | return print_line("Not connected. Use 'connect <ip>:<port>' first."); |
| 331 | } | 392 | } |
| 332 | self.login_error = None; | 393 | self.login_error = None; |
| 333 | self.pending = Some(Pending::Register); | 394 | vec![self.begin_request(Pending::Register, format!("REGISTER {user} {credential}"))] |
| 334 | vec![Effect::SendLine(format!("REGISTER {user} {credential}"))] | ||
| 335 | } | 395 | } |
| 336 | Command::Games => vec![Effect::Print(game_lines(&self.games()))], | 396 | Command::Games => vec![Effect::Print(game_lines(&self.games()))], |
| 337 | Command::Ping => { | 397 | Command::Ping => { |
| 338 | if !self.connected { | 398 | if !self.connected { |
| 339 | return print_line("Not connected. Use 'connect <ip>:<port>' first."); | 399 | return print_line("Not connected. Use 'connect <ip>:<port>' first."); |
| 340 | } | 400 | } |
| 341 | self.pending = Some(Pending::Ping); | 401 | vec![self.begin_request(Pending::Ping, "PING".to_string())] |
| 342 | vec![Effect::SendLine("PING".to_string())] | ||
| 343 | } | 402 | } |
| 344 | Command::Echo(message) => { | 403 | Command::Echo(message) => { |
| 345 | if !self.connected { | 404 | if !self.connected { |
| @@ -348,8 +407,7 @@ impl ClientCore { | |||
| 348 | if message.trim().is_empty() { | 407 | if message.trim().is_empty() { |
| 349 | return print_line("Usage: send <message>"); | 408 | return print_line("Usage: send <message>"); |
| 350 | } | 409 | } |
| 351 | self.pending = Some(Pending::Echo); | 410 | vec![self.begin_request(Pending::Echo, message)] |
| 352 | vec![Effect::SendLine(message)] | ||
| 353 | } | 411 | } |
| 354 | Command::Info => self.info_effects(), | 412 | Command::Info => self.info_effects(), |
| 355 | Command::Play { game, bet } => self.play_game(&game, bet), | 413 | Command::Play { game, bet } => self.play_game(&game, bet), |
| @@ -458,6 +516,7 @@ impl ClientCore { | |||
| 458 | 516 | ||
| 459 | fn exit_game(&mut self) -> Vec<Effect> { | 517 | fn exit_game(&mut self) -> Vec<Effect> { |
| 460 | self.crash_started = None; | 518 | self.crash_started = None; |
| 519 | self.crash_started_ms = None; | ||
| 461 | self.screen = Screen::Lobby; | 520 | self.screen = Screen::Lobby; |
| 462 | self.game.end_game(); | 521 | self.game.end_game(); |
| 463 | vec![ | 522 | vec![ |
| @@ -475,19 +534,17 @@ impl ClientCore { | |||
| 475 | return self.exit_game(); | 534 | return self.exit_game(); |
| 476 | }; | 535 | }; |
| 477 | self.game.set_blackjack_status("forfeiting"); | 536 | self.game.set_blackjack_status("forfeiting"); |
| 478 | self.pending = Some(Pending::BlackjackForfeit); | 537 | vec![self.begin_request( |
| 479 | vec![Effect::SendLine(format!( | 538 | Pending::BlackjackForfeit, |
| 480 | "BLACKJACK_ACTION {} forfeit", | 539 | format!("BLACKJACK_ACTION {} forfeit", hand.session_id), |
| 481 | hand.session_id | 540 | )] |
| 482 | ))] | ||
| 483 | } | 541 | } |
| 484 | 542 | ||
| 485 | fn forfeit_crash(&mut self) -> Vec<Effect> { | 543 | fn forfeit_crash(&mut self) -> Vec<Effect> { |
| 486 | let Some(session) = self.game.crash_session_id() else { | 544 | let Some(session) = self.game.crash_session_id() else { |
| 487 | return self.exit_game(); | 545 | return self.exit_game(); |
| 488 | }; | 546 | }; |
| 489 | self.pending = Some(Pending::CrashForfeit); | 547 | vec![self.begin_request(Pending::CrashForfeit, format!("CRASH_FORFEIT {session}"))] |
| 490 | vec![Effect::SendLine(format!("CRASH_FORFEIT {session}"))] | ||
| 491 | } | 548 | } |
| 492 | 549 | ||
| 493 | fn place_roulette_chip_on(&mut self, target: BetTarget) -> Vec<Effect> { | 550 | fn place_roulette_chip_on(&mut self, target: BetTarget) -> Vec<Effect> { |
| @@ -533,10 +590,8 @@ impl ClientCore { | |||
| 533 | if self.game.begin_spin().is_err() { | 590 | if self.game.begin_spin().is_err() { |
| 534 | return Vec::new(); | 591 | return Vec::new(); |
| 535 | } | 592 | } |
| 536 | self.pending = Some(Pending::RouletteSpin); | ||
| 537 | |||
| 538 | let mut effects = self.board_effects(); | 593 | let mut effects = self.board_effects(); |
| 539 | effects.push(Effect::SendLine(request)); | 594 | effects.push(self.begin_request(Pending::RouletteSpin, request)); |
| 540 | effects | 595 | effects |
| 541 | } | 596 | } |
| 542 | 597 | ||
| @@ -566,9 +621,8 @@ impl ClientCore { | |||
| 566 | } | 621 | } |
| 567 | 622 | ||
| 568 | self.game.set_blackjack_status("dealing"); | 623 | self.game.set_blackjack_status("dealing"); |
| 569 | self.pending = Some(Pending::BlackjackStart); | ||
| 570 | let mut effects = self.board_effects(); | 624 | let mut effects = self.board_effects(); |
| 571 | effects.push(Effect::SendLine(request)); | 625 | effects.push(self.begin_request(Pending::BlackjackStart, request)); |
| 572 | effects | 626 | effects |
| 573 | } | 627 | } |
| 574 | 628 | ||
| @@ -582,15 +636,21 @@ impl ClientCore { | |||
| 582 | if action == "double" && !hand.actions.iter().any(|available| available == "double") { | 636 | if action == "double" && !hand.actions.iter().any(|available| available == "double") { |
| 583 | return Vec::new(); | 637 | return Vec::new(); |
| 584 | } | 638 | } |
| 585 | self.pending = Some(Pending::BlackjackAction); | 639 | vec![self.begin_request( |
| 586 | vec![Effect::SendLine(format!( | 640 | Pending::BlackjackAction, |
| 587 | "BLACKJACK_ACTION {} {}", | 641 | format!("BLACKJACK_ACTION {} {}", hand.session_id, action), |
| 588 | hand.session_id, action | 642 | )] |
| 589 | ))] | ||
| 590 | } | 643 | } |
| 591 | 644 | ||
| 592 | /// Redraw the rising multiplier. Called by the terminal loop on a timer. | 645 | /// Redraw the rising multiplier. Called by the terminal loop on a timer. |
| 593 | pub fn tick(&mut self) -> Vec<Effect> { | 646 | pub fn tick(&mut self) -> Vec<Effect> { |
| 647 | if self.clock_synced { | ||
| 648 | if let Some(started_ms) = self.crash_started_ms { | ||
| 649 | let now = unix_time_ms() as i64 + self.server_offset_ms; | ||
| 650 | let elapsed_ms = (now - started_ms as i64).max(0) as u64; | ||
| 651 | return self.tick_at(Duration::from_millis(elapsed_ms)); | ||
| 652 | } | ||
| 653 | } | ||
| 594 | let Some(started) = self.crash_started else { | 654 | let Some(started) = self.crash_started else { |
| 595 | return Vec::new(); | 655 | return Vec::new(); |
| 596 | }; | 656 | }; |
| @@ -619,12 +679,16 @@ impl ClientCore { | |||
| 619 | } | 679 | } |
| 620 | 680 | ||
| 621 | pub fn is_async(&self, line: &str) -> bool { | 681 | pub fn is_async(&self, line: &str) -> bool { |
| 622 | line.trim_start().starts_with("CRASH_BUSTED ") | 682 | let line = line.trim_start(); |
| 683 | line.starts_with("CRASH_BUSTED ") || line.starts_with("crash.tick ") | ||
| 623 | } | 684 | } |
| 624 | 685 | ||
| 625 | /// Handle a server-initiated line. Returns `None` when the line is a normal | 686 | /// Handle a server-initiated line. Returns `None` when the line is a normal |
| 626 | /// request/response reply and belongs to `handle_reply`. | 687 | /// request/response reply and belongs to `handle_reply`. |
| 627 | pub fn handle_async_line(&mut self, line: &str) -> Option<Vec<Effect>> { | 688 | pub fn handle_async_line(&mut self, line: &str) -> Option<Vec<Effect>> { |
| 689 | if let Some(tick) = line.trim_start().strip_prefix("crash.tick ") { | ||
| 690 | return Some(self.apply_crash_tick(tick)); | ||
| 691 | } | ||
| 628 | if !self.is_async(line) { | 692 | if !self.is_async(line) { |
| 629 | return None; | 693 | return None; |
| 630 | } | 694 | } |
| @@ -643,13 +707,43 @@ impl ClientCore { | |||
| 643 | if self.crash_started.is_none() { | 707 | if self.crash_started.is_none() { |
| 644 | self.crash_started = Some(Instant::now()); | 708 | self.crash_started = Some(Instant::now()); |
| 645 | } | 709 | } |
| 710 | if state.started_at > 0 { | ||
| 711 | self.crash_started_ms = Some(state.started_at); | ||
| 712 | } | ||
| 646 | } else { | 713 | } else { |
| 647 | self.crash_started = None; | 714 | self.crash_started = None; |
| 715 | self.crash_started_ms = None; | ||
| 648 | } | 716 | } |
| 649 | self.game.apply_crash_state(state); | 717 | self.game.apply_crash_state(state); |
| 650 | self.board_effects() | 718 | self.board_effects() |
| 651 | } | 719 | } |
| 652 | 720 | ||
| 721 | /// Apply `crash.tick session=<id> t=<server_ms> m=<multiplier>`. | ||
| 722 | fn apply_crash_tick(&mut self, tick: &str) -> Vec<Effect> { | ||
| 723 | let mut session = None; | ||
| 724 | let mut multiplier = None; | ||
| 725 | for token in tick.split_whitespace() { | ||
| 726 | if let Some(value) = token.strip_prefix("session=") { | ||
| 727 | session = Some(value); | ||
| 728 | } else if let Some(value) = token.strip_prefix("m=") { | ||
| 729 | multiplier = value.parse::<f64>().ok(); | ||
| 730 | } | ||
| 731 | } | ||
| 732 | let Some(multiplier) = multiplier else { | ||
| 733 | return Vec::new(); | ||
| 734 | }; | ||
| 735 | if let Some(session) = session { | ||
| 736 | if self.game.crash_session_id().as_deref() != Some(session) { | ||
| 737 | return Vec::new(); | ||
| 738 | } | ||
| 739 | } | ||
| 740 | if self.game.crash_status() != "running" { | ||
| 741 | return Vec::new(); | ||
| 742 | } | ||
| 743 | self.game.set_crash_multiplier(multiplier); | ||
| 744 | self.board_effects() | ||
| 745 | } | ||
| 746 | |||
| 653 | fn start_crash(&mut self) -> Vec<Effect> { | 747 | fn start_crash(&mut self) -> Vec<Effect> { |
| 654 | let status = self.game.crash_status(); | 748 | let status = self.game.crash_status(); |
| 655 | if !matches!(status.as_str(), "ready" | "cashed" | "crashed" | "forfeit") { | 749 | if !matches!(status.as_str(), "ready" | "cashed" | "crashed" | "forfeit") { |
| @@ -671,9 +765,8 @@ impl ClientCore { | |||
| 671 | } | 765 | } |
| 672 | 766 | ||
| 673 | self.game.set_crash_status("starting"); | 767 | self.game.set_crash_status("starting"); |
| 674 | self.pending = Some(Pending::CrashStart); | ||
| 675 | let mut effects = self.board_effects(); | 768 | let mut effects = self.board_effects(); |
| 676 | effects.push(Effect::SendLine(request)); | 769 | effects.push(self.begin_request(Pending::CrashStart, request)); |
| 677 | effects | 770 | effects |
| 678 | } | 771 | } |
| 679 | 772 | ||
| @@ -681,8 +774,7 @@ impl ClientCore { | |||
| 681 | let Some(session) = self.game.crash_session_id() else { | 774 | let Some(session) = self.game.crash_session_id() else { |
| 682 | return Vec::new(); | 775 | return Vec::new(); |
| 683 | }; | 776 | }; |
| 684 | self.pending = Some(Pending::CrashCashout); | 777 | vec![self.begin_request(Pending::CrashCashout, format!("CRASH_CASHOUT {session}"))] |
| 685 | vec![Effect::SendLine(format!("CRASH_CASHOUT {session}"))] | ||
| 686 | } | 778 | } |
| 687 | 779 | ||
| 688 | fn play_game(&mut self, game_type: &str, bet: Option<u64>) -> Vec<Effect> { | 780 | fn play_game(&mut self, game_type: &str, bet: Option<u64>) -> Vec<Effect> { |
| @@ -706,8 +798,7 @@ impl ClientCore { | |||
| 706 | } | 798 | } |
| 707 | 799 | ||
| 708 | let mut effects = self.board_effects(); | 800 | let mut effects = self.board_effects(); |
| 709 | self.pending = Some(Pending::Info); | 801 | effects.push(self.begin_request(Pending::Info, "INFO".to_string())); |
| 710 | effects.push(Effect::SendLine("INFO".to_string())); | ||
| 711 | effects | 802 | effects |
| 712 | } | 803 | } |
| 713 | Err(_) => print_line("Failed to enter game"), | 804 | Err(_) => print_line("Failed to enter game"), |
| @@ -726,13 +817,12 @@ impl ClientCore { | |||
| 726 | } | 817 | } |
| 727 | 818 | ||
| 728 | self.screen = Screen::History; | 819 | self.screen = Screen::History; |
| 729 | self.pending = Some(Pending::History); | ||
| 730 | let request = if limit == crate::history::DEFAULT_HISTORY { | 820 | let request = if limit == crate::history::DEFAULT_HISTORY { |
| 731 | "HISTORY".to_string() | 821 | "HISTORY".to_string() |
| 732 | } else { | 822 | } else { |
| 733 | format!("HISTORY {limit}") | 823 | format!("HISTORY {limit}") |
| 734 | }; | 824 | }; |
| 735 | vec![Effect::SendLine(request)] | 825 | vec![self.begin_request(Pending::History, request)] |
| 736 | } | 826 | } |
| 737 | 827 | ||
| 738 | fn info_effects(&mut self) -> Vec<Effect> { | 828 | fn info_effects(&mut self) -> Vec<Effect> { |
| @@ -754,8 +844,7 @@ impl ClientCore { | |||
| 754 | ])), | 844 | ])), |
| 755 | } | 845 | } |
| 756 | 846 | ||
| 757 | self.pending = Some(Pending::Info); | 847 | effects.push(self.begin_request(Pending::Info, "INFO".to_string())); |
| 758 | effects.push(Effect::SendLine("INFO".to_string())); | ||
| 759 | effects | 848 | effects |
| 760 | } | 849 | } |
| 761 | 850 | ||
| @@ -809,7 +898,6 @@ impl ClientCore { | |||
| 809 | } | 898 | } |
| 810 | self.view().map(Effect::Render).into_iter().collect() | 899 | self.view().map(Effect::Render).into_iter().collect() |
| 811 | } | 900 | } |
| 812 | |||
| 813 | } | 901 | } |
| 814 | 902 | ||
| 815 | fn auth_result(reply: &str) -> Result<(), String> { | 903 | fn auth_result(reply: &str) -> Result<(), String> { |
