Advertisement
Guest User

AoC 2021 - Day 21

a guest
Dec 21st, 2021
293
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.18 KB | None | 0 0
  1. """
  2. Advent of Code 2021 - Day 21
  3. https://adventofcode.com/2021/day/21
  4. """
  5.  
  6. import re
  7. from collections import Counter, defaultdict
  8. from typing import Dict, List, Tuple
  9.  
  10. DAY = '21'
  11.  
  12. FULL_INPUT_FILE = f'../inputs/day{DAY}/input.full.txt'
  13. TEST_INPUT_FILE = f'../inputs/day{DAY}/input.test.txt'
  14.  
  15.  
  16. def load_data(infile_path: str) -> List[int]:
  17.     with open(infile_path, 'r', encoding='ascii') as infile:
  18.         return [int(re.search(r'(\d+)$', i).group()) for i in infile]
  19.  
  20.  
  21. def calculate_cycles(p1_start: int, p2_start: int) -> Tuple[List[int], List[int]]:
  22.     cycle_length = 10
  23.     cycle_range = range(cycle_length)
  24.     p1_moves, p2_moves = \
  25.         [[((((6 * (i + 1) - j) * 3) - 1) % 10) + 1 for i in cycle_range] for j in (4, 1)]
  26.     p1_cycle, p2_cycle = [[i] * cycle_length for i in (p1_start, p2_start)]
  27.     for i in cycle_range:
  28.         p1_cycle[i] = ((p1_moves[i] - 1 + p1_cycle[i - 1]) % 10) + 1
  29.         p2_cycle[i] = ((p2_moves[i] - 1 + p2_cycle[i - 1]) % 10) + 1
  30.     return p1_cycle, p2_cycle
  31.  
  32.  
  33. def find_winning_turn(target: int, cycle: List[int]) -> int:
  34.     full_cycles = int(target / sum(cycle))
  35.     points = full_cycles * sum(cycle)
  36.     i = 0
  37.     while points < target:
  38.         points += cycle[i]
  39.         i += 1
  40.     return (full_cycles * len(cycle)) + i
  41.  
  42.  
  43. def find_score_at_turn(turn: int, cycle: List[int]):
  44.     full_cycles = int(turn / len(cycle))
  45.     points = full_cycles * sum(cycle)
  46.     i = 0
  47.     while (full_cycles * len(cycle)) + i < turn:
  48.         points += cycle[i]
  49.         i += 1
  50.     return points
  51.  
  52.  
  53. def simulate_deterministic(p1_start: int, p2_start: int, target: int = 1000) -> int:
  54.     p1_cycle, p2_cycle = calculate_cycles(p1_start, p2_start)
  55.     p1_winning_turn = find_winning_turn(target, p1_cycle)
  56.     p2_winning_turn = find_winning_turn(target, p2_cycle)
  57.     if p1_winning_turn <= p2_winning_turn:
  58.         losing_score = find_score_at_turn(p1_winning_turn - 1, p2_cycle)
  59.         total_rolls = (6 * p1_winning_turn) - 3
  60.     else:
  61.         losing_score = find_score_at_turn(p2_winning_turn, p1_cycle)
  62.         total_rolls = 6 * p2_winning_turn
  63.     return losing_score * total_rolls
  64.  
  65.  
  66. def generate_rolls(dice: int, sides: int) -> Dict[int, int]:
  67.     possible_rolls = [()]
  68.     for _ in range(dice):
  69.         possible_rolls = [(i, *j) for i in range(1, sides + 1) for j in possible_rolls]
  70.     possible_rolls = Counter([sum(i) for i in possible_rolls])
  71.     return possible_rolls
  72.  
  73.  
  74. def simulate_dirac(p1_start: int, p2_start: int, spaces: int = 10,
  75.                    dice: int = 3, sides: int = 3, target: int = 21) -> int:
  76.     possible_rolls = generate_rolls(dice, sides)
  77.     p1_wins = p2_wins = 0
  78.  
  79.     states = {(p1_start, p2_start, 0, 0): 1}
  80.     while states:
  81.         new_states = defaultdict(int)
  82.         for (p1_pos, p2_pos, p1_score, p2_score), count in states.items():
  83.             for p1_roll in possible_rolls:
  84.                 new_p1_pos = ((p1_pos + p1_roll - 1) % spaces) + 1
  85.                 new_p1_score = p1_score + new_p1_pos
  86.                 if new_p1_score >= target:
  87.                     p1_wins += count * possible_rolls[p1_roll]
  88.                 else:
  89.                     for p2_roll in possible_rolls:
  90.                         new_p2_pos = ((p2_pos + p2_roll - 1) % spaces) + 1
  91.                         new_p2_score = p2_score + new_p2_pos
  92.                         if new_p2_score >= target:
  93.                             p2_wins += count * possible_rolls[p1_roll] * possible_rolls[p2_roll]
  94.                         else:
  95.                             new_states[new_p1_pos, new_p2_pos, new_p1_score, new_p2_score] += \
  96.                                 count * possible_rolls[p1_roll] * possible_rolls[p2_roll]
  97.         states = new_states
  98.     return max([p1_wins, p2_wins])
  99.  
  100.  
  101. def part_1(infile_path: str) -> int:
  102.     p1_start, p2_start = load_data(infile_path)
  103.     return simulate_deterministic(p1_start, p2_start)
  104.  
  105.  
  106. def part_2(infile_path: str) -> int:
  107.     p1_start, p2_start = load_data(infile_path)
  108.     return simulate_dirac(p1_start, p2_start)
  109.  
  110.  
  111. if __name__ == '__main__':
  112.     part1_answer = part_1(FULL_INPUT_FILE)
  113.     print(f'Part 1: {part1_answer}')
  114.  
  115.     part2_answer = part_2(FULL_INPUT_FILE)
  116.     print(f'Part 2: {part2_answer}')
  117.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement