232 lines
6.0 KiB
Python
Executable File
232 lines
6.0 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
from typing import Iterable
|
|
from typing import Tuple
|
|
from typing import Optional
|
|
|
|
from expanding import AutoExpanding
|
|
from expanding import GridItem
|
|
from expanding import Point
|
|
from expanding import World
|
|
from expanding import add_points
|
|
|
|
|
|
D2P = {
|
|
"se": Point(1, -1),
|
|
"sw": Point(-1, -1),
|
|
"ne": Point(1, 1),
|
|
"nw": Point(-1, 1),
|
|
"w": Point(-2, 0),
|
|
"e": Point(2, 0),
|
|
}
|
|
|
|
|
|
def directions_to_point(s: str) -> Point:
|
|
point = Point(0, 0)
|
|
direction = ""
|
|
for c in s:
|
|
direction += c
|
|
if direction in D2P:
|
|
point = add_points(point, D2P[direction])
|
|
direction = ""
|
|
|
|
if direction != "":
|
|
raise ValueError("Something left over unexpectedly")
|
|
|
|
return point
|
|
|
|
|
|
class Tile(GridItem):
|
|
|
|
def __init__(self, loc: Point, world: World, is_black: bool = False):
|
|
# Give this tile a unique id for tracking later
|
|
self.is_black = is_black
|
|
|
|
def _tock():
|
|
# print("Default tock")
|
|
pass
|
|
|
|
self.tock = _tock
|
|
|
|
super().__init__(loc, world)
|
|
|
|
def flip(self):
|
|
self.is_black = not self.is_black
|
|
|
|
def neighbors(self, expand=False) -> Iterable["Tile"]:
|
|
for diff in D2P.values():
|
|
neighbor_point = add_points(self._loc, diff)
|
|
neighbor = self._world.get_point(neighbor_point, expand=expand)
|
|
if neighbor:
|
|
yield neighbor
|
|
|
|
def tick(self):
|
|
num_black = count_black(self.neighbors(expand=True))
|
|
if self.is_black and (num_black == 0 or num_black > 2):
|
|
self.tock = self.flip
|
|
if not self.is_black and num_black == 2:
|
|
self.tock = self.flip
|
|
|
|
|
|
def count_black(tiles: Iterable[Tile]) -> int:
|
|
return sum(map(lambda x: 1 if x.is_black else 0, tiles))
|
|
|
|
|
|
class TileRow(AutoExpanding):
|
|
def __init__(self, y: int, world: World):
|
|
self._y = y
|
|
self._world = world
|
|
super().__init__()
|
|
|
|
def _item_factory(self, x: int) -> GridItem:
|
|
return Tile(Point(x, self._y), self._world)
|
|
|
|
def __repr__(self) -> str:
|
|
return "[" + " ".join((str(c) for c in self._l)) + "]"
|
|
|
|
def to_str(self) -> str:
|
|
return "".join((c.to_str() for c in self._l))
|
|
|
|
|
|
class Floor(World):
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
def _item_factory(self, y: int) -> TileRow:
|
|
return TileRow(y, self)
|
|
|
|
def iter_items(self) -> Iterable[GridItem]:
|
|
for r in self:
|
|
for t in r:
|
|
yield t
|
|
|
|
def get_point(self, p: Point, expand=True) -> Optional[GridItem]:
|
|
try:
|
|
return self.get(
|
|
p.y,
|
|
expand=expand,
|
|
).get(
|
|
p.x,
|
|
expand=expand,
|
|
)
|
|
except IndexError:
|
|
return None
|
|
|
|
def get_all_bounds(self) -> Tuple[Point, Point]:
|
|
min_y, max_y = self.get_bounds()
|
|
min_x, max_x = 0, 0
|
|
|
|
for r in self:
|
|
if r.min_index() < min_x:
|
|
min_x = r.min_index()
|
|
|
|
if r.max_index() > max_x:
|
|
max_x = r.max_index()
|
|
|
|
return Point(min_x, min_y, 0), Point(max_x, max_y, 0)
|
|
|
|
def true_up(self):
|
|
min_point, max_point = self.get_all_bounds()
|
|
|
|
for r in self:
|
|
r.resize(min_point.x, max_point.x)
|
|
|
|
def expand_all(self, i: int):
|
|
min_point, max_point = self.get_all_bounds()
|
|
min_point = add_points(min_point, Point(-i, -i))
|
|
max_point = add_points(max_point, Point(i, i))
|
|
self.get_point(min_point, expand=True)
|
|
self.get_point(max_point, expand=True)
|
|
|
|
def tick(self):
|
|
for t in self.iter_items():
|
|
t.tick()
|
|
|
|
def tock(self):
|
|
for t in self.iter_items():
|
|
t.tock()
|
|
|
|
|
|
def sample():
|
|
print(directions_to_point("nwwswee"))
|
|
|
|
seen = {Point(0, 0)}
|
|
floor = Floor()
|
|
for line in (
|
|
"sesenwnenenewseeswwswswwnenewsewsw",
|
|
"neeenesenwnwwswnenewnwwsewnenwseswesw",
|
|
"seswneswswsenwwnwse",
|
|
"nwnwneseeswswnenewneswwnewseswneseene",
|
|
"swweswneswnenwsewnwneneseenw",
|
|
"eesenwseswswnenwswnwnwsewwnwsene",
|
|
"sewnenenenesenwsewnenwwwse",
|
|
"wenwwweseeeweswwwnwwe",
|
|
"wsweesenenewnwwnwsenewsenwwsesesenwne",
|
|
"neeswseenwwswnwswswnw",
|
|
"nenwswwsewswnenenewsenwsenwnesesenew",
|
|
"enewnwewneswsewnwswenweswnenwsenwsw",
|
|
"sweneswneswneneenwnewenewwneswswnese",
|
|
"swwesenesewenwneswnwwneseswwne",
|
|
"enesenwswwswneneswsenwnewswseenwsese",
|
|
"wnwnesenesenenwwnenwsewesewsesesew",
|
|
"nenewswnwewswnenesenwnesewesw",
|
|
"eneswnwswnwsenenwnwnwwseeswneewsenese",
|
|
"neswnwewnwnwseenwseesewsenwsweewe",
|
|
"wseweeenwnesenwwwswnew",
|
|
):
|
|
p = directions_to_point(line)
|
|
print(f"Parsed point {p}")
|
|
if p in seen:
|
|
print("seen!")
|
|
seen.add(p)
|
|
floor.get_point(p, expand=True).flip()
|
|
|
|
black = count_black(floor.iter_items())
|
|
print("Day 0")
|
|
print(f"Number black is {black}")
|
|
|
|
for day in range(100):
|
|
floor.expand_all(1)
|
|
floor.true_up()
|
|
floor.tick()
|
|
floor.tock()
|
|
black = count_black(floor.iter_items())
|
|
total = sum(map(lambda x: 1, floor.iter_items()))
|
|
print(f"Day {day+1}: {black} / {total}")
|
|
|
|
|
|
def part1():
|
|
floor = Floor()
|
|
with open("input.txt") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
p = directions_to_point(line)
|
|
floor.get_point(p, expand=True).flip()
|
|
|
|
black = count_black(floor.iter_items())
|
|
print(f"Number black is {black}")
|
|
|
|
|
|
def part2():
|
|
floor = Floor()
|
|
with open("input.txt") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
p = directions_to_point(line)
|
|
floor.get_point(p, expand=True).flip()
|
|
|
|
black = count_black(floor.iter_items())
|
|
print("Day 0")
|
|
print(f"Number black is {black}")
|
|
|
|
for day in range(1, 11):
|
|
floor.tick()
|
|
floor.tock()
|
|
black = count_black(floor.iter_items())
|
|
print(f"Day {day}: {black}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sample()
|
|
# part1()
|
|
# part2()
|