mirror of
https://github.com/cupcakearmy/advent-of-code.git
synced 2024-12-23 00:26:27 +00:00
131 lines
3.2 KiB
Python
131 lines
3.2 KiB
Python
|
from typing import List, Union, Tuple
|
||
|
|
||
|
Point = Tuple[int]
|
||
|
Path = Tuple[Point]
|
||
|
Paths = [Path]
|
||
|
|
||
|
ALL_DIRECTIONS = (
|
||
|
(1, 1),
|
||
|
(1, 0),
|
||
|
(1, -1),
|
||
|
(0, 1),
|
||
|
(0, -1),
|
||
|
(-1, 1),
|
||
|
(-1, 0),
|
||
|
(-1, -1),
|
||
|
)
|
||
|
|
||
|
LETTER_MAP = {"X": 0, "M": 1, "A": 2, "S": 3}
|
||
|
|
||
|
|
||
|
class Board:
|
||
|
rows: List[List[int]]
|
||
|
|
||
|
def __init__(self, rows: List[List[int]]):
|
||
|
self.rows = rows
|
||
|
self.max_y = len(self.rows)
|
||
|
self.max_x = len(self.rows[0])
|
||
|
|
||
|
def get(self, x: int, y: int) -> Union[None, int]:
|
||
|
if x < 0 or y < 0:
|
||
|
return None
|
||
|
if x >= self.max_x or y >= self.max_y:
|
||
|
return None
|
||
|
return self.rows[y][x]
|
||
|
|
||
|
# def get_neighbours(self, x: int, y: int, target: int) -> List[Point]:
|
||
|
# n = []
|
||
|
# for dx in range(x - 1, x + 2):
|
||
|
# for dy in range(y - 1, y + 2):
|
||
|
# if self.get(dx, dy) == target:
|
||
|
# n.append((dx, dy))
|
||
|
# return n
|
||
|
|
||
|
@staticmethod
|
||
|
def parse(raw: str):
|
||
|
rows = [[LETTER_MAP[letter] for letter in line] for line in raw.splitlines()]
|
||
|
return Board(rows)
|
||
|
|
||
|
|
||
|
# def find_maze(b: Board, path: Path, next_char: int) -> Paths:
|
||
|
# x, y = path[-1]
|
||
|
# print(f"Find {x} {y} n={next_char}")
|
||
|
# paths: Paths = []
|
||
|
# neighbours = b.get_neighbours(x, y, next_char)
|
||
|
|
||
|
# # Found last letter
|
||
|
# if next_char == 3:
|
||
|
# return [path + (n,) for n in neighbours]
|
||
|
|
||
|
# # Recurse
|
||
|
# for n in neighbours:
|
||
|
# p = path + (n,)
|
||
|
# sub_paths = find(b, p, next_char + 1)
|
||
|
# if len(sub_paths):
|
||
|
# paths = paths + sub_paths
|
||
|
|
||
|
# return paths
|
||
|
|
||
|
|
||
|
def find_line(b: Board, point: Point, direction: Point) -> Union[Path, None]:
|
||
|
x, y = point
|
||
|
dx, dy = direction
|
||
|
path: Path = point
|
||
|
for i in range(1, 4):
|
||
|
px = x + (dx * i)
|
||
|
py = y + (dy * i)
|
||
|
if b.get(px, py) != i:
|
||
|
return None
|
||
|
path = path + ((x, y),)
|
||
|
return path
|
||
|
|
||
|
|
||
|
def find_x(b: Board, point: Point) -> bool:
|
||
|
x, y = point
|
||
|
for dx, dy in ALL_DIRECTIONS:
|
||
|
# Check if is "M", then the opposite for "S" and the the perpendicular
|
||
|
if b.get(x + dx, y + dy) == 1:
|
||
|
if b.get(x - dx, y - dy) == 3:
|
||
|
# Found first "MAS", check perpendicular
|
||
|
p_dx = -dy
|
||
|
p_dy = dx
|
||
|
p_a = b.get(x + p_dx, y + p_dy)
|
||
|
p_b = b.get(x - p_dx, y - p_dy)
|
||
|
if (p_a == 1 and p_b == 3) or (p_a == 3 and p_b == 1):
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
|
||
|
def solve(raw: str) -> int:
|
||
|
# Part 1
|
||
|
part1 = []
|
||
|
part2 = 0
|
||
|
|
||
|
b = Board.parse(raw)
|
||
|
|
||
|
for x in range(b.max_x):
|
||
|
for y in range(b.max_y):
|
||
|
p = b.get(x, y)
|
||
|
if p == 0:
|
||
|
for direction in ALL_DIRECTIONS:
|
||
|
found = find_line(b, (x, y), direction)
|
||
|
if found:
|
||
|
part1.append(found)
|
||
|
|
||
|
if p == 2:
|
||
|
if find_x(b, (x, y)):
|
||
|
part2 += 1
|
||
|
|
||
|
return (len(part1), part2)
|
||
|
|
||
|
|
||
|
# Test
|
||
|
with open("./2024/04/test.txt", "r") as f:
|
||
|
result = solve(f.read().strip())
|
||
|
print(result)
|
||
|
|
||
|
# Input
|
||
|
with open("./2024/04/input.txt", "r") as f:
|
||
|
result = solve(f.read().strip())
|
||
|
print(result)
|