use std::collections::{HashMap, HashSet, VecDeque};
use super::parse::ParseWarning;
pub mod channel;
pub mod graphics;
pub mod mixin;
pub mod string_value;
pub mod time;
pub use string_value::StringValue;
pub mod minor_command;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PlayerMode {
Single,
Two,
Double,
}
impl std::fmt::Display for PlayerMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Single => write!(f, "1"),
Self::Two => write!(f, "2"),
Self::Double => write!(f, "3"),
}
}
}
impl std::str::FromStr for PlayerMode {
type Err = ParseWarning;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(match s {
"1" => Self::Single,
"2" => Self::Two,
"3" => Self::Double,
_ => {
return Err(ParseWarning::SyntaxError(
"expected one of 0, 1 or 2".into(),
));
}
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum JudgeLevel {
VeryHard,
Hard,
Normal,
Easy,
OtherInt(i64),
}
impl From<i64> for JudgeLevel {
fn from(value: i64) -> Self {
match value {
0 => Self::VeryHard,
1 => Self::Hard,
2 => Self::Normal,
3 => Self::Easy,
val => Self::OtherInt(val),
}
}
}
impl<'a> TryFrom<&'a str> for JudgeLevel {
type Error = &'a str;
fn try_from(value: &'a str) -> core::result::Result<Self, Self::Error> {
value.parse::<i64>().map(Self::from).map_err(|_| value)
}
}
impl std::fmt::Display for JudgeLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::VeryHard => write!(f, "0"),
Self::Hard => write!(f, "1"),
Self::Normal => write!(f, "2"),
Self::Easy => write!(f, "3"),
Self::OtherInt(value) => write!(f, "{value}"),
}
}
}
pub(crate) fn char_to_base62(ch: char) -> Option<u8> {
ch.is_ascii_alphanumeric().then_some(ch as u8)
}
pub(crate) fn base62_to_byte(base62: u8) -> u8 {
#[allow(clippy::panic)]
match base62 {
b'0'..=b'9' => base62 - b'0',
b'A'..=b'Z' => base62 - b'A' + 10,
b'a'..=b'z' => base62 - b'a' + 36,
_ => panic!("invalid base62 byte: {base62}"),
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ObjId([u8; 2]);
impl std::fmt::Debug for ObjId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ObjId")
.field(&format!("{}{}", self.0[0] as char, self.0[1] as char))
.finish()
}
}
impl std::fmt::Display for ObjId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.0[0] as char, self.0[1] as char)
}
}
impl From<ObjId> for u16 {
fn from(value: ObjId) -> Self {
base62_to_byte(value.0[0]) as Self * 62 + base62_to_byte(value.0[1]) as Self
}
}
impl From<ObjId> for u32 {
fn from(value: ObjId) -> Self {
Into::<u16>::into(value) as Self
}
}
impl From<ObjId> for u64 {
fn from(value: ObjId) -> Self {
Into::<u16>::into(value) as Self
}
}
impl ObjId {
#[must_use]
pub const fn null() -> Self {
Self([b'0', b'0'])
}
#[must_use]
pub fn is_null(self) -> bool {
self.0 == [b'0', b'0']
}
pub fn try_from(
value: &str,
case_sensitive_obj_id: bool,
) -> core::result::Result<Self, ParseWarning> {
if value.len() != 2 {
return Err(ParseWarning::SyntaxError(format!(
"expected 2 digits as object id but found: {value}"
)));
}
let mut chars = value.bytes();
let [Some(ch1), Some(ch2), None] = [chars.next(), chars.next(), chars.next()] else {
return Err(ParseWarning::SyntaxError(format!(
"expected 2 digits as object id but found: {value}"
)));
};
if !(ch1.is_ascii_alphanumeric() && ch2.is_ascii_alphanumeric()) {
return Err(ParseWarning::SyntaxError(format!(
"expected alphanumeric characters as object id but found: {value}"
)));
}
if case_sensitive_obj_id {
Ok(Self([ch1, ch2]))
} else {
Ok(Self([ch1.to_ascii_uppercase(), ch2.to_ascii_uppercase()]))
}
}
#[must_use]
pub fn as_u16(self) -> u16 {
self.into()
}
#[must_use]
pub fn as_u32(self) -> u32 {
self.into()
}
#[must_use]
pub fn as_u64(self) -> u64 {
self.into()
}
#[must_use]
pub fn into_chars(self) -> [char; 2] {
self.0.map(|c| c as char)
}
pub const fn make_uppercase(&mut self) {
self.0[0] = self.0[0].to_ascii_uppercase();
self.0[1] = self.0[1].to_ascii_uppercase();
}
#[must_use]
pub fn is_base36(self) -> bool {
self.0
.iter()
.all(|c| c.is_ascii_digit() || c.is_ascii_uppercase())
}
#[must_use]
pub fn is_base62(self) -> bool {
self.0
.iter()
.all(|c| c.is_ascii_digit() || c.is_ascii_uppercase() || c.is_ascii_lowercase())
}
pub fn all_values() -> impl Iterator<Item = Self> {
const BASE36_CHARS: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const BASE62_CHARS: &[u8] =
b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
let base36_values = BASE36_CHARS.iter().copied().flat_map(|first| {
BASE36_CHARS
.iter()
.copied()
.map(move |second| Self([first, second]))
});
let remaining_values = BASE62_CHARS.iter().copied().flat_map(|first| {
BASE62_CHARS
.iter()
.copied()
.map(move |second| Self([first, second]))
.filter(move |obj_id| {
!obj_id.is_null() && !obj_id.is_base36() && obj_id.is_base62()
})
});
base36_values.skip(1).chain(remaining_values)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Volume {
pub relative_percent: u8,
}
impl Default for Volume {
fn default() -> Self {
Self {
relative_percent: 100,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PoorMode {
#[default]
Interrupt,
Overlay,
Hidden,
}
impl std::str::FromStr for PoorMode {
type Err = ParseWarning;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(match s {
"0" => Self::Interrupt,
"1" => Self::Overlay,
"2" => Self::Hidden,
_ => {
return Err(ParseWarning::SyntaxError(
"expected one of 0, 1 or 2".into(),
));
}
})
}
}
impl PoorMode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Interrupt => "0",
Self::Overlay => "1",
Self::Hidden => "2",
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LnType {
#[default]
Rdm,
Mgq,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum LnMode {
#[default]
Ln = 1,
Cn = 2,
Hcn = 3,
}
impl From<LnMode> for u8 {
fn from(mode: LnMode) -> Self {
match mode {
LnMode::Ln => 1,
LnMode::Cn => 2,
LnMode::Hcn => 3,
}
}
}
impl TryFrom<u8> for LnMode {
type Error = u8;
fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
Ok(match value {
1 => Self::Ln,
2 => Self::Cn,
3 => Self::Hcn,
_ => return Err(value),
})
}
}
pub struct ObjIdManager<'a, K: ?Sized> {
value_to_id: HashMap<&'a K, ObjId>,
used_ids: HashSet<ObjId>,
unused_ids: VecDeque<ObjId>,
}
impl<K> Default for ObjIdManager<'_, K>
where
K: std::hash::Hash + Eq + ?Sized,
{
fn default() -> Self {
Self::new()
}
}
impl<'a, K> ObjIdManager<'a, K>
where
K: std::hash::Hash + Eq + ?Sized,
{
#[must_use]
pub fn new() -> Self {
let unused_ids: VecDeque<ObjId> = ObjId::all_values().collect();
Self {
value_to_id: HashMap::new(),
used_ids: HashSet::new(),
unused_ids,
}
}
pub fn from_entries<I: IntoIterator<Item = (&'a K, ObjId)>>(iter: I) -> Self {
let mut value_to_id: HashMap<&'a K, ObjId> = HashMap::new();
let mut used_ids: HashSet<ObjId> = HashSet::new();
let entries: Vec<_> = iter.into_iter().collect();
for (key, assigned_id) in entries {
value_to_id.insert(key, assigned_id);
used_ids.insert(assigned_id);
}
let unused_ids: VecDeque<ObjId> = ObjId::all_values()
.filter(|id| !used_ids.contains(id))
.collect();
Self {
value_to_id,
used_ids,
unused_ids,
}
}
pub fn is_assigned(&self, key: &'a K) -> bool {
self.value_to_id.contains_key(key)
}
pub fn get_or_new_id(&mut self, key: &'a K) -> Option<ObjId> {
if let Some(&id) = self.value_to_id.get(key) {
return Some(id);
}
let new_id = self.unused_ids.pop_front()?;
self.used_ids.insert(new_id);
self.value_to_id.insert(key, new_id);
Some(new_id)
}
pub fn into_assigned_ids(self) -> impl Iterator<Item = ObjId> {
self.used_ids.into_iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_base62() {
assert_eq!(char_to_base62('/'), None);
assert_eq!(char_to_base62('0'), Some(b'0'));
assert_eq!(char_to_base62('9'), Some(b'9'));
assert_eq!(char_to_base62(':'), None);
assert_eq!(char_to_base62('@'), None);
assert_eq!(char_to_base62('A'), Some(b'A'));
assert_eq!(char_to_base62('Z'), Some(b'Z'));
assert_eq!(char_to_base62('['), None);
assert_eq!(char_to_base62('`'), None);
assert_eq!(char_to_base62('a'), Some(b'a'));
assert_eq!(char_to_base62('z'), Some(b'z'));
assert_eq!(char_to_base62('{'), None);
}
#[test]
fn test_all_values() {
let all_values: Vec<ObjId> = ObjId::all_values().collect();
assert_eq!(all_values.len(), 3843);
for (i, obj_id) in all_values.iter().enumerate() {
if i < 1295 {
assert!(
obj_id.is_base36(),
"Value at index {i} should be Base36: {obj_id:?}"
);
} else {
assert!(
!obj_id.is_base36(),
"Value at index {i} should NOT be Base36: {obj_id:?}"
);
}
}
let Some(first) = all_values.first().copied() else {
panic!("expected ObjId::all_values() to be non-empty");
};
assert_eq!(first, ObjId::try_from("01", false).unwrap()); let Some(last_base36) = all_values.get(1294).copied() else {
panic!("expected ObjId::all_values() to contain Base36 values");
};
assert_eq!(last_base36, ObjId::try_from("ZZ", false).unwrap());
assert!(!all_values.contains(&ObjId::null()));
let mut unique_values = std::collections::HashSet::new();
for value in &all_values {
assert!(
unique_values.insert(*value),
"Duplicate value found: {value:?}"
);
}
}
}