# --- Day 13: Mine Cart Madness --- # A crop of this size requires significant logistics to transport produce, soil, # fertilizer, and so on. The Elves are very busy pushing things around in carts # on some kind of rudimentary system of tracks they've come up with. # Seeing as how cart-and-track systems don't appear in recorded history for # another 1000 years, the Elves seem to be making this up as they go along. # They haven't even figured out how to avoid collisions yet. # You map out the tracks (your puzzle input) and see where you can help. # Tracks consist of straight paths (| and -), curves (/ and \), and # intersections (+). Curves connect exactly two perpendicular pieces of track; # for example, this is a closed loop: # /----\ # | | # | | # \----/ # Intersections occur when two perpendicular paths cross. At an intersection, # a cart is capable of turning left, turning right, or continuing straight. # Here are two loops connected by two intersections: # /-----\ # | | # | /--+--\ # | | | | # \--+--/ | # | | # \-----/ # Several carts are also on the tracks. Carts always face either up (^), down # (v), left (<), or right (>). (On your initial map, the track under each cart # is a straight path matching the direction the cart is facing.) # Each time a cart has the option to turn (by arriving at any intersection), it # turns left the first time, goes straight the second time, turns right the # third time, and then repeats those directions starting again with left the # fourth time, straight the fifth time, and so on. This process is independent # of the particular intersection at which the cart has arrived - that is, the # cart has no per-intersection memory. # Carts all move at the same speed; they take turns moving a single step at a # time. They do this based on their current location: carts on the top row move # first (acting from left to right), then carts on the second row move (again # from left to right), then carts on the third row, and so on. Once each cart # has moved one step, the process repeats; each of these loops is called a tick. # For example, suppose there are two carts on a straight track: # | | | | | # v | | | | # | v v | | # | | | v X # | | ^ ^ | # ^ ^ | | | # | | | | | # First, the top cart moves. It is facing down (v), so it moves down one square. # Second, the bottom cart moves. It is facing up (^), so it moves up one square. # Because all carts have moved, the first tick ends. Then, the process repeats, # starting with the first cart. The first cart moves down, then the second cart # moves up - right into the first cart, colliding with it! (The location of the # crash is marked with an X.) This ends the second and last tick. # Here is a longer example: # /->-\ # | | /----\ # | /-+--+-\ | # | | | | v | # \-+-/ \-+--/ # \------/ # /-->\ # | | /----\ # | /-+--+-\ | # | | | | | | # \-+-/ \->--/ # \------/ # /---v # | | /----\ # | /-+--+-\ | # | | | | | | # \-+-/ \-+>-/ # \------/ # /---\ # | v /----\ # | /-+--+-\ | # | | | | | | # \-+-/ \-+->/ # \------/ # /---\ # | | /----\ # | /->--+-\ | # | | | | | | # \-+-/ \-+--^ # \------/ # /---\ # | | /----\ # | /-+>-+-\ | # | | | | | ^ # \-+-/ \-+--/ # \------/ # /---\ # | | /----\ # | /-+->+-\ ^ # | | | | | | # \-+-/ \-+--/ # \------/ # /---\ # | | /----< # | /-+-->-\ | # | | | | | | # \-+-/ \-+--/ # \------/ # /---\ # | | /---<\ # | /-+--+>\ | # | | | | | | # \-+-/ \-+--/ # \------/ # /---\ # | | /--<-\ # | /-+--+-v | # | | | | | | # \-+-/ \-+--/ # \------/ # /---\ # | | /-<--\ # | /-+--+-\ | # | | | | v | # \-+-/ \-+--/ # \------/ # /---\ # | | /<---\ # | /-+--+-\ | # | | | | | | # \-+-/ \-<--/ # \------/ # /---\ # | | v----\ # | /-+--+-\ | # | | | | | | # \-+-/ \<+--/ # \------/ # /---\ # | | /----\ # | /-+--v-\ | # | | | | | | # \-+-/ ^-+--/ # \------/ # /---\ # | | /----\ # | /-+--+-\ | # | | | X | | # \-+-/ \-+--/ # \------/ # After following their respective paths for a while, the carts eventually # crash. To help prevent crashes, you'd like to know the location of the first # crash. Locations are given in X,Y coordinates, where the furthest left column # is X=0 and the furthest top row is Y=0: # 111 # 0123456789012 # 0/---\ # 1| | /----\ # 2| /-+--+-\ | # 3| | | X | | # 4\-+-/ \-+--/ # 5 \------/ # In this example, the location of the first crash is 7,3. directions = {">": [1, 0], "v": [0, 1], "<": [-1, 0], "^": [0, -1]} map = [] carts = [] with open("files/P13.txt") as f: for y, line in enumerate(f): line = line.strip("\n") for x, pos in enumerate(line): if pos in directions: dx, dy = directions[pos] carts.append( { "x": x, "y": y, "dx": dx, "dy": dy, "turn_index": 0, "deleted": False, } ) map.append(pos) cols = len(line) def move_cart(cart): cart["x"] += cart["dx"] cart["y"] += cart["dy"] def rotate_cart(cart, current): if current in {"/", "\\", "+"}: if current == "/": cart["dx"], cart["dy"] = -cart["dy"], -cart["dx"] elif current == "\\": cart["dx"], cart["dy"] = cart["dy"], cart["dx"] elif current == "+": if cart["turn_index"] == 0: cart["dx"], cart["dy"] = cart["dy"], -cart["dx"] elif cart["turn_index"] == 2: cart["dx"], cart["dy"] = -cart["dy"], cart["dx"] cart["turn_index"] = (cart["turn_index"] + 1) % 3 def part_1(): is_collision = False counter = 0 while not is_collision: counter += 1 carts.sort(key=lambda s: (s["y"], s["x"])) for cart in carts: pos = cart["y"] * cols + cart["x"] current = map[pos] if current in {"/", "\\", "+"}: rotate_cart(cart, current) move_cart(cart) coords = set() for cart in carts: pos = cart["x"], cart["y"] if pos in coords: is_collision = True crash = pos break else: coords.add(pos) print(f"The first crash is located at {crash}") # --- Part Two --- # There isn't much you can do to prevent crashes in this ridiculous system. # However, by predicting the crashes, the Elves know where to be in advance and # instantly remove the two crashing carts the moment any crash occurs. # They can proceed like this for a while, but eventually, they're going to run # out of carts. It could be useful to figure out where the last cart that # hasn't crashed will end up. # For example: # />-<\ # | | # | /<+-\ # | | | v # \>+/ # /---\ # | | # | v-+-\ # | | | | # \-+-/ | # | | # ^---^ # /---\ # | | # | /-+-\ # | v | | # \-+-/ | # ^ ^ # \---/ # /---\ # | | # | /-+-\ # | | | | # \-+-/ ^ # | | # \---/ # After four very expensive crashes, a tick ends with only one cart remaining; # its final location is 6,4. # What is the location of the last cart at the end of the first tick where it is # the only cart left? def part_2(): global map, carts counter = 0 while len(carts) > 1: counter += 1 carts.sort(key=lambda s: (s["y"], s["x"])) for cart in carts: pos = cart["y"] * cols + cart["x"] current = map[pos] if current in {"/", "\\", "+"}: rotate_cart(cart, current) move_cart(cart) coords = {} for n, cart in enumerate(carts): pos = cart["x"], cart["y"] if pos not in coords: coords[pos] = n else: cart["deleted"] = True carts[coords[pos]]["deleted"] = True carts = [cart for cart in carts if not cart["deleted"]] last_cart_pos = (carts[0]["x"], carts[0]["y"]) print(f"The last cart is located at {last_cart_pos}") if __name__ == "__main__": part_1() part_2()