DEVELOPMENT ENVIRONMENT

~liljamo/aoc2024

65ada73bf439168cfa1d7a90ee6dbacc622ab729 — Jonni Liljamo a month ago 849a172
feat: day6
3 files changed, 246 insertions(+), 0 deletions(-)

A input/day6/example
A src/day6/mod.rs
M src/main.rs
A input/day6/example => input/day6/example +10 -0
@@ 0,0 1,10 @@
....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#...

A src/day6/mod.rs => src/day6/mod.rs +227 -0
@@ 0,0 1,227 @@
use std::{
    fs::File,
    io::{BufRead, BufReader},
    path::Path,
};

pub fn solve(input: &Path) -> anyhow::Result<()> {
    println!("part one: {}", part_one(input)?);
    println!("part two: {}", part_two(input)?);

    Ok(())
}

#[derive(Default, Clone)]
struct Pos {
    x: usize,
    y: usize,
    visited: bool,
    obstacle: bool,
}

#[derive(Clone, Debug)]
struct Guard {
    x: usize,
    y: usize,
    direction: Direction,
}

impl Guard {
    fn turn(&mut self) {
        match self.direction {
            Direction::North => self.direction = Direction::East,
            Direction::East => self.direction = Direction::South,
            Direction::South => self.direction = Direction::West,
            Direction::West => self.direction = Direction::North,
        }
    }

    fn move_forward(&mut self) {
        match self.direction {
            Direction::North => self.y -= 1,
            Direction::East => self.x += 1,
            Direction::South => self.y += 1,
            Direction::West => self.x -= 1,
        }
    }
}

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

fn part_one(input: &Path) -> anyhow::Result<i32> {
    let reader = BufReader::new(File::open(input)?);

    let mut map: Vec<Vec<Pos>> = vec![];
    let mut guard: Option<Guard> = None;
    for (y, line) in reader.lines().enumerate() {
        let mut row = vec![];
        for (x, c) in line?.char_indices() {
            match c {
                '.' => row.push(Pos {
                    x,
                    y,
                    ..Default::default()
                }),
                '#' => row.push(Pos {
                    x,
                    y,
                    obstacle: true,
                    ..Default::default()
                }),
                '^' => {
                    row.push(Pos {
                        x,
                        y,
                        visited: true,
                        ..Default::default()
                    });
                    guard = Some(Guard {
                        x,
                        y,
                        direction: Direction::North,
                    });
                }
                _ => panic!("invalid map"),
            }
        }
        map.push(row);
    }
    let mut guard = guard.unwrap();

    loop {
        //print_map(&map);
        if let Some(in_front) = get_in_front(&guard, &mut map) {
            if in_front.obstacle {
                guard.turn();
            } else {
                in_front.visited = true;
                guard.move_forward();
            }
        } else {
            break;
        }
        //std::thread::sleep(std::time::Duration::from_millis(50));
    }

    //print_map(&map);

    Ok(map.iter().flatten().filter(|p| p.visited).count() as i32)
}

fn get_in_front<'a>(guard: &'a Guard, map: &'a mut [Vec<Pos>]) -> Option<&'a mut Pos> {
    match guard.direction {
        Direction::North => match map.get_mut(if guard.y == 0 {
            return None;
        } else {
            guard.y - 1
        }) {
            Some(row) => row.get_mut(guard.x),
            None => None,
        },
        Direction::East => match map.get_mut(guard.y) {
            Some(row) => row.get_mut(guard.x + 1),
            None => None,
        },
        Direction::South => match map.get_mut(guard.y + 1) {
            Some(row) => row.get_mut(guard.x),
            None => None,
        },
        Direction::West => match map.get_mut(guard.y) {
            Some(row) => row.get_mut(if guard.x == 0 {
                return None;
            } else {
                guard.x - 1
            }),
            None => None,
        },
    }
}

fn print_map(map: &[Vec<Pos>]) {
    for row in map {
        for pos in row {
            if pos.obstacle {
                print!("# ");
            } else if pos.visited {
                print!("X ");
            } else {
                print!(". ");
            }
        }
        println!();
    }
}

fn part_two(input: &Path) -> anyhow::Result<i32> {
    let reader = BufReader::new(File::open(input)?);

    let mut map: Vec<Vec<Pos>> = vec![];
    let mut guard: Option<Guard> = None;
    for (y, line) in reader.lines().enumerate() {
        let mut row = vec![];
        for (x, c) in line?.char_indices() {
            match c {
                '.' => row.push(Pos {
                    x,
                    y,
                    ..Default::default()
                }),
                '#' => row.push(Pos {
                    x,
                    y,
                    obstacle: true,
                    ..Default::default()
                }),
                '^' => {
                    row.push(Pos {
                        x,
                        y,
                        visited: true,
                        ..Default::default()
                    });
                    guard = Some(Guard {
                        x,
                        y,
                        direction: Direction::North,
                    });
                }
                _ => panic!("invalid map"),
            }
        }
        map.push(row);
    }
    let guard = guard.unwrap();

    let mut block_positions = 0;
    for pos in map.iter().flatten() {
        let mut guard = guard.clone();
        let mut modified_map = map.clone();
        modified_map[pos.y][pos.x].obstacle = true;
        let start_time = std::time::Instant::now();
        loop {
            if let Some(in_front) = get_in_front(&guard, &mut modified_map) {
                if in_front.obstacle {
                    guard.turn();
                } else {
                    in_front.visited = true;
                    guard.move_forward();
                }
            } else {
                break;
            }

            if start_time.elapsed().as_millis() >= 1 {
                block_positions += 1;
                break;
            }
        }
    }

    Ok(block_positions)
}

M src/main.rs => src/main.rs +9 -0
@@ 1,6 1,7 @@
#![allow(clippy::comparison_chain)]
#![allow(clippy::while_let_loop)]
#![allow(clippy::needless_range_loop)]
#![allow(dead_code)]
use std::path::PathBuf;

use clap::{Parser, Subcommand};


@@ 10,6 11,7 @@ mod day2;
mod day3;
mod day4;
mod day5;
mod day6;

#[derive(Parser)]
struct Args {


@@ 39,6 41,10 @@ enum DayArgs {
        #[arg(short)]
        input: PathBuf,
    },
    Day6 {
        #[arg(short)]
        input: PathBuf,
    },
}

fn main() -> anyhow::Result<()> {


@@ 60,6 66,9 @@ fn main() -> anyhow::Result<()> {
        DayArgs::Day5 { input } => {
            day5::solve(&input)?;
        }
        DayArgs::Day6 { input } => {
            day6::solve(&input)?;
        }
    }

    Ok(())