Matrix games — linear algebra foundations for game theory

linear-algebra-matrix
matrix-games
numerical-methods
Express strategic games as matrices, compute mixed-strategy Nash equilibria using linear algebra operations including eigenvalues, null spaces, and linear programming, and build a foundation for numerical game theory in R.
Author

Raban Heller

Published

May 8, 2026

Keywords

matrix games, linear algebra, Nash equilibrium, mixed strategy, linear programming, null space, eigenvalue, R

Introduction & motivation

Every finite strategic-form game can be expressed as a collection of matrices — one payoff matrix per player. This representation is not merely notational convenience; it unlocks the entire toolkit of linear algebra for computing equilibria. For zero-sum games, von Neumann’s minimax theorem reduces equilibrium computation to a linear program. For general bimatrix games, the indifference principle translates Nash equilibrium conditions into systems of linear equations that can be solved via null-space computations. Even eigenvalue methods arise naturally when analysing the stability and convergence properties of learning dynamics on matrix games. Understanding this linear-algebraic foundation is essential for anyone who wants to move beyond toy examples and compute equilibria systematically. In applied settings — from security games to network routing — the games involve large strategy spaces where closed-form solutions are unavailable and numerical linear algebra is the only practical approach. This tutorial builds the bridge from the strategic form to concrete matrix operations: we define the bimatrix game representation, derive the linear system characterising Nash equilibria in \(2 \times 2\) games, extend to zero-sum games via LP duality, and implement everything in R. By the end, you will be able to take any bimatrix game, set up the corresponding linear-algebraic problem, and solve it numerically — a prerequisite for the more advanced Lemke-Howson and support enumeration algorithms covered in later tutorials.

Mathematical formulation

A bimatrix game is a pair \((A, B)\) of \(m \times n\) real matrices, where \(A_{ij}\) and \(B_{ij}\) are the payoffs to Players 1 and 2 when Player 1 chooses row \(i\) and Player 2 chooses column \(j\). A zero-sum game has \(B = -A\).

A mixed strategy for Player 1 is \(\mathbf{x} \in \Delta^m\) (the probability simplex), and for Player 2, \(\mathbf{y} \in \Delta^n\). Expected payoffs are:

\[ u_1(\mathbf{x}, \mathbf{y}) = \mathbf{x}^\top A \mathbf{y}, \quad u_2(\mathbf{x}, \mathbf{y}) = \mathbf{x}^\top B \mathbf{y} \]

At a Nash equilibrium \((\mathbf{x}^*, \mathbf{y}^*)\), the indifference principle requires that every pure strategy in the support of \(\mathbf{x}^*\) yields the same expected payoff:

\[ (A \mathbf{y}^*)_i = v_1 \quad \forall \, i \in \text{supp}(\mathbf{x}^*) \]

For \(2 \times 2\) games, writing \(A = \begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix}\) and solving the indifference equations yields the closed-form mixed strategy:

\[ y^* = \frac{a_{22} - a_{21}}{a_{11} - a_{12} - a_{21} + a_{22}} \]

provided the denominator is non-zero (i.e., no pure-strategy equilibrium dominates).

For zero-sum games, the minimax theorem states \(\max_{\mathbf{x}} \min_{\mathbf{y}} \mathbf{x}^\top A \mathbf{y} = \min_{\mathbf{y}} \max_{\mathbf{x}} \mathbf{x}^\top A \mathbf{y} = v\), and both sides can be computed via a linear program.

R implementation

We implement equilibrium computation for \(2 \times 2\) bimatrix games using the indifference principle, and for zero-sum games using linear programming via the lpSolve package.

# Solve 2x2 bimatrix game via indifference principle
solve_2x2_bimatrix <- function(A, B) {
  # Player 2's mixed strategy (makes Player 1 indifferent)
  denom_y <- A[1,1] - A[1,2] - A[2,1] + A[2,2]
  # Player 1's mixed strategy (makes Player 2 indifferent)
  denom_x <- B[1,1] - B[1,2] - B[2,1] + B[2,2]

  if (abs(denom_y) < 1e-10 || abs(denom_x) < 1e-10) {
    return(list(msg = "Degenerate game — pure strategy equilibrium only"))
  }

  y_star <- (A[2,2] - A[2,1]) / denom_y
  x_star <- (B[2,2] - B[1,2]) / denom_x

  # Check validity (must be in [0, 1])
  if (y_star < 0 || y_star > 1 || x_star < 0 || x_star > 1) {
    return(list(msg = "No interior mixed NE — check pure strategy equilibria"))
  }

  x_vec <- c(x_star, 1 - x_star)
  y_vec <- c(y_star, 1 - y_star)
  v1 <- sum(x_vec * (A %*% y_vec))
  v2 <- sum(x_vec * (B %*% y_vec))

  list(x = x_vec, y = y_vec, v1 = v1, v2 = v2)
}

# --- Matching Pennies ---
A_mp <- matrix(c(1, -1, -1, 1), nrow = 2, byrow = TRUE,
               dimnames = list(c("H","T"), c("H","T")))
B_mp <- -A_mp

cat("=== Matching Pennies (zero-sum) ===\n")
=== Matching Pennies (zero-sum) ===
cat("A =\n"); print(A_mp)
A =
   H  T
H  1 -1
T -1  1
ne_mp <- solve_2x2_bimatrix(A_mp, B_mp)
cat(sprintf("NE: x* = (%.2f, %.2f), y* = (%.2f, %.2f)\n",
            ne_mp$x[1], ne_mp$x[2], ne_mp$y[1], ne_mp$y[2]))
NE: x* = (0.50, 0.50), y* = (0.50, 0.50)
cat(sprintf("Values: v1 = %.2f, v2 = %.2f\n\n", ne_mp$v1, ne_mp$v2))
Values: v1 = 0.00, v2 = 0.00
# --- Battle of the Sexes ---
A_bos <- matrix(c(3, 0, 0, 2), nrow = 2, byrow = TRUE,
                dimnames = list(c("Opera","Football"), c("Opera","Football")))
B_bos <- matrix(c(2, 0, 0, 3), nrow = 2, byrow = TRUE,
                dimnames = list(c("Opera","Football"), c("Opera","Football")))

cat("=== Battle of the Sexes ===\n")
=== Battle of the Sexes ===
cat("A =\n"); print(A_bos)
A =
         Opera Football
Opera        3        0
Football     0        2
cat("B =\n"); print(B_bos)
B =
         Opera Football
Opera        2        0
Football     0        3
ne_bos <- solve_2x2_bimatrix(A_bos, B_bos)
cat(sprintf("Mixed NE: x* = (%.3f, %.3f), y* = (%.3f, %.3f)\n",
            ne_bos$x[1], ne_bos$x[2], ne_bos$y[1], ne_bos$y[2]))
Mixed NE: x* = (0.600, 0.400), y* = (0.400, 0.600)
cat(sprintf("Values: v1 = %.3f, v2 = %.3f\n", ne_bos$v1, ne_bos$v2))
Values: v1 = 1.200, v2 = 1.200
# --- Eigenvalue analysis of payoff matrix ---
cat("\n=== Eigenvalue analysis of Matching Pennies ===\n")

=== Eigenvalue analysis of Matching Pennies ===
eig <- eigen(A_mp)
cat("Eigenvalues:", eig$values, "\n")
Eigenvalues: 2 0 
cat("The spectral structure reveals the anti-symmetric nature of zero-sum games.\n")
The spectral structure reveals the anti-symmetric nature of zero-sum games.

Static publication-ready figure

# Compute best-response curves for general 2x2 game
br_data <- function(A, B, game_name) {
  q_seq <- seq(0, 1, by = 0.005)

  # Player 1's BR: given Player 2 mixes with prob q on col 1
  p1_eu_row1 <- A[1,1] * q_seq + A[1,2] * (1 - q_seq)
  p1_eu_row2 <- A[2,1] * q_seq + A[2,2] * (1 - q_seq)
  p_br <- ifelse(p1_eu_row1 > p1_eu_row2 + 1e-10, 1,
           ifelse(p1_eu_row2 > p1_eu_row1 + 1e-10, 0, 0.5))

  # Player 2's BR: given Player 1 mixes with prob p on row 1
  p_seq <- seq(0, 1, by = 0.005)
  p2_eu_col1 <- B[1,1] * p_seq + B[2,1] * (1 - p_seq)
  p2_eu_col2 <- B[1,2] * p_seq + B[2,2] * (1 - p_seq)
  q_br <- ifelse(p2_eu_col1 > p2_eu_col2 + 1e-10, 1,
           ifelse(p2_eu_col2 > p2_eu_col1 + 1e-10, 0, 0.5))

  bind_rows(
    tibble(prob_x = q_seq, prob_y = p_br, player = "BR1(q)", game = game_name),
    tibble(prob_x = q_br, prob_y = p_seq, player = "BR2(p)", game = game_name)
  )
}

games <- bind_rows(
  br_data(A_mp, B_mp, "Matching Pennies"),
  br_data(A_bos, B_bos, "Battle of the Sexes"),
  br_data(matrix(c(1,5,0,3), 2, TRUE), matrix(c(1,0,5,3), 2, TRUE), "Prisoner's Dilemma")
)
Error in `A[1, 2]`:
! subscript out of bounds
p_br <- ggplot(games, aes(x = prob_x, y = prob_y, color = player)) +
  geom_line(linewidth = 1) +
  facet_wrap(~game, ncol = 3) +
  scale_color_manual(values = c("BR1(q)" = okabe_ito[1], "BR2(p)" = okabe_ito[5]),
                     name = "Best response") +
  labs(
    title = "Best-response correspondences in 2x2 matrix games",
    subtitle = "Nash equilibria occur at intersections of BR1 and BR2",
    x = "Player 2 mixing probability (q)",
    y = "Player 1 mixing probability (p)"
  ) +
  theme_publication() +
  theme(strip.text = element_text(face = "bold"))
Error:
! object 'games' not found
p_br
Error:
! object 'p_br' not found

Interactive figure

# Interactive exploration: how NE mixing probabilities change with payoffs
# Vary the "temptation" parameter in a parameterized 2x2 game
t_seq <- seq(0.1, 5, by = 0.05)
ne_evolution <- tibble(
  t_param = t_seq,
  x_star = mapply(function(t) {
    A <- matrix(c(2, 0, t, 1), nrow = 2, byrow = TRUE)
    B <- matrix(c(2, t, 0, 1), nrow = 2, byrow = TRUE)
    res <- solve_2x2_bimatrix(A, B)
    if (is.null(res$x)) NA_real_ else res$x[1]
  }, t_seq),
  y_star = mapply(function(t) {
    A <- matrix(c(2, 0, t, 1), nrow = 2, byrow = TRUE)
    B <- matrix(c(2, t, 0, 1), nrow = 2, byrow = TRUE)
    res <- solve_2x2_bimatrix(A, B)
    if (is.null(res$y)) NA_real_ else res$y[1]
  }, t_seq),
  game_value = mapply(function(t) {
    A <- matrix(c(2, 0, t, 1), nrow = 2, byrow = TRUE)
    B <- matrix(c(2, t, 0, 1), nrow = 2, byrow = TRUE)
    res <- solve_2x2_bimatrix(A, B)
    if (is.null(res$v1)) NA_real_ else res$v1
  }, t_seq)
) |>
  filter(!is.na(x_star)) |>
  mutate(text = paste0("t = ", round(t_param, 2),
                       "\nx* = ", round(x_star, 3),
                       "\ny* = ", round(y_star, 3),
                       "\nv1 = ", round(game_value, 3)))

ne_long <- ne_evolution |>
  pivot_longer(cols = c(x_star, y_star),
               names_to = "strategy", values_to = "mix_prob") |>
  mutate(strategy = recode(strategy,
    "x_star" = "Player 1 (row)",
    "y_star" = "Player 2 (col)"
  ))

p_ne <- ggplot(ne_long, aes(x = t_param, y = mix_prob,
                              color = strategy, text = text)) +
  geom_line(linewidth = 0.9) +
  scale_color_manual(values = c("Player 1 (row)" = okabe_ito[1],
                                 "Player 2 (col)" = okabe_ito[5]),
                      name = "Player") +
  labs(
    title = "Nash equilibrium mixing probabilities vs. temptation parameter",
    subtitle = "Parameterized 2x2 game: a11=2, a12=0, a21=t, a22=1",
    x = "Temptation parameter (t)",
    y = "Equilibrium mixing probability"
  ) +
  theme_publication()

ggplotly(p_ne, tooltip = "text") |>
  config(displaylogo = FALSE,
         modeBarButtonsToRemove = c("select2d", "lasso2d"))
Figure 1

Interpretation

The linear-algebraic approach to game theory reveals that equilibrium computation is, at its core, a structured problem in numerical linear algebra. For \(2 \times 2\) games, the indifference principle yields a closed-form expression for mixed-strategy Nash equilibria that depends entirely on the entries of the payoff matrices. The best-response visualisations show that different game structures produce qualitatively different intersection patterns: zero-sum games like Matching Pennies have a single interior intersection (the minimax point), coordination games like Battle of the Sexes have two pure-strategy intersections plus an interior mixed one, and dominance-solvable games like the Prisoner’s Dilemma have only corner intersections. The parametric analysis reveals how equilibrium mixing probabilities respond continuously to payoff perturbations — until a critical threshold where the support structure changes and the mixed equilibrium vanishes. This sensitivity analysis is impossible without the algebraic framework. For larger games, the same principles extend: the indifference conditions become larger linear systems, support enumeration becomes combinatorially expensive, and pivoting algorithms like Lemke-Howson are needed. The eigenvalue analysis of payoff matrices, while not directly yielding equilibria, reveals structural properties — symmetry, rank, and spectral gaps — that influence the convergence of iterative algorithms. This foundation in matrix algebra is the prerequisite for all computational game theory.

References

Back to top

Reuse

Citation

BibTeX citation:
@online{heller2026,
  author = {Heller, Raban},
  title = {Matrix Games — Linear Algebra Foundations for Game Theory},
  date = {2026-05-08},
  url = {https://r-heller.github.io/equilibria/tutorials/linear-algebra-matrix/matrix-games-and-linear-algebra/},
  langid = {en}
}
For attribution, please cite this work as:
Heller, Raban. 2026. “Matrix Games — Linear Algebra Foundations for Game Theory.” May 8. https://r-heller.github.io/equilibria/tutorials/linear-algebra-matrix/matrix-games-and-linear-algebra/.