--- /dev/null
+use std::error::Error;
+use std::path::Path;
+
+struct Puzzle {
+ map: Vec<Vec<u8>>,
+ visited: Vec<Vec<bool>>,
+}
+
+impl Puzzle {
+ pub fn new(input: &str) -> Self {
+ let mut map: Vec<Vec<u8>> = Vec::new();
+ input.lines()
+ .for_each(|l| {
+ let mut row: Vec<u8> = l.as_bytes().iter().map(|v| *v).collect();
+ row.insert(0, b' ');
+ row.push(b' ');
+ map.push(row);
+ });
+ map.insert(0, vec![b' '; map[0].len()]);
+ map.push(vec![b' '; map[0].len()]);
+
+ let visited: Vec<Vec<bool>> = vec![vec![false; map[0].len()]; map.len()];
+
+ Self { map, visited }
+ }
+
+ fn count_perimeter(&self, pos: (isize, isize), plant: u8) -> u32 {
+ let (row, col) = pos;
+ [(-1, 0), (0, 1), (1, 0), (0, -1)].iter()
+ .filter(|(dr, dc)| self.map[(row + dr) as usize][(col + dc) as usize] != plant)
+ .count() as u32
+ }
+
+ fn area_perimeter(&mut self, pos: (usize, usize), plant: u8) -> (u32, u32) {
+ let (mut area, mut perimeter) = (0, 0);
+
+ let mut region: Vec<(isize, isize)> = Vec::new();
+ region.push((pos.0 as isize, pos.1 as isize));
+
+ while let Some((row, col)) = region.pop() {
+ area += 1;
+ perimeter += self.count_perimeter((row, col), plant);
+ [(-1, 0), (0, 1), (1, 0), (0, -1)].iter()
+ .for_each(|(dr, dc)| {
+ let (r, c) = ((row + dr) as usize, (col + dc) as usize);
+ if !self.visited[r][c] && self.map[r][c] == plant {
+ self.visited[r][c] = true;
+ region.push((r as isize, c as isize));
+ }
+ });
+ }
+
+ (area, perimeter)
+ }
+}
+
+fn run_part1(input: &str) -> Result<u32, Box<dyn Error>> {
+ println!("Running {} - part 1", get_day());
+
+ let mut puzzle = Puzzle::new(input);
+
+ let mut res = 0;
+ for r in 1..(puzzle.map.len()-1) {
+ for c in 1..(puzzle.map[0].len()-1) {
+ if !puzzle.visited[r][c] {
+ puzzle.visited[r][c] = true;
+ let plant = puzzle.map[r][c];
+ let pos = (r, c);
+ let (area, perimeter) = puzzle.area_perimeter(pos, plant);
+ res += area * perimeter;
+ }
+ }
+ }
+
+ Ok(res)
+}
+
+fn run_part2(input: &str) -> Result<u32, Box<dyn Error>> {
+ println!("Running {} - part 2", get_day());
+
+ Ok(0)
+}
+
+pub fn run(input: &str) -> Result<(), Box<dyn Error>> {
+ let res = run_part1(input)?;
+ println!("{res}");
+
+ let res = run_part2(input)?;
+ println!("{res}");
+
+ Ok(())
+}
+
+fn get_day() -> String {
+ let filename = file!();
+ Path::new(filename).file_stem().unwrap().to_str().unwrap().to_string()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ static TEXT_INPUT_0: &str = "\
+AAAA
+BBCD
+BBCC
+EEEC";
+ static TEXT_INPUT_1: &str = "\
+OOOOO
+OXOXO
+OOOOO
+OXOXO
+OOOOO";
+ static TEXT_INPUT_2: &str = "\
+RRRRIICCFF
+RRRRIICCCF
+VVRRRCCFFF
+VVRCCCJFFF
+VVVVCJJCFE
+VVIVCCJJEE
+VVIIICJJEE
+MIIIIIJJEE
+MIIISIJEEE
+MMMISSJEEE";
+
+ #[test]
+ fn test_part1() {
+ assert_eq!(140, run_part1(TEXT_INPUT_0).unwrap());
+ assert_eq!(772, run_part1(TEXT_INPUT_1).unwrap());
+ assert_eq!(1930, run_part1(TEXT_INPUT_2).unwrap());
+ }
+
+ #[test]
+ fn test_part2() {
+ assert_eq!(0, run_part2(TEXT_INPUT_2).unwrap());
+ }
+}