def play(self) -> None: launch = Launch.from_area_text(self.input) for y in range(0, 150): highest_y = launch\ .get_highest_y_for_initial_y_that_lands_in_area_y(y) print(f"For y={y}: max={highest_y}") click.prompt("Visualise throw? Press any button") click.getchar() other_launch = Launch.from_area_text( "target area: x=20..30, y=-10..-5" ) print(launch) initial_velocity: Point2D = Point2D(0, 0) while True: click.echo(f"Move initial velocity {initial_velocity}") char = click.getchar("Tell") if char == "q": break elif char == "s": launch, other_launch = other_launch, launch elif char in self.OFFSET_MAP: initial_velocity = initial_velocity.offset( self.OFFSET_MAP[char], ) else: continue launch.set_path_from(Point2D(0, 0), initial_velocity) print(launch)
def pad(self, count: int) -> "Area": cls = type(self) # noinspection PyArgumentList return cls( min=Point2D(self.min.x - count, self.min.y + count), max=Point2D(self.max.x - count, self.max.y + count), )
def __str__(self) -> "str": """ >>> print(Region2DSet()) >>> print(Region2DSet([Region2D(Point2D(0, 0), Point2D(2, 2))])) (0, 0) ### ### ### """ if not self.regions: return "" min_coordinates, _ = min_and_max_tuples(region.min for region in self.regions) min_point = Point2D(min_coordinates) _, max_coordinates = min_and_max_tuples(region.max for region in self.regions) max_point = Point2D(max_coordinates) counts = {} for point in self: counts.setdefault(point, 0) counts[point] += 1 return "{min_point}\n{as_string}".format( min_point=tuple(min_point), as_string="\n".join("".join( "#" if count == 1 else str(count) if count > 1 else "." for x in range(min_point.x, max_point.x + 1) for point in [Point2D(x, y)] for count in [counts.get(point, 0)]) for y in range(min_point.y, max_point.y + 1)), )
def with_zero_x(self) -> "Area": cls = type(self) # noinspection PyArgumentList return cls( min=Point2D(0, self.min.y), max=Point2D(0, self.max.y), )
def from_area_text(cls, area_text: str) -> "Area": """ >>> Area.from_area_text("target area: x=253..280, y=-73..-46") Area(min=Point2D(x=253, y=-73), max=Point2D(x=280, y=-46)) """ min_x, max_x, min_y, max_y = \ map(int, cls.re_area.match(area_text).groups()) return cls(min=Point2D(min_x, min_y), max=Point2D(max_x, max_y))
class Challenge(BaseChallenge): def solve(self, _input, debugger: Debugger): """ >>> Challenge().default_solve() 261 """ return 261 node_set = NodeSetExtended.from_nodes_text(_input) if debugger: print(node_set.show()) return Solver().get_minimum_solution_length( node_set, debugger=debugger) OFFSETS = { '\x1b[A': Point2D(0, -1), '\x1b[D': Point2D(-1, 0), '\x1b[B': Point2D(0, 1), '\x1b[C': Point2D(1, 0), } def play(self): initial_node_set = NodeSetExtended.from_nodes_text(self.input) node_set = initial_node_set position = node_set.position empty_spot = node_set.get_empty_spot() distances_by_position_and_empty = {(position, empty_spot): 0} while True: print(node_set.show()) position = node_set.position empty_spot = node_set.get_empty_spot() distance = distances_by_position_and_empty[(position, empty_spot)] print( f"At {position} after {distance} moves, use arrow " f"keys to move empty from {empty_spot}, or r to reset: ") key = click.getchar() if key == 'r': node_set = initial_node_set elif key in self.OFFSETS: offset = self.OFFSETS[key] new_empty_spot = empty_spot.offset(offset) if not node_set.can_move_positions(new_empty_spot, empty_spot): print(f"Cannot move from {empty_spot} to {new_empty_spot}") continue node_set = node_set.move_positions(new_empty_spot, empty_spot) new_position = node_set.position new_distance = distance + 1 existing_distance = distances_by_position_and_empty\ .get((new_position, new_empty_spot)) if existing_distance is None \ or existing_distance > new_distance: distances_by_position_and_empty[ (new_position, new_empty_spot)] = new_distance else: print(f"Unknown key {repr(key)}")
def iterate_path( self, initial_position: Point2D, initial_velocity: Point2D, ) -> Iterable[Tuple[Point2D, Point2D]]: positions_and_velocities = zip( self.iterate_x_path(initial_position, initial_velocity), self.iterate_y_path(initial_position, initial_velocity), ) for (x, v_x), (y, v_y) in positions_and_velocities: position = Point2D(x, y) velocity = Point2D(v_x, v_y) yield position, velocity
def solve(self, _input, debug=False): """ >>> Challenge().default_solve() 90 """ maze = Maze(int(_input)) solution = maze.solve(Point2D(1, 1), Point2D(31, 39)) if debug: print(maze.show(solution=solution)) return len(solution) - 1
def loop(self): center = Point2D(self.width / 2, self.height / 2) offset = Point2D(0, 0) self.hilbert(center, 1, offset, Step(Step.CONST_DIRECTION_D)) if self._load_image_flag is True: pygame.image.save(self.canvas, self.save_name) while self._continue_flag is True: # self.canvas.fill(self.BACKGROUND_COLOR) pygame.display.flip() self.handle_events() pass
def try_parse(cls, text: str): """ >>> Toggle.try_parse("toggle 461,550 through 564,900") Toggle(start=Point2D(x=461, y=550), end=Point2D(x=564, y=900)) >>> Instruction.parse("toggle 461,550 through 564,900") Toggle(start=Point2D(x=461, y=550), end=Point2D(x=564, y=900)) """ match = cls.re_toggle.match(text) if not match: return None start_x, start_y, end_x, end_y = map(int, match.groups()) return cls(Point2D(start_x, start_y), Point2D(end_x, end_y))
def get_xs_that_land_in_area_for_initial_y( self, initial_y: int, ) -> Iterable[int]: if Point2D(0, self.area.min.y) in self.area: xs = range(self.area.min.x, self.area.max.x + 1) elif self.area.min.x > 0: min_x = math.floor((-1 + math.sqrt(1 + 8 * self.area.min.x)) / 2) xs = range(min_x, min_x + 500) else: max_x = math.floor((-1 - math.sqrt(1 + 8 * self.area.min.x)) / 2) xs = range(max_x, max_x - 500, -1) return (x for x in xs if self.does_path_land_in_area(Point2D(x, initial_y)))
def from_cavern_text(cls, cavern_text: str) -> "Cavern": """ >>> print(Cavern.from_cavern_text(''' ... 1163751742 ... 1381373672 ... 2136511328 ... 3694931569 ... 7463417111 ... 1319128137 ... 1359912421 ... 3125421639 ... 1293138521 ... 2311944581 ... ''')) 1163751742 1381373672 2136511328 3694931569 7463417111 1319128137 1359912421 3125421639 1293138521 2311944581 """ lines = filter(None, map(str.strip, cavern_text.splitlines())) return cls(risks={ Point2D(x, y): int(risk_str) for y, line in enumerate(lines) for x, risk_str in enumerate(line) }, )
def hilbert(self, center: Point2D, current_order: int, previous: Point2D, direction: Step): if self._continue_flag is False: return if (self.number_of_iterations >= self.render_steps): self.number_of_iterations = 0 pygame.display.flip() self.clock.tick(self.fps) self.handle_events() else: self.number_of_iterations += 1 number_of_sides = 2**(current_order - 1) x_offset = self.width / (number_of_sides * 2) y_offset = self.height / (number_of_sides * 2) offset = Point2D(x_offset, y_offset) if (current_order is self.order): trace = self.trace_path_by_direction(center, offset, direction) return self.draw_shape(previous, trace.first, trace.second, trace.third, trace.fourth) else: trace = self.trace_path_by_direction(center, offset, direction) first_point = self.hilbert(trace.first, current_order + 1, previous, trace.path.get(0)) second_point = self.hilbert(trace.second, current_order + 1, first_point, trace.path.get(1)) third_point = self.hilbert(trace.third, current_order + 1, second_point, trace.path.get(2)) fourth_point = self.hilbert(trace.fourth, current_order + 1, third_point, trace.path.get(3)) return fourth_point
def turn_corners_on(self): (min_x, min_y), (max_x, max_y) = min_and_max_tuples(self.grid) for x in (min_x, max_x): for y in (min_y, max_y): self.grid[Point2D(x, y)] = True return self
def move_direction(self, direction: DirectionEnum) -> "Herd": offset = self.OFFSET_MAP[direction] cls = type(self) # noinspection PyArgumentList return cls( cucumbers={ point: ( None if ( self.cucumbers[point] == direction and self.cucumbers[next_point] is None ) else self.cucumbers[previous_point] if ( self.cucumbers[point] is None and self.cucumbers[previous_point] == direction ) else self.cucumbers[point] ) for y in range(self.size.y) for x in range(self.size.x) for point in [Point2D(x, y)] for next_point in [self.get_next_point(point, offset)] for previous_point in [self.get_previous_point(point, offset)] }, size=self.size, )
def from_image_text(cls, image_text: str) -> "Image": """ >>> print(":", Image.from_image_text(''' ... #..#. ... #.... ... ##..# ... ..#.. ... ..### ... ''')) : ....... .#..#.. .#..... .##..#. ...#... ...###. ....... """ lines = filter(None, map(str.strip, image_text.splitlines())) light_pixels = { point for y, line in enumerate(lines) for x, character in enumerate(line) for point in [Point2D(x, y)] if character == "#" } return cls( light_pixels=light_pixels, boundary=Area.from_points(light_pixels), default_out_of_boundary=False, )
def from_printed_sheet(cls, sheet_printout: str) -> "Sheet": """ >>> print(":", Sheet.from_printed_sheet(''' ... ##### ... #...# ... #...# ... #...# ... ##### ... ''')) : ##### #...# #...# #...# ##### """ lines = filter(None, map(str.strip, sheet_printout.splitlines())) return cls( dots={ Point2D(x, y) for y, line in enumerate(lines) for x, character in enumerate(line) if character == "#" }, )
def set_default_position(self): """ >>> NodeSetExtended.from_nodes_text( ... "root@ebhq-gridcenter# df -h\\n" ... "Filesystem Size Used Avail Use%\\n" ... "/dev/grid/node-x0-y0 88T 66T 22T 75%\\n" ... "/dev/grid/node-x0-y1 85T 65T 20T 76%\\n" ... "/dev/grid/node-x0-y2 88T 67T 21T 76%\\n" ... ) NodeSetExtended(nodes={Point2D(x=0, y=0): Node(position=Point2D(x=0, y=0), size=88, used=66), Point2D(x=0, y=1): Node(position=Point2D(x=0, y=1), size=85, used=65), Point2D(x=0, y=2): Node(position=Point2D(x=0, y=2), size=88, used=67)}, position=Point2D(x=0, y=0)) >>> NodeSetExtended.from_nodes_text( ... "root@ebhq-gridcenter# df -h\\n" ... "Filesystem Size Used Avail Use%\\n" ... "/dev/grid/node-x0-y0 10T 8T 2T 80%\\n" ... "/dev/grid/node-x0-y1 11T 6T 5T 54%\\n" ... "/dev/grid/node-x0-y2 32T 28T 4T 87%\\n" ... "/dev/grid/node-x1-y0 9T 7T 2T 77%\\n" ... "/dev/grid/node-x1-y1 8T 0T 8T 0%\\n" ... "/dev/grid/node-x1-y2 11T 7T 4T 63%\\n" ... "/dev/grid/node-x2-y0 10T 6T 4T 60%\\n" ... "/dev/grid/node-x2-y1 9T 8T 1T 88%\\n" ... "/dev/grid/node-x2-y2 9T 6T 3T 66%\\n" ... ).position Point2D(x=2, y=0) """ if self.position is NotImplemented: max_x = max(node.position.x for node in self.nodes.values()) self.position = Point2D(max_x, 0)
def standard_9_buttons(cls): # noinspection PyArgumentList return cls({ Point2D(0, 0): '1', Point2D(1, 0): '2', Point2D(2, 0): '3', Point2D(0, 1): '4', Point2D(1, 1): '5', Point2D(2, 1): '6', Point2D(0, 2): '7', Point2D(1, 2): '8', Point2D(2, 2): '9', })
def __str__(self) -> "str": return "\n".join( "".join(self.amphipod_map[position_name].value if position_name in self.amphipod_map else "#" if character == "#" else "." for x, character in enumerate(line) for position in [Point2D(x, y)] for position_name in [DeepPositionNameEnum.maybe_from_position(position)]) for y, line in enumerate(DeepPositionNameEnum.MAZE_LINES))
def __contains__(self, item: Union[tuple, Point2D]) -> bool: item = Point2D(item) if not (0 <= item.y < len(self.levels)): return False if not (0 <= item.x < len(self.levels[item.y])): return False return True
def generate_rays(self, precision): self.precision = precision angle = atan2(self.direction.y, self.direction.x) _from = angle - self.fov / 2 to = angle + self.fov / 2 for i in range(0, precision): _angle = interp(i, [0, precision], [_from, to]) self.rays.append(Ray(self, Point2D(cos(_angle), sin(_angle))))
def update_distances(updated_point): x, z = updated_point.x, updated_point.z if x + 1 < self.width: update_distance(updated_point, Point2D(x + 1, z), neighbors) """if z + 1 < self.length: update_distance(updated_point, Point2D(x + 1, z + 1), neighbors) if z - 1 >= 0: update_distance(updated_point, Point2D(x + 1, z - 1), neighbors)""" if x - 1 >= 0: update_distance(updated_point, Point2D(x - 1, z), neighbors) """if z + 1 < self.length: update_distance(updated_point, Point2D(x - 1, z + 1), neighbors) if z - 1 >= 0: update_distance(updated_point, Point2D(x - 1, z - 1), neighbors)""" if z + 1 < self.length: update_distance(updated_point, Point2D(x, z + 1), neighbors) if z - 1 >= 0: update_distance(updated_point, Point2D(x, z - 1), neighbors)
def __str__(self) -> str: (min_x, max_x), (min_y, max_y) = self.get_bounding_box() return "\n".join( "".join( "#" if self[Point2D(x, y)] else "." for x in range(min_x, max_x + 1) ) for y in range(min_y, max_y + 1) )
def solve(self, _input, debug=False): """ >>> Challenge().default_solve() 135 """ maze = part_a.Maze(int(_input)) if debug: for depth in [0, 1, 2, 49]: seen = MazeSolverExtended()\ .flood(maze, Point2D(1, 1), depth, debug=debug) print(f"Depth: {depth}, seen: {len(seen)}") print(maze.show(solution=seen)) seen_50 = MazeSolverExtended()\ .flood(maze, Point2D(1, 1), 50, debug=debug) if debug: print(f"Depth: 50, seen: {len(seen_50)}") print(maze.show(solution=seen_50)) return len(seen_50)
def try_parse(cls, text: str): """ >>> Turn.try_parse("turn off 370,39 through 425,839") Turn(offset=-1, start=Point2D(x=370, y=39), end=Point2D(x=425, y=839)) >>> Turn.try_parse("turn on 370,39 through 425,839") Turn(offset=1, start=Point2D(x=370, y=39), end=Point2D(x=425, y=839)) >>> Instruction.parse("turn off 370,39 through 425,839") Turn(offset=-1, start=Point2D(x=370, y=39), end=Point2D(x=425, y=839)) >>> Instruction.parse("turn on 370,39 through 425,839") Turn(offset=1, start=Point2D(x=370, y=39), end=Point2D(x=425, y=839)) """ match = cls.re_toggle.match(text) if not match: return None offset_str, *coordinate_strs = match.groups() offset = cls.ON_MAP[offset_str] start_x, start_y, end_x, end_y = map(int, coordinate_strs) return cls(offset, Point2D(start_x, start_y), Point2D(end_x, end_y))
def update_distances(updated_point): x0, z0 = updated_point.x, updated_point.z for xn, zn in product(xrange(x0 - 1, x0 + 2), xrange(z0 - 1, z0 + 2)): if ( xn != x0 or zn != z0 ) and 0 <= xn < W and 0 <= zn < L and distance_map[xn, zn] > 0: update_distance(updated_point, Point2D(xn, zn))
def make_walls(self): self.obstacles.append( Wall(Point2D(0, 0), Point2D(self.minuature_width, 0))) self.obstacles.append( Wall(Point2D(0, 0), Point2D(0, self.minuature_height))) self.obstacles.append( Wall(Point2D(self.minuature_width, self.minuature_height), Point2D(self.minuature_width, 0))) self.obstacles.append( Wall(Point2D(self.minuature_width, self.minuature_height), Point2D(0, self.minuature_height)))
def transform(self, point: Point2D) -> Point2D: """ >>> FoldInstruction(FoldAxisEnum.X, 5).transform(Point2D(3, 2)) Point2D(x=3, y=2) >>> FoldInstruction(FoldAxisEnum.X, 5).transform(Point2D(5, 2)) Traceback (most recent call last): ... Exception: Points are not supposed to be on folding axis: ... >>> FoldInstruction(FoldAxisEnum.X, 5).transform(Point2D(6, 2)) Point2D(x=4, y=2) >>> FoldInstruction(FoldAxisEnum.Y, 5).transform(Point2D(2, 3)) Point2D(x=2, y=3) >>> FoldInstruction(FoldAxisEnum.Y, 5).transform(Point2D(2, 5)) Traceback (most recent call last): ... Exception: Points are not supposed to be on folding axis: ... >>> FoldInstruction(FoldAxisEnum.Y, 5).transform(Point2D(2, 6)) Point2D(x=2, y=4) """ if self.axis == FoldAxisEnum.X: if point.x == self.coordinate: raise Exception( f"Points are not supposed to be on folding axis: {point} " f"is on {self}" ) if point.x < self.coordinate: return point offset = point.x - self.coordinate return Point2D(self.coordinate - offset, point.y) elif self.axis == FoldAxisEnum.Y: if point.y == self.coordinate: raise Exception( f"Points are not supposed to be on folding axis: {point} " f"is on {self}" ) if point.y < self.coordinate: return point offset = point.y - self.coordinate return Point2D(point.x, self.coordinate - offset) else: raise Exception(f"Unknown folding axis {self.axis}")
def __setitem__(self, key: Union[Point2D, Tuple], value: bool) -> None: if isinstance(key, tuple): key = Point2D(key) value = bool(value) if value: self.dots.add(key) else: if self[key]: self.dots.remove(key)