use crate::player::Player; use board_network::protocol::{ClientMessage, ServerMessage}; use board_shared::board::Board; use board_shared::deck::RngDeck; use board_shared::expr::is_valid_guess; use board_shared::game::Hand; use board_shared::position::Position2d; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures::StreamExt; use rand::distributions::{Alphanumeric, DistString}; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::net::SocketAddr; use std::sync::{Arc, Mutex}; type TaggedClientMessage = (SocketAddr, ClientMessage); #[derive(Debug, Default)] pub struct Room { pub name: String, pub connections: HashMap, pub players: Vec, pub active_player: usize, pub has_started: bool, pub board: Board, pub validated_board: Board, pub deck: RngDeck, } impl Room { pub fn add_player( &mut self, addr: SocketAddr, player_name: String, tx: UnboundedSender, ) -> anyhow::Result<()> { // If the player name matches an existing player, but with a dropped connection, // then replace this player. let mut player_index = None; for (i, p) in self.players.iter().enumerate() { if p.name == player_name && p.ws.is_none() { player_index = Some(i); break; } } if let Some(i) = player_index { // Reclaim the player's spot self.broadcast(ServerMessage::PlayerReconnected(i)); self.players[i].ws = Some(tx.clone()); } else { self.broadcast(ServerMessage::PlayerConnected(player_name.clone())); player_index = Some(self.players.len()); self.players.push(Player { name: player_name, score: 0, hand: Hand::default(), ws: Some(tx.clone()), }); } let player_index = player_index.expect("A player index should have been attributed"); self.connections.insert(addr, player_index); // Send the player the current state of the room tx.unbounded_send(ServerMessage::JoinedRoom { room_name: self.name.clone(), players: self .players .iter() .map(|p| (p.name.clone(), p.score, p.ws.is_some())) .collect(), active_player: self.active_player, has_started: self.has_started, })?; Ok(()) } pub fn next_player(&mut self) { if self.connections.is_empty() { return; } loop { self.active_player = (self.active_player + 1) % self.players.len(); if self.players[self.active_player].ws.is_some() { break; } } self.broadcast(ServerMessage::PlayerTurn(self.active_player)); } pub fn on_message(&mut self, addr: SocketAddr, msg: ClientMessage) -> bool { match msg { ClientMessage::Disconnected => self.on_client_disconnected(addr), ClientMessage::CreateRoom(_) | ClientMessage::JoinRoom(_, _) => { eprintln!("[{}] Illegal client message {:?}", self.name, msg); } ClientMessage::StartGame => self.on_start_game(), ClientMessage::TileUse(pos, tile_idx) => { if let Some(p) = self.connections.get(&addr) { if *p == self.active_player { self.on_tile_use(pos, tile_idx); } } } ClientMessage::Validate => { if let Some(p) = self.connections.get(&addr) { if *p == self.active_player { self.on_validate(); } } } _ => todo!(), } !self.connections.is_empty() } fn on_start_game(&mut self) { if self.has_started { return; } self.has_started = true; self.deck = RngDeck::new_complete(); for p in &mut self.players { p.hand .complete(&mut self.deck) .expect("Not enough tiles in deck"); } self.broadcast(ServerMessage::PlayerTurn(self.active_player)); } fn on_tile_use(&mut self, pos: Position2d, tile_idx: usize) { let hand = &mut self.players[self.active_player].hand; if let Some(tile) = hand.remove(tile_idx) { self.board.set(pos.x, pos.y, tile); } } fn on_validate(&mut self) -> anyhow::Result<()> { let diff = self.board.difference(&self.validated_board); if !Board::has_alignment(&diff) { self.reset_player_moves(); self.send( self.active_player, ServerMessage::TurnRejected("Move is not aligned".to_string()), ); return Ok(()); } let is_valid = self .board .find_chains(&diff) .iter() .all(|chain| is_valid_guess(&self.board, chain) == Ok(true)); if is_valid { self.players[self.active_player] .hand .complete(&mut self.deck)?; self.next_player(); } else { self.send( self.active_player, ServerMessage::TurnRejected("Invalid expressions found".to_string()), ); } Ok(()) } fn reset_player_moves(&mut self) { let diff = self.board.difference(&self.validated_board); for pos in diff { self.broadcast(ServerMessage::TileRemoved(pos)); } self.board = self.validated_board.clone(); } fn on_client_disconnected(&mut self, addr: SocketAddr) { if let Some(p) = self.connections.remove(&addr) { self.players[p].ws = None; self.broadcast(ServerMessage::PlayerDisconnected(p)); } } fn broadcast(&self, s: ServerMessage) { for c in self.connections.values() { if let Some(ws) = &self.players[*c].ws { if let Err(e) = ws.unbounded_send(s.clone()) { eprintln!( "[{}] Failed to send broadcast to {}: {}", self.name, self.players[*c].name, e ); } } } } fn send(&self, i: usize, s: ServerMessage) { if let Some(p) = self.players[i].ws.as_ref() { if let Err(e) = p.unbounded_send(s) { eprintln!( "[{}] Failed to send message to {}: {}", self.name, self.players[i].name, e ); } } else { eprintln!("[{}] Tried sending message to inactive player", self.name); } } } type RoomPtr = Arc>; #[derive(Clone)] pub struct RoomHandle { pub write: UnboundedSender, pub room: RoomPtr, } impl RoomHandle { pub async fn run_room(&mut self, mut read: UnboundedReceiver) { while let Some((addr, msg)) = read.next().await { if !self.room.lock().unwrap().on_message(addr, msg) { break; } } } } pub type Rooms = HashMap; pub fn generate_room_name(rooms: &mut Rooms, room: RoomHandle) -> String { let mut rng = rand::thread_rng(); loop { let name = Alphanumeric.sample_string(&mut rng, 5); if let Entry::Vacant(v) = rooms.entry(name.clone()) { room.room.lock().unwrap().name = name.clone(); v.insert(room); return name; } } }