125 lines
3.4 KiB
Python
125 lines
3.4 KiB
Python
|
from helpers import *
|
||
|
|
||
|
gas_cycles = read_day(17)
|
||
|
sample_gas_cycles = '>>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>'
|
||
|
|
||
|
rocks = '''
|
||
|
####
|
||
|
|
||
|
.#.
|
||
|
###
|
||
|
.#.
|
||
|
|
||
|
..#
|
||
|
..#
|
||
|
###
|
||
|
|
||
|
#
|
||
|
#
|
||
|
#
|
||
|
#
|
||
|
|
||
|
##
|
||
|
##
|
||
|
'''.strip().split('\n\n')
|
||
|
# rock_shapes = [np.array([[1 if c=='#' else 0 for c in line] for line in r.split('\n')], dtype=np.int8).T for r in rocks]
|
||
|
rock_shapes = [np.fliplr(np.array([[1 if c=='#' else 0 for c in line] for line in r.split('\n')], dtype=np.int8).T) for r in rocks]
|
||
|
|
||
|
def simulate_rockfall(gas_cycle: str, num_rocks=50000, debug=False) -> ArrayLike: # returns height increment per rock
|
||
|
def p(message):
|
||
|
if debug:
|
||
|
print(message)
|
||
|
|
||
|
width = 7
|
||
|
height_5_rocks = 1+3+3+4+2
|
||
|
max_possible_height = (num_rocks//5 + 2) * height_5_rocks
|
||
|
len_gas_cycle = len(gas_cycle)
|
||
|
|
||
|
field = np.zeros((7, max_possible_height), dtype=np.int8)
|
||
|
height_increments = np.zeros(num_rocks, dtype=np.int8)
|
||
|
highest = 0
|
||
|
t = -1
|
||
|
for r in range(num_rocks):
|
||
|
rock_shape = rock_shapes[r%5]
|
||
|
r_width, r_height = rock_shape.shape
|
||
|
|
||
|
def check_collision(r_x, r_y):
|
||
|
r_x2 = r_x + r_width
|
||
|
r_y2 = r_y + r_height
|
||
|
if r_x < 0:
|
||
|
p(f'would hit left wall - {r_x, r_x2} at y={r_y}')
|
||
|
return True
|
||
|
if r_x2 > width:
|
||
|
p(f'would hit right wall - {r_x, r_x2} at y={r_y}')
|
||
|
return True
|
||
|
if r_y < 0:
|
||
|
p(f'would hit floor - {r_x, r_x2} at y={r_y}')
|
||
|
return True
|
||
|
return np.any(field[r_x:r_x2, r_y:r_y2] & rock_shape == 1)
|
||
|
|
||
|
# Spawn rock with bottom-left corner at x=2, y=highest+3 (y-up)
|
||
|
r_x = 2
|
||
|
r_y = highest + 3
|
||
|
while True:
|
||
|
t += 1
|
||
|
if gas_cycle[t%len_gas_cycle] == '>':
|
||
|
if not check_collision(r_x+1, r_y): # move right
|
||
|
r_x += 1
|
||
|
p('moved right')
|
||
|
else:
|
||
|
if not check_collision(r_x-1, r_y): # move left
|
||
|
r_x -= 1
|
||
|
p('moved left')
|
||
|
# move down
|
||
|
if check_collision(r_x, r_y-1):
|
||
|
field[r_x:r_x+r_width, r_y:r_y+r_height] |= rock_shape
|
||
|
old_highest = highest
|
||
|
highest = max(highest, r_y+r_height)
|
||
|
height_increments[r] = highest - old_highest
|
||
|
break
|
||
|
else:
|
||
|
r_y -= 1
|
||
|
p('moved down')
|
||
|
p(f'rock #{r} landed, highest point {highest}, gas cycle {t}, rock shape ({r_width}x{r_height}) at {r_x},{r_y}')
|
||
|
return height_increments
|
||
|
|
||
|
sample_incs = simulate_rockfall(sample_gas_cycles, 10000)
|
||
|
incs = simulate_rockfall(gas_cycles, 30000)
|
||
|
print(f'Part 1 (sample): {sample_incs[:2022].sum()}')
|
||
|
print(f'Part 1: {incs[:2022].sum()}')
|
||
|
|
||
|
|
||
|
def find_cycle_and_infer(increments: ArrayLike, search_cycle_length: int, target_index: int):
|
||
|
search_term = increments[search_cycle_length:search_cycle_length*2]
|
||
|
hits = []
|
||
|
for st in range(search_cycle_length*2, len(increments)-search_cycle_length):
|
||
|
if np.all(increments[st:st+search_cycle_length] == search_term):
|
||
|
hits.append(st)
|
||
|
print(f'Found loop starting at {st}')
|
||
|
if len(hits) > 2:
|
||
|
break
|
||
|
else:
|
||
|
print('no loop found!')
|
||
|
return
|
||
|
|
||
|
loop_len = hits[1]-hits[0]
|
||
|
loop_len2 = hits[2]-hits[1]
|
||
|
if loop_len2 == loop_len:
|
||
|
print(f'Confirmed loop of length {loop_len}')
|
||
|
else:
|
||
|
print(f'Mismatched loop lengths of {loop_len} and {loop_len2}!')
|
||
|
return
|
||
|
|
||
|
base_value = increments[:hits[0]].sum()
|
||
|
loop_values = increments[hits[0]:hits[1]].cumsum()
|
||
|
loop_total = loop_values[-1]
|
||
|
|
||
|
loops, remainder = divmod(target_index - hits[0], loop_len)
|
||
|
value = base_value + loop_total*loops
|
||
|
if remainder > 0:
|
||
|
value += loop_values[remainder-1]
|
||
|
return value
|
||
|
|
||
|
print(f'Part 2 (sample): {find_cycle_and_infer(sample_incs, 1000, 1000000000000)}')
|
||
|
print(f'Part 2: {find_cycle_and_infer(incs, 5000, 1000000000000)}')
|