logo_runtime/
drawinglib.rs

1use logo_interp::core::LogoValue;
2use logo_interp::executor_state::*;
3use crate::colors::{LogoColor, colors_count, get_color};
4use crate::common::Pos;
5use crate::state::{Delegate, PenState, State};
6
7pub fn add_drawinglib<D: Delegate + 'static>(es: &mut EState<State<D>>) {
8    es.functions.insert("show".to_string(), Function::from_proc1(show));
9
10    es.functions.insert("cg".to_string(), Function::from_proc(cg));
11    es.functions.insert("clean".to_string(), Function::from_proc(clean));
12    es.functions.insert("fill".to_string(), Function::from_proc(fill));
13
14    es.functions.insert("pu".to_string(), Function::from_proc(pu));
15    es.functions.insert("pd".to_string(), Function::from_proc(pd));
16    es.functions.insert("pe".to_string(), Function::from_proc(pe));
17
18    es.functions.insert("rt".to_string(), Function::from_proc1(rt));
19    es.functions.insert("right".to_string(), Function::from_proc1(rt));
20    es.functions.insert("lt".to_string(), Function::from_proc1(lt));
21    es.functions.insert("left".to_string(), Function::from_proc1(lt));
22    es.functions.insert("fd".to_string(), Function::from_proc1(fd));
23    es.functions.insert("bk".to_string(), Function::from_proc1(bk));
24
25    es.functions.insert("heading".to_string(), Function::from_fn(heading));
26    es.functions.insert("seth".to_string(), Function::from_proc1(seth));
27    es.functions.insert("setheading".to_string(), Function::from_proc1(seth));
28    es.functions.insert("setpos".to_string(), Function::from_proc1(setpos));
29    es.functions.insert("setx".to_string(), Function::from_proc1(setx));
30    es.functions.insert("sety".to_string(), Function::from_proc1(sety));
31    es.functions.insert("pos".to_string(), Function::from_fn(pos));
32    es.functions.insert("xcoor".to_string(), Function::from_fn(xcoor));
33    es.functions.insert("ycoor".to_string(), Function::from_fn(ycoor));
34    es.functions.insert("home".to_string(), Function::from_proc(home));
35
36    es.functions.insert("pensize".to_string(), Function::from_fn(pensize));
37    es.functions.insert("setpensize".to_string(), Function::from_proc1(setpensize));
38
39    es.functions.insert("ht".to_string(), Function::from_proc(ht));
40    es.functions.insert("st".to_string(), Function::from_proc(st));
41
42    es.functions.insert("setc".to_string(), Function::from_proc1(setc));
43    es.functions.insert("setcolor".to_string(), Function::from_proc1(setc));
44    es.functions.insert("color".to_string(), Function::from_fn(color));
45}
46
47fn show<D: Delegate>(state: &mut EState<State<D>>, val: LogoValue) -> Result<(), String> {
48    state.state.delegate.show(format!("{}", val).as_str());
49    Ok(())
50}
51
52fn cg<D: Delegate>(state: &mut EState<State<D>>) -> Result<(), String> {
53    let state = &mut state.state;
54    state.data.turtle_pos = Pos{x: 0f64, y: 0f64};
55    state.data.turtle_angle = 0f64;
56    state.delegate.clear_graphics();
57    Ok(())
58}
59
60fn clean<D: Delegate>(state: &mut EState<State<D>>) -> Result<(), String> {
61    state.state.delegate.clear_graphics();
62    Ok(())
63}
64
65fn fill<D: Delegate>(state: &mut EState<State<D>>) -> Result<(), String> {
66    let state = &mut state.state;
67    state.delegate.fill(state.data.turtle_pos, get_color(state.data.color_idx));
68    Ok(())
69}
70
71fn pu<D: Delegate>(state: &mut EState<State<D>>) -> Result<(), String> {
72    state.state.data.pen_state = PenState::Up;
73    Ok(())
74}
75
76fn pd<D: Delegate>(state: &mut EState<State<D>>) -> Result<(), String> {
77    state.state.data.pen_state = PenState::Down;
78    Ok(())
79}
80
81fn pe<D: Delegate>(state: &mut EState<State<D>>) -> Result<(), String> {
82    state.state.data.pen_state = PenState::Erase;
83    Ok(())
84}
85
86fn rt<D: Delegate>(state: &mut EState<State<D>>, val: f64) -> Result<(), String> {
87    state.state.data.turtle_angle += val;
88    Ok(())
89}
90
91fn lt<D: Delegate>(state: &mut EState<State<D>>, val: f64) -> Result<(), String> {
92    state.state.data.turtle_angle -= val;
93    Ok(())
94}
95
96fn fd<D: Delegate>(state: &mut EState<State<D>>, val: f64) -> Result<(), String> {
97    let old_pos = state.state.data.turtle_pos;
98    let angle = state.state.data.turtle_angle;
99    let delta_x = angle.to_radians().sin() * val;
100    let delta_y = angle.to_radians().cos() * val;
101    let new_pos = Pos{x: old_pos.x + delta_x, y: old_pos.y + delta_y};
102    move_turtle(&mut state.state, new_pos);
103    Ok(())
104}
105
106fn bk<D: Delegate>(state: &mut EState<State<D>>, val: f64) -> Result<(), String> {
107    fd(state, -val)
108}
109
110fn heading<D: Delegate>(state: &mut EState<State<D>>) -> Result<f64, String> {
111    Ok(state.state.data.turtle_angle)
112}
113
114fn seth<D: Delegate>(state: &mut EState<State<D>>, h: f64) -> Result<(), String> {
115    state.state.data.turtle_angle = h;
116    Ok(())
117}
118
119fn pos<D: Delegate>(state: &mut EState<State<D>>) -> Result<Vec<f64>, String> {
120    Ok(vec![state.state.data.turtle_pos.x, state.state.data.turtle_pos.y])
121}
122
123fn setpos<D: Delegate>(state: &mut EState<State<D>>, pos: Vec<f64>) -> Result<(), String> {
124    if pos.len() != 2 {
125        Err("Setpos takes exactly 2 coordinates".to_string())
126    }
127    else {
128        move_turtle(&mut state.state, Pos{ x: pos[0], y: pos[1] });
129        Ok(())
130    }
131}
132
133fn xcoor<D: Delegate>(state: &mut EState<State<D>>) -> Result<f64, String> {
134    Ok(state.state.data.turtle_pos.x)
135}
136
137fn ycoor<D: Delegate>(state: &mut EState<State<D>>) -> Result<f64, String> {
138    Ok(state.state.data.turtle_pos.y)
139}
140
141fn setx<D: Delegate>(state: &mut EState<State<D>>, x: f64) -> Result<(), String> {
142    let y = state.state.data.turtle_pos.y;
143    move_turtle(&mut state.state, Pos{x, y});
144    Ok(())
145}
146
147fn sety<D: Delegate>(state: &mut EState<State<D>>, y: f64) -> Result<(), String> {
148    let x = state.state.data.turtle_pos.x;
149    move_turtle(&mut state.state, Pos{x, y});
150    Ok(())
151}
152
153fn home<D: Delegate>(state: &mut EState<State<D>>) -> Result<(), String> {
154    move_turtle(&mut state.state, Pos{ x: 0f64, y: 0f64 });
155    state.state.data.turtle_angle = 0.0;
156    Ok(())
157}
158
159fn setpensize<D: Delegate>(state: &mut EState<State<D>>, pen_size: f64) -> Result<(), String> {
160    state.state.data.pen_size = pen_size;
161    Ok(())
162}
163
164fn pensize<D: Delegate>(state: &mut EState<State<D>>) -> Result<f64, String> {
165    Ok(state.state.data.pen_size)
166}
167
168fn ht<D: Delegate>(state: &mut EState<State<D>>) -> Result<(), String> {
169    state.state.data.turtle_visible = false;
170    Ok(())
171}
172
173fn st<D: Delegate>(state: &mut EState<State<D>>) -> Result<(), String> {
174    state.state.data.turtle_visible = true;
175    Ok(())
176}
177
178fn setc<D: Delegate>(state: &mut EState<State<D>>, color: i32) -> Result<(), String> {
179    if color < 0 || color >= colors_count() {
180        return Err(format!("Invalid color number {}", color));
181    }
182    state.state.data.color_idx = color;
183    Ok(())
184}
185
186fn color<D: Delegate>(state: &mut EState<State<D>>) -> Result<i32, String> {
187    Ok(state.state.data.color_idx)
188}
189
190fn move_turtle<D: Delegate>(state: &mut State<D>, pos: Pos) {
191    let old_pos = state.data.turtle_pos;
192    let w2 = state.data.canvas_width as f64 / 2f64;
193    let h2 = state.data.canvas_height as f64 / 2f64;
194    if pos.y > old_pos.y + f64::EPSILON {
195        let xp = intersect_horizontal(old_pos, pos, h2, -w2, w2);
196        if xp.is_some() {
197            draw_line(state, old_pos, Pos{x: xp.unwrap(), y: h2});
198            state.data.turtle_pos = Pos{x: xp.unwrap(), y: -h2};
199            move_turtle(state, Pos{x: pos.x, y: pos.y - state.data.canvas_height as f64});
200            return;
201        }
202    }
203    if pos.y + f64::EPSILON < old_pos.y {
204        let xp = intersect_horizontal(old_pos, pos, -h2, -w2, w2);
205        if xp.is_some() {
206            draw_line(state, old_pos, Pos{x: xp.unwrap(), y: -h2});
207            state.data.turtle_pos = Pos{x: xp.unwrap(), y: h2};
208            move_turtle(state, Pos{x: pos.x, y: pos.y + state.data.canvas_height as f64});
209            return;
210        }
211    }
212    if pos.x > old_pos.x + f64::EPSILON {
213        let yp = intersect_vertical(old_pos, pos, w2, -h2, h2);
214        if yp.is_some() {
215            draw_line(state, old_pos, Pos{x: w2, y: yp.unwrap()});
216            state.data.turtle_pos = Pos{x: -w2, y: yp.unwrap()};
217            move_turtle(state, Pos{x: pos.x - state.data.canvas_width as f64, y: pos.y});
218            return;
219        }
220    }
221    if pos.x + f64::EPSILON < old_pos.x {
222        let yp = intersect_vertical(old_pos, pos, -w2, -h2, h2);
223        if yp.is_some() {
224            draw_line(state, old_pos, Pos{x: -w2, y: yp.unwrap()});
225            state.data.turtle_pos = Pos{x: w2, y: yp.unwrap()};
226            move_turtle(state, Pos{x: pos.x + state.data.canvas_width as f64, y: pos.y});
227            return;
228        }
229    }
230    state.data.turtle_pos = pos;
231    draw_line(state, old_pos, pos);
232}
233
234fn draw_line<D: Delegate>(state: &mut State<D>, p1: Pos, p2: Pos) {
235    let mut color = get_color(state.data.color_idx);
236    if state.data.pen_state == PenState::Erase {
237        color = LogoColor{r: 255, g: 255, b: 255};
238    }
239    if state.data.pen_state != PenState::Up {
240        state.delegate.draw_line(p1, p2, state.data.pen_size, color);
241    }
242}
243
244fn intersect_horizontal(p1: Pos, p2: Pos, y: f64, x1: f64, x2: f64) -> Option<f64> {
245    if p1.y.min(p2.y) > y || p1.y.max(p2.y) < y {
246        return None;
247    }
248    let xp = p1.x - (p1.y - y) / (p1.y - p2.y) * (p1.x - p2.x);
249    if xp >= x1 && xp <= x2 {
250        Some(xp)
251    }
252    else {
253        None
254    }
255}
256
257fn intersect_vertical(p1: Pos, p2: Pos, x: f64, y1: f64, y2: f64) -> Option<f64> {
258    if p1.x.min(p2.x) > x || p1.x.max(p2.x) < x {
259        return None;
260    }
261    let yp = p1.y - (p1.x - x) / (p1.x - p2.x) * (p1.y - p2.y);
262    if yp >= y1 && yp <= y2 {
263        Some(yp)
264    }
265    else {
266        None
267    }
268}
269
270#[test]
271fn test_move_turtle() {
272    use crate::state::NoOpDelegate;
273    use approx::assert_relative_eq;
274
275    let mut state = EState::new(State::new(800, 450, NoOpDelegate{}));
276    fd(&mut state, 50.0).unwrap();
277    assert_relative_eq!(state.state.data.turtle_pos.y, 50.0, epsilon = 0.00001);
278    fd(&mut state, 400.0).unwrap();
279    assert_relative_eq!(state.state.data.turtle_pos.y, 0.0, epsilon = 0.00001);
280    rt(&mut state, 90.0).unwrap();
281    fd(&mut state, 500.0).unwrap();
282    assert_relative_eq!(state.state.data.turtle_pos.x, -300.0, epsilon = 0.00001);
283}