1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/// Represents an Etterna chart rate (music speed).
///
/// As in Etterna, this value can only be a multiple of 0.05. The value can't be negative, nor NaN
/// or infinity.
///
/// When printed, a [`Rate`] is formatted as usual in Etterna; two floating point digits and an `x`
/// at the end: `0.85x, 1.00x, 2.40x`
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rate {
	// this value is 20x the real rate, e.g. `1.15x` would be 23
	x20: u32,
}

impl Rate {
	/// Rounds to the nearest valid rate.
	///
	/// Returns None if the given value is negative or too large
	pub fn from_f32(r: f32) -> Option<Self> {
		// Some(Self { x20: (r * 20.0).round().try_into().ok()? })
		if r < 0.0 || r > u32::MAX as f32 {
			None
		} else {
			Some(Self {
				x20: (r * 20.0).round() as u32,
			})
		}
	}

	/// Parses a string into a rate. The string needs to be in the format `\d+\.\d+[05]?`
	///
	/// Returns None if parsing failed
	// TODO: Rework this to not rely on float parsing but parse the digits directly
	pub fn from_string(string: &str) -> Option<Self> {
		// not the most efficient but /shrug
		Self::from_f32(string.parse().ok()?)
	}

	/// Create a new rate from a value that is equal to the real rate multiplied by 20.
	///
	/// Due to the fact that Etterna ratings are always multiples of 0.05, every rate can be
	/// precicely represented precisely with a whole number when multiplied by 20.
	pub fn from_x20(x20: u32) -> Self {
		Self { x20 }
	}

	/// Returns an f32 representation of this rate.
	///
	/// ```rust
	/// # use etterna::Rate;
	/// assert_eq!(Rate::from_string("1.40").unwrap().as_f32(), 1.4);
	/// ```
	pub fn as_f32(self) -> f32 {
		self.x20 as f32 / 20.0
	}

	/// Returns this rate multiplied by 20. This will always result in a whole number, hence this
	/// function returns an integer.
	///
	/// ```rust
	/// # use etterna::Rate;
	/// assert_eq!(Rate::from_string("1.45").unwrap().as_x20(), 29);
	/// ```
	pub fn as_x20(self) -> u32 {
		self.x20
	}
}

impl std::fmt::Display for Rate {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "{}.{:02}x", (self.x20 * 5) / 100, (self.x20 * 5) % 100)
	}
}

impl std::fmt::Debug for Rate {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		std::fmt::Display::fmt(self, f)
	}
}

impl Default for Rate {
	fn default() -> Self {
		Self::from_x20(20)
	}
}

impl From<f32> for Rate {
	fn from(value: f32) -> Self {
		Self::from_f32(value).expect("Invalid rate string")
	}
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
pub struct RateParseError;
impl std::fmt::Display for RateParseError {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "invalid rate")
	}
}
impl std::error::Error for RateParseError {}

impl std::str::FromStr for Rate {
	type Err = RateParseError;

	fn from_str(s: &str) -> Result<Self, Self::Err> {
		Self::from_string(s).ok_or(RateParseError)
	}
}

impl std::ops::Add for Rate {
	type Output = Self;

	fn add(self, rhs: Self) -> Self::Output {
		Self::from_x20(self.x20 + rhs.x20)
	}
}

impl std::ops::Sub for Rate {
	type Output = Self;

	fn sub(self, rhs: Self) -> Self::Output {
		Self::from_x20(self.x20 - rhs.x20)
	}
}

impl std::ops::AddAssign for Rate {
	fn add_assign(&mut self, other: Self) {
		self.x20 += other.x20;
	}
}

impl std::ops::SubAssign for Rate {
	fn sub_assign(&mut self, other: Self) {
		self.x20 -= other.x20;
	}
}