import aoclib inp = aoclib.get_input(8, parser=aoclib.parse_lines_with_func(lambda line: [int(c) for c in line])) class Forest: class Directions: UP = (-1, 0) DOWN = (1, 0) LEFT = (0, -1) RIGHT = (0, 1) ALL = (UP, DOWN, LEFT, RIGHT) def __init__(self, grid): # assumes rect grid self.grid = grid self.max = (len(grid[0]), len(grid)) def is_tree_visible(self, x: int, y: int): return any([self._tree_visible_from(direction, x, y) for direction in self.Directions.ALL]) def _tree_visible_from(self, direction, x, y): main = self._get_tree(x, y) others = self._get_all_trees_in_dir(direction, x, y) return all([o < main for o in others]) def _get_all_trees_in_dir(self, direction, x, y): trees = [] dx, dy = direction while True: x += dx y += dy tree = self._get_tree(x, y) if tree is not None: trees.append(tree) else: return trees def scenic_score_for_pos(self, x, y): prod = 1 for d in self.Directions.ALL: prod *= self._get_scenic_score_in_dir(d, x, y) if prod == 0: break return prod def _get_scenic_score_in_dir(self, direction, x, y): house = self._get_tree(x, y) count = 0 for tree in self._get_trees_in_dir_iter(direction, x, y): count += 1 if tree >= house: break return count def _get_trees_in_dir_iter(self, direction, x, y): dx, dy = direction while True: x += dx y += dy tree = self._get_tree(x, y) if tree is not None: yield tree else: break def _get_tree(self, x, y): if x < 0 or y < 0: return None try: return self.grid[y][x] except IndexError: return None def part1(): f = Forest(inp) xmax, ymax = f.max count = 0 for x in range(xmax): for y in range(ymax): if f.is_tree_visible(x, y): count += 1 return count def part2(): f = Forest(inp) xmax, ymax = f.max highest = 0 for x in range(xmax): for y in range(ymax): if (current := f.scenic_score_for_pos(x, y)) > highest: highest = current return highest aoclib.main(part1, part2)