logic_long/
logiclong.rs

1use std::{fmt, str::FromStr};
2
3use lazy_static::lazy_static;
4use rand::{self, Rng};
5use regex::Regex;
6
7#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
8pub struct LogicLong<T = u32>
9where
10    T: Copy + ?Sized + From<u32>,
11    u32: From<T>,
12{
13    pub high: T,
14    pub low: T,
15    pub tag: String,
16}
17
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub enum LogicLongError {
20    InvalidTag(String),
21    InvalidHighID(u32),
22}
23
24lazy_static! {
25    pub static ref VALID_REGEX: Regex = Regex::new("^#[oO0289PYLQGRJCUVpylqgrjcuv]+$").unwrap();
26    pub static ref FIX_REGEX: Regex = Regex::new("[^A-Z0-9]+").unwrap();
27}
28
29const ORDER: [char; 14] = ['0', '2', '8', '9', 'P', 'Y', 'L', 'Q', 'G', 'R', 'J', 'C', 'U', 'V'];
30const BASE: u64 = 14;
31
32impl<T> LogicLong<T>
33where
34    T: Copy + ?Sized + From<u32>,
35    u32: From<T>,
36{
37    pub fn new(high: T, low: T) -> Self {
38        let mut logic_long = Self { high, low, tag: String::new() };
39        logic_long.tag = logic_long.to_tag();
40        logic_long
41    }
42
43    pub(crate) fn parse_tag(tag: &str) -> Result<Self, LogicLongError> {
44        let tag = Self::fix_tag(tag);
45        let mut total: u64 = 0;
46
47        // iterate backwards
48        for (index, char) in tag.replace('#', "").chars().rev().enumerate() {
49            // get position of char in arr
50            let position = ORDER
51                .iter()
52                .position(|&x| x == char)
53                .ok_or_else(|| LogicLongError::InvalidTag(tag.clone()))?;
54            // total += position times 14 to the power of index
55            total += position as u64 * BASE.pow(index as u32);
56        }
57
58        let (high, low) = (((total % 256) as u32), ((total / 256) as u32));
59
60        Ok(Self { high: high.into(), low: low.into(), tag })
61    }
62
63    /// Returns a "proper" tag, i.e. starts with # always and is purely uppercase with no 0s or Os
64    #[must_use]
65    pub fn is_valid_tag(tag: &str) -> bool {
66        VALID_REGEX.is_match(&tag.to_uppercase().replace('O', "0"))
67    }
68
69    #[must_use]
70    pub fn fix_tag(tag: &str) -> String {
71        "#".to_owned() + &FIX_REGEX.replace_all(&tag.to_uppercase(), "").replace('O', "0")
72    }
73
74    #[must_use]
75    /// Players have a max high of 100, clans/wars/messages seem to be much higher
76    pub fn random(max_high: u32) -> Self {
77        let mut rng = rand::thread_rng();
78        let high = rng.gen_range(0..max_high).into();
79        let low = rng.gen::<u32>().into();
80
81        Self::new(high, low)
82    }
83
84    pub fn to_tag(&self) -> String {
85        let mut tag = String::new();
86        // let mut total: u64 = self.high.into() + self.low.into() * 0x100;
87        let mut total = u64::from(u32::from(self.high)) + u64::from(u32::from(self.low)) * 0x100;
88        let mut b14;
89
90        while total != 0 {
91            b14 = total % 14;
92            total /= 14;
93            tag.insert(0, ORDER[b14 as usize]);
94        }
95        Self::fix_tag(&tag)
96    }
97}
98
99impl<T> fmt::Display for LogicLong<T>
100where
101    T: Copy + ?Sized + From<u32>,
102    u32: From<T>,
103{
104    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105        write!(f, "{}", self.tag)
106    }
107}
108
109impl<T> fmt::Debug for LogicLong<T>
110where
111    T: Copy + ?Sized + From<u32> + fmt::Display,
112    u32: From<T>,
113{
114    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
115        write!(f, "LogicLong {{ high: {}, low: {}, tag: {} }}", self.high, self.low, self.tag)
116    }
117}
118
119impl<T> FromStr for LogicLong<T>
120where
121    T: Copy + ?Sized + From<u32>,
122    u32: From<T>,
123{
124    type Err = LogicLongError;
125    fn from_str(s: &str) -> Result<Self, Self::Err> {
126        Self::parse_tag(s)
127    }
128}
129
130impl<T> From<LogicLong<T>> for String
131where
132    T: Copy + ?Sized + From<u32>,
133    u32: From<T>,
134{
135    fn from(logic_long: LogicLong<T>) -> Self {
136        logic_long.tag
137    }
138}
139
140impl fmt::Display for LogicLongError {
141    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
142        match self {
143            Self::InvalidTag(tag) => write!(f, "{tag} is not a valid tag."),
144            Self::InvalidHighID(high) => write!(f, "Invalid high ID: {high}"),
145        }
146    }
147}
148
149impl std::error::Error for LogicLongError {}