Adversarial search

Minimax

Assume the worst opponent, then play your best move.

Interactive demo coming soon β€” read the theory below

The 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

  1. Model the game as a tree: the current position is the root, and each branch is a legal move leading to a new position.
  2. Alternate the layers by whose turn it is: a MAX layer (you) then a MIN layer (opponent), then MAX again, and so on.
  3. Stop growing the tree at a depth limit (or at a finished game). These stopping positions are called leaves.
  4. 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).
  5. 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.
  6. Keep folding values upward until every node has a value that assumes perfect play from both sides.
  7. At the root (your turn), pick the move whose branch produced the best backed-up value, then actually play that move.
  8. 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

Complete?Yes, for finite games. Because it searches every branch down to the depth limit or the game's end, it always returns a move β€” it never gets stuck. The catch is that "complete" here means it finishes the search, not that it foresees an infinite game.
Optimal?Plays optimally against an optimal opponent. If you search to the true end of the game, Minimax guarantees the best achievable result assuming the opponent also plays perfectly. Against a weaker opponent it stays safe but may not exploit their mistakes maximally, and if you stop at a depth limit, its play is only as good as the evaluation function.
TimeO(b^d) β€” branching factor b (moves per turn) raised to the depth d searched. This exponential blowup is why full chess is out of reach and why pruning and depth limits are needed.
SpaceO(bΒ·d) β€” only the current path from root to leaf plus its siblings lives on the recursion stack at once, so memory is modest even though time is exponential.

πŸ‘ 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.