use std::cmp::Ordering;
use std::f64;
use std::f64::consts::PI;
use std::fmt;
use serde::{Serialize, Deserialize};
use crate::radians::Radians;
use crate::rand::{Random, RandomRange};
use crate::Distance;
const MIN_SPEED: i32 = 1;
const MAX_SPEED: i32 = 25;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, Hash, Serialize, Deserialize)]
pub(crate) enum SpeedLevel {
Value(i32),
Instant,
}
impl PartialOrd for SpeedLevel {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
use SpeedLevel::*;
match (*self, *other) {
(Value(value), Value(ref other_value)) => value.partial_cmp(other_value),
(Instant, Instant) => Some(Ordering::Equal),
(Value(_), Instant) => Some(Ordering::Less),
(Instant, Value(_)) => Some(Ordering::Greater),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Speed(SpeedLevel);
impl Speed {
pub fn instant() -> Self {
Speed(SpeedLevel::Instant)
}
pub fn is_instant(self) -> bool {
match self {
Speed(SpeedLevel::Instant) => true,
_ => false,
}
}
pub(crate) fn to_movement(self) -> Distance {
use SpeedLevel::*;
match self.0 {
Value(speed) => f64::from(speed) * 50.0,
Instant => f64::INFINITY,
}
}
pub(crate) fn to_rotation(self) -> Radians {
use SpeedLevel::*;
Radians::from_radians_value(match self.0 {
Value(speed) => f64::from(speed) * (3.0 * PI),
Instant => f64::INFINITY,
})
}
}
impl fmt::Display for Speed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use SpeedLevel::*;
match self.0 {
Value(speed) => write!(f, "Speed::from({})", speed),
Instant => write!(f, "Speed::instant()"),
}
}
}
impl PartialEq<i32> for Speed {
fn eq(&self, other: &i32) -> bool {
self.eq(&Speed::from(*other))
}
}
impl PartialOrd<i32> for Speed {
fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
self.partial_cmp(&Speed::from(*other))
}
}
impl Random for Speed {
fn random() -> Self {
RandomRange::random_range(MIN_SPEED, MAX_SPEED)
}
}
impl<B: Into<Speed>> RandomRange<B> for Speed {
fn random_range(low: B, high: B) -> Self {
let low = low.into();
let high = high.into();
if let (Speed(SpeedLevel::Value(low)), Speed(SpeedLevel::Value(high))) = (low, high) {
if low < MIN_SPEED || high > MAX_SPEED {
panic!("The boundaries must be within the valid range of difficulties");
}
Speed(SpeedLevel::Value(RandomRange::random_range(low, high)))
} else {
panic!("At least one of the bounds provided to random_range() was Speed::instant()");
}
}
}
impl<'a> From<&'a str> for Speed {
fn from(s: &'a str) -> Self {
use SpeedLevel::*;
Speed(match s {
"slowest" => Value(1),
"slower" => Value(5),
"slow" => Value(8),
"normal" => Value(10),
"fast" => Value(12),
"faster" => Value(15),
"instant" => Instant,
_ => panic!(
"Invalid speed specified, use one of the words: \"slowest\", \"slower\", \"slow\", \"normal\", \"fast\", \"faster\", \"instant\""
),
})
}
}
impl From<i32> for Speed {
fn from(n: i32) -> Self {
use SpeedLevel::*;
Speed(match n {
0 => panic!("Invalid speed: 0. If you wanted to set the speed to instant, please use the string \"instant\" or Speed::instant()"),
n if n >= MIN_SPEED && n <= MAX_SPEED => Value(n),
n => panic!("Invalid speed: {}. Must be a value between {} and {}", n, MIN_SPEED, MAX_SPEED),
})
}
}
impl From<f64> for Speed {
fn from(n: f64) -> Self {
(n.round() as i32).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Turtle;
#[test]
fn display() {
let speed: Speed = "instant".into();
assert_eq!(format!("{}", speed), "Speed::instant()");
for value in 1..MAX_SPEED {
let speed: Speed = value.into();
assert_eq!(format!("{}", speed), format!("Speed::from({})", value));
}
}
#[test]
fn speed_strings() {
let mut turtle = Turtle::new();
turtle.set_speed("slowest");
assert_eq!(turtle.speed(), 1);
turtle.set_speed("slower");
assert_eq!(turtle.speed(), 5);
turtle.set_speed("slow");
assert_eq!(turtle.speed(), 8);
turtle.set_speed("normal");
assert_eq!(turtle.speed(), 10);
turtle.set_speed("fast");
assert_eq!(turtle.speed(), 12);
turtle.set_speed("faster");
assert_eq!(turtle.speed(), 15);
turtle.set_speed("instant");
assert_eq!(turtle.speed(), Speed::instant());
}
#[test]
#[should_panic(
expected = "Invalid speed specified, use one of the words: \"slowest\", \"slower\", \"slow\", \"normal\", \"fast\", \"faster\", \"instant\""
)]
fn invalid_speed() {
let mut turtle = Turtle::new();
turtle.set_speed("wrong");
}
#[test]
fn speed_values() {
let mut turtle = Turtle::new();
for speed in 1..(MAX_SPEED + 1) {
turtle.set_speed(speed);
assert_eq!(turtle.speed(), speed);
}
}
#[test]
#[should_panic(expected = "Invalid speed: 26. Must be a value between 1 and 25")]
fn speed_value_out_of_range() {
let mut turtle = Turtle::new();
turtle.set_speed(26);
}
#[test]
#[should_panic(expected = "Invalid speed: 20394. Must be a value between 1 and 25")]
fn speed_value_out_of_range2() {
let mut turtle = Turtle::new();
turtle.set_speed(20394);
}
#[test]
#[should_panic(expected = "Invalid speed: -1. Must be a value between 1 and 25")]
fn speed_value_out_of_range_negative() {
let mut turtle = Turtle::new();
turtle.set_speed(-1);
}
#[test]
#[should_panic(
expected = "Invalid speed: 0. If you wanted to set the speed to instant, please use the string \"instant\" or Speed::instant()"
)]
fn disallow_zero() {
let mut turtle = Turtle::new();
turtle.set_speed(0);
}
#[test]
fn speed_values_f64() {
let mut turtle = Turtle::new();
for speed in 1..MAX_SPEED {
turtle.set_speed(speed as f64 + 0.4);
assert_eq!(turtle.speed(), speed);
}
}
}