gromnie_runner/
client_naming.rs1pub fn encode_client_id(id: u32) -> String {
8 let id = id.min(456975);
10
11 let mut id = id;
12 let mut code = String::with_capacity(4);
13
14 for _ in 0..4 {
15 let char_idx = (id % 26) as u8;
16 code.insert(0, (b'A' + char_idx) as char);
17 id /= 26;
18 }
19
20 code
21}
22
23#[allow(dead_code)]
27pub fn decode_client_id(code: &str) -> Option<u32> {
28 if code.len() != 4 {
29 return None;
30 }
31
32 let mut id: u32 = 0;
33
34 for (pos, ch) in code.chars().enumerate() {
35 if !ch.is_ascii_uppercase() {
36 return None;
37 }
38
39 let digit = ch as u32 - b'A' as u32;
40 let power = 4 - 1 - pos; id += digit * 26_u32.pow(power as u32);
42 }
43
44 Some(id)
45}
46
47pub struct ClientNaming {
49 #[allow(dead_code)]
50 client_id: u32,
51 code: String,
52}
53
54impl ClientNaming {
55 pub fn new(client_id: u32) -> Self {
57 let code = encode_client_id(client_id);
58 Self { client_id, code }
59 }
60
61 pub fn account_name(&self) -> String {
65 format!("Load-{}", self.code)
66 }
67
68 pub fn password(&self) -> String {
70 self.account_name()
71 }
72
73 pub fn character_name(&self) -> String {
77 format!("{}-A", self.account_name())
78 }
79
80 #[allow(dead_code)]
82 pub fn code(&self) -> &str {
83 &self.code
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn test_encode_decode_roundtrip() {
93 for id in [0, 1, 25, 26, 27, 100, 675, 456975] {
94 let encoded = encode_client_id(id);
95 let decoded = decode_client_id(&encoded).expect("decode should succeed");
96 assert_eq!(
97 id, decoded,
98 "Roundtrip failed for id {}: {} -> {} -> {}",
99 id, encoded, encoded, decoded
100 );
101 }
102 }
103
104 #[test]
105 fn test_encode_known_values() {
106 assert_eq!(encode_client_id(0), "AAAA");
107 assert_eq!(encode_client_id(1), "AAAB");
108 assert_eq!(encode_client_id(25), "AAAZ");
109 assert_eq!(encode_client_id(26), "AABA");
110 assert_eq!(encode_client_id(27), "AABB");
111 }
112
113 #[test]
114 fn test_client_naming() {
115 let naming = ClientNaming::new(0);
116 assert_eq!(naming.code(), "AAAA");
117 assert_eq!(naming.account_name(), "Load-AAAA");
118 assert_eq!(naming.password(), "Load-AAAA");
119 assert_eq!(naming.character_name(), "Load-AAAA-A");
120 }
121
122 #[test]
123 fn test_encode_higher_values() {
124 assert_eq!(encode_client_id(675), "AAZZ");
125 assert_eq!(encode_client_id(456975), "ZZZZ");
126 assert_eq!(encode_client_id(456976), "ZZZZ"); }
128
129 #[test]
130 fn test_decode_known_values() {
131 assert_eq!(decode_client_id("AAAA"), Some(0));
132 assert_eq!(decode_client_id("AAAB"), Some(1));
133 assert_eq!(decode_client_id("AAAZ"), Some(25));
134 assert_eq!(decode_client_id("AABA"), Some(26));
135 assert_eq!(decode_client_id("AABB"), Some(27));
136 assert_eq!(decode_client_id("ZZZZ"), Some(456975));
137 }
138
139 #[test]
140 fn test_decode_invalid_input() {
141 assert_eq!(decode_client_id("AAA"), None);
142 assert_eq!(decode_client_id("AAAAA"), None);
143 assert_eq!(decode_client_id("AAAa"), None);
144 assert_eq!(decode_client_id("AA!A"), None);
145 assert_eq!(decode_client_id(""), None);
146 }
147
148 #[test]
149 fn test_client_naming_higher_id() {
150 let naming = ClientNaming::new(123);
151 let encoded = encode_client_id(123);
152 assert_eq!(naming.code(), encoded.as_str());
153 assert_eq!(naming.account_name(), format!("Load-{}", encoded));
154 assert_eq!(naming.character_name(), format!("Load-{}-A", encoded));
155 }
156
157 #[test]
158 fn test_max_clients() {
159 let max_id = 456975; let naming = ClientNaming::new(max_id);
161 assert_eq!(naming.code(), "ZZZZ");
162 assert_eq!(naming.account_name(), "Load-ZZZZ");
163
164 let over_max = ClientNaming::new(456976);
165 assert_eq!(over_max.code(), "ZZZZ");
166 }
167
168 #[test]
169 fn test_encode_sequential() {
170 let expected = vec![
171 "AAAA", "AAAB", "AAAC", "AAAD", "AAAE", "AAAF", "AAAG", "AAAH", "AAAI", "AAAJ",
172 ];
173
174 for (i, &exp) in expected.iter().enumerate() {
175 assert_eq!(encode_client_id(i as u32), exp, "Failed at index {}", i);
176 }
177 }
178}