diff --git a/2019/day17.py b/2019/day17.py index 0c22a12..ca06987 100644 --- a/2019/day17.py +++ b/2019/day17.py @@ -1,29 +1,89 @@ -from collections import defaultdict - from grid import Grid from intcode import Intcode +from vector import * def main(): with open("day17.txt", "rt") as input_file: codes = Intcode.parse_input(input_file.read().rstrip()) - camera = Camera(codes) - camera.run() - intersections = camera.intersect() + # Part 1 + robot = Robot(codes) + robot.run() + intersections = robot.intersect() print("Sum:", sum(x * y for x, y in intersections)) + # Part 2 + start = robot.start + sx, sy, so = start + print(f"Start at {(sx, sy)} oriented {so}.") + path = compute_path(robot.img, start) + print(f"Traced path: {path}.") + # Patterns found in my editor from the above result... + patterns = { + "M": "ABBACBCCBA", + "A": ['R', 10, 'R', 8, 'L', 10, 'L', 10], + "B": ['R', 8, 'L', 6, 'L', 6], + "C": ['L', 10, 'R', 10, 'L', 6], + } + codes[0] = 2 + robot = Robot(codes, patterns=patterns, video_fb=False) + robot.run() -class Camera(Intcode): - def __init__(self, *args, **kwargs): +N = ord("\n") +S = ord("#") + + +class Robot(Intcode): + + ORI_U = (0, -1) + ORI_R = (1, 0) + ORI_D = (0, +1) + ORI_L = (-1, 0) + ORI_INDEXES = {ORI_U: 0, ORI_R: 1, ORI_D: 2, ORI_L: 3} + ASCII_ORI = {"^": ORI_U, ">": ORI_R, "v": ORI_D, "<": ORI_L} + + def __init__(self, *args, patterns=None, video_fb=False, **kwargs): super().__init__(*args, **kwargs) self.out_x = 0 self.out_y = 0 self.img = Grid() + self.start = None + self.moving = False + self.video_fb = video_fb + self.video_last_byte = None + self.patterns = patterns + self.robot_inputs = [] + if self.patterns: + self.input_gen() + + def input_data(self): + self.moving = True + data = self.robot_inputs.pop(0) + return data + + def input_gen(self): + self.robot_inputs += [ord(x) for x in ",".join(self.patterns["M"])] + [N] + for pname in "ABC": + pdata = ",".join(map(lambda x: str(x), self.patterns[pname])) + self.robot_inputs += [ord(x) for x in pdata] + [N] + self.robot_inputs += [ord("y" if self.video_fb else "n"), N] def output_data(self, data): - if data == ord("\n"): + if data > 256: + print("Output", data) + return + + if self.video_fb: + print(chr(data), end="") + if all(d == N for d in [data, self.video_last_byte]): + input() + self.video_last_byte = data + if self.moving: + return + + if data == N: self.out_x, self.out_y = 0, self.out_y + 1 else: self.img.g[self.out_y][self.out_x] = data @@ -32,19 +92,49 @@ class Camera(Intcode): def intersect(self): intersections = [] g = self.img.g - for x, col in list(g.items()).copy(): - for y, v in list(col.items()).copy(): - if all( - e == ord("#") - for e in [v, g[x][y-1], g[x][y+1], g[x+1][y], g[x-1][y]] - ): - print("O", end="") + for y, row in list(g.items()).copy(): + for x, v in list(row.items()).copy(): + if all(e == S for e in [v, g[y][x-1], g[y][x+1], g[y+1][x], g[y-1][x]]): intersections.append((x, y)) - else: - print(chr(v), end="") - print() + elif chr(v) in Robot.ASCII_ORI.keys(): + self.start = (x, y, Robot.ASCII_ORI[chr(v)]) return intersections +def compute_path(grid, start): + pos, o = (start[0], start[1]), start[2] + path = [] + steps = 0 + while True: + forward_pos = v2a(pos, o) + near_pos = grid.near_items(pos) + if near_pos[forward_pos] == S: + pos = forward_pos + steps += 1 + continue + if steps: + path.append(steps) + steps = 0 + + for n in [p for p, v in near_pos.items() if v == S]: + res = turn(pos, n, o) + if res is not None: + path.append(res[0]) + o = res[1] + break + else: + break + + return path + +def turn(pos, next_pos, cur_ori): + new_ori = (next_pos[0] - pos[0], next_pos[1] - pos[1]) + diff = (Robot.ORI_INDEXES[new_ori] - Robot.ORI_INDEXES[cur_ori]) % 4 + if diff == 1: + return "R", new_ori + elif diff == 3: + return "L", new_ori + + if __name__ == "__main__": main() diff --git a/2019/grid.py b/2019/grid.py index e41e8ec..7e6e6f8 100644 --- a/2019/grid.py +++ b/2019/grid.py @@ -6,7 +6,33 @@ class Grid: def __init__(self): self.g = defaultdict(lambda: defaultdict(int)) - def values(self): - for a, chunk in self.g.items(): - for b, v in chunk.items(): - yield a, b, v + @staticmethod + def near(p): + """Return a tuple of neighbor positions (up, left, down, right).""" + return ( + (p[0] , p[1] - 1), + (p[0] + 1, p[1] ), + (p[0] , p[1] + 1), + (p[0] - 1, p[1] ), + ) + + def near_items(self, p): + """Return a dict of neighbor positions to values (U, L, D, R).""" + return {pos: self.g[pos[1]][pos[0]] for pos in Grid.near(p)} + + def print_near(self, p): + print("".join([ + chr(self.g[p[1] - 1][p[0] - 1]), + chr(self.g[p[1] - 1][p[0]]), + chr(self.g[p[1] - 1][p[0] + 1]), + ])) + print("".join([ + chr(self.g[p[1] ][p[0] - 1]), + chr(self.g[p[1] ][p[0]]), + chr(self.g[p[1] ][p[0] + 1]), + ])) + print("".join([ + chr(self.g[p[1] + 1][p[0] - 1]), + chr(self.g[p[1] + 1][p[0]]), + chr(self.g[p[1] + 1][p[0] + 1]), + ])) diff --git a/2019/vector.py b/2019/vector.py new file mode 100644 index 0000000..8bbb707 --- /dev/null +++ b/2019/vector.py @@ -0,0 +1,2 @@ +def v2a(a, b): + return a[0] + b[0], a[1] + b[1]