use std::sync::Arc; use actix::prelude::*; use actix::{Actor, Context, Handler}; use uuid::Uuid; use crate::data::{BoatsLayout, GameMap, GameRules}; 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 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, } impl Game { pub fn new(rules: GameRules) -> Self { Self { rules, players: vec![], status: GameStatus::Created, map_0: None, map_1: None, } } /// 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"); } } 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 { assert_eq!(self.status, GameStatus::WaitingForBoatsDisposition); 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(); } } }