spru-util 0.1.0

An experimental framework for building multiplayer strategy and digital board games.
Documentation
mod error;
pub use error::Error;

use std::marker::PhantomData;

use derive_where::derive_where;
use spru::{common::error::AnyResult, player};
use tagset::tagset;
use telety::telety;

use crate::cloned;

/// Stores some state per player
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PlayerMap<PlayerState> {
    map: Vec<Option<(player::Id, PlayerState)>>,
}

impl<PlayerState> PlayerMap<PlayerState> {
    pub fn count(&self) -> usize {
        self.map.iter().flatten().count()
    }

    pub fn get(&self, id: player::Id) -> Result<&PlayerState, Error> {
        self.map
            .get(id.into_u32() as usize)
            .and_then(Option::as_ref)
            .map(|(_, state)| state)
            .ok_or(Error::PlayerDoesNotExist(id))
    }

    pub fn iter(&self) -> impl Iterator<Item = (player::Id, &PlayerState)> {
        self.map.iter().flatten().map(|(id, state)| (*id, state))
    }

    pub fn expect_player(&self, id: player::Id) -> &PlayerState {
        self.get(id).unwrap()
    }
}

pub fn create<PlayerState>() -> Create<PlayerState> {
    cloned::create(PlayerMap { map: vec![] })
}

pub fn add_player<PlayerState>(
    id: player::Id,
    player_state: PlayerState,
) -> AddPlayer<PlayerState> {
    AddPlayer { id, player_state }
}

pub fn remove_player<PlayerState>(id: player::Id) -> RemovePlayer<PlayerState> {
    RemovePlayer {
        id,
        _p: PhantomData,
    }
}

pub fn destroy<PlayerState>() -> Destroy<PlayerState> {
    cloned::destroy()
}

pub type Create<PlayerState> = cloned::Create<PlayerMap<PlayerState>>;

pub type Destroy<PlayerState> = cloned::Destroy<PlayerMap<PlayerState>>;

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, spru::action::Update)]
pub struct AddPlayer<PlayerState> {
    id: player::Id,
    player_state: PlayerState,
}

impl<PlayerState: Clone> spru::action::Update for AddPlayer<PlayerState> {
    type T = PlayerMap<PlayerState>;
    type Undo = RemovePlayer<PlayerState>;

    #[allow(refining_impl_trait)]
    fn update(&self, value: &mut Self::T) -> AnyResult<Self::Undo> {
        let index = self.id.into_u32() as usize;
        while value.map.len() <= index {
            value.map.push(None);
        }

        match &mut value.map[index] {
            Some(_) => Err(Error::PlayerAlreadyExists(self.id).into()),
            option @ None => {
                *option = Some((self.id, self.player_state.clone()));
                Ok(remove_player(self.id))
            }
        }
    }
}

#[derive_where(Debug, Clone, Serialize, Deserialize)]
#[derive(spru::action::Update)]
pub struct RemovePlayer<PlayerState> {
    id: player::Id,
    _p: PhantomData<PlayerState>,
}

impl<PlayerState> spru::action::Update for RemovePlayer<PlayerState> {
    type T = PlayerMap<PlayerState>;
    type Undo = AddPlayer<PlayerState>;

    #[allow(refining_impl_trait)]
    fn update(&self, value: &mut Self::T) -> AnyResult<Self::Undo> {
        let index = self.id.into_u32() as usize;
        if let Some(player_state) = value.map.get_mut(index)
            && let Some((_, player_state)) = player_state.take()
        {
            return Ok(add_player(self.id, player_state));
        }

        Err(Error::PlayerDoesNotExist(self.id).into())
    }
}

#[telety(crate::player_map)]
#[tagset(Create<PlayerState>)]
#[tagset(Destroy<PlayerState>)]
#[tagset(AddPlayer<PlayerState>)]
#[tagset(RemovePlayer<PlayerState>)]
#[tagset(reserved(..8))]
pub struct Actions<PlayerState>;