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 @dataclass 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()