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

commit 3f6cc45aa78990392a25e6dc7ff69e6b5070a82c
parent a13d83511102bf95c1f2fe307a917d1a87fdd3a0
Author: Eamon Caddigan <eamon.caddigan@gmail.com>
Date:   Tue, 21 Dec 2021 12:38:10 -0500

Solution to day 21, part 1

Diffstat:
Aday21_part1.py | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 77 insertions(+), 0 deletions(-)

diff --git a/day21_part1.py b/day21_part1.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +"""Advent of Code 2021, day 21 (part 1): Dirac Dice +Simulating an Advent of Code board game (which is deterministic)""" + +# I love how AoC zero indexes things that don't matter (like scanner numbers in +# day 19), but not when it does (like when we're working with modular +# arithmetic today). +# +# This is a total inelegant hack job; I want to rush through part 1 and see +# what part 2 has in store for me. If I hate go play using the same rules to a +# much higher score, I could leverage the fact that the landing positions seem +# to go through cycles. If I need to deal with random dice, I'll have to do +# something totally different. So rather than overthink things, I'm just wasing +# CPU cycles and saving my own time. + +from typing import Tuple, cast +from utils import get_puzzle_input + +EXAMPLE_INPUT = \ +"""Player 1 starting position: 4 +Player 2 starting position: 8 +""" + +def parse_input(input_string: str) -> Tuple[int, int]: + """Given the puzzle input, return a tuple containing player 1 and player + 2's starting positions, respectively""" + # Hacky, leans on the number being either one digit padded with a space or + # two digits + return cast(Tuple[int, int], + tuple(int(l[-2:]) for l in input_string.split('\n')[0:2])) + +def simulate_game(player_1_start_position: int, + player_2_start_position: int, + final_score: int) -> Tuple[int, int, int]: + """Run a game until either player crosses the `final_score` threshold, then + return a tuple containing player 1's score, player 2's score, and the + number of die rolls""" + # Just hacking this one together to get through, it's been a long day. + die_rolls = 0 + player_1_score = 0 + player_2_score = 0 + player_1_position = player_1_start_position + player_2_position = player_2_start_position + next_roll = 1 + while max(player_1_score, player_2_score) < final_score: + for _ in range(3): + player_1_position += next_roll + # 100 -> 1 -> 2 ... + next_roll = next_roll % 100 + 1 + die_rolls += 1 + player_1_position = 1 + (player_1_position - 1) % 10 + player_1_score += player_1_position + if player_1_score >= final_score: + break + for _ in range(3): + player_2_position += next_roll + next_roll = next_roll % 100 + 1 + die_rolls += 1 + player_2_position = 1 + (player_2_position - 1) % 10 + player_2_score += player_2_position + return (player_1_score, player_2_score, die_rolls) + +def solve_puzzle(input_string: str) -> int: + """Return the numeric solution to the puzzle""" + player_1_score, player_2_score, die_rolls = simulate_game( + *(parse_input(input_string) + (1000,)) + ) + return min(player_1_score, player_2_score) * die_rolls + +def main() -> None: + """Run when the file is called as a script""" + assert solve_puzzle(EXAMPLE_INPUT) == 739785 + print("Product of losing score and number of die rolls:""", + solve_puzzle(get_puzzle_input(21))) + +if __name__ == "__main__": + main()