use terminal_graphics::Colour;
#[derive(Clone, Debug, PartialEq)]
pub struct PixelArt {
width: usize,
height: usize,
cells: Vec<Option<Colour>>,
}
impl PixelArt {
pub fn new(art: &str, legend: &[(char, Colour)]) -> PixelArt {
let art = art.strip_prefix('\n').unwrap_or(art);
let rows: Vec<&str> = art.lines().collect();
let width = rows
.iter()
.map(|row| row.chars().count())
.max()
.unwrap_or(0);
let height = rows.len();
let mut cells = vec![None; width * height];
for (y, row) in rows.iter().enumerate() {
for (x, ch) in row.chars().enumerate() {
cells[y * width + x] = legend
.iter()
.find(|(known, _)| *known == ch)
.map(|(_, colour)| *colour);
}
}
PixelArt {
width,
height,
cells,
}
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn flipped(&self) -> PixelArt {
let mut cells = Vec::with_capacity(self.cells.len());
for row in self.cells.chunks(self.width.max(1)).take(self.height) {
cells.extend(row.iter().rev());
}
PixelArt {
width: self.width,
height: self.height,
cells,
}
}
pub fn pixel(&self, x: usize, y: usize) -> Option<Colour> {
if x >= self.width || y >= self.height {
return None;
}
self.cells[y * self.width + x]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_character_is_looked_up_in_the_legend() {
let art = PixelArt::new("RB\nBR", &[('R', Colour::Red), ('B', Colour::Blue)]);
assert_eq!((art.width(), art.height()), (2, 2));
assert_eq!(art.pixel(0, 0), Some(Colour::Red));
assert_eq!(art.pixel(1, 0), Some(Colour::Blue));
assert_eq!(art.pixel(0, 1), Some(Colour::Blue));
}
#[test]
fn a_character_not_in_the_legend_is_transparent_and_a_leading_newline_is_dropped() {
let art = PixelArt::new("\nR .\n.R ", &[('R', Colour::Red)]);
assert_eq!((art.width(), art.height()), (3, 2));
assert_eq!(art.pixel(0, 0), Some(Colour::Red));
assert_eq!(art.pixel(1, 0), None, "a space is transparent");
assert_eq!(art.pixel(2, 0), None, "a dot is transparent");
assert_eq!(art.pixel(1, 1), Some(Colour::Red));
}
#[test]
fn short_rows_are_padded_with_transparent_pixels_to_the_widest_row() {
let art = PixelArt::new("RRR\nR", &[('R', Colour::Red)]);
assert_eq!((art.width(), art.height()), (3, 2));
assert_eq!(art.pixel(0, 1), Some(Colour::Red));
assert_eq!(art.pixel(2, 1), None);
}
#[test]
fn flipped_mirrors_the_picture_left_to_right() {
let art = PixelArt::new(
"RB\n.G",
&[
('R', Colour::Red),
('B', Colour::Blue),
('G', Colour::Green),
],
);
let mirrored = art.flipped();
assert_eq!(mirrored.pixel(0, 0), Some(Colour::Blue));
assert_eq!(mirrored.pixel(1, 0), Some(Colour::Red));
assert_eq!(mirrored.pixel(0, 1), Some(Colour::Green));
assert_eq!(mirrored.pixel(1, 1), None);
assert_eq!(
mirrored.flipped(),
art,
"flipping twice gives the picture back"
);
}
#[test]
fn a_pixel_outside_the_picture_is_none() {
let art = PixelArt::new("R", &[('R', Colour::Red)]);
assert_eq!(art.pixel(1, 0), None);
assert_eq!(art.pixel(0, 1), None);
assert_eq!(art.pixel(usize::MAX, usize::MAX), None);
}
}