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();
while let Some(in_front) = get_in_front(&guard, &mut map) {
if in_front.obstacle {
guard.turn();
} else {
in_front.visited = true;
guard.move_forward();
}
}
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();
while 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();
}
if start_time.elapsed().as_millis() >= 1 {
block_positions += 1;
break;
}
}
}
Ok(block_positions)
}