1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
/// Represents a rectangular collection of chars to render as sprites or
/// screens.
pub struct Image {
    /// The width of the image in chars.
    pub width: u32,

    /// The height of the image in chars.
    pub height: u32,

    /// The foreground color of each char in the image.
    pub fore_image: Vec<u32>,

    /// The background color of each char in the image.
    pub back_image: Vec<u32>,

    /// The char to render at each position in the image.
    pub text_image: Vec<u32>,
}

/// A point in 2D space.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Point {
    pub x: i32,
    pub y: i32,
}

impl Point {
    pub fn new(x: i32, y: i32) -> Self {
        Self { x, y }
    }
}

/// A rectangle in 2D space.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rect {
    pub x: i32,
    pub y: i32,
    pub width: u32,
    pub height: u32,
}

impl Rect {
    /// Creates a new rectangle with the given position and dimensions.
    pub fn new(x: i32, y: i32, width: u32, height: u32) -> Self {
        Self {
            x,
            y,
            width,
            height,
        }
    }

    /// Creates a new rectangle from two points.
    ///
    /// The points do not need to be in any particular order. The rectangle will
    /// be the smallest rectangle that contains both points. If the points are
    /// the same, the rectangle will have zero width and height. If the points
    /// are on the same vertical or horizontal line, the rectangle will have
    /// zero width or height respectively.
    ///
    /// # Arguments
    ///
    /// * `p1` - The first point.
    /// * `p2` - The second point.
    ///
    /// # Returns
    ///
    /// A new rectangle that contains both points.
    ///
    pub fn from_points(p1: Point, p2: Point) -> Self {
        let x = p1.x.min(p2.x);
        let y = p1.y.min(p2.y);
        let width = (p1.x - p2.x).unsigned_abs();
        let height = (p1.y - p2.y).unsigned_abs();
        Self {
            x,
            y,
            width,
            height,
        }
    }

    /// Creates a new rectangle from a point and size.
    ///
    /// The point is the top-left corner of the rectangle.
    ///
    /// # Arguments
    ///
    /// * `p` - The top-left corner of the rectangle.
    /// * `width` - The width of the rectangle.
    /// * `height` - The height of the rectangle.
    ///
    /// # Returns
    ///
    /// A new rectangle with the given top-left corner and dimensions.
    ///
    pub fn from_point_and_size(p: Point, width: u32, height: u32) -> Self {
        Self {
            x: p.x,
            y: p.y,
            width,
            height,
        }
    }

    /// Returns the union of this rectangle and another rectangle.
    ///
    /// The union of two rectangles is the smallest rectangle that contains both
    /// rectangles.
    ///
    /// # Arguments
    ///
    /// * `other` - The other rectangle to union with.
    ///
    /// # Returns
    ///
    /// A new rectangle that contains both rectangles.
    ///
    pub fn union(&self, other: Self) -> Self {
        let x = self.x.min(other.x);
        let y = self.y.min(other.y);
        let width = (self.x + self.width as i32).max(other.x + other.width as i32) - x;
        let height = (self.y + self.height as i32).max(other.y + other.height as i32) - y;
        Self {
            x,
            y,
            width: width as u32,
            height: height as u32,
        }
    }

    /// Returns the intersection of this rectangle and another rectangle.
    ///
    /// The intersection of two rectangles is the largest rectangle that is
    /// contained within both rectangles.
    ///
    /// # Arguments
    ///
    /// * `other` - The other rectangle to intersect with.
    ///
    /// # Returns
    ///
    /// A new rectangle that is contained within both rectangles.
    ///
    pub fn intersect(&self, other: Self) -> Self {
        let x = self.x.max(other.x);
        let y = self.y.max(other.y);
        let width = (self.x + self.width as i32).min(other.x + other.width as i32) - x;
        let height = (self.y + self.height as i32).min(other.y + other.height as i32) - y;
        Self {
            x,
            y,
            width: width as u32,
            height: height as u32,
        }
    }

    /// Creates a new rectangle by clipping this rectangle to the given
    /// dimensions.
    ///
    /// The new rectangle will be the largest rectangle that fits within the
    /// given dimensions and is contained within this rectangle.
    ///
    /// The point returned is the offset of the top-left corner of the new
    /// rectangle within the given original rectangle.  This is useful for
    /// rendering the new rectangle within the original rectangle.
    ///
    /// # Arguments
    ///
    /// * `width` - The width of the rectangle to clip within.
    /// * `height` - The height of the rectangle to clip within.
    ///
    /// # Returns
    ///
    /// A new rectangle that fits within the given dimensions and is contained
    /// within this rectangle.  Also returns the offset of the top-left corner
    /// of the new rectangle within the original rectangle.
    ///
    pub fn clip_within(&self, width: u32, height: u32) -> (Self, Point) {
        (
            self.intersect(Rect::new(0, 0, width, height)),
            Point::new(-(self.x.min(0)), -(self.y.min(0))),
        )
    }
}

/// A single character to render with colour information.
#[derive(Clone, Copy, Debug)]
pub struct Char {
    pub ch: u32,
    pub ink: u32,
    pub paper: u32,
}

impl Char {
    /// Creates a new 8-bit character with the given char, ink and paper
    /// colours. The char is converted to a u32.
    ///
    /// # Arguments
    ///
    /// * `ch` - The char to render.  This is an 8-bit value.
    /// * `ink` - The foreground colour of the char.
    /// * `paper` - The background colour of the char.
    ///
    /// # Returns
    ///
    /// A new character with the given char, ink and paper colours.
    ///
    pub fn new(ch: u8, ink: u32, paper: u32) -> Self {
        Self {
            ch: ch as u32,
            ink,
            paper,
        }
    }

    /// Creates a new 32-bit character with the given char, ink and paper
    /// colours.
    ///
    /// # Arguments
    ///
    /// * `ch` - The char to render.  This is a 32-bit value.
    /// * `ink` - The foreground colour of the char.
    /// * `paper` - The background colour of the char.
    ///
    /// # Returns
    ///
    /// A new character with the given char, ink and paper colours.
    ///
    pub fn new_u32(ch: u32, ink: u32, paper: u32) -> Self {
        Self { ch, ink, paper }
    }

    /// Creates a new character with the given char, ink and paper colours. The
    /// char is converted to a u8.
    ///
    /// # Arguments
    ///
    /// * `ch` - The char to render.  This is a normal Rust char converted to an
    ///   8-bit value.
    /// * `ink` - The foreground colour of the char.
    /// * `paper` - The background colour of the char.
    ///
    pub fn new_char(ch: char, ink: u32, paper: u32) -> Self {
        let char_byte = ch as u8;
        Self::new(char_byte, ink, paper)
    }
}

impl Image {
    /// Creates a new image with the given dimensions.
    ///
    /// # Arguments
    ///
    /// * `width` - The width of the image in chars.
    /// * `height` - The height of the image in chars.
    ///
    /// # Returns
    ///
    /// A new image with the given dimensions.  The image is filled with
    /// character zero.
    ///
    pub fn new(width: u32, height: u32) -> Self {
        let size = (width * height) as usize;
        Self {
            width,
            height,
            fore_image: vec![0; size],
            back_image: vec![0; size],
            text_image: vec![0; size],
        }
    }

    /// Returns the index of the char at the given coordinates.
    ///
    /// # Arguments
    ///
    /// * `p` - The coordinates of the char to get the index of.
    ///
    /// # Returns
    ///
    /// The index of the char at the given coordinates, or `None` if the
    /// coordinates are out of bounds.
    ///
    pub fn point_to_index(&self, p: Point) -> Option<usize> {
        self.coords_to_index(p.x, p.y)
    }

    /// Returns the index of the char at the given coordinates.
    ///
    /// # Arguments
    ///
    /// * `x` - The x coordinate of the char to get the index of.
    /// * `y` - The y coordinate of the char to get the index of.
    ///
    /// # Returns
    ///
    /// The index of the char at the given coordinates, or `None` if the
    /// coordinates are out of bounds.
    ///
    pub fn coords_to_index(&self, x: i32, y: i32) -> Option<usize> {
        if x < 0 || y < 0 {
            None
        } else {
            let x = x as u32;
            let y = y as u32;
            if x < self.width && y < self.height {
                Some((y * self.width + x) as usize)
            } else {
                None
            }
        }
    }

    /// Clears the image with a given ink and paper colour.
    ///
    /// # Arguments
    ///
    /// * `ink` - The foreground colour to clear the image with.
    /// * `paper` - The background colour to clear the image with.
    ///
    pub fn clear(&mut self, ink: u32, paper: u32) {
        self.fore_image
            .iter_mut()
            .for_each(|fore_image| *fore_image = ink);
        self.back_image
            .iter_mut()
            .for_each(|back_image| *back_image = paper);
        self.text_image
            .iter_mut()
            .for_each(|text_image| *text_image = 0);
    }

    /// Draws a character at the given coordinates.
    ///
    /// # Arguments
    ///
    /// * `p` - The coordinates to draw the character at.
    /// * `ch` - The character to draw.
    ///
    /// # Notes
    ///
    /// If the coordinates are out of bounds, the character is not drawn.
    ///
    pub fn draw_char(&mut self, p: Point, ch: Char) {
        if let Some(index) = self.point_to_index(p) {
            self.fore_image[index] = ch.ink;
            self.back_image[index] = ch.paper;
            self.text_image[index] = ch.ch;
        }
    }

    /// Draws a string at the given coordinates.
    ///
    /// # Arguments
    ///
    /// * `p` - The coordinates to draw the string at.
    /// * `text` - The string to draw.
    /// * `ink` - The foreground colour of the string.
    /// * `paper` - The background colour of the string.
    ///
    /// # Notes
    ///
    /// If the coordinates are out of bounds, the string is clipped.
    ///
    pub fn draw_string(&mut self, p: Point, text: &str, ink: u32, paper: u32) {
        let (text_rect, str_offset) =
            Rect::from_point_and_size(p, text.len() as u32, 1).clip_within(self.width, self.height);
        if str_offset.y == 0 {
            let str_slice =
                &text[str_offset.x as usize..(str_offset.x + text_rect.width as i32) as usize];

            if let Some(i) = self.coords_to_index(text_rect.x, text_rect.y) {
                let w = text_rect.width as usize;
                self.fore_image[i..i + w].iter_mut().for_each(|x| *x = ink);
                self.back_image[i..i + w]
                    .iter_mut()
                    .for_each(|x| *x = paper);
                self.text_image[i..i + w]
                    .iter_mut()
                    .zip(str_slice.bytes())
                    .for_each(|(x, y)| *x = y as u32);
            }
        }
    }

    /// Draws a rectangle at the given coordinates and dimensions using the
    /// given character.
    ///
    /// # Arguments
    ///
    /// * `p` - The coordinates to draw the rectangle at.
    /// * `width` - The width of the rectangle.
    /// * `height` - The height of the rectangle.
    /// * `ch` - The character to draw the rectangle with.
    ///
    /// # Notes
    ///
    /// If the coordinates are out of bounds, the rectangle is clipped.
    ///
    pub fn draw_filled_rect(&mut self, rect: Rect, ch: Char) {
        let (rect, _) = rect.clip_within(self.width, self.height);

        if let Some(mut i) = self.coords_to_index(rect.x, rect.y) {
            let w = rect.width as usize;
            let h = rect.height;
            (0..h).for_each(|_| {
                self.fore_image[i..i + w]
                    .iter_mut()
                    .for_each(|x| *x = ch.ink);
                self.back_image[i..i + w]
                    .iter_mut()
                    .for_each(|x| *x = ch.paper);
                self.text_image[i..i + w]
                    .iter_mut()
                    .for_each(|x| *x = ch.ch);

                i += self.width as usize;
            });
        }
    }

    /// Returns a rectangle representing the bounds of the image.
    ///
    /// # Returns
    ///
    /// A rectangle representing the bounds of the image.
    ///
    /// # Notes
    ///
    /// The rectangle returned is always at the origin.
    ///
    /// The width and height of the rectangle are the same as the width and
    /// height of the image.
    ///
    pub fn rect(&self) -> Rect {
        Rect::from_point_and_size(Point::new(0, 0), self.width, self.height)
    }
}