1use std::collections::{HashMap, HashSet, VecDeque};
6
7use super::{error::Result, prelude::ParseWarning};
8
9pub mod channel;
10pub mod graphics;
11pub mod mixin;
12pub mod time;
13
14pub mod minor_command;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub enum PlayerMode {
24 Single,
26 Two,
28 Double,
30}
31
32impl std::fmt::Display for PlayerMode {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 Self::Single => write!(f, "1"),
36 Self::Two => write!(f, "2"),
37 Self::Double => write!(f, "3"),
38 }
39 }
40}
41
42impl std::str::FromStr for PlayerMode {
43 type Err = ParseWarning;
44
45 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
46 Ok(match s {
47 "1" => Self::Single,
48 "2" => Self::Two,
49 "3" => Self::Double,
50 _ => {
51 return Err(ParseWarning::SyntaxError(
52 "expected one of 0, 1 or 2".into(),
53 ));
54 }
55 })
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
67pub enum JudgeLevel {
68 VeryHard,
70 Hard,
72 Normal,
74 Easy,
76 OtherInt(i64),
79}
80
81impl From<i64> for JudgeLevel {
82 fn from(value: i64) -> Self {
83 match value {
84 0 => Self::VeryHard,
85 1 => Self::Hard,
86 2 => Self::Normal,
87 3 => Self::Easy,
88 val => Self::OtherInt(val),
89 }
90 }
91}
92
93impl<'a> TryFrom<&'a str> for JudgeLevel {
94 type Error = &'a str;
95 fn try_from(value: &'a str) -> core::result::Result<Self, Self::Error> {
96 value.parse::<i64>().map(Self::from).map_err(|_| value)
97 }
98}
99
100impl std::fmt::Display for JudgeLevel {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 match self {
103 Self::VeryHard => write!(f, "0"),
104 Self::Hard => write!(f, "1"),
105 Self::Normal => write!(f, "2"),
106 Self::Easy => write!(f, "3"),
107 Self::OtherInt(value) => write!(f, "{value}"),
108 }
109 }
110}
111
112pub(crate) const fn char_to_base62(ch: char) -> Option<u8> {
113 match ch {
114 '0'..='9' | 'A'..='Z' | 'a'..='z' => Some(ch as u32 as u8),
115 _ => None,
116 }
117}
118
119pub(crate) fn base62_to_byte(base62: u8) -> u8 {
120 match base62 {
121 b'0'..=b'9' => base62 - b'0',
122 b'A'..=b'Z' => base62 - b'A' + 10,
123 b'a'..=b'z' => base62 - b'a' + 36,
124 _ => unreachable!(),
125 }
126}
127
128#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
133pub struct ObjId([u8; 2]);
134
135impl std::fmt::Debug for ObjId {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 f.debug_tuple("ObjId")
138 .field(&format!("{}{}", self.0[0] as char, self.0[1] as char))
139 .finish()
140 }
141}
142
143impl std::fmt::Display for ObjId {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 write!(f, "{}{}", self.0[0] as char, self.0[1] as char)
146 }
147}
148
149impl From<ObjId> for u16 {
150 fn from(value: ObjId) -> Self {
151 base62_to_byte(value.0[0]) as u16 * 62 + base62_to_byte(value.0[1]) as u16
152 }
153}
154
155impl From<ObjId> for u32 {
156 fn from(value: ObjId) -> Self {
157 Into::<u16>::into(value) as u32
158 }
159}
160
161impl From<ObjId> for u64 {
162 fn from(value: ObjId) -> Self {
163 Into::<u16>::into(value) as u64
164 }
165}
166
167impl ObjId {
168 #[must_use]
170 pub const fn null() -> Self {
171 Self([b'0', b'0'])
172 }
173
174 #[must_use]
176 pub fn is_null(self) -> bool {
177 self.0 == [b'0', b'0']
178 }
179
180 pub fn try_from(value: &str, case_sensitive_obj_id: bool) -> Result<Self> {
184 if value.len() != 2 {
185 return Err(ParseWarning::SyntaxError(format!(
186 "expected 2 digits as object id but found: {value}"
187 )));
188 }
189 let mut chars = value.bytes();
190 let [Some(ch1), Some(ch2), None] = [chars.next(), chars.next(), chars.next()] else {
191 return Err(ParseWarning::SyntaxError(format!(
192 "expected 2 digits as object id but found: {value}"
193 )));
194 };
195 if !(ch1.is_ascii_alphanumeric() && ch2.is_ascii_alphanumeric()) {
196 return Err(ParseWarning::SyntaxError(format!(
197 "expected alphanumeric characters as object id but found: {value}"
198 )));
199 }
200 if case_sensitive_obj_id {
201 Ok(Self([ch1, ch2]))
202 } else {
203 Ok(Self([ch1.to_ascii_uppercase(), ch2.to_ascii_uppercase()]))
204 }
205 }
206
207 #[must_use]
209 pub fn as_u16(self) -> u16 {
210 self.into()
211 }
212
213 #[must_use]
215 pub fn as_u32(self) -> u32 {
216 self.into()
217 }
218
219 #[must_use]
221 pub fn as_u64(self) -> u64 {
222 self.into()
223 }
224
225 #[must_use]
227 pub fn into_chars(self) -> [char; 2] {
228 self.0.map(|c| c as char)
229 }
230
231 pub const fn make_uppercase(&mut self) {
233 self.0[0] = self.0[0].to_ascii_uppercase();
234 self.0[1] = self.0[1].to_ascii_uppercase();
235 }
236
237 #[must_use]
239 pub fn is_base36(self) -> bool {
240 self.0
241 .iter()
242 .all(|c| c.is_ascii_digit() || c.is_ascii_uppercase())
243 }
244
245 #[must_use]
247 pub fn is_base62(self) -> bool {
248 self.0
249 .iter()
250 .all(|c| c.is_ascii_digit() || c.is_ascii_uppercase() || c.is_ascii_lowercase())
251 }
252
253 pub fn all_values() -> impl Iterator<Item = Self> {
258 const BASE36_CHARS: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
259 const BASE62_CHARS: &[u8] =
260 b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
261
262 let base36_values = (0..36usize).flat_map(move |first_idx| {
264 (0..36usize)
265 .map(move |second_idx| Self([BASE36_CHARS[first_idx], BASE36_CHARS[second_idx]]))
266 });
267
268 let remaining_values = (0..62usize).flat_map(move |first_idx| {
270 (0..62usize)
271 .map(move |second_idx| Self([BASE62_CHARS[first_idx], BASE62_CHARS[second_idx]]))
272 .filter(move |obj_id| {
273 !obj_id.is_null() && !obj_id.is_base36() && obj_id.is_base62()
275 })
276 });
277
278 base36_values.skip(1).chain(remaining_values)
282 }
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
287#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
288pub struct Volume {
289 pub relative_percent: u8,
291}
292
293impl Default for Volume {
294 fn default() -> Self {
295 Self {
296 relative_percent: 100,
297 }
298 }
299}
300
301#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
303#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
304pub enum PoorMode {
305 #[default]
307 Interrupt,
308 Overlay,
310 Hidden,
312}
313
314impl std::str::FromStr for PoorMode {
315 type Err = ParseWarning;
316
317 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
318 Ok(match s {
319 "0" => Self::Interrupt,
320 "1" => Self::Overlay,
321 "2" => Self::Hidden,
322 _ => {
323 return Err(ParseWarning::SyntaxError(
324 "expected one of 0, 1 or 2".into(),
325 ));
326 }
327 })
328 }
329}
330
331impl PoorMode {
332 #[must_use]
334 pub const fn as_str(self) -> &'static str {
335 match self {
336 Self::Interrupt => "0",
337 Self::Overlay => "1",
338 Self::Hidden => "2",
339 }
340 }
341}
342
343#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
345#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
346pub enum LnType {
347 #[default]
349 Rdm,
350 Mgq,
352}
353
354#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
356#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
357#[repr(u8)]
358pub enum LnMode {
359 #[default]
361 Ln = 1,
362 Cn = 2,
364 Hcn = 3,
366}
367
368impl From<LnMode> for u8 {
369 fn from(mode: LnMode) -> u8 {
370 match mode {
371 LnMode::Ln => 1,
372 LnMode::Cn => 2,
373 LnMode::Hcn => 3,
374 }
375 }
376}
377
378impl TryFrom<u8> for LnMode {
379 type Error = u8;
380 fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
381 Ok(match value {
382 1 => Self::Ln,
383 2 => Self::Cn,
384 3 => Self::Hcn,
385 _ => return Err(value),
386 })
387 }
388}
389
390pub struct ObjIdManager<'a, K: ?Sized> {
393 value_to_id: HashMap<&'a K, ObjId>,
394 used_ids: HashSet<ObjId>,
395 unused_ids: VecDeque<ObjId>,
396}
397
398impl<'a, K> Default for ObjIdManager<'a, K>
399where
400 K: std::hash::Hash + Eq + ?Sized,
401{
402 fn default() -> Self {
403 Self::new()
404 }
405}
406
407impl<'a, K> ObjIdManager<'a, K>
408where
409 K: std::hash::Hash + Eq + ?Sized,
410{
411 #[must_use]
413 pub fn new() -> Self {
414 let unused_ids: VecDeque<ObjId> = ObjId::all_values().collect();
415
416 Self {
417 value_to_id: HashMap::new(),
418 used_ids: HashSet::new(),
419 unused_ids,
420 }
421 }
422
423 pub fn from_entries<I: IntoIterator<Item = (&'a K, ObjId)>>(iter: I) -> Self {
425 let mut value_to_id: HashMap<&'a K, ObjId> = HashMap::new();
426 let mut used_ids: HashSet<ObjId> = HashSet::new();
427
428 let entries: Vec<_> = iter.into_iter().collect();
430
431 for (key, assigned_id) in entries {
433 value_to_id.insert(key, assigned_id);
434 used_ids.insert(assigned_id);
435 }
436
437 let unused_ids: VecDeque<ObjId> = ObjId::all_values()
438 .filter(|id| !used_ids.contains(id))
439 .collect();
440
441 Self {
442 value_to_id,
443 used_ids,
444 unused_ids,
445 }
446 }
447
448 pub fn is_assigned(&self, key: &'a K) -> bool {
450 self.value_to_id.contains_key(key)
451 }
452
453 pub fn get_or_new_id(&mut self, key: &'a K) -> Option<ObjId> {
455 if let Some(&id) = self.value_to_id.get(key) {
456 Some(id)
457 } else if let Some(new_id) = self.unused_ids.pop_front() {
458 self.used_ids.insert(new_id);
459 self.value_to_id.insert(key, new_id);
460 Some(new_id)
461 } else {
462 None
463 }
464 }
465
466 pub fn into_assigned_ids(self) -> impl Iterator<Item = ObjId> {
468 self.used_ids.into_iter()
469 }
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475
476 #[test]
477 fn test_base62() {
478 assert_eq!(char_to_base62('/'), None);
479 assert_eq!(char_to_base62('0'), Some(b'0'));
480 assert_eq!(char_to_base62('9'), Some(b'9'));
481 assert_eq!(char_to_base62(':'), None);
482 assert_eq!(char_to_base62('@'), None);
483 assert_eq!(char_to_base62('A'), Some(b'A'));
484 assert_eq!(char_to_base62('Z'), Some(b'Z'));
485 assert_eq!(char_to_base62('['), None);
486 assert_eq!(char_to_base62('`'), None);
487 assert_eq!(char_to_base62('a'), Some(b'a'));
488 assert_eq!(char_to_base62('z'), Some(b'z'));
489 assert_eq!(char_to_base62('{'), None);
490 }
491
492 #[test]
493 fn test_all_values() {
494 let all_values: Vec<ObjId> = ObjId::all_values().collect();
495
496 assert_eq!(all_values.len(), 3843);
498
499 for (i, obj_id) in all_values.iter().enumerate() {
501 if i < 1295 {
502 assert!(
503 obj_id.is_base36(),
504 "Value at index {} should be Base36: {:?}",
505 i,
506 obj_id
507 );
508 } else {
509 assert!(
510 !obj_id.is_base36(),
511 "Value at index {} should NOT be Base36: {:?}",
512 i,
513 obj_id
514 );
515 }
516 }
517
518 assert_eq!(all_values[0], ObjId::try_from("01", false).unwrap()); assert_eq!(all_values[1294], ObjId::try_from("ZZ", false).unwrap()); assert!(!all_values.contains(&ObjId::null()));
524
525 let mut unique_values = std::collections::HashSet::new();
527 for value in &all_values {
528 assert!(
529 unique_values.insert(*value),
530 "Duplicate value found: {:?}",
531 value
532 );
533 }
534 }
535}