···11+import fileinput
22+from utils import Point, N, S, E, W
33+44+55+ROCKS = [
66+ [Point(0, 0), Point(1, 0), Point(2, 0), Point(3, 0)],
77+ [Point(1, 0), Point(0, 1), Point(1, 1), Point(2, 1), Point(1, 2)],
88+ [Point(0, 0), Point(1, 0), Point(2, 0), Point(2, 1), Point(2, 2)],
99+ [Point(0, 0), Point(0, 1), Point(0, 2), Point(0, 3)],
1010+ [Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)],
1111+ ]
1212+1313+1414+pattern = fileinput.input()[0].strip()
1515+lcm = len(pattern) * len(ROCKS)
1616+1717+def simulate(target_rocks, key_height=10):
1818+ tower_hats = {}
1919+ curr_rock = None
2020+2121+ move_num = 0
2222+ rocks = 0
2323+ height = 0
2424+ bonus_height = 0
2525+2626+ chamber = {Point(x, 0): "#" for x in range(7)}
2727+2828+ while rocks < target_rocks:
2929+ if curr_rock is None:
3030+ # Check for a cycle.
3131+ if rocks % lcm == 0 and bonus_height == 0:
3232+ key = ''
3333+ for y in range(key_height):
3434+ key += ''.join(chamber.get(Point(x, height - y), ".") for x in range(7))
3535+ key += '\n'
3636+3737+ # Jump forward because we found a cycle, then finish up the simulation.
3838+ if key in tower_hats:
3939+ last_height, last_rocks = tower_hats[key]
4040+4141+ # Deltas from the last time we saw the hat.
4242+ cycle_height = height - last_height
4343+ cycle_rocks = rocks - last_rocks
4444+4545+ # Safe to jump forward this many cycles.
4646+ n_cycles = (target_rocks - rocks) // cycle_rocks
4747+4848+ # Store how much extra height we gained implicitly,
4949+ # otherwise future pieces will spawn way too high
5050+ # compared to the state of the chamber.
5151+ bonus_height += n_cycles * cycle_height
5252+ rocks += n_cycles * cycle_rocks
5353+5454+ else:
5555+ tower_hats[key] = (height, rocks)
5656+5757+ # Spawn new rock.
5858+ curr_rock = [p + N * (height + 4) + (E * 2) for p in ROCKS[rocks % len(ROCKS)]]
5959+6060+6161+ # Try to move the falling rock based on the jet of gas.
6262+ if pattern[move_num % len(pattern)] == '>':
6363+ # Does the right wall block us?
6464+ if any(p.x >= 6 for p in curr_rock):
6565+ pass
6666+ # Does another rock at rest block us?
6767+ elif any(p + E in chamber for p in curr_rock):
6868+ pass
6969+ else:
7070+ curr_rock = [p + E for p in curr_rock]
7171+ else:
7272+ if any(p.x <= 0 for p in curr_rock):
7373+ pass
7474+ elif any(p + W in chamber for p in curr_rock):
7575+ pass
7676+ else:
7777+ curr_rock = [p + W for p in curr_rock]
7878+7979+ # Do we come to rest?
8080+ if any(chamber.get(p + S) == '#' for p in curr_rock):
8181+ for r in curr_rock:
8282+ chamber[r] = "#"
8383+ height = max(height, r.y)
8484+8585+ curr_rock = None
8686+ rocks += 1
8787+8888+ else:
8989+ curr_rock = [r + S for r in curr_rock]
9090+9191+ move_num += 1
9292+9393+ return height + bonus_height
9494+9595+print("Part 1:", simulate(2022))
9696+print("Part 2:", simulate(1_000_000_000_000, key_height=16)) # 18 good