use crate::ast::Direction;
use crate::types::Length as Inches;
use super::types::PointIn;
#[derive(Debug, Clone, Copy, Default)]
struct CoordFlags {
value: u8,
}
impl CoordFlags {
const X_SET: u8 = 1;
const Y_SET: u8 = 2;
const BOTH_SET: u8 = 3;
fn new() -> Self {
Self { value: 0 }
}
fn x_is_set(self) -> bool {
self.value & Self::X_SET != 0
}
fn y_is_set(self) -> bool {
self.value & Self::Y_SET != 0
}
fn both_set(self) -> bool {
self.value == Self::BOTH_SET
}
fn mark_x_set(&mut self) {
self.value |= Self::X_SET;
}
fn mark_y_set(&mut self) {
self.value |= Self::Y_SET;
}
fn reset(&mut self) {
self.value = 0;
}
fn set_both(&mut self) {
self.value = Self::BOTH_SET;
}
}
#[derive(Debug)]
pub struct PathBuilder {
points: Vec<PointIn>,
coord_flags: CoordFlags,
then_flag: bool,
current_direction: Direction,
}
impl PathBuilder {
pub fn new(start: PointIn) -> Self {
Self {
points: vec![start],
coord_flags: CoordFlags::new(),
then_flag: false,
current_direction: Direction::Right,
}
}
pub fn mark_then(&mut self) {
self.then_flag = true;
}
fn current_point(&self) -> PointIn {
*self.points.last().expect("Path should never be empty")
}
fn current_point_mut(&mut self) -> &mut PointIn {
self.points.last_mut().expect("Path should never be empty")
}
fn push_new_point(&mut self) {
let current = self.current_point();
self.points.push(current);
self.coord_flags.reset();
}
fn maybe_create_new_point(&mut self) {
if self.then_flag || self.coord_flags.both_set() || self.points.len() <= 1 {
if self.then_flag || self.coord_flags.both_set() {
self.push_new_point();
}
self.then_flag = false;
}
}
pub fn add_direction(&mut self, dir: Direction, distance: Inches) {
self.maybe_create_new_point();
match dir {
Direction::Up => {
if self.coord_flags.y_is_set() {
self.push_new_point();
}
self.current_point_mut().y = self.current_point().y + distance;
self.coord_flags.mark_y_set();
}
Direction::Down => {
if self.coord_flags.y_is_set() {
self.push_new_point();
}
self.current_point_mut().y = self.current_point().y - distance;
self.coord_flags.mark_y_set();
}
Direction::Right => {
if self.coord_flags.x_is_set() {
self.push_new_point();
}
self.current_point_mut().x = self.current_point().x + distance;
self.coord_flags.mark_x_set();
}
Direction::Left => {
if self.coord_flags.x_is_set() {
self.push_new_point();
}
self.current_point_mut().x = self.current_point().x - distance;
self.coord_flags.mark_x_set();
}
}
self.current_direction = dir;
}
pub fn set_even_with(&mut self, dir: Direction, target: PointIn) {
self.maybe_create_new_point();
match dir {
Direction::Up | Direction::Down => {
if self.coord_flags.y_is_set() {
self.push_new_point();
}
self.current_point_mut().y = target.y;
self.coord_flags.mark_y_set();
}
Direction::Right | Direction::Left => {
if self.coord_flags.x_is_set() {
self.push_new_point();
}
self.current_point_mut().x = target.x;
self.coord_flags.mark_x_set();
}
}
self.current_direction = dir;
}
pub fn set_endpoint(&mut self, point: PointIn) {
if self.points.len() <= 1 || self.coord_flags.both_set() || self.then_flag {
self.push_new_point();
}
*self.current_point_mut() = point;
self.coord_flags.set_both();
self.then_flag = false;
}
pub fn add_heading(&mut self, angle_degrees: f64, distance: Inches) {
self.push_new_point();
self.then_flag = false;
let angle_rad = angle_degrees.to_radians();
let dx = distance.raw() * angle_rad.sin();
let dy = distance.raw() * angle_rad.cos();
let pt = self.current_point_mut();
pt.x += Inches::inches(dx);
pt.y += Inches::inches(dy);
let normalized = angle_degrees.rem_euclid(360.0);
self.current_direction = if normalized <= 45.0 || normalized > 315.0 {
Direction::Up
} else if normalized <= 135.0 {
Direction::Right
} else if normalized <= 225.0 {
Direction::Down
} else {
Direction::Left
};
self.coord_flags.mark_y_set();
}
pub fn direction(&self) -> Direction {
self.current_direction
}
pub fn set_direction(&mut self, dir: Direction) {
self.current_direction = dir;
}
pub fn build(self) -> Vec<PointIn> {
self.points
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
pub fn start(&self) -> PointIn {
self.points[0]
}
pub fn end(&self) -> PointIn {
self.current_point()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Point;
fn pt(x: f64, y: f64) -> PointIn {
Point::new(Inches::inches(x), Inches::inches(y))
}
fn assert_point_eq(actual: PointIn, expected: PointIn) {
const EPSILON: f64 = 1e-10;
assert!(
(actual.x.raw() - expected.x.raw()).abs() < EPSILON,
"x mismatch: {} != {}",
actual.x.raw(),
expected.x.raw()
);
assert!(
(actual.y.raw() - expected.y.raw()).abs() < EPSILON,
"y mismatch: {} != {}",
actual.y.raw(),
expected.y.raw()
);
}
#[test]
fn test_simple_direction_right() {
let mut builder = PathBuilder::new(pt(0.0, 0.0));
builder.add_direction(Direction::Right, Inches::inches(2.0));
let path = builder.build();
assert_eq!(path.len(), 1); assert_point_eq(path[0], pt(2.0, 0.0));
}
#[test]
fn test_direction_up_then_right_same_point() {
let mut builder = PathBuilder::new(pt(0.0, 0.0));
builder.add_direction(Direction::Up, Inches::inches(1.0));
builder.add_direction(Direction::Right, Inches::inches(2.0));
let path = builder.build();
assert_eq!(path.len(), 1); assert_point_eq(path[0], pt(2.0, 1.0));
}
#[test]
fn test_direction_up_then_keyword_right_new_point() {
let mut builder = PathBuilder::new(pt(0.0, 0.0));
builder.add_direction(Direction::Up, Inches::inches(1.0));
builder.mark_then();
builder.add_direction(Direction::Right, Inches::inches(2.0));
let path = builder.build();
assert_eq!(path.len(), 2);
assert_point_eq(path[0], pt(0.0, 1.0));
assert_point_eq(path[1], pt(2.0, 1.0));
}
#[test]
fn test_even_with_left() {
let start = pt(10.0, 3.0);
let target = pt(5.0, 10.0);
let mut builder = PathBuilder::new(start);
builder.set_even_with(Direction::Left, target);
let path = builder.build();
assert_eq!(path.len(), 1);
assert_point_eq(path[0], pt(5.0, 3.0));
}
#[test]
fn test_even_with_up() {
let start = pt(3.0, 2.0);
let target = pt(5.0, 10.0);
let mut builder = PathBuilder::new(start);
builder.set_even_with(Direction::Up, target);
let path = builder.build();
assert_eq!(path.len(), 1);
assert_point_eq(path[0], pt(3.0, 10.0));
}
#[test]
fn test_even_with_then_direction() {
let start = pt(10.0, 3.0);
let target = pt(5.0, 10.0);
let mut builder = PathBuilder::new(start);
builder.set_even_with(Direction::Left, target);
builder.mark_then();
builder.add_direction(Direction::Up, Inches::inches(1.0));
let path = builder.build();
assert_eq!(path.len(), 2);
assert_point_eq(path[0], pt(5.0, 3.0));
assert_point_eq(path[1], pt(5.0, 4.0));
}
#[test]
fn test_direction_then_even_with() {
let start = pt(0.0, 0.0);
let target = pt(10.0, 5.0);
let mut builder = PathBuilder::new(start);
builder.add_direction(Direction::Right, Inches::inches(2.0));
builder.mark_then();
builder.set_even_with(Direction::Up, target);
let path = builder.build();
assert_eq!(path.len(), 2);
assert_point_eq(path[0], pt(2.0, 0.0));
assert_point_eq(path[1], pt(2.0, 5.0));
}
#[test]
fn test_same_axis_movements_create_new_point() {
let mut builder = PathBuilder::new(pt(0.0, 0.0));
builder.add_direction(Direction::Right, Inches::inches(2.0));
builder.add_direction(Direction::Right, Inches::inches(3.0));
let path = builder.build();
assert_eq!(path.len(), 2);
assert_point_eq(path[0], pt(2.0, 0.0));
assert_point_eq(path[1], pt(5.0, 0.0));
}
#[test]
fn test_set_endpoint() {
let mut builder = PathBuilder::new(pt(0.0, 0.0));
builder.set_endpoint(pt(5.0, 5.0));
let path = builder.build();
assert_eq!(path.len(), 2);
assert_point_eq(path[0], pt(0.0, 0.0));
assert_point_eq(path[1], pt(5.0, 5.0));
}
#[test]
fn test_heading_movement() {
let mut builder = PathBuilder::new(pt(0.0, 0.0));
builder.add_heading(90.0, Inches::inches(1.0));
let path = builder.build();
assert_eq!(path.len(), 2);
assert_point_eq(path[0], pt(0.0, 0.0));
assert!((path[1].x.raw() - 1.0).abs() < 1e-10);
assert!(path[1].y.raw().abs() < 1e-10);
}
#[test]
fn test_complex_path() {
let mut builder = PathBuilder::new(pt(0.0, 0.0));
builder.add_direction(Direction::Right, Inches::inches(2.0));
builder.add_direction(Direction::Up, Inches::inches(1.0));
builder.mark_then();
builder.set_even_with(Direction::Left, pt(1.0, 5.0));
builder.mark_then();
builder.add_direction(Direction::Down, Inches::inches(0.5));
let path = builder.build();
assert_eq!(path.len(), 3);
assert_point_eq(path[0], pt(2.0, 1.0)); assert_point_eq(path[1], pt(1.0, 1.0)); assert_point_eq(path[2], pt(1.0, 0.5)); }
}