algar 0.4.1

Algebraic structures, higher-kinded types and other category theory bad ideas
Documentation
use std::f32::consts::PI;

#[derive(PartialEq, Debug, Clone)]
pub enum PenState {
    Up,
    Down,
}

#[derive(PartialEq, Debug, Clone)]
pub enum PenColor {
    Black,
    Red,
}

#[derive(PartialEq, Debug, Clone)]
pub struct Position(f32, f32);

impl Position {
    pub fn new(x: f32, y: f32) -> Self {
        Position(x, y)
    }
}

#[derive(PartialEq, Debug, Clone)]
pub struct Angle(f32);

impl Angle {
    pub fn new(x: f32) -> Self {
        Angle(x)
    }
}

#[derive(PartialEq, Debug, Clone)]
pub struct Distance(f32);

impl Distance {
    pub fn new(x: f32) -> Self {
        Distance(x)
    }
}

#[derive(PartialEq, Debug, Clone)]
pub struct Turtle {
    pub position: Position,
    pub angle: Angle,
    pub state: PenState,
    pub color: PenColor,
}

impl Default for Turtle {
    fn default() -> Self {
        Self {
            position: Position(0.0, 0.0),
            angle: Angle(0.0),
            state: PenState::Up,
            color: PenColor::Black,
        }
    }
}

impl Turtle {
    pub fn r#move(self, d: Distance) -> Self {
        Self {
            position: Self::calc_new_position(&d, &self.angle, &self.position),
            ..self
        }
    }

    /// The same as `r#move` but returns a tuple ((), Turtle)
    pub fn r#move2(self, d: Distance) -> ((), Self) {
        (
            (),
            Self {
                position: Self::calc_new_position(&d, &self.angle, &self.position),
                ..self
            },
        )
    }

    pub fn turn(self, angle: Angle) -> Self {
        Self {
            angle: Angle(angle.0),
            ..self
        }
    }

    /// The same as `turn` but returns a tuple ((), Turtle)
    pub fn turn2(self, angle: Angle) -> ((), Self) {
        (
            (),
            Self {
                angle: Angle(angle.0),
                ..self
            },
        )
    }

    pub fn set_pen_state(self, pen_state: PenState) -> Self {
        Self {
            state: pen_state,
            ..self
        }
    }

    pub fn set_pen_color(self, pen_color: PenColor) -> Self {
        Self {
            color: pen_color,
            ..self
        }
    }

    fn calc_new_position(distance: &Distance, angle: &Angle, cur_position: &Position) -> Position {
        let angle_in_rads = angle.0 * PI / 180.0;
        let new_x = cur_position.0 + distance.0 * angle_in_rads.cos();
        let new_y = cur_position.1 + distance.0 * angle_in_rads.sin();

        Position(new_x.ceil(), new_y.ceil())
    }
}

#[cfg(test)]
mod test {

    use super::{Angle, Distance, PenColor, PenState, Position, Turtle};

    #[test]
    fn test_move() {
        let t = Turtle::default();

        assert_eq!(t.r#move(Distance(1.0)).position, Position(1.0, 0.0));
    }

    #[test]
    fn test_turn() {
        let t = Turtle::default();

        assert_eq!(t.turn(Angle(90.0)).angle, Angle(90.0));
    }

    #[test]
    fn test_state() {
        let t = Turtle::default();

        assert_eq!(t.set_pen_state(PenState::Down).state, PenState::Down);
    }

    #[test]
    fn test_color() {
        let t = Turtle::default();

        assert_eq!(t.set_pen_color(PenColor::Red).color, PenColor::Red);
    }
}