use std::{fs::File, io::Error, io::ErrorKind};
use sipp::{
decoder::{ByteStreamCharDecoder, Utf8Decoder},
parser::Parser,
};
fn main() -> Result<(), Error> {
let file = File::open("examples/Acornsoft_Logo_command_sequence.txt")?;
let decoder = Utf8Decoder::wrap(file);
let mut parser = Parser::wrap(decoder);
let mut turtle = Turtle::new();
println!("Initial state of turtle:\n{:?}", turtle);
while let Some(c) = parser.peek()? {
if c == '\n' {
parser.read()?;
continue;
}
if c == '#' {
parser.skip_while(|c| c != '\n')?;
continue;
}
match c {
'D' => parser.require_str("DRAW")?,
'F' => parse_forward_command(&mut parser, &mut turtle)?,
'R' => parse_right_command(&mut parser, &mut turtle)?,
'L' => parse_left_command(&mut parser, &mut turtle)?,
_ => {
return Err(Error::new(ErrorKind::InvalidData, "Unrecognised command!"));
}
}
}
println!("Final state of turtle:\n{:?}", turtle);
Ok(())
}
fn require_whitespace(parser: &mut Parser<Utf8Decoder<File>, File>) -> Result<(), Error> {
let found_space = parser.skip_while(|c| c == ' ')?;
if !found_space {
return Err(Error::new(
std::io::ErrorKind::InvalidData,
"Whitespace required after instruction!",
));
}
Ok(())
}
fn require_numeric_value(
parser: &mut Parser<Utf8Decoder<File>, File>,
command_name: &'static str,
) -> Result<u16, Error> {
if let Some(variable) = parser.read_up_to('\n')? {
if let Ok(degrees) = variable.parse::<u16>() {
Ok(degrees)
} else {
Err(Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid number for {} command!", command_name),
))
}
} else {
Err(Error::new(
std::io::ErrorKind::InvalidData,
format!("Number must follow {} command!", command_name),
))
}
}
fn parse_forward_command(
parser: &mut Parser<Utf8Decoder<File>, File>,
turtle: &mut Turtle,
) -> Result<(), Error> {
parser.require_str("FORWARD")?;
require_whitespace(parser)?;
let move_amount = require_numeric_value(parser, "FORWARD")?;
println!("Turning turtle forward by {} pixels.", move_amount);
turtle.forward(move_amount);
Ok(())
}
fn parse_right_command(
parser: &mut Parser<Utf8Decoder<File>, File>,
turtle: &mut Turtle,
) -> Result<(), Error> {
parser.require_str("RIGHT")?;
require_whitespace(parser)?;
let degrees = require_numeric_value(parser, "RIGHT")?;
println!("Turning turtle right by {} degrees.", degrees);
turtle.right(degrees);
Ok(())
}
fn parse_left_command(
parser: &mut Parser<Utf8Decoder<File>, File>,
turtle: &mut Turtle,
) -> Result<(), Error> {
parser.require_str("LEFT")?;
require_whitespace(parser)?;
let degrees = require_numeric_value(parser, "LEFT")?;
println!("Turning turtle left by {} degrees.", degrees);
turtle.left(degrees);
Ok(())
}
#[derive(Debug)]
struct Turtle {
heading: u16,
x_pos: f64,
y_pos: f64,
}
impl Turtle {
fn new() -> Turtle {
Turtle {
heading: 0,
x_pos: 0.0,
y_pos: 0.0,
}
}
fn forward(&mut self, move_amount: u16) {
let alpha = (self.heading as f64) * std::f64::consts::TAU / 360.0;
self.x_pos += (move_amount as f64) * alpha.sin();
self.y_pos += (move_amount as f64) * alpha.cos();
}
fn right(&mut self, degrees: u16) {
self.heading += degrees;
self.heading %= 360;
}
fn left(&mut self, degrees: u16) {
self.heading -= degrees;
self.heading %= 360;
}
}