use { anyhow::{ anyhow, Context, Result, }, std::{ env::args, fs::File, io::{ BufRead, BufReader, }, path::Path, }, }; #[derive(Clone,Copy,Debug)] enum Direction { Up, Down, Left, Right, } #[derive(Clone,Debug,PartialEq)] struct Position { x: usize, y: usize, } #[derive(Debug)] struct Trees { size: usize, heights: Vec, } impl Trees { fn new_with_size(size: usize, heights: Vec) -> Result { let len = heights.len(); if size * size == len { Ok(Self { size, heights, }) } else { Err(anyhow!("Could not create Trees: {size}² ≠ {len}.")) } } fn size(&self) -> usize { self.size } fn height(&self, position: &Position) -> u8 { let x = position.x as usize; let y = position.y as usize; if let Some(height) = self.heights.get(y * self.size + x) { *height } else { panic!("Invalid attempt to access element x: {x}, y: {y} in Trees with size: {}.", self.size); } } fn is_visible(&self, position: &Position, direction: Direction) -> bool { let target_height = self.height(position); match direction { Direction::Up => { for distance in 0..position.y { let cmp_height = self.height(&Position { x: position.x, y: distance }); if cmp_height >= target_height { return false; } } }, Direction::Down => { for distance in (position.y + 1)..self.size { let cmp_height = self.height(&Position { x: position.x, y: distance }); if cmp_height >= target_height { return false; } } }, Direction::Left => { for distance in 0..position.x { let cmp_height = self.height(&Position { x: distance, y: position.y }); if cmp_height >= target_height { return false; } } }, Direction::Right => { for distance in (position.x + 1)..self.size { let cmp_height = self.height(&Position { x: distance, y: position.y }); if cmp_height >= target_height { return false; } } }, } true } fn distance(&self, start: &Position, direction: &Direction) -> usize { let target_height = self.height(start); let position = start.clone(); let mut distance = 0; match direction { Direction::Up => { for i in (0..position.y).rev() { let cmp_height = self.height(&Position { x: position.x, y: i }); distance += 1; if cmp_height >= target_height { break; } } }, Direction::Down => { for i in (position.y + 1)..self.size { let cmp_height = self.height(&Position { x: position.x, y: i }); distance += 1; if cmp_height >= target_height { break; } } }, Direction::Left => { for i in (0..position.x).rev() { let cmp_height = self.height(&Position { x: i, y: position.y }); distance += 1; if cmp_height >= target_height { break; } } }, Direction::Right => { for i in (position.x + 1)..self.size { let cmp_height = self.height(&Position { x: i, y: position.y }); distance += 1; if cmp_height >= target_height { break; } } }, } distance } } fn read_input>(filename: T) -> Result { let reader = BufReader::new(File::open(filename)?); let mut size = 0; let heights = reader.lines().map( |v| { size += 1; let s = v?; s.bytes().map(|height| match height { b'0'..=b'9' => Ok(height - b'0'), _ => Err(anyhow!("Invalid digit: '{height}'")), }).collect::>>() } ).collect::>>()?.into_iter().flatten().collect(); Trees::new_with_size(size, heights) } fn part1(input: &Trees) -> Result { let mut positions: Vec = vec![]; for direction in [Direction::Up, Direction::Down, Direction::Left, Direction::Right].iter() { for y in 0..input.size() { for x in 0..input.size() { let position = Position { x, y }; if input.is_visible(&position, *direction) && !positions.contains(&position) { positions.push(position); } } } } Ok(positions.len()) } fn part2(input: &Trees) -> Result { (0..input.size()).flat_map(|y| (0..input.size()).map(move |x| { let position = Position { x, y }; [Direction::Up, Direction::Down, Direction::Left, Direction::Right].iter() .map(|direction| { input.distance(&position, direction) }).product() } )).max().ok_or_else(|| anyhow!("Really, a tree patch with a 1-by-1 grid?")) } fn main() -> Result<()> { let ( do_part_1, do_part_2 ) = aoc::do_parts(); let filename = args().nth(1).ok_or_else(|| anyhow!("Missing input filename"))?; let input = read_input(filename).context("Could not read input")?; if do_part_1 { let solution = part1(&input).context("No solution for part 1")?; println!("Part1, solution found to be: {}", solution); } if do_part_2 { let solution = part2(&input).context("No solution for part 2")?; println!("Part2, solution found to be: {}", solution); } Ok(()) }