### re: AoC Day 13: Mine Cart Madness VIEW POST

Alright, now I'm making up ground on the ones I got behind on. I actually really enjoyed this one! It was fun tuning things to be readable and easy to follow.

"""Day 13: Mine Cart Madness

Figure out when carts on tracks will crash
"""

from typing import List

class Vector:
"""A 2D vector.  Right is +X, Down is +Y"""

def __init__(self, x, y):
self.x = x
self.y = y

return Vector(self.x + other.x, self.y + other.y)

def __eq__(self, other):
return self.x == other.x and self.y == other.y

def __repr__(self):
return f"Vector({self.x}, {self.y})"

def __str__(self):
return f"<{self.x}, {self.y}>"

def __hash__(self):
return hash((self.x, self.y))

NORTH = Vector(0, -1)
SOUTH = Vector(0, 1)
EAST = Vector(1, 0)
WEST = Vector(-1, 0)
DIRECTIONS = [NORTH, EAST, SOUTH, WEST]

class Cart:
"""A cart that drives around the mine"""

def __init__(self, position: Vector, direction: Vector):
self.position = position
self.direction = direction
self.next_turn = "left"

def move(self):
self.position += self.direction

def turn_right(self):
self.direction = DIRECTIONS[
(DIRECTIONS.index(self.direction) + 1) % len(DIRECTIONS)
]

def turn_left(self):
self.direction = DIRECTIONS[
(DIRECTIONS.index(self.direction) - 1) % len(DIRECTIONS)
]

def handle_next_turn(self):
"""Update own direction based on the next turn value specified in the prompt"""
if self.next_turn == "left":
self.turn_left()
self.next_turn = "straight"
elif self.next_turn == "straight":
self.next_turn = "right"
elif self.next_turn == "right":
self.turn_right()
self.next_turn = "left"

class Mine:
"""A mine that has a bunch of different carts on tracks"""

def __init__(self, map: str):
self.map = []
self.carts: List[Cart] = []
for y, line in enumerate(map.splitlines()):
self.map.append([])
for x, c in enumerate(line):
if c == '>':
self.carts.append(Cart(Vector(x, y), EAST))
self.map[-1].append('-')
elif c == '<':
self.carts.append(Cart(Vector(x, y), WEST))
self.map[-1].append('-')
elif c == '^':
self.carts.append(Cart(Vector(x, y), NORTH))
self.map[-1].append('|')
elif c == 'v':
self.carts.append(Cart(Vector(x, y), SOUTH))
self.map[-1].append('|')
else:
self.map[-1].append(c)

def find_first_collision(self) -> Vector:
"""Simulates the mine until two carts collide.  Returns that x, y pair"""
while True:
self.sort_carts()

for cart in self.carts:
cart.move()
self.turn_cart(cart)

if self.is_collision(cart):
return cart.position

def last_cart_location(self) -> Vector:
"""Simulates the mine, removing carts when they collide.
Returns the x, y pair of the last cart standing
"""
while True:
self.sort_carts()

crashed = set()
for cart in self.carts:
if cart.position in crashed:
continue
cart.move()
self.turn_cart(cart)

if self.is_collision(cart):
continue

self.carts = [cart
for cart in self.carts
if cart.position not in crashed]
if len(self.carts) == 1:
return self.carts[0].position

def sort_carts(self):
"""Carts are always run in reading order, top to bottom, left to right"""
self.carts.sort(key=lambda cart: (cart.position.y, cart.position.x))

def is_collision(self, cart: Cart) -> bool:
"""Checks whether or not a cart is running into any other carts"""
return sum(other.position == cart.position for other in self.carts) > 1

def turn_cart(self, cart: Cart):
"""Depending on what kind of track the cart is on, turn it appropriately"""
track_type = self.map[cart.position.y][cart.position.x]
if track_type == '+':
cart.handle_next_turn()
elif track_type == '/':
if cart.direction == NORTH or cart.direction == SOUTH:
cart.turn_right()
else:
cart.turn_left()
elif track_type == '\\':
if cart.direction == NORTH or cart.direction == SOUTH:
cart.turn_left()
else:
cart.turn_right()

if __name__ == "__main__":
with open("python/data/day13.txt", "r") as f: