# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

import collections
import itertools
import pathlib

# ## part 1 ##

test_start = (4, 8)
puzzle_start = (5, 9)
board_size = 10


def new_deterministic_die(num_sides):
    return itertools.cycle(range(1, num_sides+1))


def take(n, iterable):
    "Return first n items of the iterable as a list"
    return list(itertools.islice(iterable, n))


def move_player(loc, score, die):
    s = sum(take(3, die))
    loc = (loc + s) % board_size # positions labeled 0, 1, .., 9
    score += loc + 1 # add 1 because true board values are 1, 2, ..., 10
    return loc, score, die


def rolls(loc1, loc2, score1, score2, numrolls, die):
    s1 = sum(take(3, die))
    s2 = sum(take(3, die))
    print(s1, s2)
    loc1 = (loc1 + s1) % board_size # positions labeled 0, 1, ..., 9
    loc2 = (loc2 + s2) % board_size
    score1 += loc1 + 1 # add 1 because true board values are 1, 2, ..., 10
    score2 += loc2 + 1
    numrolls += 3 + 3
    return loc1, loc2, score1, score2, numrolls, die


def solve(starting_pos):
    loc1 = starting_pos[0] - 1 # board positions labeled 0, 1, ..., 9
    loc2 = starting_pos[1] - 1
    die = new_deterministic_die(100)
    score1, score2 = 0, 0
    numrolls = 0
    while True:
        loc1, score1, die = move_player(loc1, score1, die)
        numrolls += 3
        if score1 >= 1000:
            break
        loc2, score2, die = move_player(loc2, score2, die)
        numrolls += 3
        if score2 >= 1000:
            break
    return min(score1, score2)*numrolls



solve(test_start)

solve(puzzle_start)

# ## part 2 ##
#
# Each "universe" has a state (l1, s1, l2, s2), where l and s correspond to the location and the score
# of players 1 and 2. A move is 3 dice rolls, and each die roll splits a state into 3 new states, were
# one of them has a die role of +1, one w/ +2, and onw w/ +3. After 3 rolls, there will be 27 new states:
#
#     +3, +4, +5
#         +4, +5, +6
#             +5, +6, +7
#         +4, +5, +6
#             +5, +6, +7
#                 +6, +7, +8
#             +5, +6, +7
#                 +6, +7, +8
#                     +7, +8, +9
#                 
# So +3 (1x), +4 (3x), +5 (6x), +6 (7x), +7 (6x), +8 (3x), +9 (1x).
#         

newcases = {+3: 1, +4: 3, +5: 6, +6: 7, +7: 6, +8: 3, +9: 1}

newcases


def solve2(starting_pos):
    l1, l2 = starting_pos
    states = {(l1-1, 0, l2-1, 0): 1}
    wins1, wins2 = 0, 0
    while True:
        if len(states) == 0:
            break
        newstates = collections.defaultdict(int)
        # player 1 rolls
        for (l1, s1, l2, s2), replicas in states.items():
            for offset, cnt in newcases.items():
                newl1 = (l1 + offset) % board_size
                news1 = s1 + (newl1 + 1)
                if news1 >= 21:
                    wins1 += replicas*cnt
                else:
                    newstates[(newl1, news1, l2, s2)] += replicas*cnt
        states = newstates
        if len(states) == 0:
            break
        newstates = collections.defaultdict(int)
        # player 2 rolls
        for (l1, s1, l2, s2), replicas in states.items():
            for offset, cnt in newcases.items():
                newl2 = (l2 + offset) % board_size
                news2 = s2 + (newl2 + 1)
                if news2 >= 21:
                    wins2 += replicas*cnt
                else:
                    newstates[(l1, s1, newl2, news2)] += replicas*cnt
        states = newstates
    return max(wins1, wins2)


solve2(test_start)

solve2(puzzle_start)



StarcoderdataJupyterScriptsDedupFiltered