median blog

Advent of Code 2024 Day 6 - Rust

Part One

Day six marks the first 2D puzzle for 2024. In this case, the puzzle input represents an environment with sparsely placed walls. There's a guard who walks around, turning right any time he hits a wall but otherwise moving forward. We need to determine how many tiles he covers before leaving the area.

Because the movement is deterministic, we only need to store the guard's current location. We simulate one step at a time, either moving forward in the current direction or tuning right if there is a wall in front of us.

    let mut position: Bearing = Bearing {
        pos: (-1, -1),
        d: Direction::North,
    };

As we move long, we use a HashSet to store the locations of visited tiles. We do this rather than counting steps because we need to count the number of unique squares rather than the total steps. The enums and helper functions below make this relatively simple to piece together. I suspected I'd need similar functions later in the month, so I moved these to a collection of utility functions.

#[derive(Debug, Hash, Eq, PartialEq, Clone)]
pub enum Direction {
    North,
    East,
    West,
    South,
}

#[derive(Debug)]
pub enum Rotation {
    Left,
    Right,
}

pub fn move_direction(start: &(i32, i32), direction: &Direction) -> (i32, i32) {
    match direction {
        Direction::North => (start.0 - 1, start.1),
        Direction::East => (start.0, start.1 + 1),
        Direction::West => (start.0, start.1 - 1),
        Direction::South => (start.0 + 1, start.1),
    }
}

pub fn turn(direction: &Direction, rotation: Rotation) -> Direction {
    match direction {
        Direction::North => match rotation {
            Rotation::Left => {
                return Direction::West;
            }
            Rotation::Right => {
                return Direction::East;
            }
        },
        Direction::East => match rotation {
            Rotation::Left => {
                return Direction::North;
            }
            Rotation::Right => {
                return Direction::South;
            }
        },
        Direction::West => match rotation {
            Rotation::Left => {
                return Direction::South;
            }
            Rotation::Right => {
                return Direction::North;
            }
        },
        Direction::South => match rotation {
            Rotation::Left => {
                return Direction::East;
            }
            Rotation::Right => {
                return Direction::West;
            }
        },
    }
}

pub fn is_in_bounds(rows: i32, cols: i32, row: i32, col: i32) -> bool {
    if row < 0 || col < 0 {
        return false;
    }

    if row >= rows || col >= cols {
        return false;
    }

    true
}

The main movement loop checked if the guard was in bounds, took a theoretical step forward and either moved to the empty spot or turned right if there was a wall in front of them.

Part Two

For part two, we had to find locations where an extra wall would cause the guard to get stuck in a loop. I updated the main movement loop to run a second inner loop whenever we moved into a blank space. This inner loop would instead pretend we'd hit a wall and turn right. If, in this inner loop, the guard moved over the same tile twice while facing the same direction, we'd found a potential extra wall location. If they left the map, then we wouldn't have.

The complete solution can be found here.