diff --git a/rust/cli_player/src/client.rs b/rust/cli_player/src/client.rs index d70f401..c189656 100644 --- a/rust/cli_player/src/client.rs +++ b/rust/cli_player/src/client.rs @@ -5,7 +5,9 @@ use futures::{SinkExt, StreamExt}; use hyper_rustls::ConfigBuilderExt; use sea_battle_backend::data::GameRules; use sea_battle_backend::human_player_ws::{ClientMessage, ServerMessage}; -use sea_battle_backend::server::{BotPlayQuery, PlayRandomQuery}; +use sea_battle_backend::server::{ + AcceptInviteQuery, BotPlayQuery, CreateInviteQuery, PlayRandomQuery, +}; use sea_battle_backend::utils::res_utils::{boxed_error, Res}; use std::fmt::Display; use std::sync::mpsc::TryRecvError; @@ -61,6 +63,38 @@ impl Client { .await } + /// Start a play by creating an invite + pub async fn start_create_invite(rules: &GameRules, player_name: D) -> Res { + Self::connect_url( + &cli_args().remote_server_uri, + &format!( + "/play/create_invite?{}", + serde_urlencoded::to_string(&CreateInviteQuery { + rules: rules.clone(), + player_name: player_name.to_string() + }) + .unwrap() + ), + ) + .await + } + + /// Start a play by accepting an invite + pub async fn start_accept_invite(code: String, player_name: D) -> Res { + Self::connect_url( + &cli_args().remote_server_uri, + &format!( + "/play/accept_invite?{}", + serde_urlencoded::to_string(&AcceptInviteQuery { + code, + player_name: player_name.to_string() + }) + .unwrap() + ), + ) + .await + } + /// Do connect to a server, returning async fn connect_url(server: &str, uri: &str) -> Res { let mut ws_url = server.replace("http", "ws"); diff --git a/rust/cli_player/src/main.rs b/rust/cli_player/src/main.rs index 5586499..88c3496 100644 --- a/rust/cli_player/src/main.rs +++ b/rust/cli_player/src/main.rs @@ -21,7 +21,9 @@ use cli_player::ui_screens::input_screen::InputScreen; use cli_player::ui_screens::popup_screen::PopupScreen; use cli_player::ui_screens::select_play_mode_screen::{SelectPlayModeResult, SelectPlayModeScreen}; use cli_player::ui_screens::*; -use sea_battle_backend::consts::{MAX_PLAYER_NAME_LENGTH, MIN_PLAYER_NAME_LENGTH}; +use sea_battle_backend::consts::{ + INVITE_CODE_LENGTH, MAX_PLAYER_NAME_LENGTH, MIN_PLAYER_NAME_LENGTH, +}; use sea_battle_backend::data::GameRules; use sea_battle_backend::utils::res_utils::Res; @@ -72,8 +74,8 @@ async fn run_dev( ))? } -/// Ask the user to specify its username -fn query_username(terminal: &mut Terminal) -> Res { +/// Ask the user to specify the name he should be identified with +fn query_player_name(terminal: &mut Terminal) -> Res { let mut hostname = hostname::get()?.to_string_lossy().to_string(); if hostname.len() > MAX_PLAYER_NAME_LENGTH { hostname = hostname[0..MAX_PLAYER_NAME_LENGTH].to_string(); @@ -103,29 +105,49 @@ async fn run_app(terminal: &mut Terminal) -> Res { let choice = SelectPlayModeScreen::default().show(terminal)?; if let ScreenResult::Ok(c) = choice { - if c.need_user_name() && username.is_empty() { - username = query_username(terminal)?; + if c.need_player_name() && username.is_empty() { + username = query_player_name(terminal)?; + } + + if c.need_custom_rules() { + rules = match GameRulesConfigurationScreen::new(rules.clone()).show(terminal)? { + ScreenResult::Ok(r) => r, + ScreenResult::Canceled => continue, + } } } + PopupScreen::new("šŸ”Œ Connecting...").show_once(terminal)?; + let client = match choice { ScreenResult::Ok(SelectPlayModeResult::PlayRandom) => { - PopupScreen::new("Connecting...").show_once(terminal)?; - Client::start_random_play(&username).await? } // Play against bot ScreenResult::Ok(SelectPlayModeResult::PlayAgainstBot) => { - // First, ask for custom rules - rules = match GameRulesConfigurationScreen::new(rules.clone()).show(terminal)? { - ScreenResult::Ok(r) => r, - ScreenResult::Canceled => continue, + Client::start_bot_play(&rules).await? + } + + // Create invite + ScreenResult::Ok(SelectPlayModeResult::CreateInvite) => { + Client::start_create_invite(&rules, &username).await? + } + + // Join invite + ScreenResult::Ok(SelectPlayModeResult::AcceptInvite) => { + let code = match InputScreen::new("Invite code") + .set_min_length(INVITE_CODE_LENGTH) + .set_max_length(INVITE_CODE_LENGTH) + .show(terminal)? + .value() + { + None => continue, + Some(v) => v, }; - // Then connect to server - PopupScreen::new("Connecting...").show_once(terminal)?; - Client::start_bot_play(&rules).await? + PopupScreen::new("šŸ”Œ Connecting...").show_once(terminal)?; + Client::start_accept_invite(code, &username).await? } ScreenResult::Canceled | ScreenResult::Ok(SelectPlayModeResult::Exit) => return Ok(()), diff --git a/rust/cli_player/src/ui_screens/game_screen.rs b/rust/cli_player/src/ui_screens/game_screen.rs index 1224532..e957e7a 100644 --- a/rust/cli_player/src/ui_screens/game_screen.rs +++ b/rust/cli_player/src/ui_screens/game_screen.rs @@ -53,21 +53,21 @@ impl GameStatus { pub fn status_text(&self) -> &str { match self { - GameStatus::Connecting => "Connecting...", - GameStatus::WaitingForAnotherPlayer => "Waiting for another player...", - GameStatus::OpponentConnected => "Opponent connected!", - GameStatus::WaitingForOpponentBoatsConfig => "Waiting for ### boats configuration", - GameStatus::OpponentReady => "### is ready!", - GameStatus::Starting => "Game is starting...", - GameStatus::MustFire => "You must fire!", - GameStatus::OpponentMustFire => "### must fire!", - GameStatus::WonGame => "You win the game!", - GameStatus::LostGame => "### wins the game. You loose.", - GameStatus::RematchRequestedByOpponent => "Rematch requested by ###", - GameStatus::RematchRequestedByPlayer => "Rematch requested by you", - GameStatus::RematchAccepted => "Rematch accepted!", - GameStatus::RematchRejected => "Rematch rejected!", - GameStatus::OpponentLeftGame => "Opponent left game!", + GameStatus::Connecting => "šŸ”Œ Connecting...", + GameStatus::WaitingForAnotherPlayer => "šŸ•‘ Waiting for another player...", + GameStatus::OpponentConnected => "āœ… Opponent connected!", + GameStatus::WaitingForOpponentBoatsConfig => "šŸ•‘ Waiting for ### boats configuration", + GameStatus::OpponentReady => "āœ… ### is ready!", + GameStatus::Starting => "šŸ•‘ Game is starting...", + GameStatus::MustFire => "šŸšØ You must fire!", + GameStatus::OpponentMustFire => "šŸ’£ ### must fire!", + GameStatus::WonGame => "šŸŽ‰ You win the game!", + GameStatus::LostGame => "šŸ˜æ ### wins the game. You loose.", + GameStatus::RematchRequestedByOpponent => "ā“ Rematch requested by ###", + GameStatus::RematchRequestedByPlayer => "ā“ Rematch requested by you", + GameStatus::RematchAccepted => "āœ… Rematch accepted!", + GameStatus::RematchRejected => "āŒ Rematch rejected!", + GameStatus::OpponentLeftGame => "ā›” Opponent left game!", } } } @@ -83,10 +83,10 @@ enum Buttons { impl Buttons { pub fn text(&self) -> &str { match self { - Buttons::RequestRematch => "Request rematch", - Buttons::AcceptRematch => "Accept rematch", - Buttons::RejectRematch => "Reject rematch", - Buttons::QuitGame => "Quit game", + Buttons::RequestRematch => "ā“ Request rematch", + Buttons::AcceptRematch => "āœ… Accept rematch", + Buttons::RejectRematch => "āŒ Reject rematch", + Buttons::QuitGame => "āŒ Quit game", } } } @@ -241,7 +241,7 @@ impl GameScreen { } ServerMessage::InvalidInviteCode => { - PopupScreen::new("Invalid invite code!").show(terminal)?; + PopupScreen::new("āŒ Invalid invite code!").show(terminal)?; return Ok(ScreenResult::Ok(())); } @@ -475,7 +475,7 @@ impl GameScreen { if !self.status.can_show_game_maps() { if self.status == GameStatus::WaitingForAnotherPlayer { if let Some(code) = &self.invite_code { - status_text.push_str(&format!("\n Invite code: {}", code)); + status_text.push_str(&format!("\n\nšŸŽ« Invite code: {}", code)); } } diff --git a/rust/cli_player/src/ui_screens/select_play_mode_screen.rs b/rust/cli_player/src/ui_screens/select_play_mode_screen.rs index e49ec81..519d3de 100644 --- a/rust/cli_player/src/ui_screens/select_play_mode_screen.rs +++ b/rust/cli_player/src/ui_screens/select_play_mode_screen.rs @@ -17,14 +17,22 @@ pub enum SelectPlayModeResult { #[default] PlayAgainstBot, PlayRandom, + CreateInvite, + AcceptInvite, Exit, } impl SelectPlayModeResult { /// Specify whether a selected play mode requires a user name or not - pub fn need_user_name(&self) -> bool { + pub fn need_player_name(&self) -> bool { self != &SelectPlayModeResult::PlayAgainstBot && self != &SelectPlayModeResult::Exit } + + /// Specify whether a selected play mode requires a the user to specify its own game rules or + /// not + pub fn need_custom_rules(&self) -> bool { + self == &SelectPlayModeResult::PlayAgainstBot || self == &SelectPlayModeResult::CreateInvite + } } #[derive(Debug, Clone)] @@ -33,17 +41,25 @@ struct PlayModeDescription { value: SelectPlayModeResult, } -const AVAILABLE_PLAY_MODES: [PlayModeDescription; 3] = [ +const AVAILABLE_PLAY_MODES: [PlayModeDescription; 5] = [ PlayModeDescription { - name: "Play against bot (offline)", + name: "šŸ¤– Play against bot (offline)", value: SelectPlayModeResult::PlayAgainstBot, }, PlayModeDescription { - name: "Play against random player (online)", + name: "šŸŽ² Play against random player (online)", value: SelectPlayModeResult::PlayRandom, }, PlayModeDescription { - name: "Exit app", + name: "āž• Create play invite (online)", + value: SelectPlayModeResult::CreateInvite, + }, + PlayModeDescription { + name: "šŸŽ« Accept play invite (online)", + value: SelectPlayModeResult::AcceptInvite, + }, + PlayModeDescription { + name: "āŒ Exit app", value: SelectPlayModeResult::Exit, }, ]; @@ -92,7 +108,7 @@ impl SelectPlayModeScreen { } fn ui(&mut self, f: &mut Frame) { - let area = centered_rect_size(50, 5, &f.size()); + let area = centered_rect_size(50, 2 + AVAILABLE_PLAY_MODES.len() as u16, &f.size()); // Create a List from all list items and highlight the currently selected one let items = AVAILABLE_PLAY_MODES