summaryrefslogtreecommitdiffstats
path: root/14.py
diff options
context:
space:
mode:
authorTomasz Kramkowski <tomasz@kramkow.ski>2023-12-14 14:25:18 +0000
committerTomasz Kramkowski <tomasz@kramkow.ski>2023-12-14 14:25:18 +0000
commit1b1d5350680b3dee215443143d166424793374d7 (patch)
treece675aa2bc16d8db196ba08293157658fe175546 /14.py
parentef213fd0c871fc871f758941298ad839f998dc21 (diff)
downloadaoc2023-1b1d5350680b3dee215443143d166424793374d7.tar.gz
aoc2023-1b1d5350680b3dee215443143d166424793374d7.tar.xz
aoc2023-1b1d5350680b3dee215443143d166424793374d7.zip
day 14
Diffstat (limited to '14.py')
-rw-r--r--14.py132
1 files changed, 132 insertions, 0 deletions
diff --git a/14.py b/14.py
new file mode 100644
index 0000000..7047839
--- /dev/null
+++ b/14.py
@@ -0,0 +1,132 @@
+# pyright: strict
+from enum import Enum, auto
+from itertools import chain, count
+from sys import stdin
+from typing import Self, Type
+
+
+class Dir(Enum):
+ NORTH = auto()
+ EAST = auto()
+ SOUTH = auto()
+ WEST = auto()
+
+
+class Tile(Enum):
+ AIR = auto()
+ ROUND = auto()
+ CUBE = auto()
+
+ @staticmethod
+ def from_str(s: str) -> "Tile":
+ match s:
+ case ".":
+ return Tile.AIR
+ case "O":
+ return Tile.ROUND
+ case "#":
+ return Tile.CUBE
+ case _:
+ raise ValueError
+
+ def __str__(self) -> str:
+ match self:
+ case Tile.AIR:
+ return "."
+ case Tile.ROUND:
+ return "O"
+ case Tile.CUBE:
+ return "#"
+
+
+class Platform:
+ platform: list[Tile]
+ width: int
+ height: int
+
+ def __init__(self, state: tuple[tuple[Tile, ...], ...]):
+ self.platform = list(chain.from_iterable(state))
+ self.width = len(state[0])
+ self.height = len(state)
+
+ def settle(self, direction: Dir):
+ if direction in {Dir.NORTH, Dir.SOUTH}:
+ lines = self.width
+ line_length = self.height
+ if direction == Dir.NORTH:
+ xlate = lambda l, i: l + i * self.width
+ else:
+ xlate = lambda l, i: l + (self.height - i - 1) * self.width
+ else:
+ lines = self.height
+ line_length = self.width
+ if direction == Dir.EAST:
+ xlate = lambda l, i: l * self.width + i
+ else:
+ xlate = lambda l, i: l * self.width + (self.width - i - 1)
+
+ for line in range(lines):
+ floor = 0
+ for i in range(line_length):
+ pos = xlate(line, i)
+ if self.platform[pos] == Tile.ROUND:
+ self.platform[xlate(line, floor)], self.platform[pos] = (
+ self.platform[pos],
+ self.platform[xlate(line, floor)],
+ )
+ floor += 1
+ elif self.platform[pos] == Tile.CUBE:
+ floor = i + 1
+
+ @property
+ def load(self) -> int:
+ return sum(
+ self.height - y
+ for y in range(self.height)
+ for x in range(self.width)
+ if self.platform[x + self.width * y] == Tile.ROUND
+ )
+
+ def cycle(self):
+ self.settle(Dir.NORTH)
+ self.settle(Dir.EAST)
+ self.settle(Dir.SOUTH)
+ self.settle(Dir.WEST)
+
+ @classmethod
+ def from_str(cls: Type[Self], s: str) -> Self:
+ return cls(tuple(tuple(Tile.from_str(c) for c in l) for l in s.split("\n")))
+
+ def __str__(self) -> str:
+ return "\n".join(
+ "".join(map(str, self.platform[p : p + self.width]))
+ for p in range(0, self.width * self.height, self.width)
+ )
+
+ def __hash__(self) -> int:
+ return hash((self.width, self.height) + tuple(self.platform))
+
+
+p = Platform.from_str(stdin.read().rstrip())
+
+past: dict[int, int] = dict()
+past[hash(p)] = 0
+
+p.settle(Dir.NORTH)
+print(p.load)
+
+for i in count(1):
+ p.cycle()
+ h = hash(p)
+ if h in past:
+ cycle_start = past[h]
+ cycle_length = i - past[h]
+ break
+ past[hash(p)] = i
+else:
+ raise ValueError
+
+for i in range((1000000000 - cycle_start) % cycle_length):
+ p.cycle()
+
+print(p.load)