aboutsummaryrefslogtreecommitdiff
path: root/games/rstnode/rst-core/src/lobby.rs
//! 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<BTreeMap<LobbyId, MetaLobby>>,
}

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<MetaLobby, LobbyErr> {
        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<F, T>(&self, id: LobbyId, cb: F) -> Result<T, LobbyErr>
    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<Color>,
    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::<Vec<_>>()
            .len()
        {
            0 => Err(LobbyErr::NotAllReady),
            _ => Ok(()),
        }
    }
}