use std::fmt;
use crate::renderer::backend::RenderBackend;
#[derive(Clone, Copy, Default)]
pub struct LineFlags {
pub up: bool,
pub down: bool,
pub left: bool,
pub right: bool,
}
pub struct Grid {
cells: Vec<Vec<char>>,
protected: Vec<Vec<bool>>,
line_flags: Vec<Vec<LineFlags>>,
pub width: usize,
pub height: usize,
}
impl Grid {
pub fn new(width: usize, height: usize) -> Self {
Self {
cells: vec![vec![' '; width]; height],
protected: vec![vec![false; width]; height],
line_flags: vec![vec![LineFlags::default(); width]; height],
width,
height,
}
}
pub fn set(&mut self, x: usize, y: usize, c: char) {
if x < self.width && y < self.height {
self.cells[y][x] = c;
}
}
#[allow(dead_code)]
pub fn set_protected(&mut self, x: usize, y: usize, c: char) {
if x < self.width && y < self.height {
self.cells[y][x] = c;
self.protected[y][x] = true;
}
}
pub fn mark_protected(&mut self, x: usize, y: usize) {
if x < self.width && y < self.height {
self.protected[y][x] = true;
}
}
pub fn set_if_empty(&mut self, x: usize, y: usize, c: char) -> bool {
if x < self.width && y < self.height && !self.protected[y][x] {
self.cells[y][x] = c;
return true;
}
false
}
pub fn set_line_with_merge(
&mut self,
x: usize,
y: usize,
c: char,
is_horizontal: bool,
chars: &JunctionChars,
) -> bool {
if x >= self.width || y >= self.height || self.protected[y][x] {
return false;
}
if is_horizontal {
self.line_flags[y][x].left = true;
self.line_flags[y][x].right = true;
} else {
self.line_flags[y][x].up = true;
self.line_flags[y][x].down = true;
}
let flags = &self.line_flags[y][x];
let has_h = flags.left || flags.right;
let has_v = flags.up || flags.down;
self.cells[y][x] = if has_h && has_v {
chars.cross
} else {
c
};
true
}
#[allow(dead_code)]
pub fn is_protected(&self, x: usize, y: usize) -> bool {
if x < self.width && y < self.height {
self.protected[y][x]
} else {
true }
}
#[allow(dead_code)]
pub fn get(&self, x: usize, y: usize) -> Option<char> {
if x < self.width && y < self.height {
Some(self.cells[y][x])
} else {
None
}
}
}
impl RenderBackend for Grid {
fn set(&mut self, x: usize, y: usize, c: char) {
Grid::set(self, x, y, c)
}
fn set_if_empty(&mut self, x: usize, y: usize, c: char) -> bool {
Grid::set_if_empty(self, x, y, c)
}
fn mark_protected(&mut self, x: usize, y: usize) {
Grid::mark_protected(self, x, y)
}
fn set_line_with_merge(
&mut self,
x: usize,
y: usize,
c: char,
is_horizontal: bool,
chars: &JunctionChars,
) -> bool {
Grid::set_line_with_merge(self, x, y, c, is_horizontal, chars)
}
fn dimensions(&self) -> (usize, usize) {
(self.width, self.height)
}
fn get(&self, x: usize, y: usize) -> Option<char> {
Grid::get(self, x, y)
}
}
#[allow(dead_code)]
pub struct JunctionChars {
pub cross: char, pub t_up: char, pub t_down: char, pub ml: char, pub mr: char, }
impl fmt::Display for Grid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let last_non_empty = self
.cells
.iter()
.rposition(|row| row.iter().any(|&c| c != ' '))
.unwrap_or(0);
for (i, row) in self.cells[..=last_non_empty].iter().enumerate() {
let line: String = row.iter().collect();
let trimmed = line.trim_end();
write!(f, "{}", trimmed)?;
if i < last_non_empty {
writeln!(f)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grid_creation() {
let grid = Grid::new(5, 3);
assert_eq!(grid.width, 5);
assert_eq!(grid.height, 3);
}
#[test]
fn test_grid_set_get() {
let mut grid = Grid::new(5, 3);
grid.set(2, 1, 'X');
assert_eq!(grid.get(2, 1), Some('X'));
assert_eq!(grid.get(0, 0), Some(' '));
}
#[test]
fn test_grid_bounds_check() {
let mut grid = Grid::new(5, 3);
grid.set(10, 10, 'X'); assert_eq!(grid.get(10, 10), None);
}
#[test]
fn test_grid_display() {
let mut grid = Grid::new(3, 2);
grid.set(0, 0, 'A');
grid.set(2, 1, 'B');
let s = grid.to_string();
assert_eq!(s, "A\n B");
}
#[test]
fn test_grid_protected() {
let mut grid = Grid::new(5, 3);
grid.set_protected(2, 1, 'N'); assert!(grid.is_protected(2, 1));
let written = grid.set_if_empty(2, 1, '│');
assert!(!written);
assert_eq!(grid.get(2, 1), Some('N')); }
#[test]
fn test_grid_set_if_empty() {
let mut grid = Grid::new(5, 3);
let written = grid.set_if_empty(1, 1, '─');
assert!(written);
assert_eq!(grid.get(1, 1), Some('─'));
}
#[test]
fn test_junction_merging() {
let mut grid = Grid::new(5, 5);
let jchars = JunctionChars {
cross: '┼',
t_up: '┴',
t_down: '┬',
ml: '├',
mr: '┤',
};
grid.set_line_with_merge(1, 2, '─', true, &jchars);
grid.set_line_with_merge(2, 2, '─', true, &jchars);
grid.set_line_with_merge(3, 2, '─', true, &jchars);
grid.set_line_with_merge(2, 1, '│', false, &jchars);
grid.set_line_with_merge(2, 2, '│', false, &jchars);
grid.set_line_with_merge(2, 3, '│', false, &jchars);
assert_eq!(grid.get(2, 2), Some('┼'));
}
}