use super::io::read_bytes;
use crate::{
error::PuzError,
types::{Grid, FREE_SQUARE, TAKEN_SQUARE},
};
use std::io::{BufReader, Read};
pub(crate) fn parse_grids<R: Read>(
reader: &mut BufReader<R>,
width: u8,
height: u8,
) -> Result<Grid, PuzError> {
let board_size = (width as usize) * (height as usize);
let solution_bytes = read_bytes(reader, board_size)?;
let solution_chars: String = solution_bytes.iter().map(|&b| b as char).collect();
let blank_bytes = read_bytes(reader, board_size)?;
let blank_chars: String = blank_bytes.iter().map(|&b| b as char).collect();
let solution = string_to_grid(&solution_chars, width as usize);
let blank = string_to_grid(&blank_chars, width as usize);
validate_grid_consistency(&solution, &blank, width, height)?;
Ok(Grid { blank, solution })
}
fn string_to_grid(s: &str, width: usize) -> Vec<String> {
s.chars()
.collect::<Vec<char>>()
.chunks(width)
.map(|chunk| chunk.iter().collect::<String>())
.collect()
}
fn validate_grid_consistency(
solution: &[String],
blank: &[String],
width: u8,
height: u8,
) -> Result<(), PuzError> {
if solution.len() != height as usize || blank.len() != height as usize {
return Err(PuzError::InvalidGrid {
reason: format!(
"Grid height mismatch: expected {}, got solution: {}, blank: {}",
height,
solution.len(),
blank.len()
),
});
}
for (i, (sol_row, blank_row)) in solution.iter().zip(blank.iter()).enumerate() {
if sol_row.len() != width as usize || blank_row.len() != width as usize {
return Err(PuzError::InvalidGrid {
reason: format!(
"Grid width mismatch at row {}: expected {}, got solution: {}, blank: {}",
i,
width,
sol_row.len(),
blank_row.len()
),
});
}
for (j, (sol_char, blank_char)) in sol_row.chars().zip(blank_row.chars()).enumerate() {
if (sol_char == TAKEN_SQUARE) != (blank_char == TAKEN_SQUARE) {
return Err(PuzError::InvalidGrid {
reason: format!(
"Grid consistency error at ({i}, {j}): blocked squares don't match"
),
});
}
}
}
Ok(())
}
pub(crate) fn cell_needs_across_clue(grid: &[String], row: usize, col: usize) -> bool {
if let Some(row_str) = grid.get(row) {
if let Some(this_char) = row_str.chars().nth(col) {
if this_char == FREE_SQUARE {
if let Some(next_char) = row_str.chars().nth(col + 1) {
if next_char == FREE_SQUARE {
return col == 0 || row_str.chars().nth(col - 1) == Some(TAKEN_SQUARE);
}
}
}
}
}
false
}
pub(crate) fn cell_needs_down_clue(grid: &[String], row: usize, col: usize) -> bool {
if let Some(row_str) = grid.get(row) {
if let Some(this_char) = row_str.chars().nth(col) {
if this_char == FREE_SQUARE {
if let Some(next_row) = grid.get(row + 1) {
if let Some(next_char) = next_row.chars().nth(col) {
if next_char == FREE_SQUARE {
return row == 0
|| grid.get(row - 1).and_then(|r| r.chars().nth(col))
== Some(TAKEN_SQUARE);
}
}
}
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_parse_grids_valid() {
let width = 3u8;
let height = 3u8;
let solution_data = b"ABC.DEFGH";
let blank_data = b"---.-----";
let mut data = Vec::new();
data.extend_from_slice(solution_data);
data.extend_from_slice(blank_data);
let mut reader = BufReader::new(Cursor::new(data));
let grid = parse_grids(&mut reader, width, height).unwrap();
assert_eq!(grid.solution.len(), 3);
assert_eq!(grid.blank.len(), 3);
assert_eq!(grid.solution[0], "ABC");
assert_eq!(grid.solution[1], ".DE");
assert_eq!(grid.solution[2], "FGH");
assert_eq!(grid.blank[0], "---");
assert_eq!(grid.blank[1], ".--");
assert_eq!(grid.blank[2], "---");
}
#[test]
fn test_parse_grids_all_black() {
let width = 2u8;
let height = 2u8;
let solution_data = b"....";
let blank_data = b"....";
let mut data = Vec::new();
data.extend_from_slice(solution_data);
data.extend_from_slice(blank_data);
let mut reader = BufReader::new(Cursor::new(data));
let grid = parse_grids(&mut reader, width, height).unwrap();
assert_eq!(grid.solution, vec!["..".to_string(), "..".to_string()]);
assert_eq!(grid.blank, vec!["..".to_string(), "..".to_string()]);
}
#[test]
fn test_parse_grids_all_free() {
let width = 2u8;
let height = 2u8;
let solution_data = b"ABCD";
let blank_data = b"----";
let mut data = Vec::new();
data.extend_from_slice(solution_data);
data.extend_from_slice(blank_data);
let mut reader = BufReader::new(Cursor::new(data));
let grid = parse_grids(&mut reader, width, height).unwrap();
assert_eq!(grid.solution, vec!["AB".to_string(), "CD".to_string()]);
assert_eq!(grid.blank, vec!["--".to_string(), "--".to_string()]);
}
#[test]
fn test_parse_grids_single_cell() {
let width = 1u8;
let height = 1u8;
let solution_data = b"A";
let blank_data = b"-";
let mut data = Vec::new();
data.extend_from_slice(solution_data);
data.extend_from_slice(blank_data);
let mut reader = BufReader::new(Cursor::new(data));
let grid = parse_grids(&mut reader, width, height).unwrap();
assert_eq!(grid.solution, vec!["A".to_string()]);
assert_eq!(grid.blank, vec!["-".to_string()]);
}
#[test]
fn test_parse_grids_large() {
let width = 15u8;
let height = 15u8;
let board_size = (width as usize) * (height as usize);
let mut solution_data = Vec::new();
let mut blank_data = Vec::new();
for i in 0..board_size {
if i % 2 == 0 {
solution_data.push(b'A');
blank_data.push(b'-');
} else {
solution_data.push(b'.');
blank_data.push(b'.');
}
}
let mut data = Vec::new();
data.extend(solution_data);
data.extend(blank_data);
let mut reader = BufReader::new(Cursor::new(data));
let grid = parse_grids(&mut reader, width, height).unwrap();
assert_eq!(grid.solution.len(), 15);
assert_eq!(grid.blank.len(), 15);
assert_eq!(grid.solution[0].len(), 15);
assert_eq!(grid.blank[0].len(), 15);
}
#[test]
fn test_parse_grids_insufficient_data() {
let width = 3u8;
let height = 3u8;
let solution_data = b"ABC.DEFGH";
let mut reader = BufReader::new(Cursor::new(solution_data));
let result = parse_grids(&mut reader, width, height);
assert!(result.is_err());
matches!(result.unwrap_err(), PuzError::IoError { .. });
}
#[test]
fn test_parse_grids_consistency_failure() {
let width = 2u8;
let height = 2u8;
let solution_data = b"A.BC";
let blank_data = b"--B-";
let mut data = Vec::new();
data.extend_from_slice(solution_data);
data.extend_from_slice(blank_data);
let mut reader = BufReader::new(Cursor::new(data));
let result = parse_grids(&mut reader, width, height);
assert!(result.is_err());
if let Err(PuzError::InvalidGrid { reason }) = result {
assert!(reason.contains("consistency error"));
} else {
panic!("Expected InvalidGrid error with consistency message");
}
}
#[test]
fn test_string_to_grid() {
let result = string_to_grid("ABCDEF", 3);
assert_eq!(result, vec!["ABC".to_string(), "DEF".to_string()]);
let result = string_to_grid("ABC", 3);
assert_eq!(result, vec!["ABC".to_string()]);
let result = string_to_grid("ABC", 1);
assert_eq!(
result,
vec!["A".to_string(), "B".to_string(), "C".to_string()]
);
let result = string_to_grid("", 1);
assert_eq!(result, Vec::<String>::new());
}
#[test]
fn test_cell_needs_across_clue() {
let grid = vec![
"---".to_string(), "...".to_string(), "--.".to_string(), ];
assert!(cell_needs_across_clue(&grid, 0, 0)); assert!(!cell_needs_across_clue(&grid, 0, 1)); assert!(!cell_needs_across_clue(&grid, 0, 2));
assert!(!cell_needs_across_clue(&grid, 1, 0)); assert!(!cell_needs_across_clue(&grid, 1, 1)); assert!(!cell_needs_across_clue(&grid, 1, 2));
assert!(cell_needs_across_clue(&grid, 2, 0)); assert!(!cell_needs_across_clue(&grid, 2, 1)); assert!(!cell_needs_across_clue(&grid, 2, 2)); }
#[test]
fn test_cell_needs_down_clue() {
let grid = vec![
"-.-".to_string(), "-.-".to_string(), "...".to_string(), ];
assert!(cell_needs_down_clue(&grid, 0, 0)); assert!(!cell_needs_down_clue(&grid, 1, 0)); assert!(!cell_needs_down_clue(&grid, 2, 0));
assert!(!cell_needs_down_clue(&grid, 0, 1)); assert!(!cell_needs_down_clue(&grid, 1, 1)); assert!(!cell_needs_down_clue(&grid, 2, 1));
assert!(cell_needs_down_clue(&grid, 0, 2)); assert!(!cell_needs_down_clue(&grid, 1, 2)); assert!(!cell_needs_down_clue(&grid, 2, 2)); }
#[test]
fn test_across_clue_edge_cases() {
let grid = vec!["-".to_string(), "-".to_string(), ".".to_string()];
assert!(!cell_needs_across_clue(&grid, 0, 0)); assert!(!cell_needs_across_clue(&grid, 1, 0)); assert!(!cell_needs_across_clue(&grid, 2, 0));
let grid = vec!["-.--.".to_string()];
assert!(!cell_needs_across_clue(&grid, 0, 0)); assert!(!cell_needs_across_clue(&grid, 0, 1)); assert!(cell_needs_across_clue(&grid, 0, 2)); assert!(!cell_needs_across_clue(&grid, 0, 3)); assert!(!cell_needs_across_clue(&grid, 0, 4)); }
#[test]
fn test_down_clue_edge_cases() {
let grid = vec!["---".to_string()];
assert!(!cell_needs_down_clue(&grid, 0, 0)); assert!(!cell_needs_down_clue(&grid, 0, 1)); assert!(!cell_needs_down_clue(&grid, 0, 2));
let grid = vec![
"-".to_string(),
".".to_string(),
"-".to_string(),
"-".to_string(),
"-".to_string(),
];
assert!(!cell_needs_down_clue(&grid, 0, 0)); assert!(!cell_needs_down_clue(&grid, 1, 0)); assert!(cell_needs_down_clue(&grid, 2, 0)); assert!(!cell_needs_down_clue(&grid, 3, 0)); assert!(!cell_needs_down_clue(&grid, 4, 0)); }
#[test]
fn test_validate_grid_consistency_dimension_mismatch() {
let solution = vec!["ABC".to_string(), "DEF".to_string()]; let blank = vec!["---".to_string()];
let result = validate_grid_consistency(&solution, &blank, 3, 2);
assert!(result.is_err());
if let Err(PuzError::InvalidGrid { reason }) = result {
assert!(reason.contains("height mismatch"));
} else {
panic!("Expected InvalidGrid error with height mismatch");
}
}
#[test]
fn test_validate_grid_consistency_width_mismatch() {
let solution = vec!["ABC".to_string(), "DE".to_string()]; let blank = vec!["---".to_string(), "--".to_string()];
let result = validate_grid_consistency(&solution, &blank, 3, 2);
assert!(result.is_err());
if let Err(PuzError::InvalidGrid { reason }) = result {
assert!(reason.contains("width mismatch"));
} else {
panic!("Expected InvalidGrid error with width mismatch");
}
}
#[test]
fn test_clue_detection_realistic_grid() {
let grid = vec![
"---".to_string(), "-.-".to_string(), "---".to_string(), ];
assert!(cell_needs_across_clue(&grid, 0, 0)); assert!(!cell_needs_across_clue(&grid, 0, 1)); assert!(!cell_needs_across_clue(&grid, 0, 2));
assert!(!cell_needs_across_clue(&grid, 1, 0)); assert!(!cell_needs_across_clue(&grid, 1, 1)); assert!(!cell_needs_across_clue(&grid, 1, 2));
assert!(cell_needs_across_clue(&grid, 2, 0)); assert!(!cell_needs_across_clue(&grid, 2, 1)); assert!(!cell_needs_across_clue(&grid, 2, 2));
assert!(cell_needs_down_clue(&grid, 0, 0)); assert!(!cell_needs_down_clue(&grid, 1, 0)); assert!(!cell_needs_down_clue(&grid, 2, 0));
assert!(!cell_needs_down_clue(&grid, 0, 1)); assert!(!cell_needs_down_clue(&grid, 1, 1)); assert!(!cell_needs_down_clue(&grid, 2, 1));
assert!(cell_needs_down_clue(&grid, 0, 2)); assert!(!cell_needs_down_clue(&grid, 1, 2)); assert!(!cell_needs_down_clue(&grid, 2, 2));
assert!(!cell_needs_across_clue(&grid, 1, 1)); assert!(!cell_needs_down_clue(&grid, 1, 1)); }
}