#![allow(dead_code)]
use crate::chars::*;
pub struct Grid {
chars: Vec<Vec<char>>,
used: Vec<Vec<bool>>,
pub width: usize,
pub height: usize,
}
impl Grid {
pub fn new(input: &str) -> Self {
let input = preprocess(input);
let lines: Vec<&str> = input.lines().collect();
let height = lines.len();
let width = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
let mut chars = Vec::with_capacity(height);
let mut used = Vec::with_capacity(height);
for line in &lines {
let mut row: Vec<char> = line.chars().collect();
while row.len() < width {
row.push(' ');
}
chars.push(row);
used.push(vec![false; width]);
}
Self {
chars,
used,
width,
height,
}
}
#[inline]
pub fn get(&self, x: i32, y: i32) -> char {
if x < 0 || y < 0 {
return ' ';
}
let x = x as usize;
let y = y as usize;
if y >= self.height || x >= self.width {
return ' ';
}
self.chars[y][x]
}
pub fn set_used(&mut self, x: i32, y: i32) {
if x < 0 || y < 0 {
return;
}
let x = x as usize;
let y = y as usize;
if y < self.height && x < self.width {
self.used[y][x] = true;
}
}
pub fn is_used(&self, x: i32, y: i32) -> bool {
if x < 0 || y < 0 {
return false;
}
let x = x as usize;
let y = y as usize;
if y >= self.height || x >= self.width {
return false;
}
self.used[y][x]
}
pub fn is_solid_v_line_at(&self, x: i32, y: i32) -> bool {
let c = self.get(x, y);
is_solid_v_line(c)
}
pub fn is_double_v_line_at(&self, x: i32, y: i32) -> bool {
let c = self.get(x, y);
is_double_v_line(c)
}
pub fn is_solid_h_line_at(&self, x: i32, y: i32) -> bool {
let c = self.get(x, y);
is_solid_h_line(c)
}
pub fn is_squiggle_h_line_at(&self, x: i32, y: i32) -> bool {
let c = self.get(x, y);
is_squiggle_h_line(c)
}
pub fn is_double_h_line_at(&self, x: i32, y: i32) -> bool {
let c = self.get(x, y);
is_double_h_line(c)
}
pub fn is_any_h_line_at(&self, x: i32, y: i32) -> bool {
let c = self.get(x, y);
is_any_h_line(c)
}
pub fn is_solid_b_line_at(&self, x: i32, y: i32) -> bool {
let c = self.get(x, y);
is_solid_b_line(c)
}
pub fn is_solid_d_line_at(&self, x: i32, y: i32) -> bool {
let c = self.get(x, y);
is_solid_d_line(c)
}
pub fn is_v_line_at_with<F>(&self, x: i32, y: i32, pred: F) -> bool
where
F: Fn(char) -> bool,
{
let c = self.get(x, y);
if !pred(c) {
return false;
}
let above = self.get(x, y - 1);
let below = self.get(x, y + 1);
pred(above)
|| pred(below)
|| is_top_vertex(above)
|| is_bottom_vertex(below)
|| is_arrow_head(above)
|| is_arrow_head(below)
}
pub fn is_h_line_at_with<F>(&self, x: i32, y: i32, pred: F) -> bool
where
F: Fn(char) -> bool,
{
let c = self.get(x, y);
if !pred(c) {
return false;
}
let left = self.get(x - 1, y);
let right = self.get(x + 1, y);
pred(left) || pred(right) || is_vertex(left) || is_vertex(right)
}
pub fn text_start(&self, start_x: i32, y: i32, _spaces: u32) -> Option<i32> {
let mut x = start_x;
while x < self.width as i32 {
let c = self.get(x, y);
if c != ' ' && !self.is_used(x, y) {
return Some(x);
}
x += 1;
}
None
}
pub fn extract_text(&mut self, start_x: i32, y: i32, spaces: u32) -> String {
let mut result = String::new();
let mut x = start_x;
let mut space_count = 0;
while x < self.width as i32 {
let c = self.get(x, y);
if c == ' ' {
space_count += 1;
if space_count >= spaces && spaces > 0 {
while result.ends_with(' ') {
result.pop();
}
break;
}
result.push(c);
} else if self.is_used(x, y) {
while result.ends_with(' ') {
result.pop();
}
break;
} else {
space_count = 0;
result.push(c);
self.set_used(x, y);
}
x += 1;
}
while result.ends_with(' ') {
result.pop();
}
result
}
}
fn preprocess(input: &str) -> String {
let input = remove_leading_space(input);
let input = equalize_line_lengths(&input);
hide_markers(&input)
}
fn remove_leading_space(input: &str) -> String {
let lines: Vec<&str> = input.lines().collect();
let min_spaces = lines
.iter()
.filter(|l| !l.trim().is_empty())
.map(|l| l.len() - l.trim_start().len())
.min()
.unwrap_or(0);
if min_spaces == 0 {
return input.to_string();
}
lines
.iter()
.map(|l| {
if l.len() >= min_spaces {
&l[min_spaces..]
} else {
""
}
})
.collect::<Vec<_>>()
.join("\n")
}
fn equalize_line_lengths(input: &str) -> String {
let lines: Vec<&str> = input.lines().collect();
let max_len = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
lines
.iter()
.map(|l| {
let len = l.chars().count();
if len < max_len {
let padding = " ".repeat(max_len - len);
format!("{}{}", l, padding)
} else {
l.to_string()
}
})
.collect::<Vec<_>>()
.join("\n")
}
fn hide_markers(input: &str) -> String {
let lines: Vec<Vec<char>> = input.lines().map(|l| l.chars().collect()).collect();
let height = lines.len();
let get = |x: i32, y: i32| -> char {
if y < 0 || y >= height as i32 {
return ' ';
}
let row = &lines[y as usize];
if x < 0 || x >= row.len() as i32 {
return ' ';
}
row[x as usize]
};
let is_letter = |c: char| -> bool { c.is_ascii_alphabetic() };
let mut result: Vec<Vec<char>> = lines.clone();
for y in 0..height {
for x in 0..lines[y].len() {
let c = lines[y][x];
let xi = x as i32;
let yi = y as i32;
if c == 'o' || c == 'v' || c == 'V' {
let left = get(xi - 1, yi);
let right = get(xi + 1, yi);
if is_letter(left) || is_letter(right) {
result[y][x] = match c {
'o' => '\u{E000}', 'v' => '\u{E001}', 'V' => '\u{E002}', _ => c,
};
}
}
}
}
result
.iter()
.map(|row| row.iter().collect::<String>())
.collect::<Vec<_>>()
.join("\n")
}
pub fn unhide_markers(input: &str) -> String {
input
.replace('\u{E000}', "o")
.replace('\u{E001}', "v")
.replace('\u{E002}', "V")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grid_creation() {
let grid = Grid::new("+--+\n| |\n+--+");
assert_eq!(grid.width, 4);
assert_eq!(grid.height, 3);
assert_eq!(grid.get(0, 0), '+');
assert_eq!(grid.get(1, 0), '-');
assert_eq!(grid.get(0, 1), '|');
assert_eq!(grid.get(1, 1), ' ');
}
#[test]
fn test_grid_out_of_bounds() {
let grid = Grid::new("AB\nCD");
assert_eq!(grid.get(-1, 0), ' ');
assert_eq!(grid.get(0, -1), ' ');
assert_eq!(grid.get(10, 0), ' ');
assert_eq!(grid.get(0, 10), ' ');
}
#[test]
fn test_grid_used_tracking() {
let mut grid = Grid::new("AB");
assert!(!grid.is_used(0, 0));
grid.set_used(0, 0);
assert!(grid.is_used(0, 0));
assert!(!grid.is_used(1, 0));
}
#[test]
fn test_remove_leading_space() {
let input = " abc\n def";
let result = remove_leading_space(input);
assert_eq!(result, "abc\ndef");
}
#[test]
fn test_equalize_line_lengths() {
let input = "ab\na";
let result = equalize_line_lengths(input);
assert_eq!(result, "ab\na ");
}
#[test]
fn test_text_extraction() {
let mut grid = Grid::new("Test String");
let text = grid.extract_text(0, 0, 2);
assert_eq!(text, "Test String");
}
}