from functools import reduce def all_throws(d): """The sequence of all distinct sequences of D dice.""" if d == 0: return [[]] else: return [ d1 + [f] for d1 in all_throws(d-1) for f in range(1, 7) ] def score(throw): """The score from the dice sequence THROW.""" if any(map(lambda f: f == 1, throw)): return sum([ f for f in throw if f == 1 ]) else: return sum(throw) def throws(n): all_throws_n = all_throws(n) def throws_scoring(k): return [ x for x in all_throws_n if score(x) == k ] return [ throws_scoring(k) for k in range(6*n+1) ] def pretty_throws(s): for k in range(1, len(s)): print("{:2d}: ".format(k), end="") for count, throw in enumerate(s[k]): if (count % 5 == 0 and count > 0): print("\n ", end="") print(throw, end=" ") print() # Gaming # Naive, brute-force approach: Try all all possibilites def forced_win(N, score=0): if score >= N: return True if score + 1 >= N: return False for p in range(1, 3): if not forced_win(N, score+p): return True return False # Approach specialized to this game. We lose if SCORE == N-1. we win if # N-4 <= SCORE < N-1, since we can force the opponent's revised score to # N-1. This in turn means that we lose if SCORE == N-5, and win if # N-8 <= SCORE < N-5 (since we can force the score to N-4<=SCORE= N or (-score) % N != 1 # Here are some naive players that don't take advantage of the specific # structure of this game. def best_play(N, opponent, score=0): if score >= N: return [] for p in range(1, 3): if not forced_win(N, score+p): other_move = opponent(N, score+p) if other_move is None: return [p] else: return [p, other_move] + best_play(N, opponent, score+p+other_move) other_move = opponent(N, score+1) if other_move is None: return [1] else: return [1, other_move] + best_play(N, opponent, score+1+other_move) def stupid_opponent(N, score): return None if score >= N else 1 def smart_opponent(N, score): if score >= N: return None for p in range(1, 3): if not forced_win(N, score+p): return p return 1