twgame-core 0.9.0

Game trait, helper types and helper functions for twgame
Documentation
use std::num::IntErrorKind;
use teehistorian::chunks::ConsoleCommand;

pub enum Command<'a> {
    Team(i32),
    ToggleLock,
    SetLock(bool),
    SaveEmpty,
    Save(&'a [u8]),
    Load(&'a [u8]),
    Kill,
    Team0Mode,
    Pause,
    Spec,
}

impl<'a> Command<'a> {
    pub fn serialize(&self) -> String {
        match self {
            Command::Team(team) => format!("team {team}"),
            Command::ToggleLock => "lock".to_string(),
            Command::SetLock(lock) => format!("lock {}", *lock as u8),
            Command::SaveEmpty => "save".to_string(),
            Command::Save(s) => format!("save {}", std::str::from_utf8(s).unwrap()),
            Command::Load(s) => format!("load {}", std::str::from_utf8(s).unwrap()),
            Command::Kill => "kill".to_string(),
            Command::Team0Mode => "team0mode".to_string(),
            Command::Pause => "pause".to_string(),
            Command::Spec => "spec".to_string(),
        }
    }

    pub fn i32(param: &[u8]) -> i32 {
        let _p: i64 = 0;
        // TODO: match function parsing int in ddnet

        // starting with text -> unlock (false)
        // text behind digits -> ignore
        // positive out of range -> lock(true)
        // negative out of range -> unlock (false)
        // u64 % u32::max == 0 -> unlock (false)
        let param: &str = std::str::from_utf8(param).unwrap_or("0");
        match param.parse::<i64>() {
            Ok(i) => i as i32,
            Err(err) => match err.kind() {
                IntErrorKind::PosOverflow => i32::MAX,
                IntErrorKind::NegOverflow => 0,
                _ => 0,
            },
        }
    }

    pub fn from_teehistorian(cmd: &ConsoleCommand<'a>) -> Option<Self> {
        match cmd.cmd {
            b"team" => {
                let arg = cmd.args.first()?;
                let arg = std::str::from_utf8(arg).ok()?;
                let team = arg.parse::<i32>().ok()?;
                Some(Command::Team(team))
            }
            b"lock" => {
                if let Some(arg) = cmd.args.first() {
                    let i = Command::i32(arg);
                    Some(Command::SetLock(i != 0))
                } else {
                    Some(Command::ToggleLock)
                }
            }
            b"unlock" => Some(Command::SetLock(false)),
            b"load" => cmd.args.first().map(|arg| Command::Load(arg)),
            b"save" => {
                if let Some(arg) = cmd.args.first() {
                    Some(Command::Save(arg))
                } else {
                    Some(Command::SaveEmpty)
                }
            }
            b"kill" => Some(Command::Kill),
            b"team0mode" => Some(Command::Team0Mode),
            b"pause" => Some(Command::Pause),
            b"spec" => Some(Command::Spec),
            _ => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::Command;

    #[test]
    fn serialize_commands() {
        assert_eq!(Command::Team(1).serialize(), "team 1");
        assert_eq!(Command::ToggleLock.serialize(), "lock");
        assert_eq!(Command::SetLock(true).serialize(), "lock 1");
        assert_eq!(Command::SetLock(false).serialize(), "lock 0");
        assert_eq!(Command::Kill.serialize(), "kill");
        assert_eq!(Command::Pause.serialize(), "pause");
        assert_eq!(Command::Spec.serialize(), "spec");
        assert_eq!(Command::SaveEmpty.serialize(), "save");
        assert_eq!(Command::Save(b"abc").serialize(), "save abc");
        assert_eq!(Command::Load(b"123").serialize(), "load 123");
        assert_eq!(Command::Load(b"").serialize(), "load ");
        assert_eq!(Command::Load(b"abc def").serialize(), "load abc def");
    }
}