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:
A | day21_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()