Minimax
Assume the worst opponent, then play your best move.
Interactive demo coming soon β read the theory belowThe idea
Minimax is a decision rule for two-player, zero-sum games (one player's gain is the other's loss), like tic-tac-toe or chess. You explore possible future moves as a tree and assume both players play perfectly: on your turns you pick the move that maximizes your score (you are MAX), and you assume the opponent picks the move that minimizes it (they are MIN). You look ahead to some depth, score those far-off positions, then bring those values back up the tree so the move you make right now already accounts for the opponent's best replies.
How it works
- Model the game as a tree: the current position is the root, and each branch is a legal move leading to a new position.
- Alternate the layers by whose turn it is: a MAX layer (you) then a MIN layer (opponent), then MAX again, and so on.
- Stop growing the tree at a depth limit (or at a finished game). These stopping positions are called leaves.
- Score each leaf with an evaluation function: high numbers are good for MAX, low numbers are good for MIN (e.g. +1 win, 0 draw, -1 loss).
- Back the values up one layer at a time. At a MAX node, take the highest child value; at a MIN node, take the lowest child value.
- Keep folding values upward until every node has a value that assumes perfect play from both sides.
- At the root (your turn), pick the move whose branch produced the best backed-up value, then actually play that move.
- On the opponent's real reply, repeat the whole search from the new position.
OPEN, CLOSED & the data structures
["A game tree of positions, expanded on the fly by recursion (no explicit tree object needed β the call stack IS the tree).", "A turn flag per node marking whether it is a MAX layer or a MIN layer.", "A depth counter that decreases each level and triggers the leaf evaluation when it hits 0.", "An evaluation function that turns a non-terminal position into a single number.", "The recursion returns a backed-up value per node; the root also remembers which move gave the best value."]
Pseudocode
function minimax(state, depth, isMaxPlayer):
if depth == 0 or state is terminal:
return evaluate(state) # score from MAX's point of view
if isMaxPlayer: # MAX wants the highest value
best = -infinity
for each move in legalMoves(state):
value = minimax(apply(state, move), depth - 1, false)
best = max(best, value)
return best
else: # MIN wants the lowest value
best = +infinity
for each move in legalMoves(state):
value = minimax(apply(state, move), depth - 1, true)
best = min(best, value)
return best
# To choose the actual move at the root:
# pick the move whose minimax(child, depth-1, false) value is largest.
At a glance
π When to use it
- Two-player, turn-based, zero-sum games where one side's win is the other's loss (tic-tac-toe, checkers, Connect Four, chess).
- Games with perfect information β both players see the whole board, no hidden cards or dice.
- Small enough games to search to the end, or larger ones where a good evaluation function and a depth limit are acceptable.
- As the foundation you extend with alpha-beta pruning to search deeper for the same effort.
β οΈ Watch out for
- The tree grows exponentially (b^d) β even modest games explode without pruning; almost always pair it with alpha-beta pruning.
- A depth limit means you evaluate unfinished positions, so results are only as smart as your evaluation function.
- It assumes the opponent plays perfectly; against a flawed opponent it is safe but may leave easy wins on the table.
- It needs zero-sum, perfect-information games β it doesn't directly handle chance (dice), hidden info, or more than two players.
- Remember whose turn each layer is: mixing up MAX and MIN silently produces terrible moves.
- The value is from MAX's perspective throughout β MIN minimizing that same number is what makes the math consistent.