1#![cfg_attr(not(test), no_std)]
24
25use core::cmp;
26use core::{char, f32};
27
28extern crate alloc;
29
30use alloc::collections::BTreeMap;
31use alloc::format;
32use alloc::string::String;
33use alloc::vec::Vec;
34
35#[allow(unused_imports)]
36use num_traits::real::Real;
37
38static PIXEL_MAP: [[u8; 2]; 4] = [[0x01, 0x08], [0x02, 0x10], [0x04, 0x20], [0x40, 0x80]];
39
40#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct Canvas {
43 chars: BTreeMap<(u16, u16), (u8, char, bool)>,
44 width: u16,
45 height: u16,
46}
47
48impl Canvas {
49 pub fn new(width: u32, height: u32) -> Canvas {
54 Canvas {
55 chars: BTreeMap::default(),
56 width: (width / 2) as u16,
57 height: (height / 4) as u16,
58 }
59 }
60
61 pub fn clear(&mut self) {
63 self.chars.clear();
64 }
65
66 pub fn set(&mut self, x: u32, y: u32) {
68 let (row, col) = ((x / 2) as u16, (y / 4) as u16);
69 let a = self.chars.entry((row, col)).or_insert((0, ' ', false));
70 a.0 |= PIXEL_MAP[y as usize % 4][x as usize % 2];
71 a.1 = ' ';
72 a.2 = false;
73 }
74
75 pub fn set_char(&mut self, x: u32, y: u32, c: char) {
77 let (row, col) = ((x / 2) as u16, (y / 4) as u16);
78 let a = self.chars.entry((row, col)).or_insert((0, ' ', false));
79 a.0 = 0;
80 a.1 = c;
81 a.2 = false;
82 }
83
84 pub fn text(&mut self, x: u32, y: u32, max_width: u32, text: &str) {
86 for (i, c) in text.chars().enumerate() {
87 let w = i as u32 * 2;
88 if w > max_width {
89 return;
90 }
91 self.set_char(x + w, y, c);
92 }
93 }
94
95 pub fn unset(&mut self, x: u32, y: u32) {
97 let (row, col) = ((x / 2) as u16, (y / 4) as u16);
98 let a = self.chars.entry((row, col)).or_insert((0, ' ', false));
99 a.0 &= !PIXEL_MAP[y as usize % 4][x as usize % 2];
100 }
101
102 pub fn toggle(&mut self, x: u32, y: u32) {
104 let (row, col) = ((x / 2) as u16, (y / 4) as u16);
105 let a = self.chars.entry((row, col)).or_insert((0, ' ', false));
106 a.0 ^= PIXEL_MAP[y as usize % 4][x as usize % 2];
107 }
108
109 pub fn get(&self, x: u32, y: u32) -> bool {
111 let (row, col) = ((x / 2) as u16, (y / 4) as u16);
112 self.chars.get(&(row, col)).map_or(false, |a| {
113 let dot_index = PIXEL_MAP[y as usize % 4][x as usize % 2];
114 a.0 & dot_index != 0
115 })
116 }
117
118 pub fn rows(&self) -> Vec<String> {
123 let mut maxrow = self.width;
124 let mut maxcol = self.height;
125 for &(x, y) in self.chars.keys() {
126 if x > maxrow {
127 maxrow = x;
128 }
129 if y > maxcol {
130 maxcol = y;
131 }
132 }
133
134 let mut result = Vec::with_capacity(maxcol as usize + 1);
135 for y in 0..=maxcol {
136 let mut row = String::with_capacity(maxrow as usize + 1);
137 for x in 0..=maxrow {
138 let cell = self.chars.get(&(x, y)).cloned().unwrap_or((0, ' ', false));
139 match cell {
140 (0, _, _) => row.push(cell.1),
141 (_, _, false) => row.push(char::from_u32(0x2800 + cell.0 as u32).unwrap()),
142 (_, _, true) => {
143 row = format!(
144 "{0}{1}",
145 row,
146 String::from(char::from_u32(0x2800 + cell.0 as u32).unwrap())
147 )
148 }
149 };
150 }
151 result.push(row);
152 }
153 result
154 }
155
156 pub fn frame(&self) -> String {
158 self.rows().join("\n")
159 }
160
161 pub fn line(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) {
163 let xdiff = cmp::max(x1, x2) - cmp::min(x1, x2);
164 let ydiff = cmp::max(y1, y2) - cmp::min(y1, y2);
165 let xdir = if x1 <= x2 { 1 } else { -1 };
166 let ydir = if y1 <= y2 { 1 } else { -1 };
167
168 let r = cmp::max(xdiff, ydiff);
169
170 for i in 0..=r {
171 let mut x = x1 as i32;
172 let mut y = y1 as i32;
173
174 if ydiff != 0 {
175 y += ((i * ydiff) / r) as i32 * ydir;
176 }
177 if xdiff != 0 {
178 x += ((i * xdiff) / r) as i32 * xdir;
179 }
180
181 self.set(x as u32, y as u32);
182 }
183 }
184}
185
186pub struct Turtle {
188 pub x: f32,
189 pub y: f32,
190 pub brush: bool,
191 pub rotation: f32,
192 pub cvs: Canvas,
193}
194
195impl Turtle {
196 pub fn new(x: f32, y: f32) -> Turtle {
200 Turtle {
201 cvs: Canvas::new(0, 0),
202 x,
203 y,
204 brush: true,
205 rotation: 0.0,
206 }
207 }
208
209 pub fn from_canvas(x: f32, y: f32, cvs: Canvas) -> Turtle {
213 Turtle {
214 cvs,
215 x,
216 y,
217 brush: true,
218 rotation: 0.0,
219 }
220 }
221
222 pub fn width(mut self, width: u32) -> Turtle {
224 self.cvs.width = width as u16;
225 self
226 }
227
228 pub fn height(mut self, height: u32) -> Turtle {
230 self.cvs.height = height as u16;
231 self
232 }
233
234 pub fn up(&mut self) {
236 self.brush = false;
237 }
238
239 pub fn down(&mut self) {
241 self.brush = true;
242 }
243
244 pub fn toggle(&mut self) {
246 self.brush = !self.brush;
247 }
248
249 pub fn forward(&mut self, dist: f32) {
251 let x = self.x + degrees_to_radians(self.rotation).cos() * dist;
252 let y = self.y + degrees_to_radians(self.rotation).sin() * dist;
253 self.teleport(x, y);
254 }
255
256 pub fn back(&mut self, dist: f32) {
258 self.forward(-dist);
259 }
260
261 pub fn teleport(&mut self, x: f32, y: f32) {
266 if self.brush {
267 self.cvs.line(
268 cmp::max(0, self.x.round() as i32) as u32,
269 cmp::max(0, self.y.round() as i32) as u32,
270 cmp::max(0, x.round() as i32) as u32,
271 cmp::max(0, y.round() as i32) as u32,
272 );
273 }
274 self.x = x;
275 self.y = y;
276 }
277
278 pub fn right(&mut self, angle: f32) {
280 self.rotation += angle;
281 }
282
283 pub fn left(&mut self, angle: f32) {
285 self.rotation -= angle;
286 }
287
288 pub fn frame(&self) -> String {
290 self.cvs.frame()
291 }
292}
293
294fn degrees_to_radians(deg: f32) -> f32 {
295 deg * (f32::consts::PI / 180.0f32)
296}