Chapter 176
Yahtzee Strategy
Riddler Express
You are playing a one-turn game of Yahtzee in which the only goal is to maximize the score on this single turn. After the second of three rolls, your five dice show . Keeping all five scores points for the full house. Rerolling the two s aims at the -point Yahtzee but risks a lowly three- or four-of-a-kind, scored as the sum of the five dice. What is the best strategy?
The Riddler, FiveThirtyEight, March 9, 2018(original post)
Solution
The benchmark. Standing pat scores a flat points. Any reroll is worthwhile only if its expected score exceeds . The decision is whether the upside of a Yahtzee outweighs the cost of frequently landing on a hand worth less than .
Reroll the two s. Two fresh dice land in equally likely ways. Sort the outcomes by the resulting hand.
Both come up : way out of . Hand is five s, scored as a Yahtzee, points.
Both match (but not both ): ways (both , both , both , both , or both ). Hand is three s plus a pair, a full house worth .
Otherwise: ways. Hand is three or four s with two non-matching extras, scored as the sum of the five dice (the puzzle’s stated convention for "lowly three- or four-of-a-kind"). The two rerolled dice sum on average to , and the three kept s contribute , so the average sum is .
Expected score from rerolling the two s, That falls well short of the certain .
Every other reroll is also worse than . The same enumeration over each of the proper subsets of dice to reroll, with the scoring category chosen optimally after the third roll (Yahtzee, four-of-a-kind, full house, large or small straight, three-of-a-kind, or sum), gives the table below. The best reroll, "keep one and one , reroll the other three", scores about points on average. Even that best alternative is short of .
| Strategy (kept dice) | Expected score |
|---|---|
| Keep all | |
| Keep one and one , reroll the rest | |
| Keep , reroll one | |
| Keep , reroll two s | |
| Keep two s, reroll three s | |
| Keep , reroll the rest | |
| Keep , reroll the two s | |
| Keep nothing, reroll all five |
So the best policy is to keep the hand exactly as it is.
The computation
Enumerate every possible reroll subset of the hand , average the optimal Yahtzee scoring category over the outcomes for dice rerolled, and return the best.
For each subset of kept dice, list every outcome of the rerolled dice.
Score each resulting hand by the maximum of (Yahtzee , large straight , small straight , full house , three- or four-of-a-kind = sum, plain sum).
Average and report the best strategy.
from itertools import product, combinations
def score(d):
s = sorted(d)
cs = sorted([s.count(x) for x in set(s)], reverse=True)
tot = sum(s)
best = tot
if cs[0] == 5: best = max(best, 50)
if cs == [3, 2]: best = max(best, 25)
uniq = sorted(set(s))
if uniq == [1,2,3,4,5] or uniq == [2,3,4,5,6]:
best = max(best, 40)
for start in (1, 2, 3):
if all(v in uniq for v in range(start, start + 4)):
best = max(best, 30); break
return best
hand = (4, 4, 4, 5, 5)
results = []
seen = set()
for r in range(6):
for keep_idx in combinations(range(5), r):
kept = tuple(sorted(hand[i] for i in keep_idx))
if kept in seen: continue
seen.add(kept)
n_re = 5 - r
if n_re == 0:
ev = score(kept)
else:
tot = 0
for out in product(range(1, 7), repeat=n_re):
tot += score(kept + out)
ev = tot / 6**n_re
results.append((ev, kept))
results.sort(reverse=True)
for ev, kept in results[:5]:
print(f"keep {kept} EV = {ev:.4f}")
The script prints "keep EV " at the top, confirming the boxed strategy.
Riddler Classic
It is the final turn of a Yahtzee game and the only category left to score is a large straight (all five dice consecutive, so or ). On the first of the three rolls you roll , where . You may reroll the in hope of a , or reroll the and the in hope of landing some combination of and . What is the best strategy for hitting a large straight?
The Riddler, FiveThirtyEight, March 9, 2018(original post)
Solution
The setup. Two rolls remain. Each roll lets you choose a subset of dice to keep and reroll the rest. Success means ending the third roll with a large straight.
Strategy A: reroll only . The hand becomes plus one fresh die. Success requires that die to land on . On the second roll this happens with probability . If it fails, the same one-die reroll on the third roll has probability as well. So
Strategy B: reroll . Hold and reroll two fresh dice. Now there are two routes to a large straight: pair for , or pair for . Treat the two new dice as an ordered pair, with equally likely outcomes, and split by what the second roll produced. In each branch we play the third roll optimally.
| Second-roll outcome | Count of | Best third-roll success |
|---|---|---|
| Large straight already | ||
| Has a , no or | keep , reroll for : | |
| Has a or , no | keep endpoint, reroll for : | |
| All in | reroll both, hit pair: |
The counts close: . Combining,
Compare. and . The single-die reroll wins by percentage points. A full dynamic-programming sweep over every subset of dice to keep (not only the two named strategies) gives the same optimal probability for any starting , including (rerolling either the or the is equivalent: hold and reroll the for a ).
The computation
Solve the Yahtzee large-straight subproblem by backward induction over the sorted-hand state with the number of rolls remaining.
Define = probability of finishing with a large straight when the current sorted hand is and rolls remain.
At , return if is a large straight else .
At , maximize over all subsets of to keep, averaging over the outcomes.
from itertools import product, combinations
from functools import lru_cache
def is_LS(h):
s = sorted(h)
return s == [1,2,3,4,5] or s == [2,3,4,5,6]
@lru_cache(maxsize=None)
def V(h, k):
if k == 0:
return 1.0 if is_LS(h) else 0.0
best = 0.0
seen = set()
for r in range(6):
for keep_idx in combinations(range(5), r):
kept = tuple(sorted(h[i] for i in keep_idx))
if kept in seen: continue
seen.add(kept)
n_re = 5 - r
if n_re == 0:
v = 1.0 if is_LS(kept) else 0.0
else:
tot = 0.0
for out in product(range(1,7), repeat=n_re):
tot += V(tuple(sorted(kept + out)), k - 1)
v = tot / 6**n_re
if v > best:
best = v
return best
for X in [1, 2, 4, 5, 6]:
h = tuple(sorted((1, 2, 4, 5, X)))
print(f"X={X}: optimal P = {V(h, 2):.6f} = 11/36 = {11/36:.6f}")
The script prints for every , matching the boxed answer.