Add tests for no replay after hit rule

This commit is contained in:
Pierre HUBERT 2022-09-19 19:29:11 +02:00
parent 7cfd7a4899
commit 6832aebedb
17 changed files with 280 additions and 132 deletions

View File

@ -1,7 +1,7 @@
use actix::Addr; use actix::Addr;
use uuid::Uuid; use uuid::Uuid;
use crate::data::{BoatsLayout, Coordinates, CurrentGameStatus, EndGameMap, FireResult, GameRules}; use crate::data::{BoatsLayout, Coordinates, CurrentGameStatus, FireResult, GameRules};
use crate::game::{Fire, Game, Player, RespondRequestRematch, SetBoatsLayout}; use crate::game::{Fire, Game, Player, RespondRequestRematch, SetBoatsLayout};
#[derive(Clone)] #[derive(Clone)]
@ -64,9 +64,9 @@ impl Player for LinearBot {
fn other_player_strike_result(&self, _c: Coordinates, _res: FireResult) {} fn other_player_strike_result(&self, _c: Coordinates, _res: FireResult) {}
fn lost_game(&self, _your_map: EndGameMap, _opponent_map: EndGameMap) {} fn lost_game(&self, _status: CurrentGameStatus) {}
fn won_game(&self, _your_map: EndGameMap, _opponent_map: EndGameMap) {} fn won_game(&self, _status: CurrentGameStatus) {}
fn opponent_requested_rematch(&self) { fn opponent_requested_rematch(&self) {
self.game.do_send(RespondRequestRematch(self.uuid, true)); self.game.do_send(RespondRequestRematch(self.uuid, true));

View File

@ -1,7 +1,7 @@
use actix::Addr; use actix::Addr;
use uuid::Uuid; use uuid::Uuid;
use crate::data::{BoatsLayout, Coordinates, CurrentGameStatus, EndGameMap, FireResult, GameRules}; use crate::data::{BoatsLayout, Coordinates, CurrentGameStatus, FireResult, GameRules};
use crate::game::{Fire, Game, Player, RespondRequestRematch, SetBoatsLayout}; use crate::game::{Fire, Game, Player, RespondRequestRematch, SetBoatsLayout};
#[derive(Clone)] #[derive(Clone)]
@ -64,9 +64,9 @@ impl Player for RandomBot {
fn other_player_strike_result(&self, _c: Coordinates, _res: FireResult) {} fn other_player_strike_result(&self, _c: Coordinates, _res: FireResult) {}
fn lost_game(&self, _your_map: EndGameMap, _opponent_map: EndGameMap) {} fn lost_game(&self, _status: CurrentGameStatus) {}
fn won_game(&self, _your_map: EndGameMap, _opponent_map: EndGameMap) {} fn won_game(&self, _status: CurrentGameStatus) {}
fn opponent_requested_rematch(&self) { fn opponent_requested_rematch(&self) {
self.game.do_send(RespondRequestRematch(self.uuid, true)); self.game.do_send(RespondRequestRematch(self.uuid, true));

View File

@ -213,6 +213,42 @@ impl BoatsLayout {
Ok(boats) Ok(boats)
} }
/// Generate boats layout that put boats at the beginning of the map
pub fn layout_for_boats_at_beginning_of_map(rules: &GameRules) -> std::io::Result<Self> {
let mut boats = Self(Vec::with_capacity(rules.boats_list().len()));
let mut list = rules.boats_list();
list.sort();
list.reverse();
for y in 0..rules.map_height {
for x in 0..rules.map_width {
if list.is_empty() {
break;
}
let position = BoatPosition {
start: Coordinates::new(x as i32, y as i32),
len: list[0],
direction: BoatDirection::Right,
};
if boats.is_acceptable_boat_position(&position, rules) {
list.remove(0);
boats.0.push(position);
}
}
}
if !list.is_empty() {
return Err(std::io::Error::new(
ErrorKind::Other,
"Un-usable game rules!",
));
}
Ok(boats)
}
fn is_acceptable_boat_position(&self, pos: &BoatPosition, rules: &GameRules) -> bool { fn is_acceptable_boat_position(&self, pos: &BoatPosition, rules: &GameRules) -> bool {
// Check if boat coordinates are valid // Check if boat coordinates are valid
if pos.all_coordinates().iter().any(|c| !c.is_valid(rules)) { if pos.all_coordinates().iter().any(|c| !c.is_valid(rules)) {

View File

@ -16,6 +16,10 @@ impl CurrentGameMapStatus {
pub fn did_fire_at_location(&self, c: Coordinates) -> bool { pub fn did_fire_at_location(&self, c: Coordinates) -> bool {
self.successful_strikes.contains(&c) || self.failed_strikes.contains(&c) self.successful_strikes.contains(&c) || self.failed_strikes.contains(&c)
} }
pub fn number_of_fires(&self) -> usize {
self.successful_strikes.len() + self.failed_strikes.len()
}
} }
struct PrintableCurrentGameMapStatus(GameRules, CurrentGameMapStatus); struct PrintableCurrentGameMapStatus(GameRules, CurrentGameMapStatus);
@ -207,12 +211,20 @@ impl CurrentGameStatus {
boats_size.first().cloned() boats_size.first().cloned()
} }
pub fn get_your_map(&self) -> String {
PrintableCurrentGameMapStatus(self.rules.clone(), self.your_map.clone()).get_map()
}
pub fn get_opponent_map(&self) -> String {
PrintableCurrentGameMapStatus(self.rules.clone(), self.opponent_map.clone()).get_map()
}
pub fn print_your_map(&self) { pub fn print_your_map(&self) {
PrintableCurrentGameMapStatus(self.rules.clone(), self.your_map.clone()).print_map() print!("{}", self.get_your_map());
} }
pub fn print_opponent_map(&self) { pub fn print_opponent_map(&self) {
PrintableCurrentGameMapStatus(self.rules.clone(), self.opponent_map.clone()).print_map() print!("{}", self.get_opponent_map());
} }
} }

View File

@ -1,22 +0,0 @@
use std::fmt::Write;
use crate::data::{BoatsLayout, MapCellContent};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EndGameMap {
pub boats: BoatsLayout,
pub grid: Vec<Vec<MapCellContent>>,
}
impl EndGameMap {
pub fn get_map(&self) -> String {
let mut s = String::new();
for row in &self.grid {
for col in row {
write!(&mut s, "{} ", col.letter()).unwrap();
}
s.push('\n');
}
s
}
}

View File

@ -1,7 +1,5 @@
use crate::data::boats_layout::{BoatsLayout, Coordinates}; use crate::data::boats_layout::{BoatsLayout, Coordinates};
use crate::data::{ use crate::data::*;
BoatPosition, CurrentGameMapStatus, EndGameMap, GameRules, MapCellContent, PrintableMap,
};
#[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum FireResult { pub enum FireResult {
@ -110,19 +108,6 @@ impl GameMap {
sunk_boats: self.sunk_boats.clone(), sunk_boats: self.sunk_boats.clone(),
} }
} }
pub fn final_map(&self) -> EndGameMap {
EndGameMap {
boats: self.boats_config.clone(),
grid: (0..self.rules.map_height)
.map(|y| {
(0..self.rules.map_width)
.map(|x| self.get_cell_content(Coordinates::new(x as i32, y as i32)))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(),
}
}
} }
impl PrintableMap for GameMap { impl PrintableMap for GameMap {

View File

@ -38,6 +38,11 @@ impl GameRules {
self self
} }
pub fn with_player_continue_on_hit(mut self, c: bool) -> Self {
self.player_continue_on_hit = c;
self
}
/// Set the list of boats for this configuration /// Set the list of boats for this configuration
pub fn set_boats_list(&mut self, boats: &[usize]) { pub fn set_boats_list(&mut self, boats: &[usize]) {
self.boats_str = boats self.boats_str = boats

View File

@ -1,6 +1,5 @@
pub use boats_layout::*; pub use boats_layout::*;
pub use current_game_status::*; pub use current_game_status::*;
pub use end_game_map::*;
pub use game_map::*; pub use game_map::*;
pub use game_rules::*; pub use game_rules::*;
pub use play_config::*; pub use play_config::*;
@ -8,7 +7,6 @@ pub use printable_map::*;
mod boats_layout; mod boats_layout;
mod current_game_status; mod current_game_status;
mod end_game_map;
mod game_map; mod game_map;
mod game_rules; mod game_rules;
mod play_config; mod play_config;

View File

@ -1,3 +1,6 @@
use std::fmt::Write;
use std::string::String;
use crate::data::Coordinates; use crate::data::Coordinates;
#[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
@ -27,6 +30,12 @@ pub trait PrintableMap {
fn map_cell_content(&self, c: Coordinates) -> MapCellContent; fn map_cell_content(&self, c: Coordinates) -> MapCellContent;
fn print_map(&self) { fn print_map(&self) {
print!("{}", self.get_map());
}
fn get_map(&self) -> String {
let mut out = String::with_capacity(100);
let mut y = 0; let mut y = 0;
let mut x; let mut x;
loop { loop {
@ -38,11 +47,11 @@ pub trait PrintableMap {
break; break;
} }
print!("{} ", content.letter()); write!(out, "{} ", content.letter()).unwrap();
x += 1; x += 1;
} }
println!(); out.push('\n');
// x == 0 <=> we reached the end of the map // x == 0 <=> we reached the end of the map
if x == 0 { if x == 0 {
@ -51,5 +60,7 @@ pub trait PrintableMap {
y += 1; y += 1;
} }
out
} }
} }

View File

@ -32,9 +32,9 @@ pub trait Player {
fn other_player_strike_result(&self, c: Coordinates, res: FireResult); fn other_player_strike_result(&self, c: Coordinates, res: FireResult);
fn lost_game(&self, your_map: EndGameMap, opponent_map: EndGameMap); fn lost_game(&self, status: CurrentGameStatus);
fn won_game(&self, your_map: EndGameMap, opponent_map: EndGameMap); fn won_game(&self, status: CurrentGameStatus);
fn opponent_requested_rematch(&self); fn opponent_requested_rematch(&self);
@ -163,11 +163,9 @@ impl Game {
{ {
self.status = GameStatus::Finished; self.status = GameStatus::Finished;
let winner_map = self.player_map(self.turn).final_map(); self.players[self.turn].won_game(self.get_game_status_for_player(self.turn));
let looser_map = self.player_map(opponent(self.turn)).final_map(); self.players[opponent(self.turn)]
.lost_game(self.get_game_status_for_player(opponent(self.turn)));
self.players[self.turn].won_game(winner_map.clone(), looser_map.clone());
self.players[opponent(self.turn)].lost_game(looser_map, winner_map);
return; return;
} }
@ -215,7 +213,9 @@ impl Game {
CurrentGameStatus { CurrentGameStatus {
rules: self.rules.clone(), rules: self.rules.clone(),
your_map: self.player_map(id).current_map_status(false), your_map: self.player_map(id).current_map_status(false),
opponent_map: self.player_map(opponent(id)).current_map_status(true), opponent_map: self
.player_map(opponent(id))
.current_map_status(self.status != GameStatus::Finished),
} }
} }
} }

View File

@ -1,7 +1,7 @@
use actix::Addr; use actix::Addr;
use uuid::Uuid; use uuid::Uuid;
use crate::data::{Coordinates, CurrentGameStatus, EndGameMap, FireResult, GameRules}; use crate::data::*;
use crate::game::*; use crate::game::*;
use crate::human_player_ws::{ClientMessage, HumanPlayerWS, ServerMessage}; use crate::human_player_ws::{ClientMessage, HumanPlayerWS, ServerMessage};
@ -74,18 +74,12 @@ impl Player for HumanPlayer {
}); });
} }
fn lost_game(&self, your_map: EndGameMap, opponent_map: EndGameMap) { fn lost_game(&self, status: CurrentGameStatus) {
self.player.do_send(ServerMessage::LostGame { self.player.do_send(ServerMessage::LostGame { status });
your_map,
opponent_map,
});
} }
fn won_game(&self, your_map: EndGameMap, opponent_map: EndGameMap) { fn won_game(&self, status: CurrentGameStatus) {
self.player.do_send(ServerMessage::WonGame { self.player.do_send(ServerMessage::WonGame { status });
your_map,
opponent_map,
});
} }
fn opponent_requested_rematch(&self) { fn opponent_requested_rematch(&self) {

View File

@ -1,17 +1,15 @@
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use crate::bots::linear_bot::LinearBot;
use actix::prelude::*; use actix::prelude::*;
use actix::{Actor, Handler, StreamHandler}; use actix::{Actor, Handler, StreamHandler};
use actix_web_actors::ws; use actix_web_actors::ws;
use actix_web_actors::ws::{CloseCode, CloseReason, Message, ProtocolError, WebsocketContext}; use actix_web_actors::ws::{CloseCode, CloseReason, Message, ProtocolError, WebsocketContext};
use uuid::Uuid; use uuid::Uuid;
use crate::bots::linear_bot::LinearBot;
use crate::bots::random_bot::RandomBot; use crate::bots::random_bot::RandomBot;
use crate::data::{ use crate::data::{BoatsLayout, BotType, Coordinates, CurrentGameStatus, FireResult, GameRules};
BoatsLayout, BotType, Coordinates, CurrentGameStatus, EndGameMap, FireResult, GameRules,
};
use crate::game::{AddPlayer, Game}; use crate::game::{AddPlayer, Game};
use crate::human_player::HumanPlayer; use crate::human_player::HumanPlayer;
@ -73,12 +71,10 @@ pub enum ServerMessage {
result: FireResult, result: FireResult,
}, },
LostGame { LostGame {
your_map: EndGameMap, status: CurrentGameStatus,
opponent_map: EndGameMap,
}, },
WonGame { WonGame {
your_map: EndGameMap, status: CurrentGameStatus,
opponent_map: EndGameMap,
}, },
OpponentRequestedRematch, OpponentRequestedRematch,
OpponentAcceptedRematch, OpponentAcceptedRematch,

View File

@ -160,13 +160,10 @@ impl BotClient {
pos.human_print(), pos.human_print(),
result result
), ),
ServerMessage::LostGame { ServerMessage::LostGame { status } => {
your_map,
opponent_map,
} => {
log::debug!("We lost game :("); log::debug!("We lost game :(");
log::debug!("Other game:\n{}\n", opponent_map.get_map()); log::debug!("Opponent map:\n{}", status.get_opponent_map());
log::debug!("Our game:\n{}\n", your_map.get_map()); log::debug!("Our map:\n{}\n", status.get_your_map());
if remaining_games > 0 { if remaining_games > 0 {
socket socket
@ -178,13 +175,10 @@ impl BotClient {
break; break;
} }
} }
ServerMessage::WonGame { ServerMessage::WonGame { status } => {
your_map,
opponent_map,
} => {
log::debug!("We won the game !!!!"); log::debug!("We won the game !!!!");
log::debug!("Other game:\n{}\n", opponent_map.get_map()); log::debug!("Opponent map:\n{}\n", status.get_opponent_map());
log::debug!("Our game:\n{}\n", your_map.get_map()); log::debug!("Our map:\n{}\n", status.get_your_map());
if remaining_games > 0 { if remaining_games > 0 {
socket socket

View File

@ -1,11 +1,12 @@
use tokio::task; use tokio::task;
use crate::args::Args; use crate::args::Args;
use crate::data::{BotType, Coordinates, GameRules}; use crate::data::{BoatsLayout, BotType, Coordinates, GameRules};
use crate::human_player_ws::ServerMessage; use crate::human_player_ws::ServerMessage;
use crate::server::start_server; use crate::server::start_server;
use crate::test::bot_client::ClientEndResult; 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::play_utils::check_no_replay_on_hit;
use crate::test::{bot_client, TestPort}; use crate::test::{bot_client, TestPort};
fn check_strikes_are_linear(msg: &ServerMessage) { fn check_strikes_are_linear(msg: &ServerMessage) {
@ -49,3 +50,95 @@ async fn full_game() {
}) })
.await; .await;
} }
#[tokio::test]
async fn full_game_no_replay_on_hit() {
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::LinearBotNoReplayOnHit,
)));
wait_for_port(TestPort::LinearBotNoReplayOnHit.port()).await;
let res = bot_client::BotClient::new(TestPort::LinearBotNoReplayOnHit.as_url())
.with_rules(
GameRules::random_players_rules()
.with_player_continue_on_hit(false)
.with_bot_type(BotType::Linear),
)
.with_server_msg_callback(check_no_replay_on_hit)
.run_client()
.await
.unwrap();
assert_eq!(res, ClientEndResult::Finished);
})
.await;
}
#[tokio::test]
async fn full_game_no_replay_on_hit_two() {
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::LinearBotNoReplayOnHit,
)));
wait_for_port(TestPort::LinearBotNoReplayOnHit.port()).await;
let rules = GameRules::random_players_rules()
.with_player_continue_on_hit(false)
.with_bot_type(BotType::Linear);
let layout = BoatsLayout::layout_for_boats_at_beginning_of_map(&rules).unwrap();
let res = bot_client::BotClient::new(TestPort::LinearBotNoReplayOnHit.as_url())
.with_rules(rules.clone())
.with_layout(layout)
.with_server_msg_callback(check_no_replay_on_hit)
.run_client()
.await
.unwrap();
assert_eq!(res, ClientEndResult::Finished);
})
.await;
}
#[tokio::test]
async fn full_game_with_replay_on_hit() {
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::LinearBotNoReplayOnHit,
)));
wait_for_port(TestPort::LinearBotNoReplayOnHit.port()).await;
let rules = GameRules::random_players_rules()
.with_player_continue_on_hit(true)
.with_bot_type(BotType::Linear);
let layout = BoatsLayout::layout_for_boats_at_beginning_of_map(&rules).unwrap();
let res = bot_client::BotClient::new(TestPort::LinearBotNoReplayOnHit.as_url())
.with_rules(rules.clone())
.with_layout(layout)
.with_server_msg_callback(|msg| {
if let ServerMessage::LostGame { status } | ServerMessage::WonGame { status } =
msg
{
assert!(
status.opponent_map.number_of_fires()
< status.your_map.number_of_fires()
);
}
})
.run_client()
.await
.unwrap();
assert_eq!(res, ClientEndResult::Finished);
})
.await;
}

View File

@ -6,13 +6,14 @@ use crate::server::start_server;
use crate::test::bot_client; use crate::test::bot_client;
use crate::test::bot_client::ClientEndResult; 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::play_utils::check_no_replay_on_hit;
use crate::test::TestPort; use crate::test::TestPort;
#[tokio::test] #[tokio::test]
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();
bot_client::BotClient::new(TestPort::ClientInvalidPort.as_url()) bot_client::BotClient::new(TestPort::RandomBotClientInvalidPort.as_url())
.run_client() .run_client()
.await .await
.unwrap_err(); .unwrap_err();
@ -28,10 +29,12 @@ async fn invalid_rules() {
let mut rules = GameRules::random_players_rules(); let mut rules = GameRules::random_players_rules();
rules.map_width = 0; rules.map_width = 0;
task::spawn_local(start_server(Args::for_test(TestPort::ClientInvalidRules))); task::spawn_local(start_server(Args::for_test(
wait_for_port(TestPort::ClientInvalidRules.port()).await; TestPort::RandomBotClientInvalidRules,
)));
wait_for_port(TestPort::RandomBotClientInvalidRules.port()).await;
bot_client::BotClient::new(TestPort::ClientInvalidRules.as_url()) bot_client::BotClient::new(TestPort::RandomBotClientInvalidRules.as_url())
.with_rules(rules.clone()) .with_rules(rules.clone())
.with_layout( .with_layout(
BoatsLayout::gen_random_for_rules(&GameRules::random_players_rules()).unwrap(), BoatsLayout::gen_random_for_rules(&GameRules::random_players_rules()).unwrap(),
@ -50,10 +53,10 @@ 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 {
task::spawn_local(start_server(Args::for_test(TestPort::FullGame))); task::spawn_local(start_server(Args::for_test(TestPort::RandomBotFullGame)));
wait_for_port(TestPort::FullGame.port()).await; wait_for_port(TestPort::RandomBotFullGame.port()).await;
let res = bot_client::BotClient::new(TestPort::FullGame.as_url()) let res = bot_client::BotClient::new(TestPort::RandomBotFullGame.as_url())
.run_client() .run_client()
.await .await
.unwrap(); .unwrap();
@ -72,15 +75,16 @@ async fn full_game_no_touching_boats() {
let mut rules = GameRules::random_players_rules(); let mut rules = GameRules::random_players_rules();
rules.boats_can_touch = false; rules.boats_can_touch = false;
task::spawn_local(start_server(Args::for_test( task::spawn_local(start_server(Args::for_test(
TestPort::FullGameNoTouchingBoats, TestPort::RandomBotFullGameNoTouchingBoats,
))); )));
wait_for_port(TestPort::FullGameNoTouchingBoats.port()).await; wait_for_port(TestPort::RandomBotFullGameNoTouchingBoats.port()).await;
let res = bot_client::BotClient::new(TestPort::FullGameNoTouchingBoats.as_url()) let res =
.with_rules(rules) bot_client::BotClient::new(TestPort::RandomBotFullGameNoTouchingBoats.as_url())
.run_client() .with_rules(rules)
.await .run_client()
.unwrap(); .await
.unwrap();
assert_eq!(res, ClientEndResult::Finished); assert_eq!(res, ClientEndResult::Finished);
}) })
@ -97,21 +101,22 @@ async fn invalid_boats_layout_number_of_boats() {
let mut rules = GameRules::random_players_rules(); let mut rules = GameRules::random_players_rules();
rules.boats_can_touch = false; rules.boats_can_touch = false;
task::spawn_local(start_server(Args::for_test( task::spawn_local(start_server(Args::for_test(
TestPort::InvalidBoatsLayoutNumberOfBoats, TestPort::RandomBotInvalidBoatsLayoutNumberOfBoats,
))); )));
wait_for_port(TestPort::InvalidBoatsLayoutNumberOfBoats.port()).await; wait_for_port(TestPort::RandomBotInvalidBoatsLayoutNumberOfBoats.port()).await;
let mut rules_modified = rules.clone(); let mut rules_modified = rules.clone();
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(
bot_client::BotClient::new(&TestPort::InvalidBoatsLayoutNumberOfBoats.as_url()) &TestPort::RandomBotInvalidBoatsLayoutNumberOfBoats.as_url(),
.with_rules(rules) )
.with_layout(layout) .with_rules(rules)
.run_client() .with_layout(layout)
.await .run_client()
.unwrap(); .await
.unwrap();
assert_eq!(res, ClientEndResult::InvalidBoatsLayout); assert_eq!(res, ClientEndResult::InvalidBoatsLayout);
}) })
@ -128,21 +133,23 @@ async fn invalid_boats_layout_len_of_a_boat() {
let mut rules = GameRules::random_players_rules(); let mut rules = GameRules::random_players_rules();
rules.boats_can_touch = false; rules.boats_can_touch = false;
task::spawn_local(start_server(Args::for_test( task::spawn_local(start_server(Args::for_test(
TestPort::InvalidBoatsLayoutLenOfABoat, TestPort::RandomBotInvalidBoatsLayoutLenOfABoat,
))); )));
wait_for_port(TestPort::InvalidBoatsLayoutLenOfABoat.port()).await; wait_for_port(TestPort::RandomBotInvalidBoatsLayoutLenOfABoat.port()).await;
let mut rules_modified = rules.clone(); let mut rules_modified = rules.clone();
let previous = rules_modified.pop_boat(); let previous = rules_modified.pop_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 = bot_client::BotClient::new(&TestPort::InvalidBoatsLayoutLenOfABoat.as_url()) let res = bot_client::BotClient::new(
.with_rules(rules) &TestPort::RandomBotInvalidBoatsLayoutLenOfABoat.as_url(),
.with_layout(layout) )
.run_client() .with_rules(rules)
.await .with_layout(layout)
.unwrap(); .run_client()
.await
.unwrap();
assert_eq!(res, ClientEndResult::InvalidBoatsLayout); assert_eq!(res, ClientEndResult::InvalidBoatsLayout);
}) })
@ -157,12 +164,36 @@ async fn full_game_multiple_rematches() {
local_set local_set
.run_until(async move { .run_until(async move {
task::spawn_local(start_server(Args::for_test( task::spawn_local(start_server(Args::for_test(
TestPort::FullGameMultipleRematch, TestPort::RandomBotFullGameMultipleRematch,
))); )));
wait_for_port(TestPort::FullGameMultipleRematch.port()).await; wait_for_port(TestPort::RandomBotFullGameMultipleRematch.port()).await;
let res = bot_client::BotClient::new(TestPort::FullGameMultipleRematch.as_url()) let res =
.with_number_plays(5) bot_client::BotClient::new(TestPort::RandomBotFullGameMultipleRematch.as_url())
.with_number_plays(5)
.run_client()
.await
.unwrap();
assert_eq!(res, ClientEndResult::Finished);
})
.await;
}
#[tokio::test]
async fn full_game_no_replay_on_hit() {
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::RandomBotNoReplayOnHit,
)));
wait_for_port(TestPort::RandomBotNoReplayOnHit.port()).await;
let res = bot_client::BotClient::new(TestPort::RandomBotNoReplayOnHit.as_url())
.with_rules(GameRules::random_players_rules().with_player_continue_on_hit(false))
.with_server_msg_callback(check_no_replay_on_hit)
.run_client() .run_client()
.await .await
.unwrap(); .unwrap();

View File

@ -2,14 +2,16 @@ use crate::args::Args;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
enum TestPort { enum TestPort {
ClientInvalidPort = 20000, RandomBotClientInvalidPort = 20000,
ClientInvalidRules, RandomBotClientInvalidRules,
FullGame, RandomBotFullGame,
FullGameNoTouchingBoats, RandomBotFullGameNoTouchingBoats,
InvalidBoatsLayoutNumberOfBoats, RandomBotInvalidBoatsLayoutNumberOfBoats,
InvalidBoatsLayoutLenOfABoat, RandomBotInvalidBoatsLayoutLenOfABoat,
FullGameMultipleRematch, RandomBotFullGameMultipleRematch,
RandomBotNoReplayOnHit,
LinearBotFullGame, LinearBotFullGame,
LinearBotNoReplayOnHit,
} }
impl TestPort { impl TestPort {
@ -36,3 +38,4 @@ pub mod bot_client;
mod bot_client_bot_linear_play; mod bot_client_bot_linear_play;
mod bot_client_bot_random_play; mod bot_client_bot_random_play;
mod network_utils; mod network_utils;
mod play_utils;

View File

@ -0,0 +1,12 @@
use crate::human_player_ws::ServerMessage;
/// Make sure player can not replay after successful hit
pub fn check_no_replay_on_hit(msg: &ServerMessage) {
if let ServerMessage::OpponentMustFire { status } | ServerMessage::RequestFire { status } = msg
{
let diff =
status.opponent_map.number_of_fires() as i32 - status.your_map.number_of_fires() as i32;
assert!(diff <= 1);
}
}