98 lines
2.4 KiB
Python
98 lines
2.4 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
import itertools
|
||
|
import sys
|
||
|
from enum import StrEnum
|
||
|
from typing import NamedTuple
|
||
|
|
||
|
|
||
|
class Point(NamedTuple):
|
||
|
x: int
|
||
|
y: int
|
||
|
|
||
|
|
||
|
class Direction(StrEnum):
|
||
|
UP = "U"
|
||
|
DOWN = "D"
|
||
|
LEFT = "L"
|
||
|
RIGHT = "R"
|
||
|
|
||
|
def apply(self, pos: Point, n: int = 1) -> Point:
|
||
|
DIRECTIONS = {
|
||
|
"U": Point(-1, 0),
|
||
|
"D": Point(1, 0),
|
||
|
"L": Point(0, -1),
|
||
|
"R": Point(0, 1),
|
||
|
}
|
||
|
dx, dy = DIRECTIONS[self.value]
|
||
|
return Point(pos.x + dx * n, pos.y + dy * n)
|
||
|
|
||
|
|
||
|
DigPlanStep = tuple[Direction, int]
|
||
|
DigPlan = list[DigPlanStep]
|
||
|
|
||
|
|
||
|
def solve(input: list[str]) -> int:
|
||
|
def parse_line(line: str) -> DigPlanStep:
|
||
|
_, _, color = line.split()
|
||
|
color = color[2:-1]
|
||
|
n = color[:-1]
|
||
|
dir = {
|
||
|
"0": "R",
|
||
|
"1": "D",
|
||
|
"2": "L",
|
||
|
"3": "U",
|
||
|
}[color[-1]]
|
||
|
return Direction(dir), int(n, base=16)
|
||
|
|
||
|
def parse(input: list[str]) -> DigPlan:
|
||
|
return list(map(parse_line, input))
|
||
|
|
||
|
def dig_trench(plan: DigPlan) -> list[Point]:
|
||
|
points = [Point(0, 0)]
|
||
|
|
||
|
for direction, n in plan:
|
||
|
points.append(direction.apply(points[-1], n))
|
||
|
# The trench should loop back to the start, make sure we don't count it twice
|
||
|
if points[-1] == Point(0, 0):
|
||
|
del points[-1]
|
||
|
|
||
|
return points
|
||
|
|
||
|
def lagoon_volume(trench: list[Point]) -> int:
|
||
|
def shoelace_area(points: list[Point]) -> int:
|
||
|
# Must be integer because pipes follow the grid, and can't cut squares in half
|
||
|
return abs(
|
||
|
sum(
|
||
|
(points[i - 1].x * points[i].y) - (points[i].x * points[i - 1].y)
|
||
|
for i in range(len(points))
|
||
|
)
|
||
|
// 2
|
||
|
)
|
||
|
|
||
|
def perimeter(points: list[Point]) -> int:
|
||
|
res = 0
|
||
|
|
||
|
for p, n in itertools.pairwise(itertools.chain(points, [points[0]])):
|
||
|
res += abs(n.x - p.x) + abs(n.y - p.y)
|
||
|
|
||
|
return res
|
||
|
|
||
|
area = shoelace_area(trench)
|
||
|
trench_points = perimeter(trench)
|
||
|
interior_points = area - trench_points // 2 + 1
|
||
|
return interior_points + trench_points
|
||
|
|
||
|
plan = parse(input)
|
||
|
trench = dig_trench(plan)
|
||
|
return lagoon_volume(list(trench))
|
||
|
|
||
|
|
||
|
def main() -> None:
|
||
|
input = sys.stdin.read().splitlines()
|
||
|
print(solve(input))
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|