def main(): argparse = ArgumentParser() argparse.add_argument("file", type=str, nargs="?", default="21-input.txt") args = argparse.parse_args() with open(args.file, "r") as f: program = [int(c) for c in f.read().split(',')] instructions = [ ord(c) for c in "".join(l + "\n" for l in ( # if a hole is at distance two, jump now "NOT B T", "OR T J", # if a hole is at distance three, jump now "NOT C T", "OR T J", # But only if there is ground at distance 4 "AND D J", # And there is ground at distance 8 "AND H J", # If we're about to walk into a hole, jump anyway "NOT A T", "OR T J", "RUN")) ] vm = IntVM(program, inputs=instructions, output_func=lambda s: print(chr(s), end='', flush=True) if s < 127 else print(s)) vm.run()
def main(): argparse = ArgumentParser() argparse.add_argument("file", type=str, nargs="?", default="21-input.txt") args = argparse.parse_args() with open(args.file, "r") as f: program = [int(c) for c in f.read().split(',')] instructions = [ ord(c) for c in "".join(l + "\n" for l in ( "NOT A J", # jump if we're about to walk into a hole # if a hole is at distance two and there is ground at distance four, # jump now because maybe distance five is a hole "NOT B T", "AND D T", "OR T J", # if a hole is at distance three and there is ground at distance four, # jump now because maybe distance five is a hole "NOT C T", "AND D T", "OR T J", "WALK")) ] vm = IntVM(program, inputs=instructions, output_func=lambda s: print(chr(s), end='', flush=True) if s < 127 else print(s)) vm.run()
class Arcade: EMPTY = 0 WALL = 1 BLOCK = 2 PADDLE = 3 BALL = 4 TILES = (" ", "█", "▒", "━", "●") def __init__(self, program, screen_w=37, screen_h=20): self.screen = np.zeros((screen_w, screen_h), dtype=int) self.program = program self.vm = IntVM(program, inputs=[], output_func=MultiOutput(self._draw, 3)) def _draw(self, x, y, tile): self.screen[x, y] = tile def run(self): self.vm.run() def draw_screen(self): for row in self.screen.T: print(''.join(self.TILES[t] if 0 <= t < len(self.TILES) else "?" for t in row))
class Imager: def __init__(self, program): self.program = program self.pixel_cache = {} self.vm = IntVM(self.program) def beam_at(self, y, x): try: return self.pixel_cache[(y, x)] except KeyError: def process_pixel(val): self.pixel_cache[(y, x)] = val self.vm.input_gen = iter((x, y)) self.vm.output_func = process_pixel self.vm.run() return self.pixel_cache[(y, x)] def find_left(self, y, x): # Assumes x to be to the left of the starting pixel while not self.beam_at(y, x): x += 1 # Returns first pixel inside beam return x def find_right(self, y, x): # Assumes x to be to the left of, or inside the beam while not self.beam_at(y, x): x += 1 while self.beam_at(y, x): x += 1 # Returns last pixel inside beam return x - 1 def find_square(self, size=100): # Top right edge ty, tx = 10, 0 # Bottom left edge by, bx = 10 + size - 1, 0 tx = self.find_right(ty, tx) bx = self.find_left(by, bx) while tx - bx < size - 1: ty += 1 by += 1 tx = self.find_right(ty, tx) bx = self.find_left(by, bx) print("Width of rectangle of height {} at y={}: {}..{}={}".format( size, ty, bx, tx, tx - bx + 1)) return (ty, tx), (by, bx) def get_map(self, miny, maxy, minx, maxx): return np.array([[self.beam_at(y, x) for x in range(minx, maxx)] for y in range(miny, maxy)], dtype=int)
def get_tractor_map(self): vm = IntVM(self.program) image = np.zeros((50, 50), dtype=int) for y in range(50): for x in range(50): def process_pixel(val): image[y, x] = val vm.input_gen = iter([x, y]) vm.output_func = process_pixel vm.run() return image
class Imager: SPACE = 0 SCAFFOLD = 1 def __init__(self, program): self.img_coord = 0, 0 self.robot_coord = None self.program = program self.vm = IntVM(self.program, inputs=[], output_func=self.process_pixel) self.image = np.zeros((40, 51), dtype=int) self.map = "" def run(self): self.vm.run() def process_pixel(self, value): self.map = self.map + chr(value) x, y = self.img_coord if value == ord('\n'): x, y = 0, y + 1 elif value == ord('.'): self.image[x, y] = self.SPACE x += 1 elif value in (ord('#'), ord('<'), ord('^'), ord('>'), ord('v')): self.image[x, y] = self.SCAFFOLD self.robot_coord = x, y x += 1 self.img_coord = x, y def get_answer(self): intersections = (self.image[1:-1, 1:-1] == self.SCAFFOLD) & \ (self.image[0:-2, 1:-1] == self.SCAFFOLD) & \ (self.image[2:, 1:-1] == self.SCAFFOLD) & \ (self.image[1:-1, 0:-2] == self.SCAFFOLD) & \ (self.image[1:-1, 2:] == self.SCAFFOLD) return sum((x + 1) * (y + 1) for x, y in np.argwhere(intersections))
class Arcade: EMPTY = 0 WALL = 1 BLOCK = 2 PADDLE = 3 BALL = 4 TILES = (" ", "█", "▒", "═", "o") JOY_LEFT = -1 JOY_MIDDLE = 0 JOY_RIGHT = 1 def __init__(self, program, quarters=2, fps=10): self.program = program.copy() self.program[0] = quarters self.vm = IntVM(self.program, inputs=self, output_func=MultiOutput(self._output, 3)) self.score = 0 self.paddle_x = 0 self.joystick_position = self.JOY_MIDDLE self.scr = None self.last_screen = 0 self.screen_interval = 1 / fps if fps > 0 else 0 def __iter__(self): return iter(lambda: self.joystick_position, -2) def _output(self, x, y, tile): if (x, y) == (-1, 0): self.score = tile else: self.scr.addstr( y, x, self.TILES[tile] if 0 <= tile < len(self.TILES) else "?") if tile == self.BALL: self.ball_x = x self.adjust_joystick() elif tile == self.PADDLE: self.paddle_x = x self.adjust_joystick() self.draw_screen() def adjust_joystick(self): if self.ball_x < self.paddle_x: self.joystick_position = self.JOY_LEFT elif self.ball_x > self.paddle_x: self.joystick_position = self.JOY_RIGHT else: self.joystick_position = self.JOY_MIDDLE def run(self, stdscr): self.scr = stdscr self.scr.nodelay(1) curses.curs_set(0) self.vm.run() self.scr.nodelay(0) self.scr.addstr( 23, 0, "Score: {}, GAME OVER - press key to exit".format(self.score)) self.scr.refresh() self.scr.getkey() def draw_screen(self): if self.scr.getch() == 3: sys.exit(0) if self.screen_interval != 0: now = time.monotonic() if now - self.last_screen < self.screen_interval: time.sleep(self.screen_interval - (now - self.last_screen)) self.scr.addstr(23, 0, "Score: {}".format(self.score)) self.scr.refresh() self.last_screen = time.monotonic()
class OxygenFinder: NORTH = 1 SOUTH = 2 WEST = 3 EAST = 4 deltas = { NORTH: (0, -1), SOUTH: (0, +1), WEST: (-1, 0), EAST: (+1, 0), } STATUS_WALL = 0 STATUS_MOVED = 1 STATUS_FOUND_OXYGEN = 2 TILE_UNKNOWN = 0 TILE_EMPTY = 1 TILE_WALL = 2 TILE_OXYGEN = 3 TILES = [".", " ", "#", "O"] def __init__(self, program, fps=10, explore_map=False): self.map = np.zeros((1000, 1000), dtype=int) self.current_coord = 500, 500 self.initial_coord = self.current_coord self.oxygen_coord = None self.oxygen_distance = None self.map[self.current_coord] = self.TILE_EMPTY self.current_direction = self.WEST self.explore_queue = [] x, y = self.current_coord self.min_x, self.min_y, self.max_x, self.max_y = x, y, x, y self.queue_surrounding_tiles() self.curses_window = None self.explore_map = explore_map self.finished = False self.last_screen = 0 self.screen_interval = 1 / fps if fps > 0 else 0 self.other_screen_interval = 0 self.program = program self.vm = IntVM(self.program, inputs=self, output_func=self.process_output) def __iter__(self): return iter(self.get_input, -1) def find_path(self, start, to): dist_map = np.zeros(self.map.shape, dtype=int) dist_map.fill(10000000) dist_map[to] = 0 stack = [to] while stack: x, y = stack.pop(-1) if (x, y) == start: break current_dist = dist_map[x, y] + 1 for candidate in (x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1): if self.map[candidate] not in (self.TILE_WALL, self.TILE_OXYGEN) and \ (self.map[candidate] != self.TILE_UNKNOWN or self.map[x, y] != self.TILE_UNKNOWN) \ and current_dist < dist_map[candidate]: stack.append(candidate) dist_map[candidate] = current_dist min_dist = 100000 min_direction = None x, y = start for direction, (dx, dy) in self.deltas.items(): candidate = (x + dx, y + dy) if dist_map[candidate] < min_dist: min_dist = dist_map[candidate] min_direction = direction return min_direction, min_dist def get_input(self): if self.explore_queue: direction, _ = self.find_path(self.current_coord, self.explore_queue[-1]) self.current_direction = direction else: self.vm.stop() return self.current_direction def get_next_coord(self, direction=None): direction = self.current_direction if direction is None else direction x, y = self.current_coord dx, dy = self.deltas[direction] return x + dx, y + dy def queue_surrounding_tiles(self): for direction in (self.NORTH, self.EAST, self.WEST, self.SOUTH): nx, ny = self.get_next_coord(direction) if self.map[nx, ny] == self.TILE_UNKNOWN and ( nx, ny) not in self.explore_queue: add = True if self.oxygen_distance is not None and not self.explore_map: _, dist = self.find_path(self.oxygen_coord, (nx, ny)) add = dist < self.oxygen_distance if add: self.explore_queue.append((nx, ny)) def process_output(self, value): # Process a robot status code if value == self.STATUS_WALL: wall_coord = self.get_next_coord() self.map[wall_coord] = self.TILE_WALL try: self.explore_queue.remove(wall_coord) except ValueError: pass elif value == self.STATUS_MOVED: self.current_coord = self.get_next_coord() self.map[self.current_coord] = self.TILE_EMPTY try: self.explore_queue.remove(self.current_coord) except ValueError: pass self.queue_surrounding_tiles() elif value == self.STATUS_FOUND_OXYGEN: self.current_coord = self.get_next_coord() self.map[self.current_coord] = self.TILE_OXYGEN self.oxygen_coord = self.current_coord try: self.explore_queue.remove(self.current_coord) except ValueError: pass _, self.oxygen_distance = self.find_path(self.initial_coord, self.oxygen_coord) self.finished = len(self.explore_queue) == 0 x, y = self.current_coord self.min_x = min(self.min_x, x) self.min_y = min(self.min_y, y) self.max_x = max(self.max_x, x) self.max_y = max(self.max_y, y) self.frame() def draw_map(self): sx, sy = self.min_x - 1, self.min_y - 1 my, mx = self.curses_window.getmaxyx() for y in range(self.min_y - 1, self.max_y + 2): for x in range(self.min_x - 1, self.max_x + 2): if y - sy + 1 < my and x - sx < mx: self.curses_window.addch(y - sy + 1, x - sx, ord(self.TILES[self.map[x, y]])) x, y = self.current_coord if y - sy < my and x - sx < mx and self.current_direction: self.curses_window.addch( y - sy + 1, x - sx, ord(["^", "v", "<", ">"][self.current_direction - 1])) x, y = self.initial_coord if y - sy < my and x - sx < mx: self.curses_window.addch(y - sy + 1, x - sx, ord("X")) def frame(self): key = self.curses_window.getch() if key == 3: # Ctrl+C sys.exit(0) elif key == ord(' '): self.switch_ffwd() if self.screen_interval != 0: now = time.monotonic() if now - self.last_screen < self.screen_interval: time.sleep(self.screen_interval - (now - self.last_screen)) self.draw_map() self.curses_window.refresh() self.last_screen = time.monotonic() def run(self): while not self.finished: self.vm.reset() self.current_coord = self.initial_coord self.vm.run() if self.explore_map: self.save_map() def run_curses(self, stdscr): self.curses_window = stdscr self.curses_window.nodelay(1) curses.curs_set(0) self.curses_window.addstr(0, 0, "Spacebar = speed up") self.run() self.draw_map() self.curses_window.nodelay(0) _, oxygen_distance = self.find_path(self.initial_coord, self.oxygen_coord) oxygen_distance += 1 # Answer is to get on top of the oxygen, self.curses_window.addstr( 0, 0, "Distance {} -> {} = {}".format(self.initial_coord, self.oxygen_coord, oxygen_distance)) self.curses_window.refresh() # Wait for ctrl+c while self.curses_window.getch() != 3: pass def switch_ffwd(self): self.screen_interval, self.other_screen_interval = self.other_screen_interval, self.screen_interval def save_map(self): sx, sy = self.min_x - 1, self.min_y - 1 ex, ey = self.max_x + 2, self.max_y + 2 with open("15-map.txt", "w") as f: f.writelines( ''.join(self.TILES[t] if (x, y) != self.initial_coord else "X" for x, t in enumerate(self.map[sx:ex, y], sx)) + "\n" for y in range(sy, ey))
class Imager: SPACE = 0 SCAFFOLD = 1 NORTH = 0 EAST = 1 SOUTH = 2 WEST = 3 deltas = [(0, -1), (1, 0), (0, 1), (-1, 0)] directions = {'<': WEST, '>': EAST, '^': NORTH, 'v': SOUTH} def __init__(self, program): self.img_coord = 0, 0 self.robot_coord = None self.robot_direction = None self.program = program self.input = [] self.vm = IntVM(self.program, inputs=self.input, output_func=self.process_pixel) self.image = np.zeros((40, 51), dtype=int) self.raw_map = "" self.map = None def get_initial_image(self): self.vm.program[0] = 1 self.vm.output_func = self.process_pixel self.vm.run() w, h = self.image.shape # Add zeroes to the edge of the map to avoid peppering everything with boundary checks self.map = np.zeros((w + 2, h + 2), dtype=int) self.map[1:-1, 1:-1] = self.image self.robot_coord = self.robot_coord[0] + 1, self.robot_coord[1] + 1 def process_pixel(self, raw_value): x, y = self.img_coord value = chr(raw_value) self.raw_map = self.raw_map + value if value == '\n': x, y = 0, y + 1 elif value == '.': self.image[x, y] = self.SPACE x += 1 elif value in ('#', '<', '^', '>', 'v'): self.image[x, y] = self.SCAFFOLD if value in self.directions: self.robot_coord = x, y self.robot_direction = self.directions[value] x += 1 self.img_coord = x, y def get_desired_route(self): at_end = False x, y = self.robot_coord direction = self.robot_direction while not at_end: dx, dy = self.deltas[direction] if self.map[x + dx, y + dy] == self.SCAFFOLD: x += dx y += dy yield '1' else: rdx, rdy = self.deltas[(direction + 1) % 4] ldx, ldy = self.deltas[(direction - 1) % 4] if self.map[x + rdx, y + rdy] == self.SCAFFOLD: direction = (direction + 1) % 4 yield 'R' elif self.map[x + ldx, y + ldy] == self.SCAFFOLD: direction = (direction - 1) % 4 yield 'L' else: at_end = True def route_output(self, value): if value > 128: print("Answer: {}".format(value)) def run_route(self, main_func, a_func, b_func, c_func): self.vm.program[0] = 2 self.vm.output_func = self.route_output config_str = "\n".join([main_func, a_func, b_func, c_func, "n", ""]) self.vm.input_gen = iter(ord(c) for c in config_str) self.vm.run()