Expectimax
Minimax for games with dice: average over luck instead of fearing the worst.
Interactive demo coming soon β read the theory belowThe idea
Expectimax is a game-playing search that works like minimax but adds CHANCE nodes to handle randomness (dice rolls, card shuffles, coin flips). At MAX nodes you pick the best move, at MIN nodes the opponent picks their best move, and at CHANCE nodes you take the probability-weighted average of the possible random outcomes. This gives a score that reflects what you'd expect to happen on average, rather than assuming the dice always turn against you.
How it works
- Build (or explore) a game tree where each node is a game state. Label each layer by whose 'turn' it is: MAX (you), MIN (opponent, if the game has one), or CHANCE (a random event like a dice roll).
- Recurse downward until you hit a terminal state (game over) or a depth limit, then score that leaf using the game's outcome or an evaluation function.
- At a MAX node, compute the value of every child and keep the MAXIMUM β you'll choose the move that helps you most.
- At a MIN node, compute the value of every child and keep the MINIMUM β assume the opponent chooses the move that hurts you most.
- At a CHANCE node, compute the value of every child and take the EXPECTED VALUE: multiply each outcome's value by its probability and sum them (a weighted average).
- Pass each computed value back up to the parent, combining them according to that parent's node type.
- At the very top (a MAX node = your turn), pick the child move with the highest backed-up value and play it.
OPEN, CLOSED & the data structures
A game tree with three node types: MAX, MIN (optional), and CHANCE. Each CHANCE node also needs a probability distribution over its children (e.g. each die face = 1/6). The algorithm is usually a single recursive function that carries the current state, the node type (or whose turn it is), and a remaining depth. It returns a numeric value up the tree; the top-level call also remembers which move produced the best value.
Pseudocode
function expectimax(state, depth):
if state is terminal or depth == 0:
return evaluate(state)
if node_type(state) == MAX:
best = -infinity
for each action a in actions(state):
best = max(best, expectimax(result(state, a), depth - 1))
return best
if node_type(state) == MIN: # optional layer
worst = +infinity
for each action a in actions(state):
worst = min(worst, expectimax(result(state, a), depth - 1))
return worst
if node_type(state) == CHANCE:
total = 0
for each outcome o in outcomes(state):
total = total + probability(o) * expectimax(result(state, o), depth - 1)
return total # expected (average) value
At a glance
π When to use it
- Games that mix strategy with randomness: backgammon (dice), many card games (shuffled deck), or dice-based board games.
- Single-agent 'games against nature' where random events happen but no adversary β just MAX and CHANCE layers.
- Any decision problem where you want the best expected outcome given known probabilities, not a paranoid worst-case plan.
- When outcomes are genuinely random and it would be too pessimistic to assume the worst (which is what plain minimax does).
β οΈ Watch out for
- Do NOT assume the worst at chance nodes. That's minimax's job. Chance nodes average; they don't minimize.
- Chance nodes blow up the branching factor β a die adds 6 branches per node β so trees get huge fast and depth limits are usually needed.
- Standard alpha-beta pruning does not apply cleanly to chance nodes, because an average can't be bounded by seeing just one outcome. You often must expand all children of a chance node.
- Expectimax is sensitive to the exact numbers your evaluation function produces (not just their order), because it multiplies and averages them. A poorly scaled heuristic gives skewed expectations.
- Your probabilities must be correct. Wrong odds at chance nodes lead to confidently wrong 'expected' values.
- The MIN layer is optional. In solo games against randomness there is no opponent, so you use only MAX and CHANCE.