Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Give out-of-range values passed to bitset-backed sets consistent, descriptive panics.
- Return an error instead of panicking when an image contains no detectable grid.
- Calculate queen and X pixel ratios against the inspected image area.
- Preserve spaces used as blank markers at the edges of partial-state grids.

## [1.0.0] - 2025-04-23

Expand Down
22 changes: 20 additions & 2 deletions src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl FromStr for InputSquares {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self> {
let lines = s.trim().lines().collect::<Vec<_>>();
let lines = s.trim_matches(['\n', '\r']).lines().collect::<Vec<_>>();
let size = lines.len();
for (line_num, &line) in lines.iter().enumerate() {
ensure!(
Expand Down Expand Up @@ -115,7 +115,7 @@ impl FromStr for QueensFile {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self> {
let lines = s.trim().lines().collect::<Vec<_>>();
let lines = s.trim_matches(['\n', '\r']).lines().collect::<Vec<_>>();
let lines_len = lines.len();
ensure!(lines_len != 0, "Invalid solve state: no lines found.");

Expand Down Expand Up @@ -151,6 +151,15 @@ mod tests {
Ok(())
}

#[test]
fn input_squares_preserve_edge_spaces() -> Result<()> {
let input_squares = InputSquares::from_str(" Qxx\nxx..\nx...\n \n")?;

assert_eq!(input_squares.0[0], None);
assert!(input_squares.0[12..].iter().all(Option::is_none));
Ok(())
}

#[test]
fn input_squares_display() -> Result<()> {
let input_str = "Qxxx\nxx..\nx...\nx...";
Expand Down Expand Up @@ -239,4 +248,13 @@ mod tests {
);
Ok(())
}
#[test]
fn queens_file_preserves_edge_spaces() -> Result<()> {
let file = QueensFile::from_str("wwww\nkkkk\nrrrr\nbbbb\n\n \n....\n____\n \n")?;
let squares = &file.squares.as_ref().unwrap().0;

assert!(squares[..4].iter().all(Option::is_none));
assert!(squares[12..].iter().all(Option::is_none));
Ok(())
}
}
28 changes: 27 additions & 1 deletion src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,9 @@ fn get_other_ratio(view: &SubImage<&RgbImage>, rgb_color: &Rgb<u8>) -> f32 {
.pixels()
.filter(|(_, _, p)| color_distance(*p, *rgb_color) > COLOR_DISTANCE_THRESHOLD)
.count();
(other_count as f32) / ((width * height) as f32)
let center_width = width - (2 * width / BORDER_DENOM);
let center_height = height - (2 * height / BORDER_DENOM);
(other_count as f32) / ((center_width * center_height) as f32)
}

fn find_grid_ranges(img: &RgbImage, range: Range<u32>, is_vertical: bool) -> Vec<Range<u32>> {
Expand All @@ -208,6 +210,9 @@ fn find_grid_ranges(img: &RgbImage, range: Range<u32>, is_vertical: bool) -> Vec
});
grid_ranges.extend(grid_ranges_iter);
let grid_ranges_len = grid_ranges.len();
if grid_ranges_len == 0 {
return grid_ranges;
}
let median_grid_length = grid_ranges
.clone()
.select_nth_unstable_by(grid_ranges_len / 2, |a, b| a.len().cmp(&b.len()))
Expand Down Expand Up @@ -318,3 +323,24 @@ fn map_image_to_square_colors(image_colors: &[Rgb<u8>]) -> [SquareColor; MAX_UNI

image_to_square_color
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn analyze_grid_image_rejects_blank_image() {
let img = RgbImage::from_pixel(64, 64, Rgb([255, 255, 255]));
assert!(analyze_grid_image(&img).is_err());
}

#[test]
fn other_ratio_uses_only_the_inspected_area() {
let white = Rgb([255, 255, 255]);
let mut img = RgbImage::from_pixel(10, 10, white);
img.put_pixel(0, 0, Rgb([0, 0, 0]));
img.put_pixel(5, 5, Rgb([0, 0, 0]));

assert_eq!(get_other_ratio(&img.view(0, 0, 10, 10), &white), 1.0 / 64.0);
}
}
Loading