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}