use std::f64::consts::PI;
use crate::{
canvas::Paint,
utils::{RsilleErr, MIN_DIFFERENCE},
Canvas,
};
use crate::color::Color;
#[derive(Debug, Clone, PartialEq)]
pub struct Turtle {
procedures: Vec<Procedure>,
anime_proc: Option<Vec<Procedure>>,
anime_step: f64,
frame_count: usize,
}
impl Turtle {
pub fn new() -> Self {
Self {
procedures: Vec::new(),
anime_proc: None,
anime_step: 10.0,
frame_count: 0,
}
}
pub fn penup(&mut self) {
self.add_procedure(Procedure::PenUp);
}
pub fn up(&mut self) {
self.penup();
}
pub fn pendown(&mut self) {
self.add_procedure(Procedure::PenDown);
}
pub fn down(&mut self) {
self.pendown();
}
pub fn forward<T>(&mut self, step: T)
where
T: Into<f64>,
{
self.add_procedure(Procedure::Forward(step.into()));
}
pub fn fd<T>(&mut self, step: T)
where
T: Into<f64>,
{
self.forward(step);
}
pub fn backward<T>(&mut self, step: T)
where
T: Into<f64>,
{
self.forward(-step.into());
}
pub fn back<T>(&mut self, step: T)
where
T: Into<f64>,
{
self.backward(step);
}
pub fn bk<T>(&mut self, step: T)
where
T: Into<f64>,
{
self.backward(step);
}
pub fn right<T>(&mut self, angle: T)
where
T: Into<f64>,
{
self.add_procedure(Procedure::Right(angle.into()));
}
pub fn rt<T>(&mut self, angle: T)
where
T: Into<f64>,
{
self.right(angle);
}
pub fn left<T>(&mut self, angle: T)
where
T: Into<f64>,
{
self.right(-angle.into());
}
pub fn lt<T>(&mut self, angle: T)
where
T: Into<f64>,
{
self.left(angle);
}
pub fn circle<T>(&mut self, radius: T, extent: T)
where
T: Into<f64>,
{
self.add_procedure(Procedure::Circle(radius.into(), extent.into(), 100));
}
pub fn circle_with_steps<T>(&mut self, radius: T, extent: T, steps: usize)
where
T: Into<f64>,
{
self.add_procedure(Procedure::Circle(radius.into(), extent.into(), steps));
}
pub fn goto<T>(&mut self, x: T, y: T)
where
T: Into<f64>,
{
self.add_procedure(Procedure::Goto(x.into(), y.into()));
}
pub fn teleport<T>(&mut self, x: T, y: T)
where
T: Into<f64>,
{
self.add_procedure(Procedure::Teleport(x.into(), y.into()));
}
pub fn home(&mut self) {
self.add_procedure(Procedure::Home);
}
pub fn colorful(&mut self, color: Color) {
self.add_procedure(Procedure::Colorful(color));
}
pub fn color(&mut self, color: Color) {
self.colorful(color);
}
pub fn anime(&mut self) {
use Procedure::*;
let mut anime_proc = Vec::new();
let astep = self.anime_step;
for p in &self.procedures {
match p {
PenDown | PenUp | Right(_) | Teleport(_, _) | Home => {
anime_proc.push(*p);
}
Forward(step) => {
let mut step = *step;
while step > astep {
anime_proc.push(Forward(astep));
step -= astep;
}
if (step - astep).abs() < MIN_DIFFERENCE {
anime_proc.push(Forward(astep));
} else {
anime_proc.push(Forward(step));
}
}
Goto(_, _) => {
anime_proc.push(*p);
}
Circle(radius, extent, steps) => {
let mut extent = *extent;
if PI * radius * extent <= 180.0 * astep {
anime_proc.push(*p);
} else {
let e = 180.0 * astep / (PI * radius);
while extent > e {
anime_proc.push(Circle(*radius, e, *steps));
extent -= e;
}
if (extent - e).abs() < MIN_DIFFERENCE {
anime_proc.push(Circle(*radius, e, *steps));
} else {
anime_proc.push(Circle(*radius, extent, *steps));
}
}
}
_ => anime_proc.push(*p),
}
}
self.anime_proc = Some(anime_proc);
}
pub fn update(&mut self) -> bool {
use Procedure::*;
if let Some(procs) = &self.anime_proc {
if self.frame_count >= procs.len() {
return true;
}
while let Some(p) = procs.get(self.frame_count) {
match p {
Forward(_) | Goto(_, _) | Circle(_, _, _) => {
self.frame_count += 1;
break;
}
_ => {
self.frame_count += 1;
}
}
}
}
false
}
pub fn set_anime_step<T>(&mut self, step: T)
where
T: Into<f64>,
{
self.anime_step = step.into();
}
fn add_procedure(&mut self, p: Procedure) {
self.procedures.push(p);
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
enum Procedure {
PenDown,
PenUp,
Forward(f64), Right(f64), Teleport(f64, f64), Home,
Goto(f64, f64), Circle(f64, f64, usize), Colorful(Color),
}
fn forward(
canvas: &mut Canvas,
x: f64,
y: f64,
heading: f64,
pen: bool,
step: f64,
color: Color,
) -> (f64, f64) {
let (sr, cr) = heading.to_radians().sin_cos();
let txy = (x + cr * step, y + sr * step);
if pen {
canvas.line_colorful((x, y), txy, color);
}
txy
}
impl Paint for Turtle {
fn paint<T>(&self, canvas: &mut Canvas, x: T, y: T) -> Result<(), RsilleErr>
where
T: Into<f64>,
{
use Procedure::*;
let (x, y) = (x.into(), y.into());
let (home_x, home_y) = (x, y);
let (mut pen, mut heading, mut x, mut y) = (true, 0.0, x, y);
let mut color = Color::Reset;
let procs = if let Some(procs) = &self.anime_proc {
&procs[0..self.frame_count]
} else {
&self.procedures
};
for p in procs {
match p {
PenDown => {
pen = true;
}
PenUp => {
pen = false;
}
Forward(step) => {
(x, y) = forward(canvas, x, y, heading, pen, *step, color);
}
Right(angle) => {
heading -= angle;
}
Teleport(tx, ty) => {
x = *tx;
y = *ty;
}
Home => {
(x, y) = (home_x, home_y);
}
Goto(tx, ty) => {
if pen {
canvas.line_colorful((x, y), (*tx, *ty), color);
}
x = *tx;
y = *ty;
}
Circle(radius, extent, steps) => {
let angle = extent / *steps as f64;
for _ in 0..*steps {
(x, y) = forward(
canvas,
x,
y,
heading,
pen,
2.0 * radius * (angle / 2.0).to_radians().sin(),
color,
);
heading -= angle;
}
}
Colorful(c) => {
color = *c;
}
}
}
Ok(())
}
}