Added rematch feature
This commit is contained in:
		@@ -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<ClientEndResult, Box<dyn Error>> {
 | 
					 | 
				
			||||||
    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)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -5,7 +5,7 @@ use crate::data::{BoatPosition, CurrentGameMapStatus, EndGameMap, GameRules};
 | 
				
			|||||||
pub enum FireResult {
 | 
					pub enum FireResult {
 | 
				
			||||||
    Missed,
 | 
					    Missed,
 | 
				
			||||||
    Hit,
 | 
					    Hit,
 | 
				
			||||||
    Sunk,
 | 
					    Sunk(BoatPosition),
 | 
				
			||||||
    Rejected,
 | 
					    Rejected,
 | 
				
			||||||
    AlreadyTargetedPosition,
 | 
					    AlreadyTargetedPosition,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -111,7 +111,7 @@ impl GameMap {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                FireResult::Sunk
 | 
					                FireResult::Sunk(*b)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@ pub trait Player {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    fn request_fire(&self, status: CurrentGameStatus);
 | 
					    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);
 | 
					    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 lost_game(&self, your_map: EndGameMap, opponent_map: EndGameMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn won_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 {
 | 
					fn opponent(index: usize) -> usize {
 | 
				
			||||||
@@ -49,6 +55,8 @@ enum GameStatus {
 | 
				
			|||||||
    WaitingForBoatsDisposition,
 | 
					    WaitingForBoatsDisposition,
 | 
				
			||||||
    Started,
 | 
					    Started,
 | 
				
			||||||
    Finished,
 | 
					    Finished,
 | 
				
			||||||
 | 
					    RematchRequested,
 | 
				
			||||||
 | 
					    RematchRejected,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Game {
 | 
					pub struct Game {
 | 
				
			||||||
@@ -109,7 +117,7 @@ impl Game {
 | 
				
			|||||||
    fn request_fire(&self) {
 | 
					    fn request_fire(&self) {
 | 
				
			||||||
        self.players[self.turn].request_fire(self.get_game_status_for_player(self.turn));
 | 
					        self.players[self.turn].request_fire(self.get_game_status_for_player(self.turn));
 | 
				
			||||||
        self.players[opponent(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 {
 | 
					    fn player_map(&self, id: usize) -> &GameMap {
 | 
				
			||||||
@@ -143,7 +151,9 @@ impl Game {
 | 
				
			|||||||
            return;
 | 
					            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;
 | 
					            self.status = GameStatus::Finished;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let winner_map = self.player_map(self.turn).final_map();
 | 
					            let winner_map = self.player_map(self.turn).final_map();
 | 
				
			||||||
@@ -155,7 +165,7 @@ impl Game {
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (result == FireResult::Sunk || result == FireResult::Hit)
 | 
					        if matches!(result, FireResult::Sunk(_) | FireResult::Hit)
 | 
				
			||||||
            && !self.rules.player_continue_on_hit
 | 
					            && !self.rules.player_continue_on_hit
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            self.turn = opponent(self.turn);
 | 
					            self.turn = opponent(self.turn);
 | 
				
			||||||
@@ -164,6 +174,35 @@ impl Game {
 | 
				
			|||||||
        self.request_fire();
 | 
					        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
 | 
					    /// Get current game status for a specific player
 | 
				
			||||||
    fn get_game_status_for_player(&self, id: usize) -> CurrentGameStatus {
 | 
					    fn get_game_status_for_player(&self, id: usize) -> CurrentGameStatus {
 | 
				
			||||||
        CurrentGameStatus {
 | 
					        CurrentGameStatus {
 | 
				
			||||||
@@ -260,3 +299,42 @@ impl Handler<Fire> for Game {
 | 
				
			|||||||
        self.handle_fire(msg.1)
 | 
					        self.handle_fire(msg.1)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Message, Debug)]
 | 
				
			||||||
 | 
					#[rtype(result = "()")]
 | 
				
			||||||
 | 
					pub struct RequestRematch(pub Uuid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Handler<RequestRematch> 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<RespondRequestRematch> 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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ use actix::Addr;
 | 
				
			|||||||
use uuid::Uuid;
 | 
					use uuid::Uuid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::data::{Coordinates, CurrentGameStatus, EndGameMap, FireResult, GameRules};
 | 
					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};
 | 
					use crate::human_player_ws::{ClientMessage, HumanPlayerWS, ServerMessage};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct HumanPlayer {
 | 
					pub struct HumanPlayer {
 | 
				
			||||||
@@ -40,7 +40,7 @@ impl Player for HumanPlayer {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn notify_other_player_ready(&self) {
 | 
					    fn notify_other_player_ready(&self) {
 | 
				
			||||||
        self.player.do_send(ServerMessage::OtherPlayerReady);
 | 
					        self.player.do_send(ServerMessage::OpponentReady);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn notify_game_starting(&self) {
 | 
					    fn notify_game_starting(&self) {
 | 
				
			||||||
@@ -51,9 +51,9 @@ impl Player for HumanPlayer {
 | 
				
			|||||||
        self.player.do_send(ServerMessage::RequestFire { status });
 | 
					        self.player.do_send(ServerMessage::RequestFire { status });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn other_player_must_fire(&self, status: CurrentGameStatus) {
 | 
					    fn opponent_must_fire(&self, status: CurrentGameStatus) {
 | 
				
			||||||
        self.player
 | 
					        self.player
 | 
				
			||||||
            .do_send(ServerMessage::OtherPlayerMustFire { status });
 | 
					            .do_send(ServerMessage::OpponentMustFire { status });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn strike_result(&self, c: Coordinates, res: FireResult) {
 | 
					    fn strike_result(&self, c: Coordinates, res: FireResult) {
 | 
				
			||||||
@@ -83,6 +83,18 @@ impl Player for HumanPlayer {
 | 
				
			|||||||
            opponent_map,
 | 
					            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 {
 | 
					impl HumanPlayer {
 | 
				
			||||||
@@ -95,6 +107,14 @@ impl HumanPlayer {
 | 
				
			|||||||
                self.game.do_send(SetBoatsLayout(self.uuid, layout))
 | 
					                self.game.do_send(SetBoatsLayout(self.uuid, layout))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ClientMessage::Fire { location } => self.game.do_send(Fire(self.uuid, location)),
 | 
					            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))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,9 @@ pub enum ClientMessage {
 | 
				
			|||||||
    StopGame,
 | 
					    StopGame,
 | 
				
			||||||
    BoatsLayout { layout: BoatsLayout },
 | 
					    BoatsLayout { layout: BoatsLayout },
 | 
				
			||||||
    Fire { location: Coordinates },
 | 
					    Fire { location: Coordinates },
 | 
				
			||||||
 | 
					    RequestRematch,
 | 
				
			||||||
 | 
					    AcceptRematch,
 | 
				
			||||||
 | 
					    RejectRematch,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Message)]
 | 
					#[derive(Message)]
 | 
				
			||||||
@@ -52,9 +55,9 @@ pub enum ServerMessage {
 | 
				
			|||||||
        errors: Vec<String>,
 | 
					        errors: Vec<String>,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    WaitingForOtherPlayerConfiguration,
 | 
					    WaitingForOtherPlayerConfiguration,
 | 
				
			||||||
    OtherPlayerReady,
 | 
					    OpponentReady,
 | 
				
			||||||
    GameStarting,
 | 
					    GameStarting,
 | 
				
			||||||
    OtherPlayerMustFire {
 | 
					    OpponentMustFire {
 | 
				
			||||||
        status: CurrentGameStatus,
 | 
					        status: CurrentGameStatus,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    RequestFire {
 | 
					    RequestFire {
 | 
				
			||||||
@@ -76,6 +79,9 @@ pub enum ServerMessage {
 | 
				
			|||||||
        your_map: EndGameMap,
 | 
					        your_map: EndGameMap,
 | 
				
			||||||
        opponent_map: EndGameMap,
 | 
					        opponent_map: EndGameMap,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    OpponentRequestedRematch,
 | 
				
			||||||
 | 
					    OpponentAcceptedRematch,
 | 
				
			||||||
 | 
					    OpponentRejectedRematch,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct HumanPlayerWS {
 | 
					pub struct HumanPlayerWS {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,6 @@
 | 
				
			|||||||
extern crate core;
 | 
					extern crate core;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod args;
 | 
					pub mod args;
 | 
				
			||||||
#[cfg(test)]
 | 
					 | 
				
			||||||
pub mod bot_client;
 | 
					 | 
				
			||||||
pub mod consts;
 | 
					pub mod consts;
 | 
				
			||||||
pub mod data;
 | 
					pub mod data;
 | 
				
			||||||
pub mod game;
 | 
					pub mod game;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ use actix::Addr;
 | 
				
			|||||||
use uuid::Uuid;
 | 
					use uuid::Uuid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::data::{BoatsLayout, Coordinates, CurrentGameStatus, EndGameMap, FireResult, GameRules};
 | 
					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)]
 | 
					#[derive(Clone)]
 | 
				
			||||||
pub struct RandomBot {
 | 
					pub struct RandomBot {
 | 
				
			||||||
@@ -54,7 +54,7 @@ impl Player for RandomBot {
 | 
				
			|||||||
            .do_send(Fire(self.uuid, status.find_valid_random_fire_location()));
 | 
					            .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) {}
 | 
					    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 lost_game(&self, _your_map: EndGameMap, _opponent_map: EndGameMap) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn won_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) {}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										205
									
								
								sea_battle_backend/src/test/bot_client.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								sea_battle_backend/src/test/bot_client.rs
									
									
									
									
									
										Normal file
									
								
							@@ -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<BoatsLayout>,
 | 
				
			||||||
 | 
					    number_plays: usize,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl BotClient {
 | 
				
			||||||
 | 
					    pub fn new<D>(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<ClientEndResult, Box<dyn Error>> {
 | 
				
			||||||
 | 
					        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)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
use tokio::task;
 | 
					use tokio::task;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::args::Args;
 | 
					use crate::args::Args;
 | 
				
			||||||
use crate::bot_client;
 | 
					 | 
				
			||||||
use crate::bot_client::ClientEndResult;
 | 
					 | 
				
			||||||
use crate::data::{BoatsLayout, GameRules};
 | 
					use crate::data::{BoatsLayout, GameRules};
 | 
				
			||||||
use crate::server::start_server;
 | 
					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::network_utils::wait_for_port;
 | 
				
			||||||
use crate::test::TestPort;
 | 
					use crate::test::TestPort;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -12,14 +12,10 @@ use crate::test::TestPort;
 | 
				
			|||||||
async fn invalid_port() {
 | 
					async fn invalid_port() {
 | 
				
			||||||
    let _ = env_logger::builder().is_test(true).try_init();
 | 
					    let _ = env_logger::builder().is_test(true).try_init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let rules = GameRules::random_players_rules();
 | 
					    bot_client::BotClient::new(TestPort::ClientInvalidPort.as_url())
 | 
				
			||||||
    bot_client::run_client(
 | 
					        .run_client()
 | 
				
			||||||
        &TestPort::ClientInvalidPort.as_url(),
 | 
					        .await
 | 
				
			||||||
        &rules,
 | 
					        .unwrap_err();
 | 
				
			||||||
        BoatsLayout::gen_random_for_rules(&rules).unwrap(),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .await
 | 
					 | 
				
			||||||
    .unwrap_err();
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[tokio::test]
 | 
					#[tokio::test]
 | 
				
			||||||
@@ -35,13 +31,14 @@ async fn invalid_rules() {
 | 
				
			|||||||
            task::spawn_local(start_server(Args::for_test(TestPort::ClientInvalidRules)));
 | 
					            task::spawn_local(start_server(Args::for_test(TestPort::ClientInvalidRules)));
 | 
				
			||||||
            wait_for_port(TestPort::ClientInvalidRules.port()).await;
 | 
					            wait_for_port(TestPort::ClientInvalidRules.port()).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            bot_client::run_client(
 | 
					            bot_client::BotClient::new(TestPort::ClientInvalidRules.as_url())
 | 
				
			||||||
                &TestPort::ClientInvalidRules.as_url(),
 | 
					                .with_rules(rules.clone())
 | 
				
			||||||
                &rules,
 | 
					                .with_layout(
 | 
				
			||||||
                BoatsLayout::gen_random_for_rules(&GameRules::random_players_rules()).unwrap(),
 | 
					                    BoatsLayout::gen_random_for_rules(&GameRules::random_players_rules()).unwrap(),
 | 
				
			||||||
            )
 | 
					                )
 | 
				
			||||||
            .await
 | 
					                .run_client()
 | 
				
			||||||
            .unwrap_err();
 | 
					                .await
 | 
				
			||||||
 | 
					                .unwrap_err();
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .await;
 | 
					        .await;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -53,17 +50,13 @@ async fn full_game() {
 | 
				
			|||||||
    let local_set = task::LocalSet::new();
 | 
					    let local_set = task::LocalSet::new();
 | 
				
			||||||
    local_set
 | 
					    local_set
 | 
				
			||||||
        .run_until(async move {
 | 
					        .run_until(async move {
 | 
				
			||||||
            let rules = GameRules::random_players_rules();
 | 
					 | 
				
			||||||
            task::spawn_local(start_server(Args::for_test(TestPort::FullGame)));
 | 
					            task::spawn_local(start_server(Args::for_test(TestPort::FullGame)));
 | 
				
			||||||
            wait_for_port(TestPort::FullGame.port()).await;
 | 
					            wait_for_port(TestPort::FullGame.port()).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let res = bot_client::run_client(
 | 
					            let res = bot_client::BotClient::new(TestPort::FullGame.as_url())
 | 
				
			||||||
                &TestPort::FullGame.as_url(),
 | 
					                .run_client()
 | 
				
			||||||
                &rules,
 | 
					                .await
 | 
				
			||||||
                BoatsLayout::gen_random_for_rules(&rules).unwrap(),
 | 
					                .unwrap();
 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            .await
 | 
					 | 
				
			||||||
            .unwrap();
 | 
					 | 
				
			||||||
            assert_eq!(res, ClientEndResult::Finished);
 | 
					            assert_eq!(res, ClientEndResult::Finished);
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .await;
 | 
					        .await;
 | 
				
			||||||
@@ -83,13 +76,11 @@ async fn full_game_no_touching_boats() {
 | 
				
			|||||||
            )));
 | 
					            )));
 | 
				
			||||||
            wait_for_port(TestPort::FullGameTouchingBoats.port()).await;
 | 
					            wait_for_port(TestPort::FullGameTouchingBoats.port()).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let res = bot_client::run_client(
 | 
					            let res = bot_client::BotClient::new(TestPort::FullGameTouchingBoats.as_url())
 | 
				
			||||||
                &TestPort::FullGameTouchingBoats.as_url(),
 | 
					                .with_rules(rules)
 | 
				
			||||||
                &rules,
 | 
					                .run_client()
 | 
				
			||||||
                BoatsLayout::gen_random_for_rules(&rules).unwrap(),
 | 
					                .await
 | 
				
			||||||
            )
 | 
					                .unwrap();
 | 
				
			||||||
            .await
 | 
					 | 
				
			||||||
            .unwrap();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            assert_eq!(res, ClientEndResult::Finished);
 | 
					            assert_eq!(res, ClientEndResult::Finished);
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
@@ -114,10 +105,12 @@ async fn invalid_boats_layout_number_of_boats() {
 | 
				
			|||||||
            rules_modified.pop_boat();
 | 
					            rules_modified.pop_boat();
 | 
				
			||||||
            let layout = BoatsLayout::gen_random_for_rules(&rules_modified).unwrap();
 | 
					            let layout = BoatsLayout::gen_random_for_rules(&rules_modified).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let res =
 | 
					            let res = bot_client::BotClient::new(&TestPort::FullGameTouchingBoats.as_url())
 | 
				
			||||||
                bot_client::run_client(&TestPort::FullGameTouchingBoats.as_url(), &rules, layout)
 | 
					                .with_rules(rules)
 | 
				
			||||||
                    .await
 | 
					                .with_layout(layout)
 | 
				
			||||||
                    .unwrap();
 | 
					                .run_client()
 | 
				
			||||||
 | 
					                .await
 | 
				
			||||||
 | 
					                .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            assert_eq!(res, ClientEndResult::InvalidBoatsLayout);
 | 
					            assert_eq!(res, ClientEndResult::InvalidBoatsLayout);
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
@@ -143,12 +136,34 @@ async fn invalid_boats_layout_len_of_a_boat() {
 | 
				
			|||||||
            rules_modified.add_boat(previous - 1);
 | 
					            rules_modified.add_boat(previous - 1);
 | 
				
			||||||
            let layout = BoatsLayout::gen_random_for_rules(&rules_modified).unwrap();
 | 
					            let layout = BoatsLayout::gen_random_for_rules(&rules_modified).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let res =
 | 
					            let res = bot_client::BotClient::new(&TestPort::FullGameTouchingBoats.as_url())
 | 
				
			||||||
                bot_client::run_client(&TestPort::FullGameTouchingBoats.as_url(), &rules, layout)
 | 
					                .with_rules(rules)
 | 
				
			||||||
                    .await
 | 
					                .with_layout(layout)
 | 
				
			||||||
                    .unwrap();
 | 
					                .run_client()
 | 
				
			||||||
 | 
					                .await
 | 
				
			||||||
 | 
					                .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            assert_eq!(res, ClientEndResult::InvalidBoatsLayout);
 | 
					            assert_eq!(res, ClientEndResult::InvalidBoatsLayout);
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .await;
 | 
					        .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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -27,5 +27,7 @@ impl Args {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod client;
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					pub mod bot_client;
 | 
				
			||||||
 | 
					mod bot_client_bot_play;
 | 
				
			||||||
mod network_utils;
 | 
					mod network_utils;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user