//! The code that handles the lobby logic use crate::{ data::{Color, ColorPalette}, mailbox::Outbox, users::MetaUser, wire::{Lobby, LobbyErr, LobbyId, LobbyUpdate, LobbyUser, User, UserId}, }; use async_std::sync::{Arc, RwLock}; use std::{ collections::BTreeMap, sync::atomic::{AtomicUsize, Ordering}, }; /// A list of all the lobbies on the server pub struct LobbyList { max: AtomicUsize, lobbies: RwLock>, } impl LobbyList { pub fn new() -> Self { Self { max: 0.into(), lobbies: Default::default(), } } /// Create a new lobby pub async fn create(&self, map: String) -> LobbyId { let id = self.max.fetch_add(1, Ordering::Relaxed); self.lobbies .write() .await .insert(id, MetaLobby::create(id, map)); id } /// Remove a lobby by ID pub async fn destroy(&self, id: LobbyId) -> Result<(), LobbyErr> { self.consume(id).await.map(|_| ()) } /// Remove and return the lobby pub async fn consume(&self, id: LobbyId) -> Result { self.lobbies .write() .await .remove(&id) .map_or(Err(LobbyErr::NoSuchRoom), |l| Ok(l)) } /// Get mutable access to a lobby pub async fn get_mut(&self, id: LobbyId, cb: F) -> Result where F: Fn(&mut MetaLobby) -> T, { self.lobbies .write() .await .get_mut(&id) .map_or(Err(LobbyErr::OtherError), |ref mut l| Ok(cb(l))) } } /// Additional state held by the server /// /// The meta lobby will also sync updates to all connected users, when updates are made to the lobby pub struct MetaLobby { pub palette: Vec, pub inner: Lobby, pub outbox: Outbox, } impl MetaLobby { pub fn create(id: LobbyId, map: String) -> Self { Self { palette: Vec::palette(), inner: Lobby { id, map, players: vec![], settings: vec![], }, outbox: Outbox::new(), } } pub fn join(&mut self, user: &MetaUser) -> Lobby { let color = if &user.name == "spacekookie" { let color = Color::blue(); self.palette.without(&color); if let Some(user) = self .inner .players .iter_mut() .find(|u| u.color == Color::blue()) { user.color = self.palette.remove(0); } color } else { self.palette.remove(0) }; self.inner.players.push(LobbyUser { admin: false, id: user.id, name: user.name.clone(), ready: false, color, }); self.inner.clone() } pub fn leave(&mut self, user: &MetaUser) { let (pos, user) = self .inner .players .iter() .enumerate() .find_map(|(num, u)| { if u.id == user.id { Some((num, u)) } else { None } }) .unwrap(); self.palette.remix(user.color); self.inner.players.remove(pos); } /// Check if a user is even present in a lobby /// /// Perform this prerequisite check before making other user-specific changes to the lobby pub fn in_lobby(&self, user: UserId) -> bool { self.inner .players .iter() .find(|u| u.id == user) .map(|_| true) .unwrap_or(false) } /// Set the ready state for a user pub fn ready(&mut self, user: User, ready: bool) -> LobbyUpdate { if let Some(user) = self .inner .players .iter_mut() .find(|u| u.id == user.id) .as_mut() { user.ready = ready; } LobbyUpdate::Ready( self.inner .players .iter() .filter_map(|u| if u.ready { Some(u.id) } else { None }) .collect(), ) } /// Try to start a game, if the user can and everybody is ready pub fn start(&mut self, user: UserId) -> Result<(), LobbyErr> { if let Some(_) = self .inner .players .iter() .filter(|u| u.admin) .find(|u| u.id == user) { return Err(LobbyErr::NotAuthorized); }; match self .inner .players .iter() .filter(|u| !u.ready) .collect::>() .len() { 0 => Err(LobbyErr::NotAllReady), _ => Ok(()), } } }