use std::sync::Arc; use actix::prelude::*; use actix::{Actor, Context, Handler}; use uuid::Uuid; use crate::data::*; pub trait Player { fn get_name(&self) -> &str; fn get_uid(&self) -> Uuid; fn query_boats_layout(&self, rules: &GameRules); fn notify_other_player_ready(&self); fn notify_game_starting(&self); fn request_fire(&self, status: CurrentGameStatus); fn other_player_must_fire(&self, status: CurrentGameStatus); } fn opponent(index: usize) -> usize { match index { 0 => 1, 1 => 0, _ => unreachable!(), } } #[derive(Default, Eq, PartialEq, Debug, Copy, Clone)] enum GameStatus { #[default] Created, WaitingForBoatsDisposition, Started, } pub struct Game { rules: GameRules, players: Vec>, status: GameStatus, map_0: Option, map_1: Option, turn: usize, } impl Game { pub fn new(rules: GameRules) -> Self { Self { rules, players: vec![], status: GameStatus::Created, map_0: None, map_1: None, turn: 0, } } /// Find the ID of a player from its UUID fn player_id_by_uuid(&self, uuid: Uuid) -> usize { self.players .iter() .enumerate() .find(|p| p.1.get_uid() == uuid) .expect("Player is not member of this game!") .0 } /// Once the two player has been registered, the game may start fn query_boats_disposition(&mut self) { log::debug!("Query boats disposition"); assert_eq!(self.status, GameStatus::Created); self.status = GameStatus::WaitingForBoatsDisposition; self.players[0].query_boats_layout(&self.rules); self.players[1].query_boats_layout(&self.rules); } /// Start fires exchange fn start_fire_exchanges(&mut self) { self.status = GameStatus::Started; log::debug!( "Start fire exchanges. Player {}#{} goes first", self.players[self.turn].get_name(), self.turn ); self.players[self.turn].request_fire(self.get_game_status_for_player(self.turn)); self.players[opponent(self.turn)] .request_fire(self.get_game_status_for_player(opponent(self.turn))); } /// Get current game status for a specific player fn get_game_status_for_player(&self, _id: usize) -> CurrentGameStatus { CurrentGameStatus { rules: self.rules.clone(), } } } impl Actor for Game { type Context = Context; } #[derive(Message)] #[rtype(result = "()")] pub struct AddPlayer(pub E); impl Handler>> for Game where E: Player + 'static, { type Result = (); /// Add a new player to the game fn handle(&mut self, msg: AddPlayer>, _ctx: &mut Self::Context) -> Self::Result { assert!(self.players.len() < 2); self.players.push(msg.0); if self.players.len() == 2 { self.query_boats_disposition(); } } } #[derive(Message)] #[rtype(result = "()")] pub struct SetBoatsLayout(pub Uuid, pub BoatsLayout); impl Handler for Game { type Result = (); /// Receive game configuration of a player fn handle(&mut self, msg: SetBoatsLayout, _ctx: &mut Self::Context) -> Self::Result { if self.status != GameStatus::WaitingForBoatsDisposition { log::error!("Player attempted to set boat configuration on invalid step!"); return; } let player_index = self.player_id_by_uuid(msg.0); log::debug!("Got boat disposition for player {}", player_index); match player_index { 0 => self.map_0 = Some(GameMap::new(self.rules.clone(), msg.1)), 1 => self.map_1 = Some(GameMap::new(self.rules.clone(), msg.1)), _ => unreachable!(), } self.players[opponent(player_index)].notify_other_player_ready(); if self.map_0.is_some() && self.map_1.is_some() { self.players.iter().for_each(|p| p.notify_game_starting()); self.start_fire_exchanges(); } } } #[derive(Message, Debug)] #[rtype(result = "()")] pub struct Fire(pub Uuid, pub Coordinates); impl Handler for Game { type Result = (); fn handle(&mut self, msg: Fire, _ctx: &mut Self::Context) -> Self::Result { log::debug!("FIRE ===> {:?}", msg); } }