NUCES Faisalabad · AI Lab · Question 6

Dynamic
Wumpus World
Logic Agent

A knowledge-based agent navigating a treacherous grid, using Propositional Logic and Resolution Refutation to reason about the unknown.

Scroll to explore

Configure the Environment

Define your grid dimensions, hazard density, and agent behaviour before launching the simulation.

Grid Parameters

6×6
15%
700ms

Agent Options

Show KB Clauses Live
Show Resolution Steps
Reveal Wumpus on Death
Auto Step Mode

The Arena

The agent starts at (0,0) and explores using Resolution Refutation to confirm safe cells before moving.

Agent Metrics

Current Position(0,0)
Steps Taken0
Cells Visited0
Safe Cells Confirmed0
Danger Cells Found0
KB Clauses0
Inference Steps0
Active Percepts
StatusIdle

Legend

Agent (current)
Safe (KB proven)
Unknown / Unvisited
Danger (inferred)
Pit (revealed)
Wumpus (revealed)

Percept Stream

No percepts yet.

Knowledge Base & Resolution

Every percept is encoded into Propositional Logic. The KB is queried via Resolution Refutation before every move.

Filter: Facts Inferred Safe Unsafe All

📋 Propositional Logic KB

Initialize a world to populate the KB.

⚖ Resolution Refutation Log

Resolution steps appear here during agent reasoning.

How the Agent Thinks

A four-stage pipeline from perception to action, grounded in classical AI and formal logic.

01
👁️

Percept Acquisition

At each cell, the agent senses Breeze (pit nearby) or Stench (Wumpus nearby) and records them as ground facts in the KB.

02
🧠

KB Update

Percepts trigger axiom instantiation: ¬Breeze(r,c) → ¬Pit(r±1,c) ∧ ¬Pit(r,c±1). All new clauses are added to the KB in CNF form.

03
⚖️

Resolution Refutation

To prove Safe(r,c), the agent adds ¬Safe(r,c) to the KB and resolves until contradiction is found — confirming the cell is safe to enter.

04
🚀

Action Selection

The agent moves to the nearest KB-proven safe cell. If none, it picks the least-dangerous frontier, updating the queue each step.

Agent Log Stream

wumpus_agent.log
$ python wumpus_agent.py --mode dynamic --kb propositional [SYSTEM] Wumpus World Logic Agent v2.0 initialised [SYSTEM] Awaiting world configuration...

Python Implementation

The core AI engine — Knowledge Base, CNF conversion, and Resolution Refutation — implemented in Python.

knowledge_base.py
resolution.py
agent.py
world.py
# ── Knowledge Base — Propositional Logic ────────────────────────────── class KnowledgeBase: def __init__(self): self.clauses = [] # CNF clauses (list of frozensets) self.facts = {} # ground truth facts self.safe = set() # KB-proven safe cells self.unsafe = set() # KB-proven unsafe cells def tell(self, literal): """Add a unit clause (literal) to the KB.""" clause = frozenset([literal]) if clause not in self.clauses: self.clauses.append(clause) self.facts[literal] = True def tell_clause(self, *literals): """Add a disjunctive clause to the KB.""" clause = frozenset(literals) if clause not in self.clauses: self.clauses.append(clause) def add_breeze_axioms(self, r, c, N, breeze): """Encode Breeze(r,c) ↔ ∃adjacent Pit axioms in CNF.""" adj = [(r+1,c),(r-1,c),(r,c+1),(r,c-1)] adj = [(x,y) for x,y in adj if 0<=xand 0<=yif not breeze: for (x,y) in adj: self.tell(f"¬P_{x}_{y}") # no pit in any adjacent cell else: # Breeze → at least one adjacent pit (disjunction) self.tell_clause(*[f"P_{x}_{y}" for (x,y) in adj]) def add_stench_axioms(self, r, c, N, stench): """Encode Stench(r,c) ↔ ∃adjacent Wumpus axioms.""" adj = [(r+1,c),(r-1,c),(r,c+1),(r,c-1)] adj = [(x,y) for x,y in adj if 0<=xand 0<=yif not stench: for (x,y) in adj: self.tell(f"¬W_{x}_{y}") else: self.tell_clause(*[f"W_{x}_{y}" for (x,y) in adj])
# ── Resolution Refutation Engine ────────────────────────────────────── def resolve(ci, cj): """Resolve two clauses; return set of resolvents or None.""" resolvents = set() for literal in ci: neg = literal[1:] if literal.startswith('¬') else '¬' + literal if neg in cj: new_clause = (ci | cj) - {literal, neg} resolvents.add(frozenset(new_clause)) return resolvents def resolution_refutation(kb_clauses, query): """ Prove query by contradiction. Add ¬query to KB and resolve until ∅ (contradiction). Returns (True, steps) if query is entailed, else (False, steps). """ neg_query = query[1:] if query.startswith('¬') else '¬' + query clauses = [frozenset(c) for c in kb_clauses] clauses.append(frozenset([neg_query])) steps = [f"Added ¬query: {{{neg_query}}}"] new = set() while True: pairs = [(clauses[i], clauses[j]) for i in range(len(clauses)) for j in range(i+1, len(clauses))] for (ci, cj) in pairs: resolvents = resolve(ci, cj) if frozenset() in resolvents: steps.append("⊥ Contradiction found → query PROVED") return True, steps new |= resolvents if new.issubset(set(clauses)): steps.append("No contradiction → query UNPROVABLE") return False, steps for c in new: if c not in clauses: clauses.append(c) steps.append(f"New resolvent: {set(c)}")
# ── Knowledge-Based Wumpus Agent ────────────────────────────────────── class WumpusAgent: def __init__(self, world): self.world = world self.kb = KnowledgeBase() self.pos = (0, 0) self.visited = {(0, 0)} self.safe_q = [] # queue of KB-proven safe cells self.alive = True self.gold = False self.steps = 0 self.inferences = 0 def perceive(self): """Sense environment and update KB.""" r, c = self.pos percepts = self.world.get_percepts(r, c) # Add visited cell as safe self.kb.tell(f"Safe_{r}_{c}") self.kb.tell(f"¬P_{r}_{c}") self.kb.tell(f"¬W_{r}_{c}") # Encode percept axioms self.kb.add_breeze_axioms(r, c, self.world.N, 'Breeze' in percepts) self.kb.add_stench_axioms(r, c, self.world.N, 'Stench' in percepts) return percepts def ask_safe(self, r, c): """Use Resolution Refutation to check if (r,c) is safe.""" proved, steps = resolution_refutation( self.kb.clauses, f"Safe_{r}_{c}") self.inferences += len(steps) if proved: self.kb.safe.add((r,c)) return proved, steps def step(self): """Execute one agent step: perceive → reason → act.""" percepts = self.perceive() r, c = self.pos adj = [(r+1,c),(r-1,c),(r,c+1),(r,c-1)] adj = [(x,y) for x,y in adj if 0<=xand 0<=yand (x,y) not in self.visited] # Ask KB about each adjacent unvisited cell safe_moves = [] for cell in adj: proved, _ = self.ask_safe(*cell) if proved: safe_moves.append(cell) if safe_moves: self.pos = safe_moves[0] elif adj: self.pos = adj[0] # cautious frontier move else: return "STUCK" self.visited.add(self.pos) self.steps += 1 # Check for hazards if self.world.has_wumpus(*self.pos) or self.world.has_pit(*self.pos): self.alive = False return "DEAD" if self.world.has_gold(*self.pos): self.gold = True return "GOLD" return "OK"
# ── Dynamic Wumpus World Generator ──────────────────────────────────── import random class WumpusWorld: def __init__(self, N=6, pit_prob=0.15): self.N = N self.pit_prob = pit_prob self.pits = set() self.wumpus = None self.gold = None self._generate() def _generate(self): """Randomly place pits, Wumpus, and gold (not at start).""" cells = [(r,c) for r in range(self.N) for c in range(self.N) if (r,c) != (0,0)] for cell in cells: if random.random() < self.pit_prob: self.pits.add(cell) candidates = [c for c in cells if c not in self.pits] self.wumpus = random.choice(candidates) candidates.remove(self.wumpus) self.gold = random.choice(candidates) def get_percepts(self, r, c): """Return list of active percepts at (r,c).""" percepts = [] adj = [(r+1,c),(r-1,c),(r,c+1),(r,c-1)] adj = [(x,y) for x,y in adj if 0<=xand 0<=yif any(cell in self.pits for cell in adj): percepts.append('Breeze') if any(cell == self.wumpus for cell in adj): percepts.append('Stench') if (r,c) == self.gold: percepts.append('Glitter') return percepts has_pit = lambda self,r,c: (r,c) in self.pits has_wumpus = lambda self,r,c: (r,c) == self.wumpus has_gold = lambda self,r,c: (r,c) == self.gold