def __init__(self, prog: list[int]) -> None: self.painted: set[Coord] = set() self.white: set[Coord] = set() self.pos: Coord = Coord.origin() self.direction: Direction = Direction.UP self.machine = Machine(prog) self.machine.pause_after_output = True
class Scaffolding: def __init__(self, code: list[int]) -> None: self.m = Machine(code) self.scaffold: set[Coord] = set() self.intersections: set[Coord] = set() self.robot_loc: Coord self.robot_dir: Direction self.max: Coord = Coord.origin() self._scan_image() self._walk_scaffold() def _scan_image(self) -> None: """Find the robot and scaffold locations from IntCode machine output.""" self.m.run() image: str = "".join(chr(c) for c in self.m.output_vals) for row, line in enumerate(image.split("\n")): for col, char in enumerate(line): if char != ".": self.scaffold.add(Coord(row, col)) if (d := Direction.from_char(char)) is not None: self.robot_loc = Coord(row, col) self.robot_dir = d if col > self.max.y: self.max = Coord(self.max.y, col) if row > self.max.x: self.max = Coord(row, self.max.y)
class Player: def __init__(self, code: list[int]) -> None: self.m = Machine(code) self.m.pause_after_output = True self.m.pause_before_input = True self.ball_position: int = 0 self.paddle_position: int = 0 self.animate: bool = False self.score: int = 0 def _run(self, stdscr: Any) -> None: if self.animate: if curses.can_change_color(): curses.init_color(0, 0, 0, 0) # Set background (coloru 0) to rgb black stdscr.clear() curses.curs_set(0) # Hide the cursor while True: self.m.run() if self.m.halted: break elif len(self.m.output_vals) == 0: # Paused for input if self.paddle_position < self.ball_position: self.m.input_vals.append(1) elif self.paddle_position > self.ball_position: self.m.input_vals.append(-1) else: self.m.input_vals.append(0) elif len(self.m.output_vals) == 3: # Output full x, y, tile_id = self.m.output_vals self.m.output_vals = [] if (x, y) == (-1, 0): self.score = tile_id else: if self.animate: stdscr.addstr(y, x, tile(tile_id)) if tile_id == 4: self.ball_position = x if self.animate: stdscr.refresh() curses.napms(50) # stdscr.getkey() elif tile_id == 3: self.paddle_position = x else: # Wait for more output pass if self.animate: stdscr.refresh() stdscr.getkey() def run(self) -> Player: if self.animate: curses.wrapper(self._run) else: self._run(None) return self
def __init__(self, code: list[int]) -> None: self.m = Machine(code) self.m.pause_after_output = True self.m.pause_before_input = True self.ball_position: int = 0 self.paddle_position: int = 0 self.animate: bool = False self.score: int = 0
def __init__(self, code: list[int], debug: bool = False) -> None: self.m = Machine(code) self.m.pause_after_output = True self.m.pause_before_input = True self.loc: Coord = Coord.origin() self.walls: set[Coord] = set() # Locations we know are walls self.open: set[Coord] = {self.loc} # Locations we know are space self.oxygen: Optional[Coord] = None # Location of oxygen if known self.debug: bool = debug
def __init__(self, code: list[int]) -> None: self.m = Machine(code) self.scaffold: set[Coord] = set() self.intersections: set[Coord] = set() self.robot_loc: Coord self.robot_dir: Direction self.max: Coord = Coord.origin() self._scan_image() self._walk_scaffold()
class Robot: def __init__(self, prog: list[int]) -> None: self.painted: set[Coord] = set() self.white: set[Coord] = set() self.pos: Coord = Coord.origin() self.direction: Direction = Direction.UP self.machine = Machine(prog) self.machine.pause_after_output = True def paint(self, colour: Colour) -> Robot: self.painted.add(self.pos) if colour == Colour.WHITE: self.white.add(self.pos) else: if self.pos in self.white: self.white.remove(self.pos) return self def run(self) -> Robot: while True: if self.machine.input_vals: raise RuntimeError("Expected empty input") self.machine.input_vals = [ Colour.WHITE.value if self.pos in self.white else Colour.BLACK.value ] if self.machine.output_vals: raise RuntimeError("Expected empty outputs") while len(self.machine.output_vals) < 2: self.machine.run() if self.machine.halted: return self colour, rot = self.machine.output_vals self.machine.output_vals = [] self.paint(Colour(colour)) self.direction = self.direction.rotate(rot) self.pos = self.pos.move(self.direction) def show(self) -> Robot: xs = [pt.x for pt in self.white] ys = [pt.y for pt in self.white] x_min = min(xs) x_max = max(xs) y_min = min(ys) y_max = max(ys) for y in range(y_max, y_min - 1, -1): for x in range(x_min, x_max + 1): print("#" if Coord(x, y) in self.white else " ", end="") print() return self
def run_phase_settings_with_feedback( code: list[int], phase_settings: PhaseSettings, print_instructions: bool = False ) -> int: """Run a series machines with feedback from input 0 with given phase settings. >>> run_phase_settings_with_feedback(test_code_4, [9, 8, 7, 6, 5]) 139629729 >>> run_phase_settings_with_feedback(test_code_5, [9, 7, 8, 5, 6]) 18216 """ machines: list[Machine] = [Machine(code, [p]) for p in phase_settings] for m in machines: m.pause_after_output = True input_val: int = 0 machine_index: int = 0 while True: machines[machine_index].input_vals.append(input_val) machines[machine_index].run() if machines[machine_index].halted and machine_index == len(machines) - 1: return machines[machine_index].output_vals[-1] input_val = machines[machine_index].output_vals[-1] machine_index += 1 if machine_index >= len(machines): machine_index = 0
def run_machine( code: list[int], phase: int, input_val: int, print_instructions: bool = False ) -> int: if print_instructions: print(f"Running machine with phase setting {phase} and input {input_val}") m: Machine = Machine(code, [phase, input_val]) m.run(print_instructions) [output_value] = m.output_vals if print_instructions: print(f"Output {output_value}") return output_value
pass if self.animate: stdscr.refresh() stdscr.getkey() def run(self) -> Player: if self.animate: curses.wrapper(self._run) else: self._run(None) return self if __name__ == "__main__": # Turning this on, switches to curses animation (can't rely on redirecting stdin # as need to take key inputs) show_animation: bool = False if show_animation: with open("../aoc-data/input/2019_13.txt") as f: code: list[int] = [int(x) for x in f.read().split(",")] else: code = [int(x) for x in stdin.read().split(",")] blocks, _score = count_blocks(Machine(code).run().output_vals) print(blocks) code[0] = 2 p = Player(code) p.animate = show_animation p.run() print(p.score)
class Controller: def __init__(self, code: list[int], debug: bool = False) -> None: self.m = Machine(code) self.m.pause_after_output = True self.m.pause_before_input = True self.loc: Coord = Coord.origin() self.walls: set[Coord] = set() # Locations we know are walls self.open: set[Coord] = {self.loc} # Locations we know are space self.oxygen: Optional[Coord] = None # Location of oxygen if known self.debug: bool = debug def try_move(self, d: Direction) -> Response: """Try and move to an adjacent cell following the given direction.""" self.m.input_vals = [d.value] self.m.run() return Response(self.m.output_vals.pop()) def try_move_to_adj(self, c: Coord) -> Response: """Try and move to a given adjacent cell.""" for d in Direction: if self.loc.move(d) == c: return self.try_move(d) raise RuntimeError("Adjacent move not found", self.loc, c) def _path_to(self, target: Coord) -> list[Coord]: """Generate a path to the given cell through open cells.""" if self.debug: print("Routing from", self.loc, "to", target) # Flood fill with backlinks frontier: dict[Coord, Coord] = {self.loc: self.loc} visited: dict[Coord, Coord] = {} while target not in frontier: visited.update(frontier) new_frontier: dict[Coord, Coord] = {} for f in frontier.keys(): for n in f.neighbours(): if n == target or (n in self.open and n not in visited): new_frontier[n] = f frontier = new_frontier # Build back tracking path x = target path: list[Coord] = [] while x != self.loc: path.append(x) if x in frontier: x = frontier[x] else: x = visited[x] if self.debug: print("Path", path) return path def route_to(self, target: Coord) -> Response: """Try and move to a given cell going through known open cells.""" path = self._path_to(target) for x in reversed(path[1:]): if self.debug: print("Move to", x) response = self.try_move_to_adj(x) if response == Response.WALL: raise RuntimeError("Ran into wall during route") self.loc = x return self.try_move_to_adj(target) def steps_to_oxygen(self) -> int: """Find the number of steps from the origin to oxygen.""" self.loc = Coord.origin() if self.oxygen is None: raise RuntimeError("No oxygen found") return len(self._path_to(self.oxygen)) def oxygen_fill(self) -> int: """Find the number of minutes to fill all locations with oxygen.""" if self.oxygen is None: raise RuntimeError("No oxygen found to fill from") filled: set[Coord] = {self.oxygen} minutes: int = 0 while self.open - filled: additional: set[Coord] = set() for f in filled: for n in f.neighbours(): if n in self.open and n not in filled: additional.add(n) filled |= additional minutes += 1 return minutes def explore(self) -> Controller: """Explore the grid, completing the controllers knowledge of it.""" # Frontier is all unknown locations adjacent to places we have visited frontier: set[Coord] = self.loc.neighbours() while frontier: if self.debug: print("Loc:", self.loc) print("Fr:", " ".join([f"{c.x},{c.y}" for c in frontier])) target = self.loc.closest(frontier) frontier.discard(target) if self.debug: print("Going to", target) response = self.route_to(target) if response == Response.WALL: if self.debug: print("Wall at", target) self.walls.add(target) else: if self.debug: print("Open at", target) self.loc = target self.open.add(target) frontier |= target.neighbours() - (self.open | self.walls) if response == Response.MOVED_OXYGEN: self.oxygen = target return self def show(self) -> Controller: xs = [w.x for w in self.walls] ys = [w.y for w in self.walls] x_min = min(xs) x_max = max(xs) y_min = min(ys) y_max = max(ys) for y in range(y_max, y_min - 1, -1): for x in range(x_min, x_max + 1): if Coord(x, y) == Coord.origin(): print("X", end="") elif Coord(x, y) == self.oxygen: print("O", end="") elif Coord(x, y) in self.walls: print("#", end="") elif Coord(x, y) in self.open: print(".", end="") else: print(" ", end="") print() return self