diff --git a/sea_battle_backend/src/bot_client.rs b/sea_battle_backend/src/bot_client.rs deleted file mode 100644 index 2e073fa..0000000 --- a/sea_battle_backend/src/bot_client.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::error::Error; - -use futures::{SinkExt, StreamExt}; -use tokio_tungstenite::tungstenite::Message; - -use crate::data::{BoatsLayout, GameRules}; -use crate::human_player_ws::{ClientMessage, ServerMessage}; - -#[derive(Debug, Eq, PartialEq)] -pub enum ClientEndResult { - Finished, - InvalidBoatsLayout, -} - -pub async fn run_client( - server: &str, - requested_rules: &GameRules, - layout: BoatsLayout, -) -> Result> { - let url = format!( - "{}/play/bot?{}", - server.replace("http", "ws"), - serde_urlencoded::to_string(requested_rules).unwrap() - ); - log::debug!("Connecting to {}...", url); - let (mut socket, _) = match tokio_tungstenite::connect_async(url).await { - Ok(s) => s, - Err(e) => { - log::error!("Failed to establish WebSocket connection! {:?}", e); - return Err(Box::new(e)); - } - }; - - while let Some(chunk) = socket.next().await { - let message = match chunk? { - Message::Text(message) => { - log::trace!("TEXT message from server: {}", message); - - let msg: ServerMessage = serde_json::from_str(&message)?; - msg - } - Message::Binary(_) => { - log::debug!("BINARY message from server"); - continue; - } - Message::Ping(_) => { - log::debug!("PING from server"); - continue; - } - Message::Pong(_) => { - log::debug!("PONG from server"); - continue; - } - Message::Close(_) => { - log::debug!("CLOSE message request from server"); - break; - } - Message::Frame(_) => { - log::debug!("Frame from server"); - continue; - } - }; - - match message { - ServerMessage::WaitingForAnotherPlayer => log::debug!("Waiting for other player..."), - ServerMessage::QueryBoatsLayout { rules } => { - assert_eq!(&rules, requested_rules); - log::debug!("Server requested boats layout"); - socket - .send(Message::Text(serde_json::to_string( - &ClientMessage::BoatsLayout { - layout: layout.clone(), - }, - )?)) - .await?; - } - ServerMessage::WaitingForOtherPlayerConfiguration => { - log::debug!("Waiting for other player configuration...") - } - ServerMessage::OtherPlayerReady => log::debug!("Other player is ready!"), - ServerMessage::GameStarting => log::debug!("The game is starting..."), - ServerMessage::OtherPlayerMustFire { status } => { - assert_eq!(status.opponent_map.boats.number_of_boats(), 0); - log::debug!("Other player must fire!") - } - ServerMessage::RequestFire { status } => { - assert_eq!(status.opponent_map.boats.number_of_boats(), 0); - - let location = status.find_valid_random_fire_location(); - log::debug!("Will fire at {:?}", location); - socket - .send(Message::Text(serde_json::to_string( - &ClientMessage::Fire { location }, - )?)) - .await?; - } - ServerMessage::StrikeResult { pos, result } => { - log::debug!("Strike at {} result: {:?}", pos.human_print(), result) - } - ServerMessage::OpponentStrikeResult { pos, result } => log::debug!( - "Opponent trike at {} result: {:?}", - pos.human_print(), - result - ), - ServerMessage::LostGame { - your_map, - opponent_map, - } => { - log::debug!("We lost game :("); - log::debug!("Other game:\n{}\n", opponent_map.get_map()); - log::debug!("Our game:\n{}\n", your_map.get_map()); - break; - } - ServerMessage::WonGame { - your_map, - opponent_map, - } => { - log::debug!("We won the game !!!!"); - log::debug!("Other game:\n{}\n", opponent_map.get_map()); - log::debug!("Our game:\n{}\n", your_map.get_map()); - break; - } - ServerMessage::RejectedBoatsLayout { errors } => { - log::warn!("Rejected boat layout: {:?}", errors); - return Ok(ClientEndResult::InvalidBoatsLayout); - } - ServerMessage::SetOpponentName { name } => log::debug!("Opponent name: {}", name), - } - } - - Ok(ClientEndResult::Finished) -} diff --git a/sea_battle_backend/src/data/game_map.rs b/sea_battle_backend/src/data/game_map.rs index 2556f41..4d9f7d7 100644 --- a/sea_battle_backend/src/data/game_map.rs +++ b/sea_battle_backend/src/data/game_map.rs @@ -5,7 +5,7 @@ use crate::data::{BoatPosition, CurrentGameMapStatus, EndGameMap, GameRules}; pub enum FireResult { Missed, Hit, - Sunk, + Sunk(BoatPosition), Rejected, AlreadyTargetedPosition, } @@ -111,7 +111,7 @@ impl GameMap { } } - FireResult::Sunk + FireResult::Sunk(*b) } } } diff --git a/sea_battle_backend/src/game.rs b/sea_battle_backend/src/game.rs index 939513e..1fe3f39 100644 --- a/sea_battle_backend/src/game.rs +++ b/sea_battle_backend/src/game.rs @@ -23,7 +23,7 @@ pub trait Player { fn request_fire(&self, status: CurrentGameStatus); - fn other_player_must_fire(&self, status: CurrentGameStatus); + fn opponent_must_fire(&self, status: CurrentGameStatus); fn strike_result(&self, c: Coordinates, res: FireResult); @@ -32,6 +32,12 @@ pub trait Player { fn lost_game(&self, your_map: EndGameMap, opponent_map: EndGameMap); fn won_game(&self, your_map: EndGameMap, opponent_map: EndGameMap); + + fn opponent_requested_rematch(&self); + + fn opponent_rejected_rematch(&self); + + fn opponent_accepted_rematch(&self); } fn opponent(index: usize) -> usize { @@ -49,6 +55,8 @@ enum GameStatus { WaitingForBoatsDisposition, Started, Finished, + RematchRequested, + RematchRejected, } pub struct Game { @@ -109,7 +117,7 @@ impl Game { fn request_fire(&self) { self.players[self.turn].request_fire(self.get_game_status_for_player(self.turn)); self.players[opponent(self.turn)] - .other_player_must_fire(self.get_game_status_for_player(opponent(self.turn))); + .opponent_must_fire(self.get_game_status_for_player(opponent(self.turn))); } fn player_map(&self, id: usize) -> &GameMap { @@ -143,7 +151,9 @@ impl Game { return; } - if result == FireResult::Sunk && self.player_map(opponent(self.turn)).are_all_boat_sunk() { + if matches!(result, FireResult::Sunk(_)) + && self.player_map(opponent(self.turn)).are_all_boat_sunk() + { self.status = GameStatus::Finished; let winner_map = self.player_map(self.turn).final_map(); @@ -155,7 +165,7 @@ impl Game { return; } - if (result == FireResult::Sunk || result == FireResult::Hit) + if matches!(result, FireResult::Sunk(_) | FireResult::Hit) && !self.rules.player_continue_on_hit { self.turn = opponent(self.turn); @@ -164,6 +174,35 @@ impl Game { self.request_fire(); } + fn handle_request_rematch(&mut self, player_id: Uuid) { + self.status = GameStatus::RematchRequested; + self.turn = opponent(self.player_id_by_uuid(player_id)); + + self.players[self.turn].opponent_requested_rematch(); + } + + fn handle_request_rematch_response(&mut self, accepted: bool) { + if !accepted { + self.players[opponent(self.turn)].opponent_rejected_rematch(); + self.status = GameStatus::RematchRejected; + return; + } + + self.players[opponent(self.turn)].opponent_accepted_rematch(); + + // Swap players + let swap = self.players[1].clone(); + self.players[1] = self.players[0].clone(); + self.players[0] = swap; + + // "Forget everything" + self.status = GameStatus::Created; + self.map_0 = None; + self.map_1 = None; + + self.query_boats_disposition(); + } + /// Get current game status for a specific player fn get_game_status_for_player(&self, id: usize) -> CurrentGameStatus { CurrentGameStatus { @@ -260,3 +299,42 @@ impl Handler for Game { self.handle_fire(msg.1) } } + +#[derive(Message, Debug)] +#[rtype(result = "()")] +pub struct RequestRematch(pub Uuid); + +impl Handler for Game { + type Result = (); + + fn handle(&mut self, msg: RequestRematch, _ctx: &mut Self::Context) -> Self::Result { + if self.status != GameStatus::Finished { + log::error!("Player attempted to request rematch on invalid step!"); + return; + } + + self.handle_request_rematch(msg.0); + } +} + +#[derive(Message, Debug)] +#[rtype(result = "()")] +pub struct RespondRequestRematch(pub Uuid, pub bool); + +impl Handler for Game { + type Result = (); + + fn handle(&mut self, msg: RespondRequestRematch, _ctx: &mut Self::Context) -> Self::Result { + if self.status != GameStatus::RematchRequested { + log::error!("Player attempted to respond to request rematch on invalid step!"); + return; + } + + if self.player_id_by_uuid(msg.0) != self.turn { + log::error!("Player can not respond to its own rematch request!"); + return; + } + + self.handle_request_rematch_response(msg.1); + } +} diff --git a/sea_battle_backend/src/human_player.rs b/sea_battle_backend/src/human_player.rs index 73c220e..7c73751 100644 --- a/sea_battle_backend/src/human_player.rs +++ b/sea_battle_backend/src/human_player.rs @@ -2,7 +2,7 @@ use actix::Addr; use uuid::Uuid; use crate::data::{Coordinates, CurrentGameStatus, EndGameMap, FireResult, GameRules}; -use crate::game::{Fire, Game, Player, SetBoatsLayout}; +use crate::game::*; use crate::human_player_ws::{ClientMessage, HumanPlayerWS, ServerMessage}; pub struct HumanPlayer { @@ -40,7 +40,7 @@ impl Player for HumanPlayer { } fn notify_other_player_ready(&self) { - self.player.do_send(ServerMessage::OtherPlayerReady); + self.player.do_send(ServerMessage::OpponentReady); } fn notify_game_starting(&self) { @@ -51,9 +51,9 @@ impl Player for HumanPlayer { self.player.do_send(ServerMessage::RequestFire { status }); } - fn other_player_must_fire(&self, status: CurrentGameStatus) { + fn opponent_must_fire(&self, status: CurrentGameStatus) { self.player - .do_send(ServerMessage::OtherPlayerMustFire { status }); + .do_send(ServerMessage::OpponentMustFire { status }); } fn strike_result(&self, c: Coordinates, res: FireResult) { @@ -83,6 +83,18 @@ impl Player for HumanPlayer { opponent_map, }); } + + fn opponent_requested_rematch(&self) { + self.player.do_send(ServerMessage::OpponentRequestedRematch); + } + + fn opponent_rejected_rematch(&self) { + self.player.do_send(ServerMessage::OpponentRejectedRematch); + } + + fn opponent_accepted_rematch(&self) { + self.player.do_send(ServerMessage::OpponentAcceptedRematch); + } } impl HumanPlayer { @@ -95,6 +107,14 @@ impl HumanPlayer { self.game.do_send(SetBoatsLayout(self.uuid, layout)) } ClientMessage::Fire { location } => self.game.do_send(Fire(self.uuid, location)), + + ClientMessage::RequestRematch => self.game.do_send(RequestRematch(self.uuid)), + ClientMessage::AcceptRematch => { + self.game.do_send(RespondRequestRematch(self.uuid, true)) + } + ClientMessage::RejectRematch => { + self.game.do_send(RespondRequestRematch(self.uuid, false)) + } } } } diff --git a/sea_battle_backend/src/human_player_ws.rs b/sea_battle_backend/src/human_player_ws.rs index 890cbe9..f43898f 100644 --- a/sea_battle_backend/src/human_player_ws.rs +++ b/sea_battle_backend/src/human_player_ws.rs @@ -34,6 +34,9 @@ pub enum ClientMessage { StopGame, BoatsLayout { layout: BoatsLayout }, Fire { location: Coordinates }, + RequestRematch, + AcceptRematch, + RejectRematch, } #[derive(Message)] @@ -52,9 +55,9 @@ pub enum ServerMessage { errors: Vec, }, WaitingForOtherPlayerConfiguration, - OtherPlayerReady, + OpponentReady, GameStarting, - OtherPlayerMustFire { + OpponentMustFire { status: CurrentGameStatus, }, RequestFire { @@ -76,6 +79,9 @@ pub enum ServerMessage { your_map: EndGameMap, opponent_map: EndGameMap, }, + OpponentRequestedRematch, + OpponentAcceptedRematch, + OpponentRejectedRematch, } pub struct HumanPlayerWS { diff --git a/sea_battle_backend/src/lib.rs b/sea_battle_backend/src/lib.rs index 1eec9ea..ff7c153 100644 --- a/sea_battle_backend/src/lib.rs +++ b/sea_battle_backend/src/lib.rs @@ -1,8 +1,6 @@ extern crate core; pub mod args; -#[cfg(test)] -pub mod bot_client; pub mod consts; pub mod data; pub mod game; diff --git a/sea_battle_backend/src/random_bot.rs b/sea_battle_backend/src/random_bot.rs index 5d2e94e..948eab9 100644 --- a/sea_battle_backend/src/random_bot.rs +++ b/sea_battle_backend/src/random_bot.rs @@ -2,7 +2,7 @@ use actix::Addr; use uuid::Uuid; use crate::data::{BoatsLayout, Coordinates, CurrentGameStatus, EndGameMap, FireResult, GameRules}; -use crate::game::{Fire, Game, Player, SetBoatsLayout}; +use crate::game::{Fire, Game, Player, RespondRequestRematch, SetBoatsLayout}; #[derive(Clone)] pub struct RandomBot { @@ -54,7 +54,7 @@ impl Player for RandomBot { .do_send(Fire(self.uuid, status.find_valid_random_fire_location())); } - fn other_player_must_fire(&self, _status: CurrentGameStatus) {} + fn opponent_must_fire(&self, _status: CurrentGameStatus) {} fn strike_result(&self, _c: Coordinates, _res: FireResult) {} @@ -63,4 +63,12 @@ impl Player for RandomBot { fn lost_game(&self, _your_map: EndGameMap, _opponent_map: EndGameMap) {} fn won_game(&self, _your_map: EndGameMap, _opponent_map: EndGameMap) {} + + fn opponent_requested_rematch(&self) { + self.game.do_send(RespondRequestRematch(self.uuid, true)); + } + + fn opponent_rejected_rematch(&self) {} + + fn opponent_accepted_rematch(&self) {} } diff --git a/sea_battle_backend/src/test/bot_client.rs b/sea_battle_backend/src/test/bot_client.rs new file mode 100644 index 0000000..d6c6b45 --- /dev/null +++ b/sea_battle_backend/src/test/bot_client.rs @@ -0,0 +1,205 @@ +use std::error::Error; +use std::fmt::Display; + +use futures::{SinkExt, StreamExt}; +use tokio_tungstenite::tungstenite::Message; + +use crate::data::{BoatsLayout, GameRules}; +use crate::human_player_ws::{ClientMessage, ServerMessage}; + +#[derive(Debug, Eq, PartialEq)] +pub enum ClientEndResult { + Finished, + InvalidBoatsLayout, + OpponentRejectedRematch, +} + +pub struct BotClient { + server: String, + requested_rules: GameRules, + layout: Option, + number_plays: usize, +} + +impl BotClient { + pub fn new(server: D) -> Self + where + D: Display, + { + Self { + server: server.to_string(), + requested_rules: GameRules::random_players_rules(), + layout: None, + number_plays: 1, + } + } + + pub fn with_rules(mut self, rules: GameRules) -> Self { + self.requested_rules = rules; + self + } + + pub fn with_layout(mut self, layout: BoatsLayout) -> Self { + self.layout = Some(layout); + self + } + + pub fn with_number_plays(mut self, number: usize) -> Self { + self.number_plays = number; + self + } + + pub async fn run_client(&self) -> Result> { + let mut remaining_games = self.number_plays; + + let url = format!( + "{}/play/bot?{}", + self.server.replace("http", "ws"), + serde_urlencoded::to_string(&self.requested_rules).unwrap() + ); + log::debug!("Connecting to {}...", url); + let (mut socket, _) = match tokio_tungstenite::connect_async(url).await { + Ok(s) => s, + Err(e) => { + log::error!("Failed to establish WebSocket connection! {:?}", e); + return Err(Box::new(e)); + } + }; + + while let Some(chunk) = socket.next().await { + let message = match chunk? { + Message::Text(message) => { + log::trace!("TEXT message from server: {}", message); + + let msg: ServerMessage = serde_json::from_str(&message)?; + msg + } + Message::Binary(_) => { + log::debug!("BINARY message from server"); + continue; + } + Message::Ping(_) => { + log::debug!("PING from server"); + continue; + } + Message::Pong(_) => { + log::debug!("PONG from server"); + continue; + } + Message::Close(_) => { + log::debug!("CLOSE message request from server"); + break; + } + Message::Frame(_) => { + log::debug!("Frame from server"); + continue; + } + }; + + match message { + ServerMessage::WaitingForAnotherPlayer => { + log::debug!("Waiting for other player...") + } + ServerMessage::QueryBoatsLayout { rules } => { + assert_eq!(&rules, &self.requested_rules); + log::debug!("Server requested boats layout"); + socket + .send(Message::Text(serde_json::to_string( + &ClientMessage::BoatsLayout { + layout: self.layout.clone().unwrap_or_else(|| { + BoatsLayout::gen_random_for_rules(&self.requested_rules) + .unwrap() + }), + }, + )?)) + .await?; + } + ServerMessage::WaitingForOtherPlayerConfiguration => { + log::debug!("Waiting for other player configuration...") + } + ServerMessage::OpponentReady => log::debug!("Other player is ready!"), + ServerMessage::GameStarting => { + log::debug!("The game is starting..."); + remaining_games -= 1; + } + ServerMessage::OpponentMustFire { status } => { + assert_eq!(status.opponent_map.boats.number_of_boats(), 0); + log::debug!("Other player must fire!") + } + ServerMessage::RequestFire { status } => { + assert_eq!(status.opponent_map.boats.number_of_boats(), 0); + + let location = status.find_valid_random_fire_location(); + log::debug!("Will fire at {:?}", location); + socket + .send(Message::Text(serde_json::to_string( + &ClientMessage::Fire { location }, + )?)) + .await?; + } + ServerMessage::StrikeResult { pos, result } => { + log::debug!("Strike at {} result: {:?}", pos.human_print(), result) + } + ServerMessage::OpponentStrikeResult { pos, result } => log::debug!( + "Opponent trike at {} result: {:?}", + pos.human_print(), + result + ), + ServerMessage::LostGame { + your_map, + opponent_map, + } => { + log::debug!("We lost game :("); + log::debug!("Other game:\n{}\n", opponent_map.get_map()); + log::debug!("Our game:\n{}\n", your_map.get_map()); + + if remaining_games > 0 { + socket + .send(Message::Text(serde_json::to_string( + &ClientMessage::RequestRematch, + )?)) + .await?; + } else { + break; + } + } + ServerMessage::WonGame { + your_map, + opponent_map, + } => { + log::debug!("We won the game !!!!"); + log::debug!("Other game:\n{}\n", opponent_map.get_map()); + log::debug!("Our game:\n{}\n", your_map.get_map()); + + if remaining_games > 0 { + socket + .send(Message::Text(serde_json::to_string( + &ClientMessage::RequestRematch, + )?)) + .await?; + } else { + break; + } + } + ServerMessage::RejectedBoatsLayout { errors } => { + log::warn!("Rejected boat layout: {:?}", errors); + return Ok(ClientEndResult::InvalidBoatsLayout); + } + ServerMessage::SetOpponentName { name } => log::debug!("Opponent name: {}", name), + + ServerMessage::OpponentRequestedRematch => { + log::debug!("Opponent rejected rematch."); + } + ServerMessage::OpponentAcceptedRematch => { + log::debug!("Opponent accepted rematch"); + } + ServerMessage::OpponentRejectedRematch => { + log::debug!("Opponent rejected rematch"); + return Ok(ClientEndResult::OpponentRejectedRematch); + } + } + } + + Ok(ClientEndResult::Finished) + } +} diff --git a/sea_battle_backend/src/test/client.rs b/sea_battle_backend/src/test/bot_client_bot_play.rs similarity index 63% rename from sea_battle_backend/src/test/client.rs rename to sea_battle_backend/src/test/bot_client_bot_play.rs index 07273d0..3eb5f75 100644 --- a/sea_battle_backend/src/test/client.rs +++ b/sea_battle_backend/src/test/bot_client_bot_play.rs @@ -1,10 +1,10 @@ use tokio::task; use crate::args::Args; -use crate::bot_client; -use crate::bot_client::ClientEndResult; use crate::data::{BoatsLayout, GameRules}; use crate::server::start_server; +use crate::test::bot_client; +use crate::test::bot_client::ClientEndResult; use crate::test::network_utils::wait_for_port; use crate::test::TestPort; @@ -12,14 +12,10 @@ use crate::test::TestPort; async fn invalid_port() { let _ = env_logger::builder().is_test(true).try_init(); - let rules = GameRules::random_players_rules(); - bot_client::run_client( - &TestPort::ClientInvalidPort.as_url(), - &rules, - BoatsLayout::gen_random_for_rules(&rules).unwrap(), - ) - .await - .unwrap_err(); + bot_client::BotClient::new(TestPort::ClientInvalidPort.as_url()) + .run_client() + .await + .unwrap_err(); } #[tokio::test] @@ -35,13 +31,14 @@ async fn invalid_rules() { task::spawn_local(start_server(Args::for_test(TestPort::ClientInvalidRules))); wait_for_port(TestPort::ClientInvalidRules.port()).await; - bot_client::run_client( - &TestPort::ClientInvalidRules.as_url(), - &rules, - BoatsLayout::gen_random_for_rules(&GameRules::random_players_rules()).unwrap(), - ) - .await - .unwrap_err(); + bot_client::BotClient::new(TestPort::ClientInvalidRules.as_url()) + .with_rules(rules.clone()) + .with_layout( + BoatsLayout::gen_random_for_rules(&GameRules::random_players_rules()).unwrap(), + ) + .run_client() + .await + .unwrap_err(); }) .await; } @@ -53,17 +50,13 @@ async fn full_game() { let local_set = task::LocalSet::new(); local_set .run_until(async move { - let rules = GameRules::random_players_rules(); task::spawn_local(start_server(Args::for_test(TestPort::FullGame))); wait_for_port(TestPort::FullGame.port()).await; - let res = bot_client::run_client( - &TestPort::FullGame.as_url(), - &rules, - BoatsLayout::gen_random_for_rules(&rules).unwrap(), - ) - .await - .unwrap(); + let res = bot_client::BotClient::new(TestPort::FullGame.as_url()) + .run_client() + .await + .unwrap(); assert_eq!(res, ClientEndResult::Finished); }) .await; @@ -83,13 +76,11 @@ async fn full_game_no_touching_boats() { ))); wait_for_port(TestPort::FullGameTouchingBoats.port()).await; - let res = bot_client::run_client( - &TestPort::FullGameTouchingBoats.as_url(), - &rules, - BoatsLayout::gen_random_for_rules(&rules).unwrap(), - ) - .await - .unwrap(); + let res = bot_client::BotClient::new(TestPort::FullGameTouchingBoats.as_url()) + .with_rules(rules) + .run_client() + .await + .unwrap(); assert_eq!(res, ClientEndResult::Finished); }) @@ -114,10 +105,12 @@ async fn invalid_boats_layout_number_of_boats() { rules_modified.pop_boat(); let layout = BoatsLayout::gen_random_for_rules(&rules_modified).unwrap(); - let res = - bot_client::run_client(&TestPort::FullGameTouchingBoats.as_url(), &rules, layout) - .await - .unwrap(); + let res = bot_client::BotClient::new(&TestPort::FullGameTouchingBoats.as_url()) + .with_rules(rules) + .with_layout(layout) + .run_client() + .await + .unwrap(); assert_eq!(res, ClientEndResult::InvalidBoatsLayout); }) @@ -143,12 +136,34 @@ async fn invalid_boats_layout_len_of_a_boat() { rules_modified.add_boat(previous - 1); let layout = BoatsLayout::gen_random_for_rules(&rules_modified).unwrap(); - let res = - bot_client::run_client(&TestPort::FullGameTouchingBoats.as_url(), &rules, layout) - .await - .unwrap(); + let res = bot_client::BotClient::new(&TestPort::FullGameTouchingBoats.as_url()) + .with_rules(rules) + .with_layout(layout) + .run_client() + .await + .unwrap(); assert_eq!(res, ClientEndResult::InvalidBoatsLayout); }) .await; } + +#[tokio::test] +async fn full_game_multiple_rematches() { + let _ = env_logger::builder().is_test(true).try_init(); + + let local_set = task::LocalSet::new(); + local_set + .run_until(async move { + task::spawn_local(start_server(Args::for_test(TestPort::FullGame))); + wait_for_port(TestPort::FullGame.port()).await; + + let res = bot_client::BotClient::new(TestPort::FullGame.as_url()) + .with_number_plays(5) + .run_client() + .await + .unwrap(); + assert_eq!(res, ClientEndResult::Finished); + }) + .await; +} diff --git a/sea_battle_backend/src/test/mod.rs b/sea_battle_backend/src/test/mod.rs index bf09977..795b3db 100644 --- a/sea_battle_backend/src/test/mod.rs +++ b/sea_battle_backend/src/test/mod.rs @@ -27,5 +27,7 @@ impl Args { } } -mod client; +#[cfg(test)] +pub mod bot_client; +mod bot_client_bot_play; mod network_utils;