diff --git a/.aoc_tiles/tiles/2025/04.png b/.aoc_tiles/tiles/2025/04.png
new file mode 100644
index 0000000..2b574ec
Binary files /dev/null and b/.aoc_tiles/tiles/2025/04.png differ
diff --git a/README.md b/README.md
index ba3e2c6..2518675 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
- 2025 - 6 ⭐ - Rust
+ 2025 - 7 ⭐ - Rust
@@ -11,4 +11,7 @@
+
+
+
diff --git a/src/day4.rs b/src/day4.rs
new file mode 100644
index 0000000..ab98f5e
--- /dev/null
+++ b/src/day4.rs
@@ -0,0 +1,47 @@
+use aoc_runner_derive::{aoc, aoc_generator};
+use grid::Grid;
+
+#[aoc_generator(day4)]
+fn parse(input: &str) -> Grid {
+ input.parse().unwrap()
+}
+
+#[aoc(day4, part1)]
+fn part1(input: &Grid) -> u64 {
+ (0..input.height() * input.width())
+ .filter(|i| *input.get(&input.coord(*i as i64).unwrap()).unwrap() == b'@')
+ .map(|i| input.neighbours_count(input.coord(i as i64).unwrap(), |c| *c == b'@'))
+ .filter(|n| *n < 4)
+ .count() as u64
+}
+
+#[aoc(day4, part2)]
+fn part2(input: &Grid) -> u64 {
+ input.width() as u64
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const EXAMPLE: &str = "..@@.@@@@.
+@@@.@.@.@@
+@@@@@.@.@@
+@.@@@@..@.
+@@.@@@@.@@
+.@@@@@@@.@
+.@.@.@.@@@
+@.@@@.@@@@
+.@@@@@@@@.
+@.@.@@@.@.";
+
+ #[test]
+ fn part1_example() {
+ assert_eq!(part1(&parse(EXAMPLE)), 13);
+ }
+
+ #[test]
+ fn part2_example() {
+ assert_eq!(part2(&parse(EXAMPLE)), 0);
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 31454ef..3961a65 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
mod day1;
mod day2;
mod day3;
+mod day4;
use aoc_runner_derive::aoc_lib;
diff --git a/utils/grid/lib.rs b/utils/grid/lib.rs
index 67f810c..2b0b6e3 100644
--- a/utils/grid/lib.rs
+++ b/utils/grid/lib.rs
@@ -352,6 +352,28 @@ impl Grid {
}
}
+ /// Return the count of neighbours (8 directions) matching predicate p
+ pub fn neighbours_count(&self, c: C, mut p: P) -> usize
+ where
+ P: FnMut(&T) -> bool,
+ {
+ const DIRECTIONS: [(i64, i64); 8] = [
+ (-1, -1),
+ (0, -1),
+ (1, -1),
+ (-1, 0),
+ (1, 0),
+ (1, 1),
+ (0, 1),
+ (-1, 1),
+ ];
+ DIRECTIONS
+ .iter()
+ .map(|d| (c.x() + d.0, c.y() + d.1))
+ .filter(|c| self.get(c).is_some_and(|x| p(x)))
+ .count()
+ }
+
// fn window_compare_impl(&self, needle: &[T]) -> Vec<(i64, i64)> {
// if (self.width as usize) < needle.len() {
// return Vec::new();