unify Left and Right to turn and forward backward to move

This commit is contained in:
Franz Dietrich 2025-10-09 13:35:10 +02:00
parent 7da0dcf141
commit cebad0459c
5 changed files with 21 additions and 67 deletions

View File

@ -17,7 +17,7 @@ pub trait DirectionalMovement: WithCommands {
T: Into<Precision>, T: Into<Precision>,
{ {
let dist: Precision = distance.into(); let dist: Precision = distance.into();
self.get_commands_mut().push(TurtleCommand::Forward(dist)); self.get_commands_mut().push(TurtleCommand::Move(dist));
self self
} }
@ -26,7 +26,7 @@ pub trait DirectionalMovement: WithCommands {
T: Into<Precision>, T: Into<Precision>,
{ {
let dist: Precision = distance.into(); let dist: Precision = distance.into();
self.get_commands_mut().push(TurtleCommand::Backward(dist)); self.get_commands_mut().push(TurtleCommand::Move(-dist));
self self
} }
} }
@ -38,7 +38,7 @@ pub trait Turnable: WithCommands {
T: Into<Precision>, T: Into<Precision>,
{ {
let degrees: Precision = angle.into(); let degrees: Precision = angle.into();
self.get_commands_mut().push(TurtleCommand::Left(degrees)); self.get_commands_mut().push(TurtleCommand::Turn(-degrees));
self self
} }
@ -47,7 +47,7 @@ pub trait Turnable: WithCommands {
T: Into<Precision>, T: Into<Precision>,
{ {
let degrees: Precision = angle.into(); let degrees: Precision = angle.into();
self.get_commands_mut().push(TurtleCommand::Right(degrees)); self.get_commands_mut().push(TurtleCommand::Turn(degrees));
self self
} }
} }

View File

@ -6,13 +6,11 @@ use crate::shapes::TurtleShape;
/// Individual turtle commands /// Individual turtle commands
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum TurtleCommand { pub enum TurtleCommand {
// Movement // Movement (positive = forward, negative = backward)
Forward(Precision), Move(Precision),
Backward(Precision),
// Rotation // Rotation (positive = right/clockwise, negative = left/counter-clockwise in degrees)
Left(Precision), // degrees Turn(Precision),
Right(Precision), // degrees
// Circle drawing // Circle drawing
Circle { Circle {

View File

@ -187,10 +187,7 @@ pub(crate) fn render_world_with_tween(
fn should_draw_tween_line(command: &crate::commands::TurtleCommand) -> bool { fn should_draw_tween_line(command: &crate::commands::TurtleCommand) -> bool {
use crate::commands::TurtleCommand; use crate::commands::TurtleCommand;
matches!( matches!(command, TurtleCommand::Move(..) | TurtleCommand::Goto(..))
command,
TurtleCommand::Forward(..) | TurtleCommand::Backward(..) | TurtleCommand::Goto(..)
)
} }
/// Draw arc segments for circle tween animation /// Draw arc segments for circle tween animation

View File

@ -8,7 +8,7 @@ use macroquad::prelude::*;
/// Execute a single turtle command, updating state and adding draw commands /// Execute a single turtle command, updating state and adding draw commands
pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world: &mut TurtleWorld) { pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world: &mut TurtleWorld) {
match command { match command {
TurtleCommand::Forward(distance) => { TurtleCommand::Move(distance) => {
let start = state.position; let start = state.position;
let dx = distance * state.heading.cos(); let dx = distance * state.heading.cos();
let dy = distance * state.heading.sin(); let dy = distance * state.heading.sin();
@ -31,34 +31,7 @@ pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world:
} }
} }
TurtleCommand::Backward(distance) => { TurtleCommand::Turn(degrees) => {
let start = state.position;
let dx = -distance * state.heading.cos();
let dy = -distance * state.heading.sin();
state.position = vec2(state.position.x + dx, state.position.y + dy);
if state.pen_down {
world.add_command(DrawCommand::Line {
start,
end: state.position,
color: state.color,
width: state.pen_width,
});
// Add circle at end point for smooth line joins
world.add_command(DrawCommand::Circle {
center: state.position,
radius: state.pen_width / 2.0,
color: state.color,
filled: true,
});
}
}
TurtleCommand::Left(degrees) => {
state.heading -= degrees.to_radians();
}
TurtleCommand::Right(degrees) => {
state.heading += degrees.to_radians(); state.heading += degrees.to_radians();
} }
@ -175,7 +148,7 @@ pub fn add_draw_for_completed_tween(
world: &mut TurtleWorld, world: &mut TurtleWorld,
) { ) {
match command { match command {
TurtleCommand::Forward(_) | TurtleCommand::Backward(_) | TurtleCommand::Goto(_) => { TurtleCommand::Move(_) | TurtleCommand::Goto(_) => {
if start_state.pen_down { if start_state.pen_down {
world.add_command(DrawCommand::Line { world.add_command(DrawCommand::Line {
start: start_state.position, start: start_state.position,
@ -281,7 +254,7 @@ mod tests {
assert_eq!(state.heading, 0.0); assert_eq!(state.heading, 0.0);
// Forward 100 - should move to (100, 0) // Forward 100 - should move to (100, 0)
execute_command(&TurtleCommand::Forward(100.0), &mut state, &mut world); execute_command(&TurtleCommand::Move(100.0), &mut state, &mut world);
assert!( assert!(
(state.position.x - 100.0).abs() < 0.01, (state.position.x - 100.0).abs() < 0.01,
"After forward(100): x = {}", "After forward(100): x = {}",
@ -296,7 +269,7 @@ mod tests {
// Left 90 degrees - should face north (heading decreases by 90°) // Left 90 degrees - should face north (heading decreases by 90°)
// In screen coords: north = -90° = -π/2 // In screen coords: north = -90° = -π/2
execute_command(&TurtleCommand::Left(90.0), &mut state, &mut world); execute_command(&TurtleCommand::Turn(-90.0), &mut state, &mut world);
assert!( assert!(
(state.position.x - 100.0).abs() < 0.01, (state.position.x - 100.0).abs() < 0.01,
"After left(90): x = {}", "After left(90): x = {}",
@ -316,7 +289,7 @@ mod tests {
); );
// Forward 50 - should move north (negative Y) to (100, -50) // Forward 50 - should move north (negative Y) to (100, -50)
execute_command(&TurtleCommand::Forward(50.0), &mut state, &mut world); execute_command(&TurtleCommand::Move(50.0), &mut state, &mut world);
assert!( assert!(
(state.position.x - 100.0).abs() < 0.01, (state.position.x - 100.0).abs() < 0.01,
"Final position: x = {} (expected 100.0)", "Final position: x = {} (expected 100.0)",

View File

@ -117,10 +117,7 @@ impl TweenController {
tween.start_state.heading + angle.to_radians() * progress tween.start_state.heading + angle.to_radians() * progress
} }
}, },
TurtleCommand::Left(angle) => { TurtleCommand::Turn(angle) => {
tween.start_state.heading - angle.to_radians() * progress
}
TurtleCommand::Right(angle) => {
tween.start_state.heading + angle.to_radians() * progress tween.start_state.heading + angle.to_radians() * progress
} }
TurtleCommand::SetHeading(_) | _ => { TurtleCommand::SetHeading(_) | _ => {
@ -218,10 +215,7 @@ impl TweenController {
fn command_creates_drawing(command: &TurtleCommand) -> bool { fn command_creates_drawing(command: &TurtleCommand) -> bool {
matches!( matches!(
command, command,
TurtleCommand::Forward(_) TurtleCommand::Move(_) | TurtleCommand::Circle { .. } | TurtleCommand::Goto(_)
| TurtleCommand::Backward(_)
| TurtleCommand::Circle { .. }
| TurtleCommand::Goto(_)
) )
} }
@ -229,8 +223,8 @@ impl TweenController {
let speed = speed.max(1) as f32; let speed = speed.max(1) as f32;
let base_time = match command { let base_time = match command {
TurtleCommand::Forward(dist) | TurtleCommand::Backward(dist) => dist.abs() / speed, TurtleCommand::Move(dist) => dist.abs() / speed,
TurtleCommand::Left(angle) | TurtleCommand::Right(angle) => { TurtleCommand::Turn(angle) => {
// Rotation speed: assume 180 degrees per second at speed 100 // Rotation speed: assume 180 degrees per second at speed 100
angle.abs() / (speed * 1.8) angle.abs() / (speed * 1.8)
} }
@ -255,20 +249,12 @@ impl TweenController {
let mut target = current.clone(); let mut target = current.clone();
match command { match command {
TurtleCommand::Forward(dist) => { TurtleCommand::Move(dist) => {
let dx = dist * current.heading.cos(); let dx = dist * current.heading.cos();
let dy = dist * current.heading.sin(); let dy = dist * current.heading.sin();
target.position = vec2(current.position.x + dx, current.position.y + dy); target.position = vec2(current.position.x + dx, current.position.y + dy);
} }
TurtleCommand::Backward(dist) => { TurtleCommand::Turn(angle) => {
let dx = -dist * current.heading.cos();
let dy = -dist * current.heading.sin();
target.position = vec2(current.position.x + dx, current.position.y + dy);
}
TurtleCommand::Left(angle) => {
target.heading -= angle.to_radians();
}
TurtleCommand::Right(angle) => {
target.heading += angle.to_radians(); target.heading += angle.to_radians();
} }
TurtleCommand::Circle { TurtleCommand::Circle {