slen 0.1.1

A library for encoding and decoding loadouts from the Splatoon series.
Documentation
use crate::hexstr_to_bin;
use crate::ParseFailure;
use substring::Substring;

/// A Splatoon 1 loadout.
///
/// To know what weapon ID matches what weapon, and what ability corresponds to
/// what number, see [this
/// file](https://github.com/DeviPotato/splatoon-calculator/blob/master/weapons.js)
/// and [this
/// file](https://github.com/DeviPotato/splatoon-calculator/blob/master/abilities.js)
/// respectively.
///
/// Serialization is implemented via [`std::fmt::Display`], and deserialization via [`std::str::FromStr`].
#[derive(Debug, PartialEq, Eq)]
pub struct S1Loadout {
    /// The ID of the encoded weapon.
    wepid: u8,
    /// The abilities of the encoded loadout. `[0..=2]` are mains, `[3..]` are subs.
    abilities: [u8; 12],
}

impl std::fmt::Display for S1Loadout {
    /// Encode the loadout into a string.
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut out = format!("{:02x}", self.wepid);

        let mut astr = String::new();
        for ability in &self.abilities {
            astr.push_str(&format!("{:05b}", ability));
        }

        out.push_str(&format!("{:x}", u64::from_str_radix(&astr, 2).unwrap()));

        f.write_str(out.trim_end_matches('0'))
    }
}

impl std::str::FromStr for S1Loadout {
    type Err = ParseFailure;

    /// Parse a loadout from a string.
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut code = s.to_string();
        if code.len() > 17 {
            return Err(ParseFailure::Length);
        }

        while code.len() < 17 {
            code += "0";
        }

        let wepid = if let Ok(s) = u8::from_str_radix(code.substring(0, 2), 16) {
            s
        } else {
            return Err(ParseFailure::Value);
        };

        let raw_abilities = if let Some(s) = hexstr_to_bin(code.substring(2, code.len())) {
            s
        } else {
            return Err(ParseFailure::Value);
        };

        let mut abilities = [0; 12];

        for i in (0..raw_abilities.len()).step_by(5) {
            abilities[i / 5] =
                if let Ok(s) = u8::from_str_radix(&raw_abilities.substring(i, i + 5), 2) {
                    s
                } else {
                    return Err(ParseFailure::Value);
                };
        }

        Ok(S1Loadout { wepid, abilities })
    }
}

#[cfg(test)]
mod check {
    use crate::*;

    #[test]
    fn s1_decode() {
        assert_eq!(
            S1Loadout {
                wepid: 66,
                abilities: [21, 21, 21, 12, 12, 12, 22, 22, 22, 16, 16, 16]
            },
            "42ad6ac632d6b421".parse().unwrap()
        );
    }

    #[test]
    fn s1_encode() {
        assert_eq!(
            S1Loadout {
                wepid: 66,
                abilities: [21, 21, 21, 12, 12, 12, 22, 22, 22, 16, 16, 16]
            }
            .to_string(),
            "42ad6ac632d6b421"
        );
    }
}