Spaces:
Running
Running
| from dataclasses import dataclass | |
| file = "input.txt" | |
| with open(file) as f: | |
| data = f.readlines() | |
| grid = [] | |
| for line in data: | |
| line = line.strip("\n") | |
| grid.append(list(line)) | |
| M = len(grid) | |
| N = len(grid[0]) | |
| class Position: | |
| i: int | |
| j: int | |
| direction: str | |
| def rotate_90(self): | |
| directions = ["^", ">", "v", "<"] | |
| new_idx = (directions.index(self.direction) + 1) % len(directions) | |
| self.direction = directions[new_idx] | |
| def is_in_bounds(self, grid): | |
| M = len(grid) | |
| N = len(grid[0]) | |
| return self.i in range(M) and self.j in range(N) | |
| def get_next_pos(position: Position): | |
| i, j, direction = position.i, position.j, position.direction | |
| if direction == "^": | |
| # Up | |
| i -= 1 | |
| elif direction == "v": | |
| # Down | |
| i += 1 | |
| elif direction == "<": | |
| # Left | |
| j -= 1 | |
| elif direction == ">": | |
| # Right | |
| j += 1 | |
| return Position(i, j, direction) | |
| def get_start_pos(grid): | |
| M = len(grid) | |
| N = len(grid[0]) | |
| for i in range(M): | |
| for j in range(N): | |
| if grid[i][j] == "^": | |
| return Position(i, j, "^") | |
| def count_Xs(grid): | |
| total = 0 | |
| for i in range(M): | |
| for j in range(N): | |
| if grid[i][j] == "X": | |
| total += 1 | |
| return total | |
| def pprint(grid, pos = None): | |
| grid_copy = grid.copy() | |
| if pos: | |
| # Print the current position | |
| grid_copy[pos.i][pos.j] = pos.direction | |
| grid_str = "" | |
| for line in grid_copy: | |
| grid_str += "".join(line) | |
| grid_str += "\n" | |
| print("*"*20) | |
| print() | |
| print(grid_str) | |
| print() | |
| print("*"*20) | |
| pos = get_start_pos(grid) | |
| grid[pos.i][pos.j] = "X" # Mark starting point as visited | |
| in_bounds = True | |
| while in_bounds: | |
| # pprint(grid, pos) | |
| next_pos = get_next_pos(pos) | |
| if next_pos.is_in_bounds(grid): | |
| if grid[next_pos.i][next_pos.j] in [".", "X"]: | |
| # Valid next mode, mark as visited and move | |
| grid[pos.i][pos.j] = "X" | |
| pos = next_pos | |
| else: | |
| # Otherwise, rotate | |
| pos.rotate_90() | |
| else: | |
| # Out of bounds, game over | |
| in_bounds = False | |
| grid[pos.i][pos.j] = "X" | |
| pos = None | |
| # pprint(grid, pos) | |
| print(count_Xs(grid)) | |
| ### Part 2 | |
| prev_grid = grid.copy() | |
| def load_grid(file): | |
| with open(file) as f: | |
| data = f.readlines() | |
| grid = [] | |
| for line in data: | |
| line = line.strip("\n") | |
| grid.append(list(line)) | |
| return grid | |
| def check_is_infinite(grid): | |
| pos = get_start_pos(grid) | |
| grid[pos.i][pos.j] = "X" # Mark starting point as visited | |
| visited = set() # Keep track of positions and orientations | |
| in_bounds = True | |
| infinite_loop = False | |
| while in_bounds and not infinite_loop: | |
| # pprint(grid, pos) | |
| next_pos = get_next_pos(pos) | |
| if next_pos.is_in_bounds(grid): | |
| if grid[next_pos.i][next_pos.j] in [".", "X"]: | |
| # Valid next mode, mark as visited and move | |
| grid[pos.i][pos.j] = "X" | |
| pos = next_pos | |
| else: | |
| # Otherwise, rotate | |
| pos.rotate_90() | |
| set_pos = (pos.i, pos.j, pos.direction) | |
| if set_pos in visited: | |
| infinite_loop = True | |
| else: | |
| visited.add(set_pos) | |
| else: | |
| # Out of bounds, game over | |
| in_bounds = False | |
| grid[pos.i][pos.j] = "X" | |
| pos = None | |
| return infinite_loop | |
| file = "input.txt" | |
| # Load first to get stats | |
| # grid = load_grid(file) | |
| M = len(grid) | |
| N = len(grid[0]) | |
| # This is a very brute-force solution, takes long to run... | |
| # For every point, place an obstacle, run the game, check if it gets stuck in infinite loop or not | |
| # Surely, there must be a quicker way, but it works | |
| total = 0 | |
| for i in range(M): | |
| for j in range(N): | |
| # Reset grid | |
| grid = load_grid(file) | |
| start_pos = get_start_pos(grid) | |
| if start_pos.i == i and start_pos.j == j: | |
| # Can't set obstacle on starting point, ignore | |
| continue | |
| if prev_grid[i][j] != "X": | |
| # It never passed by there, so we can ignore | |
| continue | |
| # print(i, j) | |
| grid[i][j] = "O" # Set Obstacle | |
| if check_is_infinite(grid): | |
| total += 1 | |
| print(total) | |