This repository was archived by the owner on Dec 31, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path__init__.py
111 lines (95 loc) · 3.8 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
"""--- Day 21: Dirac Dice ---"""
from dataclasses import dataclass
from pathlib import Path
from aoc import open_utf8
@dataclass
class Game:
"""Object to store the state of the game(s) for a point in time."""
turn: int
position1: int
position2: int
score1: int = 0
score2: int = 0
universes: int = 1
def identity(self) -> str:
"""Returns a 'unique-ish' string identity for the game.
Turn is always identical as we dispose of these every two turns.
"""
return f"{self.position1},{self.position2},{self.score1},{self.score2}"
# All these variables are required + I don't want to stack a further function.
# pylint: disable=R0914
def __have_turns(game: Game, die: dict, limit: int, wins: list, next_games: {}):
"""Completes a turn on player one and a turn on player two and updates the
universe state.
:param game: Current game to complete the turn on.
:param die: The dice being used.
:param limit: Score after which the game is complete.
:param wins: List tracking the universes wins on player one and player two.
:param next_games: Dictionary for inplace update of additional games states to resolve.
:return:
"""
for roll_total_t1, universes_t1 in die.items():
# even turn for player 1
position1 = (game.position1 + roll_total_t1 - 1) % 10 + 1
score1 = game.score1 + position1
# Current universes multiplied by combinations that would produce this roll
universes1 = game.universes * universes_t1
if score1 >= limit:
wins[0] += universes1 # P1 just won in this many inverses
continue
# odd turn for player 2
for roll_total_t2, universes_t2 in die.items():
position2 = (game.position2 + roll_total_t2 - 1) % 10 + 1
score2 = game.score2 + position2
universes2 = universes1 * universes_t2 # The data from p1 is for n-1 turn
if score2 >= limit:
wins[1] += universes2
continue
new_game = Game( # Create a new game to continue this branch
game.turn + 2,
position1,
position2,
score1,
score2,
universes2,
)
if new_game.identity() not in next_games: # stage a new game.
next_games[new_game.identity()] = new_game
else: # Faster to treat these as the same rather than stage new games.
next_games[new_game.identity()].universes += new_game.universes
def simulate_game(initial_game: Game, limit: int):
"""Starts the game and computes the distruption in the universe.
:param initial_game: The starting game conditions.
:param limit: The integer score after which the game has been won.
:return:
"""
games = {initial_game.identity(): initial_game}
wins = [0, 0]
die = dirac_dice()
while len(games) > 0:
next_games = {}
for _, game in games.items():
__have_turns(game, die, limit, wins, next_games)
games = next_games
print(len(next_games))
print(wins)
return max(wins)
def dirac_dice() -> {}:
"""Possible outcomes and how many universe states have that outcome."""
die = {}
faces = [1, 2, 3]
for r1 in faces:
for r2 in faces:
for r3 in faces:
if r1 + r2 + r3 not in die:
die[r1 + r2 + r3] = 1
else:
die[r1 + r2 + r3] += 1
return die
def load_dataset(dataset_path: Path):
"""Returns starting position between 0 and 9."""
with open_utf8(dataset_path) as file:
starting = [
int(line.split(":")[1].strip()) for line in file if len(line.strip()) > 0
]
return Game(0, starting[0], starting[1])