Adversarial search

Monte Carlo Tree Search (MCTS)

Play thousands of random games, then trust what actually wins.

Interactive demo coming soon β€” read the theory below

The idea

Monte Carlo Tree Search picks strong moves by learning from experience instead of a hand-written scoring rule. It grows a search tree of possible moves, and for the promising ones it plays out many fast, random games to the finish to see how often they lead to a win. The more it looks, the smarter it gets, and it can stop and answer at any moment. This is the engine behind many top game-playing AIs, including AlphaGo.

How it works

  1. Start with a tree that holds just the current game position (the root).
  2. SELECTION: from the root, walk down the tree by repeatedly picking the child that scores best on UCB1, a formula that balances 'moves that have won a lot' against 'moves we've barely tried'.
  3. EXPANSION: when you reach a position whose moves aren't all in the tree yet, add one new child node for an untried move.
  4. SIMULATION (rollout): from that new node, play the rest of the game with quick random moves until someone wins, loses, or draws.
  5. BACKPROPAGATION: take that result and update every node on the path back to the root, adding one visit and recording the win/loss.
  6. Repeat selection to backpropagation as many times as your time or compute budget allows.
  7. When time runs out, pick the root's child with the most visits (the most-tested, most-trusted move) and play it.

OPEN, CLOSED & the data structures

["A game tree of nodes, where each node is a game position.", "Per node: N (number of times visited) and W (total reward/wins accumulated) β€” these two counters are the whole memory of what has been learned.", "Each node also knows its untried moves (for expansion) and its parent (so results can travel back up).", "The UCB1 score, computed on the fly during selection from each child's N and W plus the parent's total visits β€” no stored heuristic table."]

Pseudocode

function MCTS(root, budget):
    while within budget:
        node = SELECT(root)         # walk down using UCB1
        child = EXPAND(node)        # add one new untried move
        result = SIMULATE(child)    # random rollout to game end
        BACKPROPAGATE(child, result)
    return most_visited_child(root).move

function SELECT(node):
    while node fully expanded and not terminal:
        node = argmax over children c of
               (c.W / c.N) + C * sqrt(ln(node.N) / c.N)   # UCB1
    return node

function BACKPROPAGATE(node, result):
    while node is not null:
        node.N = node.N + 1
        node.W = node.W + result    # from that node's player's view
        node = node.parent

At a glance

Complete?Complete in the practical sense for finite games: given any position it will always return a move, and given more time it explores more of the tree. It does not exhaustively guarantee visiting every branch in bounded time, so it is not "complete" like a full tree search β€” it is a sampling method that always has an answer ready.
Optimal?Not optimal after a short run, but it converges toward optimal play in the limit: with enough simulations UCB1 concentrates on the best moves, so given effectively unlimited rollouts its choice approaches the game-theoretic best move. In real, time-limited use it plays very strongly but without a guarantee of the perfect move.
TimeO(iterations x rollout length). Each iteration does one path down the tree plus one random playout to the game's end; you choose how many iterations to run, so cost scales directly with your time/compute budget.
SpaceO(iterations) in the worst case β€” the tree grows by at most one node per iteration, so memory stays modest and grows only where the search actually looks (unlike breadth-first methods that store whole levels).

πŸ‘ When to use it

  • Games or decisions with huge branching factors where you cannot search every move (Go, complex board and card games).
  • Problems where no good heuristic/evaluation function exists β€” MCTS learns value purely from random playouts.
  • When you need an 'anytime' algorithm that can be interrupted and still return its best current move.
  • When the game can be simulated quickly to the end, so many rollouts are cheap.

⚠️ Watch out for

  • The exploration constant C in UCB1 matters: too low and it tunnel-visions on early luck, too high and it wastes time on weak moves.
  • Pure random rollouts can be misleading in games where random play looks nothing like skilled play β€” strong systems replace them with smarter playout policies or a neural network.
  • Choose the final move by most visits, not highest average win rate; a rarely tried child can have a flattering but unreliable average.
  • It needs a fast, accurate simulator; if simulating a full game is slow or impossible, MCTS struggles.
  • Results are stochastic β€” the same position can yield slightly different searches, so reproducibility needs a fixed random seed.