use std::{ collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, mem::transmute_copy, }; use termion::color; use super::grid::{Grid, Tile}; pub struct TileDisplayer { color_seed: u16, } impl TileDisplayer { pub fn new(color_seed: u16) -> Self { Self { color_seed } } const TILE_LENGTH: usize = 7; const TILE_HEIGHT: usize = 3; pub fn display(&self, tile: &Tile) -> String { match tile.value() { Some(value) => { Self::color_representation(Self::display_number(value), value, self.color_seed) } None => [ // empty tile " ", " ", " ", ] .join("\n"), } } fn display_number(value: usize) -> String { [ // number tile "┌─ ─┐", &Self::pad_both(value.to_string(), Self::TILE_LENGTH), "└─ ─┘", ] .join("\n") } fn pad_both(text: String, length: usize) -> String { let mut text = text; while text.len() < length { text = format!(" {text} "); } if text.len() > length { (&text)[..length].to_string() } else { text } } fn color_representation(text: String, value: usize, color_seed: u16) -> String { let color = Self::hashed_color(value, color_seed); let color_code = color::Bg(color); let reset_code = color::Bg(color::Reset); let text = text .split('\n') .map(|line| format!("{color_code}{line}{reset_code}")) .collect::>() .join("\n"); text } fn hashed_color(value: usize, color_seed: u16) -> color::Rgb { let mut hasher = DefaultHasher::new(); (value + color_seed as usize).hash(&mut hasher); let hash = hasher.finish(); // SAFETY: // there are no logic that relies on the value of the outputted numbers, thus it is safe to create them without constructors let [frac_a, frac_b]: [f64; 2] = unsafe { transmute_copy::<_, [u32; 2]>(&hash) } .into_iter() .map(|frac| (frac as f64) / (u32::MAX as f64)) .collect::>() .try_into() .expect("iterator is constructed from array of two values"); let mut remaining = 255f64; let r = Self::take_fraction(&mut remaining, frac_a, 150.) as u8; let g = Self::take_fraction(&mut remaining, frac_b, 150.) as u8; let b = remaining as u8; color::Rgb(r, g, b) } fn take_fraction(remainder: &mut f64, frac: f64, max: f64) -> f64 { let result = (*remainder * frac).min(max); *remainder -= result; result } } pub struct GridDisplayer { tile_displayer: TileDisplayer, } impl GridDisplayer { pub fn new(color_seed: u16) -> Self { let tile_displayer = TileDisplayer::new(color_seed); Self { tile_displayer } } /// (0: '┘'), (1: '┐'), (2: '┌'), (3: '└'), (4: '┼'), (5: '─'), (6: '├'), (7: '┤'), (8: '┴'), (9: '┬'), (10: '│') const DISPLAY_CHAR: [&'static str; 11] = ["┘", "┐", "┌", "└", "┼", "─", "├", "┤", "┴", "┬", "│"]; /// /// returns a string of multiple lines representing the grid /// pub fn display(&self, grid: &Grid) -> String { let tiles: Vec> = grid .tiles() .iter() .map(|row| { row.iter() .map(|tile| self.tile_displayer.display(tile)) .collect() }) .collect(); let row_representations: Vec<_> = tiles .iter() .map(|row_representation| { let mut row_lines = (0..TileDisplayer::TILE_HEIGHT) .map(|_| vec![]) .collect::>(); // push every item lines in [`row_lines`] for item_representation in row_representation { item_representation .split('\n') .into_iter() .zip(row_lines.iter_mut()) .for_each(|(item_line, row_line)| row_line.push(item_line.to_string())); } // join lines of [`row_lines`] let row_lines = row_lines .iter_mut() .map(|line_parts| line_parts.join(Self::DISPLAY_CHAR[10])) .map(|line| [Self::DISPLAY_CHAR[10], &line, Self::DISPLAY_CHAR[10]].join("")) .collect::>(); row_lines.join("\n") }) .collect(); [ self.first_grid_display_line(grid), row_representations.join(&self.between_grid_display_line(grid)), self.last_grid_display_line(grid), ] .join("\n") } fn first_grid_display_line(&self, grid: &Grid) -> String { let middle = (0..grid.size()) .map(|_| Self::DISPLAY_CHAR[5].repeat(TileDisplayer::TILE_LENGTH)) .collect::>() .join(Self::DISPLAY_CHAR[9]); [Self::DISPLAY_CHAR[2], &middle, Self::DISPLAY_CHAR[1]].join("") } fn between_grid_display_line(&self, grid: &Grid) -> String { let middle = (0..grid.size()) .map(|_| Self::DISPLAY_CHAR[5].repeat(TileDisplayer::TILE_LENGTH)) .collect::>() .join(Self::DISPLAY_CHAR[4]); [ "\n", Self::DISPLAY_CHAR[6], &middle, Self::DISPLAY_CHAR[7], "\n", ] .join("") } fn last_grid_display_line(&self, grid: &Grid) -> String { let middle = (0..grid.size()) .map(|_| Self::DISPLAY_CHAR[5].repeat(TileDisplayer::TILE_LENGTH)) .collect::>() .join(Self::DISPLAY_CHAR[8]); [Self::DISPLAY_CHAR[3], &middle, Self::DISPLAY_CHAR[0], "\n"].join("") } }