advent_of_code_2021

My attempts to work through the 2021 Advent of Code problems.
git clone https://git.eamoncaddigan.net/advent_of_code_2021.git
Log | Files | Refs | README | LICENSE

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()