class Robot: program: str position: Point = field(default=zero_point(2), init=False) hull: Dict[Point, Paint] = field( default_factory=lambda: defaultdict(lambda: Paint.BLACK), init=False) facing: Direction = field(default=Direction.UP, init=False) computer: Computer = field(init=False) def __post_init__(self): self.computer = Computer(Memory(map(int, self.program.split(",")))) def scan(self): self.computer.data_in.put(self.hull[self.position].value) def paint(self): self.hull[self.position] = Paint(self.computer.data_out.get()) def move(self): self.position += self.facing.value def turn(self): get = self.computer.data_out.get() self.facing = self.facing.prev() if get == 0 else self.facing.next() def iterate(self): self.scan() self.paint() self.turn() self.move() def run(self): threading.Thread(target=self.computer.run).start() while not self.computer.broken: self.iterate() return self.hull
def run(self): while not (self.computer.broken or self.oxygen): self.iterate() while not (self.computer.broken or self.position == zero_point(2)): self.iterate() self.p.terminate() self.print_hull()
def make_wire(instructions: List[Instruction]) -> set: current = zero_point(2) wire = {current} for direction, distance in instructions: for i in range(1, distance + 1): current += directions[direction] wire.add(current) return wire
def make_wire(instructions: List[Instruction]) -> Wire: current = zero_point(2) d = 0 wire = {} for direction, distance in instructions: for i in range(1, distance + 1): current += directions[direction] d += 1 if current not in wire: wire[current] = d return wire
class Robot: program: str position: Point = field(default=zero_point(2), init=False) hull: Dict[Point, Paint] = field( default_factory=lambda: defaultdict(lambda: Paint.WHITE), init=False) facing: Direction = field(default=Direction.UP, init=False) computer: Computer = field(init=False) def __post_init__(self): self.computer = Computer(Memory(map(int, self.program.split(",")))) def scan(self): self.computer.data_in.put(self.hull[self.position].value) def paint(self): self.hull[self.position] = Paint(self.computer.data_out.get()) def move(self): self.position += self.facing.value def turn(self): get = self.computer.data_out.get() self.facing = self.facing.prev() if get == 0 else self.facing.next() def iterate(self): self.scan() self.paint() self.turn() self.move() def print_hull(self): min_x = min(map(lambda p: p.x, self.hull.keys())) min_y = min(map(lambda p: p.y, self.hull.keys())) max_x = max(map(lambda p: p.x, self.hull.keys())) max_y = max(map(lambda p: p.y, self.hull.keys())) for y in range(max_y, min_y - 1, -1): for x in range(min_x, max_x + 1): if Point(x, y) in self.hull: if self.hull[Point(x, y)] == Paint.BLACK: print("██", end='') else: print("░░", end='') else: print("▒▒", end='') print() def run(self): threading.Thread(target=self.computer.run).start() while not self.computer.broken: self.iterate() self.print_hull()
def solve(data=None): data = parse_input(data) r = Robot(data) r.run() g = networkx.Graph() paths = [] for k, v in r.world.items(): if v == Status.MOVED or v == Status.OXYGEN: paths.append(k) for n in paths: g.add_node(n) for d in Direction: p = d.value + n if r.world[p] == Status.MOVED or r.world[p] == Status.OXYGEN: g.add_edge(n, p) return networkx.shortest_path_length(g, zero_point(2), r.oxygen)
def get_intersections(data: List[List[Tuple[str, int]]]) -> Set[Point]: wires = list(map(make_wire, data)) r = reduce(set.intersection, wires) r.remove(zero_point(2)) return r
class Robot: program: str position: Point = field(default=zero_point(2), init=False) facing: Direction = field(default=Direction.UP, init=False) computer: Computer = field(init=False) world: Dict[Point, Status] = field( default_factory=lambda: defaultdict(lambda: Status.UNEXPLORED), init=False) oxygen: Point = field(default=None, init=False) def __post_init__(self): self.computer = computer_from_string(self.program) self.p = Process(target=self.computer.run) self.p.start() def move(self): result = self.execute() probing = self.position + self.facing.value self.world[probing] = result if result == Status.WALL: self.facing = self.facing.prev() else: self.position = probing if result == Status.OXYGEN: self.oxygen = self.position def execute(self) -> Status: signal = Movement[Conversion(self.facing).name].value self.computer.data_in.put(signal) return Status(self.computer.data_out.get()) def iterate(self): self.explore() if self.world[self.position + self.facing.next().value] == Status.WALL: # There is a wall to our right, move forward self.move() else: self.facing = self.facing.next() self.move() def print_hull(self): min_x = min(map(lambda p: p.x, self.world.keys())) min_y = min(map(lambda p: p.y, self.world.keys())) max_x = max(map(lambda p: p.x, self.world.keys())) max_y = max(map(lambda p: p.y, self.world.keys())) for y in range(max_y, min_y - 1, -1): for x in range(min_x, max_x + 1): status = self.world[Point(x, y)] if status == Status.WALL: print("██", end='') elif status == Status.MOVED: print("░░", end='') elif status == Status.OXYGEN: print("()", end='') else: print(" ", end='') print() def run(self): while not (self.computer.broken or self.oxygen): self.iterate() while not (self.computer.broken or self.position == zero_point(2)): self.iterate() self.p.terminate() self.print_hull() def explore(self): # Pick an unexplored direction and move there explorable: List[Point] = [ d for d in Direction if self.world[self.position + d.value] == Status.UNEXPLORED ] for d in explorable: self.check_out(d) def check_out(self, d: Direction): old_direction = self.facing old_position = self.position self.facing = d self.move() self.facing = d.next().next() if old_position != self.position: self.move() self.facing = old_direction
def test_length(self): for size in range(10): with self.subTest(f'length of {size}'): self.assertEqual(size, len(zero_point(size)))
def __post_init__(self): if not self.velocity: self.velocity = zero_point(len(self.position)) self.initial_position = self.position