Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Mathematics / Game Theory

6.7.5 Minimax and Alpha-Beta Pruning

6-Mathematics/6.7.5_Minimax_and_Alpha-Beta_Pruning.cpp

Searches a finite two-player, zero-sum, perfect-information game tree using minimax and alpha-beta pruning. Minimax assumes both players play optimally: the maximizing player chooses the child with largest value, and the minimizing player chooses the child with smallest value.

Alpha-beta pruning computes the same value as minimax, but avoids exploring branches that cannot affect the final answer. It is most effective when good moves are searched first.

The example game is "take $1$ or $2$ stones": starting with stones stones, players alternate taking $1$ or $2$ stones, and the player who takes the last stone wins. The evaluation returns $1$ when the maximizing player wins and $-1$ when the minimizing player wins.

  • minimax(stones, maximizing) returns the exact game-tree value, where maximizing indicates whose turn it is.
  • alpha_beta(stones, maximizing) returns the same value using alpha-beta pruning.
  • best_take(stones) returns an optimal first move using alpha-beta pruning, either $1$ or $2$, for positive stones.

Implementation

#include <algorithm>

int minimax(int stones, bool maximizing) {
  if (stones == 0) {
    return maximizing ? -1 : 1;
  }
  int best = maximizing ? -2 : 2;
  for (int take = 1; take <= 2 && take <= stones; take++) {
    int child = minimax(stones - take, !maximizing);
    best = maximizing ? std::max(best, child) : std::min(best, child);
  }
  return best;
}

int alpha_beta(int stones, bool maximizing, int alpha = -2, int beta = 2) {
  if (stones == 0) {
    return maximizing ? -1 : 1;
  }
  int value = maximizing ? -2 : 2;
  for (int take = 1; take <= 2 && take <= stones; take++) {
    int child = alpha_beta(stones - take, !maximizing, alpha, beta);
    if (maximizing) {
      value = std::max(value, child);
      alpha = std::max(alpha, value);
    } else {
      value = std::min(value, child);
      beta = std::min(beta, value);
    }
    if (alpha >= beta) {
      break;
    }
  }
  return value;
}

int best_take(int stones) {
  int best_move = 1, best_value = -2;
  for (int take = 1; take <= 2 && take <= stones; take++) {
    int value = alpha_beta(stones - take, false);
    if (value > best_value) {
      best_value = value;
      best_move = take;
    }
  }
  return best_move;
}

Example Usage

#include <cassert>

int main() {
  assert(minimax(1, true) == 1);
  assert(minimax(3, true) == -1);
  for (int stones = 1; stones <= 10; stones++) {
    assert(minimax(stones, true) == alpha_beta(stones, true));
  }
  assert(best_take(4) == 1);  // Move to losing state with 3 stones.
  assert(best_take(5) == 2);  // Move to losing state with 3 stones.
  return 0;
}