-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy path__init__.py
More file actions
146 lines (107 loc) · 3.34 KB
/
__init__.py
File metadata and controls
146 lines (107 loc) · 3.34 KB
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
from __future__ import annotations
from typing import *
from aocpy import BaseChallenge, Vector, min_max
from enum import Enum
class CellState(Enum):
OCCUPIED = "#"
EMPTY = "."
class Direction(Enum):
NORTH = (0, -1)
EAST = (1, 0)
SOUTH = (0, 1)
WEST = (-1, 0)
ADJACENT_POSITIONS = [
(0, -1),
(1, -1),
(1, 0),
(1, 1),
(0, 1),
(-1, 1),
(-1, 0),
(-1, -1),
]
CHECK_POSITIONS = {
Direction.NORTH.value: [Direction.NORTH.value, (1, -1), (-1, -1)],
Direction.EAST.value: [Direction.EAST.value, (1, 1), (1, -1)],
Direction.SOUTH.value: [Direction.SOUTH.value, (1, 1), (-1, 1)],
Direction.WEST.value: [Direction.WEST.value, (-1, 1), (-1, -1)],
}
Crater = Dict[Vector, CellState]
def parse(instr: str) -> Crater:
res: Crater = {}
for y, line in enumerate(instr.strip().splitlines()):
for x, char in enumerate(line):
if char == CellState.OCCUPIED.value:
res[Vector(x, y)] = CellState.OCCUPIED
return res
def step(state: Crater, directions: List[Tuple[int, int]]) -> bool:
next_moves: Dict[Vector, List[Vector]] = {}
for elf in state:
for direction in directions:
has_any_adjacent = False
for adj_pos in ADJACENT_POSITIONS:
if elf + adj_pos in state:
has_any_adjacent = True
break
if not has_any_adjacent:
break
is_avail = True
for check_pos in CHECK_POSITIONS[direction.value]:
if elf + check_pos in state:
is_avail = False
break
if is_avail:
pos = elf + direction.value
x = next_moves.get(pos, [])
x.append(elf)
next_moves[pos] = x
break
for target_pos in next_moves:
if len(next_moves[target_pos]) != 1:
continue
state[target_pos] = CellState.OCCUPIED
del state[next_moves[target_pos][0]]
return len(next_moves) != 0
def print_crater(cr: Crater):
for y in range(-2, -2 + 12):
for x in range(-3, -3 + 14):
print(cr[(x, y)].value if (x, y) in cr else ".", end="")
print()
print("________________", flush=True)
def calc_open_area(state: Crater) -> int:
min_x, max_x = min_max(p.x for p in state)
min_y, max_y = min_max(p.y for p in state)
max_x += 1
max_y += 1
return ((max_x - min_x) * (max_y - min_y)) - len(state)
class Challenge(BaseChallenge):
@staticmethod
def one(instr: str) -> int:
inp = parse(instr)
directions = [
Direction.NORTH,
Direction.SOUTH,
Direction.WEST,
Direction.EAST,
]
for _ in range(10):
step(inp, directions)
directions.append(directions.pop(0))
return calc_open_area(inp)
@staticmethod
def two(instr: str) -> int:
inp = parse(instr)
directions = [
Direction.NORTH,
Direction.SOUTH,
Direction.WEST,
Direction.EAST,
]
n = 0
while True:
did_something_move = step(inp, directions)
n += 1
if not did_something_move:
break
directions.append(directions.pop(0))
return n