Mastering Oware: From Human Strategy to Artificial Intelligence
Oware (also known as Awari) is one of the more popular variants of mancala. It is a strategy game played on a board of twelve pits (six on…
Mastering Oware: From Human Strategy to Artificial Intelligence

Oware (also known as Awari) is one of the more popular variants of mancala. It is a strategy game played on a board of twelve pits (six on each side), with four seeds in each pit initially. Players alternate turns sowing and capturing seeds with the goal of collecting more seeds than their opponent. In Cameroon, you will likely know it as Songo. The video below shows a typical game in progress along with the basic rules:
[embed]
Human Strategy and Tactics
As with many board games, Oware is not a game of chance, the strongest players rely on well-planned strategies and execute well-known tactics with clear purpose. The fundamental strategy consists of moving game pieces to obtain favorable positions that enable future captures. Players minimize the number of holes containing fewer than three seeds in their own row (reducing vulnerability to captures) while maximizing such holes in the opponent’s row (increasing capture opportunities). A particularly powerful strategy involves accumulating twelve or more seeds in a single hole — called a kroo — which can complete a full turn around the board and potentially capture up to fifteen seeds in one move. Allowing an opponent to execute such a capture is often an unrecoverable strategic mistake.
Expert players employ several common tactics to gain advantage: multi-hole attacks that threaten two or more opponent holes simultaneously, forcing unavoidable captures; seed hoarding where players accumulate seeds in their territory while distributing minimal seeds to opponent pits, controlling the game’s pace; winning time by forcing opponents to provide seeds that create strategic advantages; and starvation prevention to maintain legal moves and avoid automatic loss. These human strategies form the foundation for heuristic evaluation functions in AI agents — understanding how experts evaluate positions, prioritize moves, and execute tactical sequences provides the knowledge base that can be encoded into algorithmic decision-making, pattern recognition, and opening/endgame databases.
AI Approaches to Mastering Oware
Having established the strategic foundations of human play, we now explore how Artificial Intelligence methods have been applied to model and master this ancient game. Each approach represents a different philosophy of decision-making, from random exploration to deep reinforcement learning.
The Random Agent
The Random Agent serves as a baseline model, selecting a legal move at random without strategic evaluation.
def random_agent(state):
"""
Random Agent Algorithm
Selects a random legal move without strategy
"""
moves = get_legal_moves(state)
if not moves:
return None
move = random.choice(moves)
new_state = apply_move(state, move)
return new_state
This is like if you told a child the rules and at at each step just told them to pick any move and play. It is purely stochastic and lacks intelligence, yet useful for benchmarking performance of other algorithms.
The Max Agent
You may remember greed as one of the seven deadly sins, but it is also a powerful motivator in decision-making. The Max Agent takes a greedy approach, choosing the move that yields the maximum immediate reward.
def max_agent(state):
"""
Immediate Reward Maximization Algorithm
Chooses move with highest immediate gain
"""
best_move = None
best_value = float('-inf')
for move in get_legal_moves(state):
temp_state = simulate_move(state, move)
reward = seeds_captured(temp_state) - seeds_captured(state)
if reward > best_value:
best_value = reward
best_move = move
return best_move
The motivation for this is to optimize for short-term gain. A shortcoming is that it fails to consider long-term strategic consequences.
Minimax Algorithm
The Minimax algorithm models Oware as a two-player zero-sum game. Each player alternates between maximizing and minimizing the score. It is based on a tree representation of game states.
def minimax(state, depth, maximizing_player):
"""
Minimax Search Algorithm
Explores game tree to find optimal move
"""
if depth == 0 or is_terminal(state):
return evaluate(state)
if maximizing_player:
max_eval = float('-inf')
for move in get_legal_moves(state):
eval_score = minimax(
apply_move(state, move),
depth - 1,
False
)
max_eval = max(max_eval, eval_score)
return max_eval
else:
min_eval = float('inf')
for move in get_legal_moves(state):
eval_score = minimax(
apply_move(state, move),
depth - 1,
True
)
min_eval = min(min_eval, eval_score)
return min_eval
This approach guarantees optimal results within the explored depth, but suffers from exponential growth in computation.
Alpha-Beta Pruning
Alpha-Beta Pruning improves Minimax by skipping branches that cannot influence the final decision.
def alpha_beta(state, depth, alpha, beta, maximizing_player):
"""
Alpha-Beta Pruning Algorithm
Optimizes minimax by pruning irrelevant branches
"""
if depth == 0 or is_terminal(state):
return evaluate(state)
if maximizing_player:
value = float('-inf')
for move in get_legal_moves(state):
value = max(
value,
alpha_beta(
apply_move(state, move),
depth - 1,
alpha,
beta,
False
)
)
alpha = max(alpha, value)
if beta <= alpha:
break # Beta cutoff - prune
return value
else:
value = float('inf')
for move in get_legal_moves(state):
value = min(
value,
alpha_beta(
apply_move(state, move),
depth - 1,
alpha,
beta,
True
)
)
beta = min(beta, value)
if beta <= alpha:
break # Alpha cutoff - prune
return value
This approach produces the same result as Minimax while drastically reducing computational effort.
Advanced Heuristic Minimax
This version enhances Alpha-Beta Minimax by introducing heuristic knowledge. The evaluation function combines weighted features derived from expert strategies.
The heuristic evaluation is computed as a weighted sum:
Heuristic Descriptions:
H1 — Hoard seeds in one pit: Secure long-term collection
H2 — Keep seeds on player’s side: Prevent being starved
H3 — Maximize available moves: Maintain flexibility
H4 — Maximize seeds in store: Direct scoring advantage
H5 — Move from rightmost pit: Control near-opponent side
H6 — Minimize opponent’s score: Defensive anticipation
H7 — Maximize repeat turns: Enable chained moves
H8 — Points difference: Evaluate current lead
H9 — Closeness to winning: Track victory threshold
H10 — Opponent closeness to winning: Avoid defeat
def weighted_heuristic(state, weights):
"""
Computes weighted heuristic evaluation
"""
heuristics = [
hoard_seeds(state), # H1
keep_seeds_own_side(state), # H2
count_available_moves(state), # H3
seeds_in_store(state), # H4
rightmost_pit_value(state), # H5
opponent_score(state), # H6
repeat_turn_potential(state), # H7
point_difference(state), # H8
closeness_to_win(state), # H9
opponent_closeness_win(state) # H10
]
return sum(h * w for h, w in zip(heuristics, weights))
def heuristic_minimax(state, depth, alpha, beta, maximizing_player, weights):
"""
Heuristic-enhanced Minimax with Alpha-Beta Pruning
"""
if depth == 0 or is_terminal(state):
return weighted_heuristic(state, weights)
if maximizing_player:
value = float('-inf')
for move in get_legal_moves(state):
eval_score = heuristic_minimax(
apply_move(state, move),
depth - 1,
alpha,
beta,
False,
weights
)
value = max(value, eval_score)
alpha = max(alpha, value)
if beta <= alpha:
break
return value
else:
value = float('inf')
for move in get_legal_moves(state):
eval_score = heuristic_minimax(
apply_move(state, move),
depth - 1,
alpha,
beta,
True,
weights
)
value = min(value, eval_score)
beta = min(beta, value)
if beta <= alpha:
break
return value
This strategy draws directly from the strategic principles employed by human experts, allowing the AI to evaluate positions with greater nuance. We described a few of these strategies when we discussed how a human would play the game.
Monte Carlo Tree Search (MCTS)
MCTS uses random playouts to statistically estimate the value of moves rather than explicit evaluation.
import math
class MCTSNode:
def __init__(self, state, parent=None):
self.state = state
self.parent = parent
self.children = []
self.wins = 0
self.visits = 0
self.untried_moves = get_legal_moves(state)
def ucb_score(self, c=1.414):
"""Upper Confidence Bound for Trees"""
if self.visits == 0:
return float('inf')
return (self.wins / self.visits) +
c * math.sqrt(math.log(self.parent.visits) / self.visits)
def mcts(root_state, iterations):
"""
Monte Carlo Tree Search Algorithm
"""
root = MCTSNode(root_state)
for i in range(iterations):
# Selection
node = root
while not node.untried_moves and node.children:
node = max(node.children, key=lambda n: n.ucb_score())
# Expansion
if node.untried_moves:
move = random.choice(node.untried_moves)
node.untried_moves.remove(move)
child_state = apply_move(node.state, move)
child = MCTSNode(child_state, parent=node)
node.children.append(child)
node = child
# Simulation
state = node.state
while not is_terminal(state):
move = random.choice(get_legal_moves(state))
state = apply_move(state, move)
result = get_winner(state)
# Backpropagation
while node:
node.visits += 1
node.wins += result
node = node.parent
return max(root.children, key=lambda n: n.visits).state
The process balances exploration and exploitation using the UCB formula:
Where:
-
$w_i$ = wins for node $i$
-
$n_i$ = visits to node $i$
-
$N$ = total parent visits
-
$c$ = exploration parameter
Excels in complex environments, dynamically adapting to different game states.
Asynchronous Advantage Actor-Critic (A3C) Agent
The A3C Agent employs reinforcement learning, combining multiple asynchronous agents that learn policy and value functions concurrently.
import torch
import torch.nn as nn
import torch.optim as optim
class A3CNetwork(nn.Module):
def __init__(self, state_size, action_size):
super(A3CNetwork, self).__init__()
self.fc1 = nn.Linear(state_size, 128)
self.fc2 = nn.Linear(128, 64)
# Actor (policy) head
self.policy = nn.Linear(64, action_size)
# Critic (value) head
self.value = nn.Linear(64, 1)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
policy = torch.softmax(self.policy(x), dim=-1)
value = self.value(x)
return policy, value
def a3c_agent(global_network, optimizer, gamma=0.99, max_steps=100):
"""
A3C Reinforcement Learning Algorithm
"""
local_network = A3CNetwork(state_size, action_size)
state = get_initial_state()
states, actions, rewards = [], [], []
for t in range(max_steps):
# Get action from policy
state_tensor = torch.FloatTensor(state)
policy, value = local_network(state_tensor)
action = torch.multinomial(policy, 1).item()
next_state, reward = step(state, action)
states.append(state)
actions.append(action)
rewards.append(reward)
if is_terminal(next_state):
break
state = next_state
# Calculate returns
R = 0 if is_terminal(next_state) else
local_network(torch.FloatTensor(next_state))[1].item()
returns = []
for r in reversed(rewards):
R = r + gamma * R
returns.insert(0, R)
# Update global network
optimizer.zero_grad()
actor_loss = 0
critic_loss = 0
for state, action, R in zip(states, actions, returns):
state_tensor = torch.FloatTensor(state)
policy, value = local_network(state_tensor)
advantage = R - value.item()
# Actor loss
actor_loss -= torch.log(policy[action]) * advantage
# Critic loss
critic_loss += (R - value) ** 2
total_loss = actor_loss + 0.5 * critic_loss
total_loss.backward()
# Update global network parameters
optimizer.step()
Learns from experience, achieving strong generalization without predefined heuristics.
Solving the Game
In 2002, John W. Romein and Henri E. Bal solved the game of Awari (Oware’s close variant). Using a parallel search algorithm across billions of positions, they discovered that optimal play from both sides leads to a draw.
Their database contained 204 billion entries (178 GB), one of the largest computed for any game at the time. This remarkable achievement demonstrated that:
(P1) Perfect play results in a draw
(P2) The game tree complexity is manageable with modern computing
(P3) Oware joins Checkers (draughts) as strongly solved games
Conclusion
We have reached the end, if you made it this far you probably enjoyed reading about it as much as I did. Oware is a very nice game with strategic elements that have captivated a lot of players. We have seen that even traditional African games are interesting enough to be studied by AI researchers, and that they can provide fertile ground for exploring concepts in game theory, reinforcement learning, and algorithm design.
References
(R1) Romein & Bal (2002): “Solving the Game of Awari using Parallel Retrograde Analysis”
(R2) Trevon J. Hunter: “The Exploration and Analysis of Mancala from an AI Perspective”
(R3) Joan Sala’s Oware Strategy Guide: https://auale.joansala.com/en/strategy/
메타데이터
- post_id
- e8cf5c8eb7b0
- slug
- mastering-oware-from-human-strategy-to-artificial-intelligence-e8cf5c8eb7b0
- url
- https://medium.com/@pamelafugua/mastering-oware-from-human-strategy-to-artificial-intelligence-e8cf5c8eb7b0
- canonical_url
- https://medium.com/@pamelafugua/mastering-oware-from-human-strategy-to-artificial-intelligence-e8cf5c8eb7b0
- author_url
- https://medium.com/@pamelafugua
- status
- ok
- fetched_at
- 2026-06-23 17:05:31