Spaces:
Running
Running
| from dataclasses import dataclass | |
| def read_data(file): | |
| with open(file) as f: | |
| data = f.readlines() | |
| grid = [] | |
| for count, line in enumerate(data): | |
| if line == "\n": | |
| instructions = "".join([l.strip("\n") for l in data[count:]]) | |
| break | |
| line = line.strip("\n") | |
| grid.append(list(line)) | |
| return grid, instructions | |
| class Position: | |
| i: int | |
| j: int | |
| 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, direction): | |
| i, j = position.i, position.j | |
| 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) | |
| def pprint(grid): | |
| grid_copy = grid.copy() | |
| grid_str = "" | |
| for line in grid_copy: | |
| grid_str += "".join(line) | |
| grid_str += "\n" | |
| print("*"*20) | |
| print() | |
| print(grid_str) | |
| print() | |
| print("*"*20) | |
| class Game: | |
| def __init__(self, grid, instructions): | |
| self.grid = grid | |
| self.instructions = list(instructions) | |
| self.robot_pos = self.get_robot_start_pos(self.grid) | |
| self.M = len(grid) | |
| self.N = len(grid[0]) | |
| def get_robot_start_pos(self, 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 pprint(self): | |
| pprint(grid) | |
| def get_blocks_in_direction(self, pos, direction): | |
| i, j = pos.i, pos.j | |
| if direction == "^": | |
| # Up | |
| # i -= 1 | |
| return [self.grid[ii][j] for ii in range(i, -1, -1)] | |
| elif direction == "v": | |
| # Down | |
| # i += 1 | |
| return [self.grid[ii][j] for ii in range(i, self.M)] | |
| elif direction == "<": | |
| # Left | |
| # j -= 1 | |
| return [self.grid[i][jj] for jj in range(j, -1, -1 )] | |
| elif direction == ">": | |
| # Right | |
| # j += 1 | |
| return [self.grid[i][jj] for jj in range(j, self.N)] | |
| else: | |
| raise ValueError("Invalid direction") | |
| def get_index(self, blocks: list[str], shape: str): | |
| if shape in blocks: | |
| return blocks.index(shape) | |
| else: | |
| return float("inf") | |
| def update_blocks_in_direction(self, blocks, pos, direction): | |
| i, j = pos.i, pos.j | |
| if direction == "^": | |
| # Up | |
| # i -= 1 | |
| for ii, block in zip(range(i, -1, -1), blocks): | |
| self.grid[ii][j] = block | |
| self.robot_pos.i -= 1 | |
| elif direction == "v": | |
| # Down | |
| # i += 1 | |
| for ii, block in zip(range(i, self.M), blocks): | |
| self.grid[ii][j] = block | |
| self.robot_pos.i += 1 | |
| elif direction == "<": | |
| # Left | |
| # j -= 1 | |
| # return [grid[i][jj] for jj in range(j, -1, -1 )] | |
| for jj, block in zip(range(j, -1, -1), blocks): | |
| self.grid[i][jj] = block | |
| self.robot_pos.j -= 1 | |
| elif direction == ">": | |
| # Right | |
| # j += 1 | |
| # return [grid[i][jj] for jj in range(j, self.N)] | |
| for jj, block in zip(range(j, self.N), blocks): | |
| self.grid[i][jj] = block | |
| self.robot_pos.j += 1 | |
| return | |
| def move(self, direction): | |
| # Get the blocks in the desired movement direction | |
| blocks = self.get_blocks_in_direction(self.robot_pos, direction) | |
| # Check that a free space appears before a wall | |
| free_space_idx = self.get_index(blocks, ".") | |
| wall_idx = self.get_index(blocks, "#") | |
| valid_move = free_space_idx < wall_idx | |
| if not valid_move: | |
| # Don't do anything | |
| # print("Didn't move!") | |
| return False | |
| # Shit the blocks over 1 until next free space | |
| for jj in range(free_space_idx, 0, -1): | |
| blocks[jj] = blocks[jj-1] | |
| blocks[0] = "." | |
| self.update_blocks_in_direction(blocks, self.robot_pos, direction) | |
| return True | |
| def step(self): | |
| direction = self.instructions.pop(0) | |
| is_moved = self.move(direction) | |
| return direction, is_moved | |
| def compute_gps(self, i, j): | |
| return i * 100 + j | |
| def compute_total_gps(self): | |
| total = 0 | |
| for i in range(self.M): | |
| for j in range(self.N): | |
| if self.grid[i][j] == "O": | |
| total += self.compute_gps(i, j) | |
| return total | |
| file = "input.txt" | |
| grid, instructions = read_data(file) | |
| game = Game(grid, instructions) | |
| # game.pprint() | |
| while len(game.instructions) > 0: | |
| direction, is_moved = game.step() | |
| # print("Next move: ", direction) | |
| # game.pprint() | |
| print(game.compute_total_gps()) | |
| # ## Part 2 | |
| # def get_larger_grid(grid): | |
| # M = len(grid) | |
| # N = len(grid[0]) | |
| # for i in range(M): | |
| # for j in range(N): | |
| # if grid[i][j] == "#": | |
| # grid[i][j] = "##" | |
| # elif grid[i][j] == "O": | |
| # grid[i][j] = "[]" | |
| # elif grid[i][j] == ".": | |
| # grid[i][j] = ".." | |
| # elif grid[i][j] == "@": | |
| # grid[i][j] = "@." | |
| # else: | |
| # raise ValueError("Invalid char") | |
| # for i in range(M): | |
| # grid[i] = list("".join(grid[i])) | |
| # return grid | |
| # class Game2(Game): | |
| # def get_blocks_in_direction(self, pos, direction): | |
| # i, j = pos.i, pos.j | |
| # if direction == "^": | |
| # # Up | |
| # # i -= 1 | |
| # return [self.grid[ii][j] for ii in range(i, -1, -1)] | |
| # elif direction == "v": | |
| # # Down | |
| # # i += 1 | |
| # return [self.grid[ii][j] for ii in range(i, self.M)] | |
| # elif direction == "<": | |
| # # Left | |
| # # j -= 1 | |
| # return [self.grid[i][jj] for jj in range(j, -1, -1 )] | |
| # elif direction == ">": | |
| # # Right | |
| # # j += 1 | |
| # return [self.grid[i][jj] for jj in range(j, self.N)] | |
| # else: | |
| # raise ValueError("Invalid direction") | |
| # def get_index(self, blocks: list[str], shape: str): | |
| # if shape in blocks: | |
| # return blocks.index(shape) | |
| # else: | |
| # return float("inf") | |
| # def update_blocks_in_direction(self, blocks, pos, direction): | |
| # i, j = pos.i, pos.j | |
| # if direction == "^": | |
| # # Up | |
| # # i -= 1 | |
| # for ii, block in zip(range(i, -1, -1), blocks): | |
| # self.grid[ii][j] = block | |
| # self.robot_pos.i -= 1 | |
| # elif direction == "v": | |
| # # Down | |
| # # i += 1 | |
| # import ipdb; ipdb.set_trace(); | |
| # for ii, block in zip(range(i, self.M), blocks): | |
| # self.grid[ii][j] = block | |
| # self.robot_pos.i += 1 | |
| # elif direction == "<": | |
| # # Left | |
| # # j -= 1 | |
| # # return [grid[i][jj] for jj in range(j, -1, -1 )] | |
| # for jj, block in zip(range(j, -1, -1), blocks): | |
| # self.grid[i][jj] = block | |
| # self.robot_pos.j -= 1 | |
| # elif direction == ">": | |
| # # Right | |
| # # j += 1 | |
| # # return [grid[i][jj] for jj in range(j, self.N)] | |
| # for jj, block in zip(range(j, self.N), blocks): | |
| # self.grid[i][jj] = block | |
| # self.robot_pos.j += 1 | |
| # return | |
| # def move(self, direction): | |
| # # Get the blocks in the desired movement direction | |
| # blocks = self.get_blocks_in_direction(self.robot_pos, direction) | |
| # # Check that a free space appears before a wall | |
| # free_space_idx = self.get_index(blocks, ".") | |
| # wall_idx = self.get_index(blocks, "#") | |
| # rule_1 = free_space_idx < wall_idx | |
| # if direction in ["<", ">"]: | |
| # valid_move = rule_1 | |
| # else: | |
| # rule_2 = free_space_idx < wall_idx | |
| # if not valid_move: | |
| # # Don't do anything | |
| # # print("Didn't move!") | |
| # return False | |
| # # Shit the blocks over 1 until next free space | |
| # for jj in range(free_space_idx, 0, -1): | |
| # blocks[jj] = blocks[jj-1] | |
| # blocks[0] = "." | |
| # self.update_blocks_in_direction(blocks, self.robot_pos, direction) | |
| # return True | |
| # file = "test.txt" | |
| # grid, instructions = read_data(file) | |
| # grid = get_larger_grid(grid) | |
| # game = Game2(grid, instructions) | |
| # # def get_larger_grid(grid): | |
| # # M = len(grid) | |
| # # N = len(grid[0]) | |
| # # for i in range(M): | |
| # # for j in range(N): | |
| # # if grid[i][j] == "#": | |
| # # grid[i][j] = "##" | |
| # # elif grid[i][j] == "O": | |
| # # grid[i][j] = "[]" | |
| # # elif grid[i][j] == ".": | |
| # # grid[i][j] = ".." | |
| # # elif grid[i][j] == "@": | |
| # # grid[i][j] = "@." | |
| # # else: | |
| # # raise ValueError("Invalid char") | |
| # # return grid | |
| # # | |
| # # | |
| # # class Game2(Game): | |
| # # def get_robot_start_pos(self, grid): | |
| # # | |
| # # M = len(grid) | |
| # # N = len(grid[0]) | |
| # # | |
| # # for i in range(M): | |
| # # for j in range(N): | |
| # # if "@" in grid[i][j]: | |
| # # return Position(i, j) | |
| # # | |
| # # | |
| # # def get_index(self, blocks: list[str], shape: str): | |
| # # if shape in blocks: | |
| # # return blocks.index(shape) | |
| # # else: | |
| # # return float("inf") | |
| # # | |
| # # | |
| # # def get_blocks_in_direction(self, pos, direction): | |
| # # i, j = pos.i, pos.j | |
| # # | |
| # # if direction == "^": | |
| # # # Up | |
| # # # i -= 1 | |
| # # blocks = [self.grid[ii][j] for ii in range(i, -1, -1)] | |
| # # print(blocks) | |
| # # # return "".join([b[::-1] for b in blocks]) | |
| # # return "".join([b for b in blocks]) | |
| # # | |
| # # elif direction == "v": | |
| # # # Down | |
| # # # i += 1 | |
| # # blocks = [self.grid[ii][j] for ii in range(i, self.M)] | |
| # # print(blocks) | |
| # # return "".join([b[::-1] for b in blocks]) | |
| # # | |
| # # elif direction == "<": | |
| # # # Left | |
| # # # j -= 1 | |
| # # blocks = [self.grid[i][jj] for jj in range(j, -1, -1 )] | |
| # # return "".join([b[::-1] for b in blocks]) | |
| # # | |
| # # elif direction == ">": | |
| # # # Right | |
| # # # j += 1 | |
| # # blocks = [self.grid[i][jj] for jj in range(j, self.N)] | |
| # # return "".join([b for b in blocks]) | |
| # # | |
| # # else: | |
| # # raise ValueError("Invalid direction") | |
| # # | |
| # # | |
| # # def update_blocks_in_direction(self, blocks: list[str], pos, direction): | |
| # # i, j = pos.i, pos.j | |
| # # | |
| # # | |
| # # # Simple cases first | |
| # # # Might be overcomplicating... | |
| # # if self.grid[i][j] == "@." and direction == ">": | |
| # # self.grid[i][j] = ".@" | |
| # # return True | |
| # # | |
| # # if self.grid[i][j] == ".@" and direction == "<": | |
| # # self.grid[i][j] = "@." | |
| # # return True | |
| # # | |
| # # # Not a simple case | |
| # # # First shift the blocks by 1 | |
| # # new_blocks = blocks.copy() | |
| # # new_blocks[1:] = blocks[0:-1] | |
| # # new_blocks[0] = "." | |
| # # new_blocks[-2] = "#" # Always 2 walls at end | |
| # # | |
| # # def reconstruct(new_blocks): | |
| # # | |
| # # # Group them back to 2 by 2 | |
| # # reconstructed = [] | |
| # # b = "".join(new_blocks) | |
| # # for i in range(0, len(b), 2): | |
| # # reconstructed.append(b[i:i+2]) | |
| # # return reconstructed | |
| # # | |
| # # | |
| # # | |
| # # blocks = reconstruct(new_blocks) | |
| # # | |
| # # if direction == "^": | |
| # # # Up | |
| # # # i -= 1 | |
| # # for ii, block in zip(range(i, -1, -1), blocks): | |
| # # self.grid[ii][j] = block | |
| # # self.robot_pos.i -= 1 | |
| # # | |
| # # elif direction == "v": | |
| # # # Down | |
| # # # i += 1 | |
| # # for ii, block in zip(range(i, self.M), blocks): | |
| # # self.grid[ii][j] = block | |
| # # self.robot_pos.i += 1 | |
| # # | |
| # # elif direction == "<": | |
| # # # Left | |
| # # # j -= 1 | |
| # # # return [grid[i][jj] for jj in range(j, -1, -1 )] | |
| # # | |
| # # for jj, block in zip(range(j, -1, -1), blocks): | |
| # # self.grid[i][jj] = block | |
| # # | |
| # # self.robot_pos.j -= 1 | |
| # # elif direction == ">": | |
| # # # Right | |
| # # # j += 1 | |
| # # # return [grid[i][jj] for jj in range(j, self.N)] | |
| # # | |
| # # for jj, block in zip(range(j, self.N), blocks): | |
| # # self.grid[i][jj] = block | |
| # # self.robot_pos.j += 1 | |
| # # else: | |
| # # raise ValueError("Wrong") | |
| # # | |
| # # return | |
| # # | |
| # # def move(self, direction): | |
| # # | |
| # # # Get the blocks in the desired movement direction | |
| # # blocks = self.get_blocks_in_direction(self.robot_pos, direction) | |
| # # | |
| # # import ipdb; ipdb.set_trace(); | |
| # # # Blocks are grouped 2 by 2, here we get tehn in single extended list | |
| # # blocks = list("".join(blocks)) | |
| # # | |
| # # # Check that a free space appears before a wall | |
| # # free_space_idx = self.get_index(blocks, ".") | |
| # # wall_idx = self.get_index(blocks, "#") | |
| # # | |
| # # # Check whether it is valid to move or not | |
| # # if direction in [">","<"]: | |
| # # valid_move = free_space_idx < wall_idx | |
| # # else: | |
| # # valid_1 = free_space_idx < wall_idx | |
| # # valid_2 = not("[" in blocks[:free_space_idx] and "]" in blocks[:free_space_idx]) | |
| # # | |
| # # if not valid_1: | |
| # # print("Not valid1") | |
| # # if not valid_2: | |
| # # print("Not valid2") | |
| # # | |
| # # valid_move = valid_1 and valid_2 | |
| # # | |
| # # | |
| # # if not valid_move: | |
| # # # Don't do anything | |
| # # print("Didn't move!") | |
| # # return False | |
| # # | |
| # # self.update_blocks_in_direction(blocks, self.robot_pos, direction) | |
| # # return True | |
| # # | |
| # # | |
| # # | |
| # # | |
| # # | |
| # # | |
| # # file = "test3.txt" | |
| # # | |
| # # grid, instructions = read_data(file) | |
| # # grid = get_larger_grid(grid) | |
| # # game = Game2(grid, instructions) | |
| # # | |
| # # # game.pprint() | |
| # # # while len(game.instructions) > 0: | |
| # # # direction, is_moved = game.step() | |
| # # # print("Next move: ", direction) | |
| # # # game.pprint() | |