2021-12-21 - Dirac Dice
(original .ipynb)
Day 21 puzzle input is just two players and their starting positions for a game involving dice (mine is here). Part 1 involves playing the game to completion and multiplying the score of the losing player by the number of dice rolls. Part 2 involves doing the same but with a special "quantum" die that simulates ever possible dice outcome. This was a weird one.
test_input_str = """Player 1 starting position: 4 Player 2 starting position: 8""" puzzle_input_str = """Player 1 starting position: 8 Player 2 starting position: 1""" def roller(): n = 1 while True: yield n n += 1 if n > 100: n = 1 def score(position): return position + 1 def parse_player(line): # use 0-indexed positions, makes the moving + scoring a little easier return int(line[-1]) - 1 def next_position(p, move): return (p + move) % 10 def part_one(input_str): roll_count = 0 p1, p2 = [parse_player(line) for line in input_str.split("\n")] p1_score, p2_score = 0,0 dice = roller() while True: p1_move = next(dice) + next(dice) + next(dice) roll_count += 3 p1 = next_position(p1, p1_move) p1_score += score(p1) if p1_score >= 1000: break p2_move = next(dice) + next(dice) + next(dice) roll_count += 3 p2 = next_position(p2, p2_move) p2_score += score(p2) if p2_score >= 1000: break return min(p1_score, p2_score) * roll_count assert 739785 == part_one(test_input_str) print("part one:", part_one(puzzle_input_str))
part one: 518418
%%time # Rob was curious how long it takes, so let's %%time it and see... from functools import cache def roll_sequences(): sides = (1,2,3) for r1 in sides: for r2 in sides: for r3 in sides: yield r1, r2, r3 @cache def play(p1_master, p1_score_master, p2_master, p2_score_master): p1_wins = 0 p2_wins = 0 rolls = list(roll_sequences()) for p1_roll in rolls: for p2_roll in rolls: p1, p2 = p1_master, p2_master p1_score, p2_score = p1_score_master, p2_score_master p1_move = sum(p1_roll) p1 = next_position(p1, p1_move) p1_score += score(p1) if p1_score >= 21: p1_wins += 1 break p2_move = sum(p2_roll) p2 = next_position(p2, p2_move) p2_score += score(p2) if p2_score >= 21: p2_wins += 1 continue p1_rec_wins, p2_rec_wins = play(p1, p1_score, p2, p2_score) p1_wins += p1_rec_wins p2_wins += p2_rec_wins return p1_wins, p2_wins def part_two(input_str): p1, p2 = [parse_player(line) for line in input_str.split("\n")] return max(play(p1, 0, p2, 0)) assert 444356092776315 == part_two(test_input_str) print("part two:", part_two(puzzle_input_str))
part two: 116741133558209 CPU times: user 6.98 s, sys: 3.95 ms, total: 6.99 s Wall time: 7.07 s