AI State Space Search // Classic Planning Problem

Missionaries &
Cannibals Solver

Transport 3 missionaries and 3 cannibals across a river using a boat that holds at most 2 people. Missionaries must never be outnumbered by cannibals on either bank. Explore the complete state space with 5 classical AI search algorithms.

> 11 Optimal Moves > 5 Algorithms > 16 Valid States > 3+3 People

Problem Formalization

Formally defining the state space, actions, validity constraints, and search parameters for this classic AI planning problem.

LEFT BANK [START] RIGHT BANK [GOAL] RIVER M M M C C C Initial State: (M=3, C=3, Boat=Left)

> State Encoding

Each state is a tuple:

State = (M_L, C_L, Boat)
M_L in {0,1,2,3} — missionaries on left
C_L in {0,1,2,3} — cannibals on left
Boat in {0=Left, 1=Right}

Initial: (3, 3, 0)
Goal: (0, 0, 1)

> Valid Actions

5 possible boat loads (missionaries, cannibals):

(1,0)(2,0) (0,1)(0,2) (1,1)
Boat on Left: subtract from (M_L, C_L)
Boat on Right: add to (M_L, C_L)
Then flip boat side.

> Validity Constraint

Missionaries must never be outnumbered:

(M_L = 0 OR M_L ≥ C_L) AND
(M_R = 0 OR M_R ≥ C_R) AND
0 ≤ M_L ≤ 3 AND 0 ≤ C_L ≤ 3

> State Space Stats

32
Total States
16
Valid States
11
Optimal Steps
5
Actions

Interactive Puzzle

Play the puzzle yourself! Select people on the boat's side, board them, then cross. Can you solve it in 11 moves?

> Missionaries & Cannibals
Moves: 0
State: (3,3,L) Left: 3M 3C Right: 0M 0C Boat: Left
LEFT BANK RIGHT BANK

> Select passengers (max 2)

No one boarded yet

> Move Log

AI Solver

Watch five classical AI search algorithms solve the puzzle with animated river crossing visualization.

--
Nodes Explored
--
Path Length
--
Steps
0
Heuristic Calls
Speed: 5
LEFT RIGHT

Search Tree Visualization

Watch BFS, DFS, or A* explore the state space. Nodes are colored as they are discovered.

Speed: 5
Nodes: 0 | Depth: 0
Unvisited
Frontier
Visited
Goal

Algorithm Comparison

Side-by-side performance and property comparison of all five algorithms.

AlgorithmNodesPathOptimal?Complete?TimeSpaceHeuristic
BFS----YesYesO(bd)O(bd)None
DFS----NoYes*O(bm)O(bm)None
A*----YesYesO(bd)O(bd)ceil((M+C)/2)
Greedy----NoYesO(bm)O(bm)ceil((M+C)/2)
IDDFS----YesYesO(bd)O(bd)None

Algorithm Deep Dive

Detailed breakdown of each search algorithm with pseudocode and properties.

Breadth-First Search

Explores all neighbors at current depth before moving deeper. Guarantees shortest path for unweighted graphs.

  • Optimal Yes - finds shallowest goal
  • Complete Yes - guaranteed
  • Time O(b^d)
  • Space O(b^d) stores frontier
function BFS(start): queue = [start] visited = {start} while queue not empty: node = queue.dequeue() if node == goal: return path for child in successors(node): if child not in visited: visited.add(child) queue.enqueue(child)
Best for: Optimal + small state space

Depth-First Search

Explores as far as possible along each branch before backtracking. Memory efficient but may miss shortest path.

  • Optimal No - finds any path
  • Complete Yes (with cycle detection)
  • Time O(b^m) worst case
  • Space O(bm) linear
function DFS(node, visited): if node == goal: return [node] visited.add(node.key()) for child in successors(node): if child not visited: result = DFS(child, visited) if result: return [node]+result return None
Best for: Memory limited

A* Search

Uses f(n) = g(n) + h(n) to guide search. Optimal with admissible heuristic. Most efficient informed search.

  • Optimal Yes (admissible h)
  • Complete Yes - guaranteed
  • Time O(b^d) guided
  • Space O(b^d)
function Astar(start): open = PQ[(0, start)] // (f, node) g = {start: 0} while open not empty: (f, node) = open.pop() if node == goal: return path for (child, cost) in successors: ng = g[node] + cost if ng < g.get(child, INF): g[child] = ng open.push((ng+h(child), child))
Best for: Optimal + informed

Greedy Best-First

Always expands node closest to goal. Fast but not optimal - ignores path cost entirely.

  • Optimal No - ignores g(n)
  • Complete Yes (w/ visited)
  • Time O(b^m) worst
  • Space O(b^m)
function Greedy(start): open = PQ[(h(start), start)] visited = {} while open not empty: (h, node) = open.pop() if node == goal: return path visited.add(node) for child in successors: if child not visited: open.push((h(child), child))
Best for: Speed over optimality

Iterative Deepening DFS

Combines BFS optimality with DFS memory. Repeats DFS with increasing depth limits.

  • Optimal Yes - like BFS
  • Complete Yes - guaranteed
  • Time O(b^d)
  • Space O(bd) linear!
function IDDFS(start): for limit = 0, 1, 2, ...: result = DLS(start, limit) if result: return result function DLS(node, limit): if node == goal: return [node] if limit == 0: return CUTOFF for child in successors: r = DLS(child, limit-1) if r: return [node]+r
Best for: Memory limited + optimal

AI vs Human

Complete the puzzle to see how you stack up against BFS. Play the game first!

// Human Play

Moves Made--
StatusIn Progress
Extra vs Optimal--
StrategyTrial & Error

// AI (BFS)

Moves Made11
StatusOptimal
Extra vs Optimal0
StrategyBFS (guaranteed)