Applying reinforcement learning and search theory to the game of Tic-Tac-Toe.
The board is represented as a pair of 9-bit bitboards (one per player), with
game rules, state generation, and the play loop handled by the Environment
class. Players — human or agent — are pluggable, so any combination of
human-vs-human, human-vs-agent, or agent-vs-agent play is possible. See
How to Run below.
- Value Iteration / Policy Iteration — classical dynamic-programming solutions over the full Tic-Tac-Toe state space, used as a baseline for optimal play.
- Minimax (with alpha-beta pruning) — exact adversarial search, assuming an opponent who also plays optimally.
- Q-Learning — model-free tabular learning of state-action values through self-play.
- Monte Carlo Tree Search (MCTS) — evaluates moves via simulated rollouts rather than a precomputed table or a full search of the tree.
Tic-Tac-Toe's small, fully-enumerable state space makes it a good testbed for comparing these approaches side by side before applying similar methods to larger games.
The state of the game is a tuple of three pieces of data - two integers and
one character(34, 65, 'X'). The first integer is a 9-bit bitboard
of the cross's placements, the second is the nought's, and the character
records whose turn it is to move next.
Each integer is the base-10 representation of a 9-bit bitboard over the
9 cells of the board (indexed 0–8). So 34 is 000100010 for the cross,
and 65 is 001000001 for the nought:
Taking the bitwise OR of the two boards gives the full set of occupied cells:
so the state (34, 65, 'X') represents that board configuration with
X to move next.
Each agent below is built on top of a shared MDP interface, defined by
the tuple
State space,
Action space
Transition function
If the agent's own move already ends the game (win or draw), the transition is deterministic to that terminal state.
Reward function
Discount factor
Bellman optimality equation. Value Iteration and Minimax both build on
with the optimal policy read off as
Iteratively applies the Bellman backup above to every state until values
converge within a tolerance
stopping when
The algorithm can be described with the following pseudocode [3]:
- Initialize
$\epsilon > 0$ to determine convergence condition - Initialize
$V(s)$ , for all$s \in S^{+}$ to be 0 - Loop
$\Delta \leftarrow 0 $ - Loop for each
$s \in S^+$ $v \leftarrow V(s)$ $V(s) \leftarrow \max_a \sum_{s'} P(s'|s,a) \times (R(s') + \gamma V(s'))$ $\Delta \leftarrow max(\Delta, \lvert v - V(s) \rvert) $
- Until
$\Delta < \epsilon$
Then once we find an accurate estimate for the value function, we can extract the optimal policy.
This one is a two step process where the algorithm alternates between policy evaluation and improvement until the policy stops changing:
-
Policy evaluation — solve
$V^{\pi}$ for the current fixed policy$\pi$ , by repeated application of
-
Policy improvement — given the converged
$V^{\pi}$ , greedily update the policy:
Convergence is guaranteed once step 2 leaves
The algorithm for both steps is as follows [3]:
Policy Iteration
Initialize
Policy Evaluation
- Loop
$\Delta \leftarrow 0 $ - Loop for each
$s \in S^+$ $v \leftarrow V(s)$ $V(s) \leftarrow \sum_{s'} P(s'|s,\pi(s)) \times (R(s') + \gamma V(s'))$ $\Delta \leftarrow max(\Delta, \lvert v - V(s) \rvert) $
- Until
$\Delta < \epsilon$
Policy Improvement
$\text{policy-stable} \leftarrow true $ - For each
$s \in S$ $\text{old-action} \leftarrow \pi(s)$ $V(s) \leftarrow \underset{a}{\text{argmax}} \sum_{s'} P(s'|s,a) \times (R(s') + \gamma V(s'))$ - if
$\text{old-action} \neq \pi(s)$ , then$\text{policy-stable} \leftarrow false$
- If policy-stable then stop and return
$\text{V} \approx v_* $ , and$\pi \approx \pi_* $ ; else go to policy evaluation
Unlike VI/PI, Minimax treats Tic-Tac-Toe as the two-player adversarial game it actually is, rather than folding the opponent into a stochastic transition. Each ply alternates between maximizing (the agent's turn) and minimizing (the opponent's turn):
The minimax formulation and its
A model-free method — it never uses
Training is done via self-play: the same agent plays both sides, and because the game is zero-sum, a state that is good for the player about to move is exactly as bad for their opponent. This means the bootstrapped estimate from the next state must be negated before it is folded into the update:
Action selection during training follows an
Rather than solving the whole state space up front, MCTS builds a search tree incrementally, one move at a time, using four phases repeated for a fixed budget [2]:
- Selection — descend the tree from the root using the UCB1 formula, balancing exploitation of known-good moves against exploration of under-visited ones:
where
- Expansion — add a new child node for an untried action.
- Simulation — play out a random rollout to a terminal state.
- Backpropagation — propagate the rollout's result back up the tree, incrementing visit counts and flipping the sign of the result at every level (since, as in self-play Q-learning, turns alternate and a result good for one player is bad for the other).
After the search budget is spent, the root's child with the most visits is selected as the move — visit count rather than raw value, since it is a more robust signal under partial exploration.
git clone https://github.com/<your-username>/tic-tac-toe-rl.git
cd tic-tac-toe-rlTrain an agent (Value Iteration, Policy Iteration, or Q-Learning) and save its policy/Q-table to disk:
python3 -m src.utils.trainerPlay a game, choosing which algorithm controls each side (human, Value/Policy Iteration, Minimax, Q-Learning, or MCTS):
python3 main.pyYou'll be prompted to choose an algorithm for cross and for nought;
pre-trained policies are loaded automatically from policies/.
[1] https://github.com/bsamseth/tic-tac-toe/blob/master/tictactoe.py
[2] Russell, S. J., and Peter Norvig. Artificial Intelligence: A Modern Approach. 4th ed., Pearson, 2020.
[3] Sutton, Richard S., and Andrew G. Barto. Reinforcement Learning: An Introduction. 2nd ed., The MIT Press, 2018.
[4] https://huggingface.co/learn/deep-rl-course
[5] https://paulinamoskwa.github.io/blog/2025-08-31/rl-pt1
[6] https://github.com/sonnysideupp/RL-MDP-TicTacToe/tree/main