day21_part1.py (3110B)
1 #!/usr/bin/env python 2 """Advent of Code 2021, day 21 (part 1): Dirac Dice 3 Simulating an Advent of Code board game (which is deterministic)""" 4 5 # I love how AoC zero indexes things that don't matter (like scanner numbers in 6 # day 19), but not when it does (like when we're working with modular 7 # arithmetic today). 8 # 9 # This is a total inelegant hack job; I want to rush through part 1 and see 10 # what part 2 has in store for me. If I hate go play using the same rules to a 11 # much higher score, I could leverage the fact that the landing positions seem 12 # to go through cycles. If I need to deal with random dice, I'll have to do 13 # something totally different. So rather than overthink things, I'm just wasing 14 # CPU cycles and saving my own time. 15 16 from typing import Tuple, cast 17 from utils import get_puzzle_input 18 19 EXAMPLE_INPUT = \ 20 """Player 1 starting position: 4 21 Player 2 starting position: 8 22 """ 23 24 def parse_input(input_string: str) -> Tuple[int, int]: 25 """Given the puzzle input, return a tuple containing player 1 and player 26 2's starting positions, respectively""" 27 # Hacky, leans on the number being either one digit padded with a space or 28 # two digits 29 return cast(Tuple[int, int], 30 tuple(int(l[-2:]) for l in input_string.split('\n')[0:2])) 31 32 def simulate_game(player_1_start_position: int, 33 player_2_start_position: int, 34 final_score: int) -> Tuple[int, int, int]: 35 """Run a game until either player crosses the `final_score` threshold, then 36 return a tuple containing player 1's score, player 2's score, and the 37 number of die rolls""" 38 # Just hacking this one together to get through, it's been a long day. 39 die_rolls = 0 40 player_1_score = 0 41 player_2_score = 0 42 player_1_position = player_1_start_position 43 player_2_position = player_2_start_position 44 next_roll = 1 45 while max(player_1_score, player_2_score) < final_score: 46 for _ in range(3): 47 player_1_position += next_roll 48 # 100 -> 1 -> 2 ... 49 next_roll = next_roll % 100 + 1 50 die_rolls += 1 51 player_1_position = 1 + (player_1_position - 1) % 10 52 player_1_score += player_1_position 53 if player_1_score >= final_score: 54 break 55 for _ in range(3): 56 player_2_position += next_roll 57 next_roll = next_roll % 100 + 1 58 die_rolls += 1 59 player_2_position = 1 + (player_2_position - 1) % 10 60 player_2_score += player_2_position 61 return (player_1_score, player_2_score, die_rolls) 62 63 def solve_puzzle(input_string: str) -> int: 64 """Return the numeric solution to the puzzle""" 65 player_1_score, player_2_score, die_rolls = simulate_game( 66 *(parse_input(input_string) + (1000,)) 67 ) 68 return min(player_1_score, player_2_score) * die_rolls 69 70 def main() -> None: 71 """Run when the file is called as a script""" 72 assert solve_puzzle(EXAMPLE_INPUT) == 739785 73 print("Product of losing score and number of die rolls:""", 74 solve_puzzle(get_puzzle_input(21))) 75 76 if __name__ == "__main__": 77 main()