summaryrefslogtreecommitdiffstats
path: root/14.py
blob: 6f49e37b2f04c541e9de4c917eb95c778e0c92d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from utils import open_day, Point2D, sliding_window, inbounds
from enum import Enum, auto
from itertools import count

def display(sandbox, minx, miny, maxx, maxy):
    for y in range(miny, maxy + 1):
        line = []
        for x in range(minx, maxx + 1):
            p = Point2D(x, y)
            if p in sandbox:
                if sandbox[p] == Tile.WALL:
                    line.append('#')
                else:
                    line.append('o')
            else:
                line.append('.')
            if x == 500 and y == 0:
                line[-1] = '+'
        print(''.join(line))

def point_from_str(s):
    return Point2D(*(int(d) for d in s.split(',')))

class Tile(Enum):
    WALL = auto()
    SAND = auto()

class Sandbox:
    def __init__(self, source=Point2D(500, 0)):
        self.tiles = dict()
        self.source = source
        self.minx, self.maxx = source.x, source.x
        self.miny, self.maxy = source.y, source.y
    def __setitem__(self, key, value):
        if   key.x < self.minx: self.minx = key.x
        elif key.x > self.maxx: self.maxx = key.x
        if   key.y < self.miny: self.miny = key.y
        elif key.y > self.maxy: self.maxy = key.y
        self.tiles[key] = value
    def __getitem__(self, key):
        return self.tiles[key]
    def __str__(self):
        lines = []
        for y in range(self.miny, self.maxy + 1):
            line = []
            for x in range(self.minx, self.maxx + 1):
                p = Point2D(x, y)
                match self.tiles.get(p):
                    case Tile.WALL: line.append('#')
                    case Tile.SAND: line.append('o')
                    case None:      line.append('.')
                if x == self.source.x and y == self.source.y:
                    line[-1] = '+'
            lines.append(''.join(line))
        return '\n'.join(lines)
    def simulate(self):
        bounds = (Point2D(self.minx, self.miny), Point2D(self.maxx, self.maxy))
        for units in count():
            sandpos = self.source
            while self.source not in self.tiles and inbounds(sandpos, *bounds):
                for d in (Point2D( 0, 1), Point2D(-1, 1), Point2D( 1, 1)):
                    if sandpos + d not in self.tiles:
                        sandpos += d
                        break
                else:
                    self.tiles[sandpos] = Tile.SAND
                    break
            else:
                break
        return units

sandbox = Sandbox()

with open_day(14) as f:
    for line in f:
        for b, e in sliding_window(map(point_from_str, line.rstrip().split(' -> ')), 2):
            minlx, maxlx = min(b.x, e.x), max(b.x, e.x)
            minly, maxly = min(b.y, e.y), max(b.y, e.y)
            for x in range(minlx, maxlx + 1):
                for y in range(minly, maxly + 1):
                    sandbox[Point2D(x, y)] = Tile.WALL

p1 = sandbox.simulate()
print(p1)

floory = sandbox.maxy + 2
height = floory - sandbox.source.y
for x in range(500 - height, 500 + height + 1):
    sandbox[Point2D(x, floory)] = Tile.WALL

print(p1 + sandbox.simulate())