# pyright: strict from collections.abc import Callable 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): xlate: Callable[[int, int], int] 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)