Can play full game
This commit is contained in:
parent
5b7a56d060
commit
2723724589
@ -77,6 +77,32 @@ pub async fn run_client(server: &str, rules: &GameRules) -> Result<(), Box<dyn E
|
|||||||
)?))
|
)?))
|
||||||
.await?;
|
.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,3 +17,5 @@ pub const MULTI_PLAYER_MAP_HEIGHT: usize = 10;
|
|||||||
pub const MULTI_PLAYER_BOATS_CAN_TOUCH: bool = true;
|
pub const MULTI_PLAYER_BOATS_CAN_TOUCH: bool = true;
|
||||||
pub const MULTI_PLAYER_PLAYER_CAN_CONTINUE_AFTER_HIT: bool = true;
|
pub const MULTI_PLAYER_PLAYER_CAN_CONTINUE_AFTER_HIT: bool = true;
|
||||||
pub const MULTI_PLAYER_PLAYER_BOATS: [usize; 5] = [2, 3, 3, 4, 5];
|
pub const MULTI_PLAYER_PLAYER_BOATS: [usize; 5] = [2, 3, 3, 4, 5];
|
||||||
|
|
||||||
|
pub const ALPHABET: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
@ -2,9 +2,10 @@ use std::io::ErrorKind;
|
|||||||
|
|
||||||
use rand::{Rng, RngCore};
|
use rand::{Rng, RngCore};
|
||||||
|
|
||||||
|
use crate::consts::ALPHABET;
|
||||||
use crate::data::GameRules;
|
use crate::data::GameRules;
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub enum BoatDirection {
|
pub enum BoatDirection {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
@ -95,9 +96,20 @@ impl Coordinates {
|
|||||||
&& self.x < rules.map_width as i32
|
&& self.x < rules.map_width as i32
|
||||||
&& self.y < rules.map_height as i32
|
&& self.y < rules.map_height as i32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn human_print(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"{}:{}",
|
||||||
|
match self.y < 0 || self.y >= ALPHABET.len() as i32 {
|
||||||
|
true => self.y.to_string(),
|
||||||
|
false => ALPHABET.chars().nth(self.y as usize).unwrap().to_string(),
|
||||||
|
},
|
||||||
|
self.x
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub struct BoatPosition {
|
pub struct BoatPosition {
|
||||||
start: Coordinates,
|
start: Coordinates,
|
||||||
len: usize,
|
len: usize,
|
||||||
@ -256,6 +268,10 @@ impl BoatsLayout {
|
|||||||
pub fn find_boat_at_position(&self, pos: Coordinates) -> Option<&BoatPosition> {
|
pub fn find_boat_at_position(&self, pos: Coordinates) -> Option<&BoatPosition> {
|
||||||
self.0.iter().find(|f| f.all_coordinates().contains(&pos))
|
self.0.iter().find(|f| f.all_coordinates().contains(&pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn number_of_boats(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
20
sea_battle_backend/src/data/end_game_map.rs
Normal file
20
sea_battle_backend/src/data/end_game_map.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
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 {
|
||||||
|
s.push_str(&format!("{} ", col.letter()));
|
||||||
|
}
|
||||||
|
s.push('\n');
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,34 @@
|
|||||||
use crate::data::boats_layout::{BoatsLayout, Coordinates};
|
use crate::data::boats_layout::{BoatsLayout, Coordinates};
|
||||||
use crate::data::GameRules;
|
use crate::data::{BoatPosition, EndGameMap, GameRules};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum FireResult {
|
||||||
|
Missed,
|
||||||
|
Hit,
|
||||||
|
Sunk,
|
||||||
|
Rejected,
|
||||||
|
AlreadyTargetedPosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum MapCellContent {
|
pub enum MapCellContent {
|
||||||
Invalid,
|
Invalid,
|
||||||
Nothing,
|
Nothing,
|
||||||
TouchedBoat,
|
TouchedBoat,
|
||||||
|
SunkBoat,
|
||||||
Boat,
|
Boat,
|
||||||
FailedStrike,
|
FailedStrike,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapCellContent {
|
impl MapCellContent {
|
||||||
fn letter(&self) -> &'static str {
|
pub fn letter(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
MapCellContent::Invalid => "!",
|
MapCellContent::Invalid => "!",
|
||||||
MapCellContent::Nothing => ".",
|
MapCellContent::Nothing => ".",
|
||||||
MapCellContent::TouchedBoat => "T",
|
MapCellContent::TouchedBoat => "T",
|
||||||
|
MapCellContent::SunkBoat => "S",
|
||||||
MapCellContent::Boat => "B",
|
MapCellContent::Boat => "B",
|
||||||
MapCellContent::FailedStrike => "X",
|
MapCellContent::FailedStrike => "x",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -24,6 +36,9 @@ impl MapCellContent {
|
|||||||
pub struct GameMap {
|
pub struct GameMap {
|
||||||
rules: GameRules,
|
rules: GameRules,
|
||||||
boats_config: BoatsLayout,
|
boats_config: BoatsLayout,
|
||||||
|
failed_strikes: Vec<Coordinates>,
|
||||||
|
successful_strikes: Vec<Coordinates>,
|
||||||
|
sunk_boats: Vec<BoatPosition>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameMap {
|
impl GameMap {
|
||||||
@ -31,19 +46,80 @@ impl GameMap {
|
|||||||
Self {
|
Self {
|
||||||
rules,
|
rules,
|
||||||
boats_config,
|
boats_config,
|
||||||
|
failed_strikes: vec![],
|
||||||
|
successful_strikes: vec![],
|
||||||
|
sunk_boats: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_cell_content(&self, c: Coordinates) -> MapCellContent {
|
pub fn get_cell_content(&self, c: Coordinates) -> MapCellContent {
|
||||||
//TODO : improve this
|
if !c.is_valid(&self.rules) {
|
||||||
|
return MapCellContent::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
if self.boats_config.find_boat_at_position(c).is_some() {
|
if self.failed_strikes.contains(&c) {
|
||||||
return MapCellContent::Boat;
|
return MapCellContent::FailedStrike;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(b) = self.boats_config.find_boat_at_position(c) {
|
||||||
|
if !self.successful_strikes.contains(&c) {
|
||||||
|
return MapCellContent::Boat;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.sunk_boats.contains(b) {
|
||||||
|
return MapCellContent::SunkBoat;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MapCellContent::TouchedBoat;
|
||||||
}
|
}
|
||||||
|
|
||||||
MapCellContent::Nothing
|
MapCellContent::Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fire(&mut self, c: Coordinates) -> FireResult {
|
||||||
|
if !c.is_valid(&self.rules) {
|
||||||
|
return FireResult::Rejected;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.failed_strikes.contains(&c) || self.successful_strikes.contains(&c) {
|
||||||
|
return FireResult::AlreadyTargetedPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.boats_config.find_boat_at_position(c) {
|
||||||
|
None => {
|
||||||
|
self.failed_strikes.push(c);
|
||||||
|
FireResult::Missed
|
||||||
|
}
|
||||||
|
Some(b) => {
|
||||||
|
self.successful_strikes.push(c);
|
||||||
|
|
||||||
|
if !b
|
||||||
|
.all_coordinates()
|
||||||
|
.iter()
|
||||||
|
.all(|c| self.successful_strikes.contains(c))
|
||||||
|
{
|
||||||
|
return FireResult::Hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sunk_boats.push(*b);
|
||||||
|
|
||||||
|
if !self.rules.boats_can_touch {
|
||||||
|
for c in b.neighbor_coordinates(&self.rules) {
|
||||||
|
if !self.failed_strikes.contains(&c) {
|
||||||
|
self.failed_strikes.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FireResult::Sunk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn are_all_boat_sunk(&self) -> bool {
|
||||||
|
self.sunk_boats.len() == self.boats_config.number_of_boats()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn print_map(&self) {
|
pub fn print_map(&self) {
|
||||||
for y in 0..self.rules.map_height {
|
for y in 0..self.rules.map_height {
|
||||||
for x in 0..self.rules.map_width {
|
for x in 0..self.rules.map_width {
|
||||||
@ -56,4 +132,17 @@ impl GameMap {
|
|||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<_>>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
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::*;
|
||||||
|
|
||||||
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;
|
||||||
|
@ -23,6 +23,7 @@ pub struct PlayConfiguration {
|
|||||||
pub min_boats_number: usize,
|
pub min_boats_number: usize,
|
||||||
pub max_boats_number: usize,
|
pub max_boats_number: usize,
|
||||||
pub bot_types: Vec<BotDescription>,
|
pub bot_types: Vec<BotDescription>,
|
||||||
|
pub ordinate_alphabet: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PlayConfiguration {
|
impl Default for PlayConfiguration {
|
||||||
@ -40,6 +41,7 @@ impl Default for PlayConfiguration {
|
|||||||
r#type: BotType::Random,
|
r#type: BotType::Random,
|
||||||
description: "Random strike. All the time.".to_string(),
|
description: "Random strike. All the time.".to_string(),
|
||||||
}],
|
}],
|
||||||
|
ordinate_alphabet: ALPHABET,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,14 @@ pub trait Player {
|
|||||||
fn request_fire(&self, status: CurrentGameStatus);
|
fn request_fire(&self, status: CurrentGameStatus);
|
||||||
|
|
||||||
fn other_player_must_fire(&self, status: CurrentGameStatus);
|
fn other_player_must_fire(&self, status: CurrentGameStatus);
|
||||||
|
|
||||||
|
fn 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 won_game(&self, your_map: EndGameMap, opponent_map: EndGameMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn opponent(index: usize) -> usize {
|
fn opponent(index: usize) -> usize {
|
||||||
@ -36,6 +44,7 @@ enum GameStatus {
|
|||||||
Created,
|
Created,
|
||||||
WaitingForBoatsDisposition,
|
WaitingForBoatsDisposition,
|
||||||
Started,
|
Started,
|
||||||
|
Finished,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
@ -87,9 +96,65 @@ impl Game {
|
|||||||
self.turn
|
self.turn
|
||||||
);
|
);
|
||||||
|
|
||||||
|
self.request_fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
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)]
|
||||||
.request_fire(self.get_game_status_for_player(opponent(self.turn)));
|
.other_player_must_fire(self.get_game_status_for_player(opponent(self.turn)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn player_map(&self, id: usize) -> &GameMap {
|
||||||
|
match id {
|
||||||
|
0 => self.map_0.as_ref(),
|
||||||
|
1 => self.map_1.as_ref(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn player_map_mut(&mut self, id: usize) -> &mut GameMap {
|
||||||
|
match id {
|
||||||
|
0 => self.map_0.as_mut(),
|
||||||
|
1 => self.map_1.as_mut(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle fire attempts
|
||||||
|
fn handle_fire(&mut self, c: Coordinates) {
|
||||||
|
let result = self.player_map_mut(opponent(self.turn)).fire(c);
|
||||||
|
self.players[self.turn].strike_result(c, result);
|
||||||
|
self.players[opponent(self.turn)].strike_result(c, result);
|
||||||
|
|
||||||
|
// Easiest case : player missed his fire
|
||||||
|
if result == FireResult::Missed {
|
||||||
|
self.turn = opponent(self.turn);
|
||||||
|
self.request_fire();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if 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();
|
||||||
|
let looser_map = self.player_map(opponent(self.turn)).final_map();
|
||||||
|
|
||||||
|
self.players[self.turn].won_game(winner_map.clone(), looser_map.clone());
|
||||||
|
self.players[opponent(self.turn)].lost_game(looser_map, winner_map);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == FireResult::Sunk || result == FireResult::Hit)
|
||||||
|
&& !self.rules.player_continue_on_hit
|
||||||
|
{
|
||||||
|
self.turn = opponent(self.turn);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.request_fire();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current game status for a specific player
|
/// Get current game status for a specific player
|
||||||
@ -164,6 +229,16 @@ impl Handler<Fire> for Game {
|
|||||||
type Result = ();
|
type Result = ();
|
||||||
|
|
||||||
fn handle(&mut self, msg: Fire, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: Fire, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
log::debug!("FIRE ===> {:?}", msg);
|
if self.status != GameStatus::Started {
|
||||||
|
log::error!("Player attempted to fire on invalid step!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.0 != self.players[self.turn].get_uid() {
|
||||||
|
log::error!("Player attempted to fire when it was not its turn!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.handle_fire(msg.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::data::{CurrentGameStatus, GameRules};
|
use crate::data::{Coordinates, CurrentGameStatus, EndGameMap, FireResult, GameRules};
|
||||||
use crate::game::{Fire, Game, Player, SetBoatsLayout};
|
use crate::game::{Fire, Game, Player, SetBoatsLayout};
|
||||||
use crate::human_player_ws::{ClientMessage, HumanPlayerWS, ServerMessage};
|
use crate::human_player_ws::{ClientMessage, HumanPlayerWS, ServerMessage};
|
||||||
|
|
||||||
@ -43,15 +43,44 @@ impl Player for HumanPlayer {
|
|||||||
self.player
|
self.player
|
||||||
.do_send(ServerMessage::OtherPlayerMustFire { status });
|
.do_send(ServerMessage::OtherPlayerMustFire { status });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn strike_result(&self, c: Coordinates, res: FireResult) {
|
||||||
|
self.player.do_send(ServerMessage::StrikeResult {
|
||||||
|
pos: c,
|
||||||
|
result: res,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn other_player_strike_result(&self, c: Coordinates, res: FireResult) {
|
||||||
|
self.player.do_send(ServerMessage::OpponentStrikeResult {
|
||||||
|
pos: c,
|
||||||
|
result: res,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lost_game(&self, your_map: EndGameMap, opponent_map: EndGameMap) {
|
||||||
|
self.player.do_send(ServerMessage::LostGame {
|
||||||
|
your_map,
|
||||||
|
opponent_map,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn won_game(&self, your_map: EndGameMap, opponent_map: EndGameMap) {
|
||||||
|
self.player.do_send(ServerMessage::WonGame {
|
||||||
|
your_map,
|
||||||
|
opponent_map,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HumanPlayer {
|
impl HumanPlayer {
|
||||||
pub fn handle_client_message(&self, msg: ClientMessage) {
|
pub fn handle_client_message(&self, msg: ClientMessage) {
|
||||||
match msg {
|
match msg {
|
||||||
ClientMessage::StopGame => {
|
ClientMessage::StopGame => {
|
||||||
// TODO : do something}
|
// TODO : do something
|
||||||
}
|
}
|
||||||
ClientMessage::BoatsLayout { layout } => {
|
ClientMessage::BoatsLayout { layout } => {
|
||||||
|
// TODO : check boat layout validity
|
||||||
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)),
|
||||||
|
@ -6,7 +6,9 @@ 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::data::{BoatsLayout, BotType, Coordinates, CurrentGameStatus, GameRules};
|
use crate::data::{
|
||||||
|
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;
|
||||||
use crate::random_bot::RandomBot;
|
use crate::random_bot::RandomBot;
|
||||||
@ -33,12 +35,34 @@ pub enum ClientMessage {
|
|||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum ServerMessage {
|
pub enum ServerMessage {
|
||||||
WaitingForAnotherPlayer,
|
WaitingForAnotherPlayer,
|
||||||
QueryBoatsLayout { rules: GameRules },
|
QueryBoatsLayout {
|
||||||
|
rules: GameRules,
|
||||||
|
},
|
||||||
WaitingForOtherPlayerConfiguration,
|
WaitingForOtherPlayerConfiguration,
|
||||||
OtherPlayerReady,
|
OtherPlayerReady,
|
||||||
GameStarting,
|
GameStarting,
|
||||||
OtherPlayerMustFire { status: CurrentGameStatus },
|
OtherPlayerMustFire {
|
||||||
RequestFire { status: CurrentGameStatus },
|
status: CurrentGameStatus,
|
||||||
|
},
|
||||||
|
RequestFire {
|
||||||
|
status: CurrentGameStatus,
|
||||||
|
},
|
||||||
|
StrikeResult {
|
||||||
|
pos: Coordinates,
|
||||||
|
result: FireResult,
|
||||||
|
},
|
||||||
|
OpponentStrikeResult {
|
||||||
|
pos: Coordinates,
|
||||||
|
result: FireResult,
|
||||||
|
},
|
||||||
|
LostGame {
|
||||||
|
your_map: EndGameMap,
|
||||||
|
opponent_map: EndGameMap,
|
||||||
|
},
|
||||||
|
WonGame {
|
||||||
|
your_map: EndGameMap,
|
||||||
|
opponent_map: EndGameMap,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -95,7 +119,7 @@ impl Actor for HumanPlayerWS {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for HumanPlayerWS {
|
impl StreamHandler<Result<ws::Message, ProtocolError>> for HumanPlayerWS {
|
||||||
fn handle(&mut self, msg: Result<Message, ProtocolError>, ctx: &mut Self::Context) {
|
fn handle(&mut self, msg: Result<Message, ProtocolError>, ctx: &mut Self::Context) {
|
||||||
match msg {
|
match msg {
|
||||||
Ok(Message::Ping(msg)) => ctx.pong(&msg),
|
Ok(Message::Ping(msg)) => ctx.pong(&msg),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::data::{BoatsLayout, CurrentGameStatus, GameRules};
|
use crate::data::{BoatsLayout, Coordinates, CurrentGameStatus, EndGameMap, FireResult, GameRules};
|
||||||
use crate::game::{Fire, Game, Player, SetBoatsLayout};
|
use crate::game::{Fire, Game, Player, SetBoatsLayout};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -49,4 +49,12 @@ impl Player for RandomBot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn other_player_must_fire(&self, _status: CurrentGameStatus) {}
|
fn other_player_must_fire(&self, _status: CurrentGameStatus) {}
|
||||||
|
|
||||||
|
fn 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 won_game(&self, _your_map: EndGameMap, _opponent_map: EndGameMap) {}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
use tokio::task;
|
||||||
|
|
||||||
use crate::args::Args;
|
use crate::args::Args;
|
||||||
use crate::bot_client;
|
use crate::bot_client;
|
||||||
use crate::data::GameRules;
|
use crate::data::GameRules;
|
||||||
use crate::server::start_server;
|
use crate::server::start_server;
|
||||||
use crate::test::network_utils::wait_for_port;
|
use crate::test::network_utils::wait_for_port;
|
||||||
use crate::test::TestPort;
|
use crate::test::TestPort;
|
||||||
use tokio::task;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn invalid_port() {
|
async fn invalid_port() {
|
||||||
@ -46,10 +47,31 @@ async fn full_game() {
|
|||||||
local_set
|
local_set
|
||||||
.run_until(async move {
|
.run_until(async move {
|
||||||
let rules = GameRules::random_players_rules();
|
let rules = GameRules::random_players_rules();
|
||||||
task::spawn_local(start_server(Args::for_test(TestPort::ClientInvalidRules)));
|
task::spawn_local(start_server(Args::for_test(TestPort::FullGame)));
|
||||||
wait_for_port(TestPort::ClientInvalidRules.port()).await;
|
wait_for_port(TestPort::FullGame.port()).await;
|
||||||
|
|
||||||
bot_client::run_client(&TestPort::ClientInvalidRules.as_url(), &rules)
|
bot_client::run_client(&TestPort::FullGame.as_url(), &rules)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn full_game_no_touching_boats() {
|
||||||
|
let _ = env_logger::builder().is_test(true).try_init();
|
||||||
|
|
||||||
|
let local_set = task::LocalSet::new();
|
||||||
|
local_set
|
||||||
|
.run_until(async move {
|
||||||
|
let mut rules = GameRules::random_players_rules();
|
||||||
|
rules.boats_can_touch = false;
|
||||||
|
task::spawn_local(start_server(Args::for_test(
|
||||||
|
TestPort::FullGameTouchingBoats,
|
||||||
|
)));
|
||||||
|
wait_for_port(TestPort::FullGameTouchingBoats.port()).await;
|
||||||
|
|
||||||
|
bot_client::run_client(&TestPort::FullGameTouchingBoats.as_url(), &rules)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
})
|
})
|
||||||
|
@ -4,6 +4,8 @@ use crate::args::Args;
|
|||||||
enum TestPort {
|
enum TestPort {
|
||||||
ClientInvalidPort = 20000,
|
ClientInvalidPort = 20000,
|
||||||
ClientInvalidRules,
|
ClientInvalidRules,
|
||||||
|
FullGame,
|
||||||
|
FullGameTouchingBoats,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestPort {
|
impl TestPort {
|
||||||
|
Loading…
Reference in New Issue
Block a user