P3 überarbeitet

This commit is contained in:
2025-06-28 20:56:09 +02:00
parent 3cbcf5e191
commit f38a6bf034
7 changed files with 771 additions and 0 deletions

284
P3/Task/Propositions.py Normal file
View File

@@ -0,0 +1,284 @@
from collections import deque
import re
class HornKnowledgebasedAgent:
"""Knowledge-based agent using Horn Clauses + Forward Chaining"""
def __init__(self):
"""Initialize the agent"""
self.knowledge_base = []
# ==================== MAIN INTERFACE ====================
def TELL(self, input_data):
"""Add sensor data from gym step result or legacy string
Args:
input_data: Either string ("S11") or gym step result tuple
"""
# Handle legacy string input
if isinstance(input_data, str):
self._add_to_kb(input_data)
self._auto_generate_rules(input_data)
return
# Handle gym environment step result
if isinstance(input_data, tuple) and len(input_data) >= 3:
observation, reward, terminated = input_data[0], input_data[1], input_data[2]
# Extract position (convert 0-based to 1-based)
x = observation['x'] + 1
y = observation['y'] + 1
position = f"{x}{y}"
print(f"\n🎮 Processing step for position [{x},{y}] - Terminated: {terminated}")
# Handle death vs survival
if terminated:
# Agent died - add both Wumpus AND Pit facts (Horn approximation)
self._add_to_kb(f"W{position}")
self._add_to_kb(f"P{position}")
print(f"💀 Death: W{position} ∧ P{position} (Horn approx. of W{position} P{position})")
return
else:
# Agent survived - field is safe
self._add_to_kb(f"-W{position}")
self._add_to_kb(f"-P{position}")
print(f"✅ Survival: ¬W{position} ∧ ¬P{position}")
# Process sensors
sensors = {
'stench': 'S', 'breeze': 'B', 'glitter': 'G',
'bump': 'BUMP', 'scream': 'SC'
}
for sensor_key, sensor_code in sensors.items():
sensor_value = observation.get(sensor_key, False)
if sensor_value:
sensor_fact = f"{sensor_code}{position}"
else:
sensor_fact = f"-{sensor_code}{position}"
print(f" Sensor: {sensor_fact}")
self._add_to_kb(sensor_fact)
self._auto_generate_rules(sensor_fact)
def ASK(self, query):
"""Check if KB entails query using Forward Chaining"""
result = self._forward_chaining(query)
print(f"ASK({query}): {result}")
return result
def _add_to_kb(self, sentence):
"""Add sentence to KB if not already present"""
if sentence not in self.knowledge_base:
self.knowledge_base.append(sentence)
print(f" Added: {sentence}")
# ==================== FORWARD CHAINING ====================
def _forward_chaining(self, query):
"""Forward Chaining algorithm"""
facts, rules = self._parse_kb()
# Count premises for each rule
count = {i: len(rule['premises']) for i, rule in enumerate(rules)}
# Track inferred symbols
inferred = {}
derived_facts = set(facts)
agenda = deque(facts)
while agenda:
fact = agenda.popleft()
# Check for contradiction
negated = self._negate(fact)
if negated in derived_facts:
print(f"⚠️ Contradiction: {fact} and {negated}")
return False
# Found query?
if fact == query:
return True
# Process rules
if not inferred.get(fact, False):
inferred[fact] = True
for i, rule in enumerate(rules):
if fact in rule['premises']:
count[i] -= 1
if count[i] == 0:
conclusion = rule['conclusion']
if not inferred.get(conclusion, False):
agenda.append(conclusion)
derived_facts.add(conclusion)
# Check if negation of query was derived
negated_query = self._negate(query)
if negated_query in derived_facts:
print(f" {query} is FALSE (KB entails {negated_query})")
return False
print(f" {query} cannot be proven")
return False
def _parse_kb(self):
"""Parse KB into facts and rules"""
facts = []
rules = []
for sentence in self.knowledge_base:
if '=>' in sentence:
premises_part, conclusion = sentence.split('=>')
premises = [p.strip() for p in premises_part.split(',')]
rules.append({'premises': premises, 'conclusion': conclusion.strip()})
else:
facts.append(sentence)
return facts, rules
def _negate(self, literal):
"""Negate a literal"""
return literal[1:] if literal.startswith('-') else f"-{literal}"
# ==================== RULE GENERATION ====================
def _auto_generate_rules(self, sensor):
"""Generate Horn rules from sensor facts"""
# STENCH rules
if re.match(r'^S(\d)(\d)$', sensor):
x, y = int(sensor[1]), int(sensor[2])
pos = f"{x}{y}"
# S11 => -W11 (no Wumpus in same field)
self._add_to_kb(f"S{pos}=>-W{pos}")
# S11 => W21, S11 => W12 (direct rules for neighbors)
for nx, ny in self._get_neighbors(x, y):
self._add_to_kb(f"S{pos}=>W{nx}{ny}")
print(f" Generated stench rules for S{pos}")
# NO STENCH rules
elif re.match(r'^-S(\d)(\d)$', sensor):
x, y = int(sensor[2]), int(sensor[3])
pos = f"{x}{y}"
# -S12 => -W11, -S12 => -W22 (no Wumpus in neighbors)
for nx, ny in self._get_neighbors(x, y):
self._add_to_kb(f"-S{pos}=>-W{nx}{ny}")
print(f" Generated no-stench rules for -S{pos}")
# BREEZE rules
elif re.match(r'^B(\d)(\d)$', sensor):
x, y = int(sensor[1]), int(sensor[2])
pos = f"{x}{y}"
# B11 => -P11 (no Pit in same field)
self._add_to_kb(f"B{pos}=>-P{pos}")
# B11 => P21, B11 => P12 (direct rules for neighbors)
for nx, ny in self._get_neighbors(x, y):
self._add_to_kb(f"B{pos}=>P{nx}{ny}")
print(f" Generated breeze rules for B{pos}")
# NO BREEZE rules
elif re.match(r'^-B(\d)(\d)$', sensor):
x, y = int(sensor[2]), int(sensor[3])
pos = f"{x}{y}"
# -B12 => -P11, -B12 => -P22 (no Pits in neighbors)
for nx, ny in self._get_neighbors(x, y):
self._add_to_kb(f"-B{pos}=>-P{nx}{ny}")
print(f" Generated no-breeze rules for -B{pos}")
# GLITTER rules
elif re.match(r'^G(\d)(\d)$', sensor):
x, y = int(sensor[1]), int(sensor[2])
self._add_to_kb(f"G{x}{y}=>Gold{x}{y}")
print(f" Generated glitter rule")
elif re.match(r'^-G(\d)(\d)$', sensor):
x, y = int(sensor[2]), int(sensor[3])
self._add_to_kb(f"-G{x}{y}=>-Gold{x}{y}")
print(f" Generated no-glitter rule")
# SCREAM rules
elif sensor == "SC":
self._add_to_kb("SC=>WumpusDead")
print(f" Generated scream rule")
elif sensor == "-SC":
self._add_to_kb("-SC=>WumpusAlive")
print(f" Generated no-scream rule")
def _get_neighbors(self, x, y):
"""Get valid neighbors in 4x4 grid"""
neighbors = []
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
nx, ny = x + dx, y + dy
if 1 <= nx <= 4 and 1 <= ny <= 4:
neighbors.append((nx, ny))
return neighbors
def print_kb(self):
"""Print knowledge base"""
print("\n=== Horn Clauses Knowledge Base ===")
facts = [s for s in self.knowledge_base if '=>' not in s]
rules = [s for s in self.knowledge_base if '=>' in s]
print(f"Facts ({len(facts)}):")
for i, fact in enumerate(facts, 1):
print(f" {i}. {fact}")
print(f"Rules ({len(rules)}):")
for i, rule in enumerate(rules, 1):
print(f" {i}. {rule}")
# ==================== TESTS ====================
def test_horn_gym_integration():
"""Test Horn agent with gym integration"""
print("🎯 Horn Clauses Agent - Gym Integration Test")
print("="*50)
agent = HornKnowledgebasedAgent()
# Agent survives at [1,1] with stench
step_result_1 = (
{'x': 0, 'y': 0, 'stench': True, 'breeze': False, 'glitter': False, 'bump': False, 'scream': False},
[-1], False, False, {}
)
agent.TELL(step_result_1)
# Agent dies at [2,1]
step_result_2 = (
{'x': 1, 'y': 0, 'stench': False, 'breeze': False, 'glitter': False, 'bump': False, 'scream': False},
[-1000], True, False, {}
)
agent.TELL(step_result_2)
print("\n🔍 Testing queries:")
agent.ASK("W11") # Should be FALSE (survived)
agent.ASK("P11") # Should be FALSE (survived)
agent.ASK("W21") # Should be TRUE (died)
agent.ASK("P21") # Should be False No breez at 11
agent.ASK("W12") # Should be TRUE (from S11)
agent.ASK("W22") # Should be TRUE (from S11)
agent.print_kb()
if __name__ == "__main__":
print("🏰 Horn Clauses Agent for Wumpus World")
print("="*60)
test_horn_gym_integration()
print("\n✨ Horn implementation complete!")
print(" Shows clear limitations vs CNF Resolution")

411
P3/Task/Propositions2.py Normal file
View File

@@ -0,0 +1,411 @@
from collections import deque
import re
class KnowledgebasedAgent:
"""Knowledge-based agent for Wumpus World using CNF Resolution"""
def __init__(self, method="CNF"):
"""Initialize the knowledge-based agent"""
self.method = method
self.cnf_clauses = [] # List of sets (each set is a clause)
# ==================== MAIN INTERFACE ====================
def TELL(self, step_result):
"""Add sensor data from gym environment step result
Args:
step_result: Can be either:
- String (legacy): "S11", "-B12", etc.
- Tuple from env.step(): (observation, reward, terminated, truncated, info)
"""
# Handle legacy string input
if isinstance(step_result, str):
# print(f"Added sensor: {step_result}")
self._generate_clauses_from_sensor(step_result)
return
# Handle gym environment step result
if isinstance(step_result, tuple) and len(step_result) >= 3:
observation, reward, terminated = step_result[0], step_result[1], step_result[2]
# Extract position (convert from 0-based to 1-based coordinates)
x = observation['x'] + 1 # Convert 0-based to 1-based
y = observation['y'] + 1 # Convert 0-based to 1-based
position = f"{x}{y}"
print(f"\n Processing step result for position [{x},{y}]:")
print(f" Terminated: {terminated}, Reward: {reward}")
# Extract sensors from observation
sensors = {
'stench': observation.get('stench', False),
'breeze': observation.get('breeze', False),
'glitter': observation.get('glitter', False),
'bump': observation.get('bump', False),
'scream': observation.get('scream', False)
}
# Process survival/death logic first
if terminated:
# Agent died - field contains Wumpus OR Pit
death_clause = {f"W{position}", f"P{position}"}
self.cnf_clauses.append(death_clause)
print(f" Death rule: W{position} P{position}")
# No sensor processing when agent dies
print(f" Agent died - no sensor data processed")
return
else:
# Agent survived - field is safe (no Wumpus AND no Pit)
safe_wumpus = {f"-W{position}"}
safe_pit = {f"-P{position}"}
self.cnf_clauses.append(safe_wumpus)
self.cnf_clauses.append(safe_pit)
print(f" Survival rules: ¬W{position} ∧ ¬P{position}")
# Process sensor readings
sensor_mapping = {
'stench': 'S',
'breeze': 'B',
'glitter': 'G',
'bump': 'BUMP',
'scream': 'SC'
}
for sensor_name, sensor_value in sensors.items():
sensor_code = sensor_mapping.get(sensor_name, sensor_name.upper())
if sensor_value:
# Positive sensor reading
sensor_fact = f"{sensor_code}{position}"
# print(f" Sensor: {sensor_fact}")
self._add_sensor_unit_clause(sensor_fact)
self._generate_clauses_from_sensor(sensor_fact)
else:
# Negative sensor reading
sensor_fact = f"-{sensor_code}{position}"
# print(f" Sensor: {sensor_fact}")
self._add_sensor_unit_clause(sensor_fact)
self._generate_clauses_from_sensor(sensor_fact)
return
def _add_sensor_unit_clause(self, sensor_fact):
"""Add sensor as unit clause (avoid duplicates from _generate_clauses_from_sensor)"""
unit_clause = {sensor_fact}
if unit_clause not in self.cnf_clauses:
self.cnf_clauses.append(unit_clause)
def TELL_SENSOR(self, sensor_string):
"""Legacy method for adding individual sensor strings"""
self.TELL(sensor_string)
def TELL_STEP(self, step_result):
"""Convenience method specifically for gym step results"""
self.TELL(step_result)
def ASK(self, query):
"""Check if KB entails query using CNF Resolution"""
result = self._cnf_resolution(query)
print(f"ASK({query}): {result}")
return result
def print_clauses(self):
"""Print all CNF clauses"""
print("\n=== CNF Clauses ===")
for i, clause in enumerate(self.cnf_clauses):
literals = " ".join(sorted(clause))
print(f" {i + 1}. {{{literals}}}")
# ==================== CNF RESOLUTION ALGORITHM ====================
def _cnf_resolution(self, query):
"""CNF Resolution to prove KB |= query"""
# To prove KB |= query, show KB ∧ ¬query is unsatisfiable
negated_query = self._negate(query)
test_clauses = [clause.copy() for clause in self.cnf_clauses]
test_clauses.append({negated_query})
print(f"\nProving: KB ∧ ¬{query} is unsatisfiable")
print(f"Added negated query: {{{negated_query}}}")
new_clauses = []
iteration = 0
while True:
iteration += 1
# print(f"\n--- Resolution Step {iteration} ---")
# Try all pairs of clauses
for i in range(len(test_clauses)):
for j in range(i + 1, len(test_clauses)):
resolvent = self._resolve(test_clauses[i], test_clauses[j])
if resolvent is not None:
if len(resolvent) == 0:
# Empty clause = contradiction!
print(f"Empty clause from {test_clauses[i]}{test_clauses[j]}")
print("⚡ Contradiction found! KB entails query.")
return True
# Check if new clause is actually new
if resolvent not in test_clauses and resolvent not in new_clauses:
new_clauses.append(resolvent)
# print(f"New: {resolvent} from {test_clauses[i]} ∩ {test_clauses[j]}")
# If no new clauses, we're done
if not new_clauses:
print("No new clauses. Query not entailed.")
return False
# Add new clauses
test_clauses.extend(new_clauses)
new_clauses = []
# Safety limit
if iteration > 20:
print("Max iterations reached.")
return False
def _resolve(self, clause1, clause2):
"""Resolve two clauses if they have complementary literals"""
for literal in clause1:
complement = self._negate(literal)
if complement in clause2:
# Found resolution: combine clauses, remove complementary pair
resolvent = (clause1 - {literal}) | (clause2 - {complement})
return resolvent
return None
# ==================== SENSOR RULE GENERATION ====================
def _generate_clauses_from_sensor(self, sensor):
"""Generate CNF clauses based on sensor readings"""
# Add sensor as unit clause
self.cnf_clauses.append({sensor})
# Dispatch to specific sensor handlers
if self._handle_stench_sensors(sensor): return
if self._handle_breeze_sensors(sensor): return
if self._handle_glitter_sensors(sensor): return
if self._handle_scream_sensors(sensor): return
if self._handle_bump_sensors(sensor): return
def _handle_stench_sensors(self, sensor):
"""Handle stench-related sensors"""
# STENCH: S11, S22, etc.
stench_match = re.match(r'^S(\d)(\d)$', sensor)
if stench_match:
x, y = int(stench_match.group(1)), int(stench_match.group(2))
pos = f"{x}{y}"
# S11 → ¬W11 becomes {-S11, -W11}
self.cnf_clauses.append({f"-S{pos}", f"-W{pos}"})
# S11 → (W21 W12 ...) becomes {-S11, W21, W12, ...}
neighbors = self._get_neighbors(x, y)
if neighbors:
neighbor_wumpus = [f"W{nx}{ny}" for nx, ny in neighbors]
clause = {f"-S{pos}"} | set(neighbor_wumpus)
self.cnf_clauses.append(clause)
print(f" Generated: S{pos} → ¬W{pos} ∧ (W-neighbors)")
return True
# NO STENCH: -S12, -S22, etc.
no_stench_match = re.match(r'^-S(\d)(\d)$', sensor)
if no_stench_match:
x, y = int(no_stench_match.group(1)), int(no_stench_match.group(2))
pos = f"{x}{y}"
# -S12 → ¬W(neighbors) becomes {S12, -W11}, {S12, -W22}, etc.
neighbors = self._get_neighbors(x, y)
for nx, ny in neighbors:
neighbor_pos = f"{nx}{ny}"
self.cnf_clauses.append({f"S{pos}", f"-W{neighbor_pos}"})
print(f" Generated: ¬S{pos} → ¬W-neighbors")
return True
return False
def _handle_breeze_sensors(self, sensor):
"""Handle breeze-related sensors"""
# BREEZE: B11, B22, etc.
breeze_match = re.match(r'^B(\d)(\d)$', sensor)
if breeze_match:
x, y = int(breeze_match.group(1)), int(breeze_match.group(2))
pos = f"{x}{y}"
# B11 → ¬P11 becomes {-B11, -P11}
self.cnf_clauses.append({f"-B{pos}", f"-P{pos}"})
# B11 → (P21 P12 ...) becomes {-B11, P21, P12, ...}
neighbors = self._get_neighbors(x, y)
if neighbors:
neighbor_pits = [f"P{nx}{ny}" for nx, ny in neighbors]
clause = {f"-B{pos}"} | set(neighbor_pits)
self.cnf_clauses.append(clause)
print(f" Generated: B{pos} → ¬P{pos} ∧ (P-neighbors)")
return True
# NO BREEZE: -B12, -B22, etc.
no_breeze_match = re.match(r'^-B(\d)(\d)$', sensor)
if no_breeze_match:
x, y = int(no_breeze_match.group(1)), int(no_breeze_match.group(2))
pos = f"{x}{y}"
# -B12 → ¬P(neighbors) becomes {B12, -P11}, {B12, -P22}, etc.
neighbors = self._get_neighbors(x, y)
for nx, ny in neighbors:
neighbor_pos = f"{nx}{ny}"
self.cnf_clauses.append({f"B{pos}", f"-P{neighbor_pos}"})
print(f" Generated: ¬B{pos} → ¬P-neighbors")
return True
return False
def _handle_glitter_sensors(self, sensor):
"""Handle glitter-related sensors"""
# GLITTER: G11, G22, etc.
glitter_match = re.match(r'^G(\d)(\d)$', sensor)
if glitter_match:
x, y = int(glitter_match.group(1)), int(glitter_match.group(2))
pos = f"{x}{y}"
# G11 → Gold11 becomes {-G11, Gold11}
self.cnf_clauses.append({f"-G{pos}", f"Gold{pos}"})
print(f" Generated: G{pos} → Gold{pos}")
return True
# NO GLITTER: -G11, -G22, etc.
no_glitter_match = re.match(r'^-G(\d)(\d)$', sensor)
if no_glitter_match:
x, y = int(no_glitter_match.group(1)), int(no_glitter_match.group(2))
pos = f"{x}{y}"
# -G11 → ¬Gold11 becomes {G11, -Gold11}
self.cnf_clauses.append({f"G{pos}", f"-Gold{pos}"})
print(f" Generated: ¬G{pos} → ¬Gold{pos}")
return True
return False
def _handle_scream_sensors(self, sensor):
"""Handle scream-related sensors"""
# SCREAM: Sc
if sensor == "Sc":
# Sc → WumpusDead becomes {-Sc, WumpusDead}
self.cnf_clauses.append({"-Sc", "WumpusDead"})
# WumpusDead → ¬W_anywhere: for all positions
for x in range(1, 5):
for y in range(1, 5):
self.cnf_clauses.append({"-WumpusDead", f"-W{x}{y}"})
print(f" Generated: Sc → WumpusDead → ¬W(everywhere)")
return True
# NO SCREAM: -Sc
if sensor == "-Sc":
# -Sc → WumpusAlive becomes {Sc, WumpusAlive}
self.cnf_clauses.append({"Sc", "WumpusAlive"})
print(f" Generated: ¬Sc → WumpusAlive")
return True
return False
def _handle_bump_sensors(self, sensor):
"""Handle bump-related sensors"""
# BUMP: Bump11, Bump22, etc.
bump_match = re.match(r'^Bump(\d)(\d)$', sensor)
if bump_match:
x, y = int(bump_match.group(1)), int(bump_match.group(2))
print(f" Note: Bump{x}{y} indicates wall collision")
return True
# NO BUMP: -Bump11, -Bump22, etc.
no_bump_match = re.match(r'^-Bump(\d)(\d)$', sensor)
if no_bump_match:
x, y = int(no_bump_match.group(1)), int(no_bump_match.group(2))
print(f" Note: ¬Bump{x}{y} indicates successful movement")
return True
return False
# ==================== UTILITY METHODS ====================
def _negate(self, literal):
"""Negate a literal"""
if literal.startswith('-'):
return literal[1:]
else:
return f"-{literal}"
def _get_neighbors(self, x, y):
"""Get valid neighbors in 4x4 grid"""
neighbors = []
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = x + dx, y + dy
if 1 <= nx <= 4 and 1 <= ny <= 4:
neighbors.append((nx, ny))
return neighbors
# ==================== TEST SCENARIOS ====================
def test_gym_integration():
"""Test direct integration with gym environment step results"""
print("\n🎮 Test 3: Direct Gym Environment Integration")
print("="*60)
agent = KnowledgebasedAgent("CNF")
print("📡 Simulating gym environment step results...")
# Simulate step result from your example
step_result_1 = (
{'x': 0, 'y': 0, 'gold': False, 'direction': 1, 'arrow': True,
'stench': True, 'breeze': False, 'glitter': False, 'bump': False, 'scream': False},
[-1], # reward
False, # terminated (agent survives)
False, # truncated
{'info': 'step 1'}
)
# Agent survives at position [1,1] (converted from [0,0])
agent.TELL(step_result_1)
# Simulate death scenario
step_result_2 = (
{'x': 1, 'y': 0, 'gold': False, 'direction': 1, 'arrow': True,
'stench': False, 'breeze': False, 'glitter': False, 'bump': False, 'scream': False},
[-1000], # death reward
True, # terminated (agent dies)
False, # truncated
{'info': 'death'}
)
# Agent dies at position [2,1] (converted from [1,0])
agent.TELL(step_result_2)
print("\n🔍 Testing queries with survival logic...")
agent.ASK("W11") # Should be FALSE (agent survived [1,1])
agent.ASK("P11") # Should be FALSE (agent survived [1,1])
agent.ASK("W21") # Could be TRUE (agent died at [2,1])
agent.ASK("P21") # Should be False (no breez at [1,1])
print("\n📋 Generated clauses:")
agent.print_clauses()
if __name__ == "__main__":
print("🏰 CNF Resolution for Wumpus World - Complete Implementation")
print("=" * 70)
test_gym_integration()
print("\n✨ CNF Resolution correctly handles all Wumpus World logic!")

Binary file not shown.

Binary file not shown.

View File

1
P3/ai_gym Submodule

Submodule P3/ai_gym added at aa0a38ebaf

75
P3/main.py Normal file
View File

@@ -0,0 +1,75 @@
import random
import gym
from ai_gym import fh_ac_ai_gym
from Task.Propositions import *
from Task.Propositions2 import *
#Create Gym environment
wumpus_env = gym.make('Wumpus-v0', disable_env_checker=True)
wumpus_env.reset()
wumpus_env.render()
#Define the possible actions
REVERSE_ACTION_DICT= {0: "WALK", 1:"TURNLEFT", 2:"TURNRIGHT", 3:"GRAB", 4: "SHOOT", 5:"CLIMB"}
POSSIBLE_ACTIONS = {v: k for k, v in REVERSE_ACTION_DICT.items()}
print("=" * 70)
agent = KnowledgebasedAgent("CNF")
obs = wumpus_env.step(1)
print(obs)
agent.TELL(obs)
agent.ASK("W21")
agent.ASK("W12")
wumpus_env.render()
print("=" * 70)
obs = wumpus_env.step(0)
print(obs)
agent.TELL(obs)
agent.ASK("W21")
agent.ASK("W12")
wumpus_env.render()
print("=" * 70)
obs = wumpus_env.step(0)
print(obs)
agent.TELL(obs)
agent.ASK("P12")
agent.ASK("P23")
agent.ASK("P14")
wumpus_env.render()
print("=" * 70)
wumpus_env.step(2)
obs = wumpus_env.step(0)
print(obs)
agent.TELL(obs)
agent.ASK("P12")
agent.ASK("P23")
agent.ASK("P14")
wumpus_env.render()
wumpus_env.reset()
print("=" * 70)
print("=" * 70)
print("=" * 70)
agent = HornKnowledgebasedAgent()
wumpus_env.step(1)
obs = wumpus_env.step(2)
print(obs)
agent.TELL(obs)
agent.ASK("W21")
agent.ASK("W12")
wumpus_env.render()
print("=" * 70)
obs = wumpus_env.step(0)
print(obs)
agent.TELL(obs)
agent.ASK("W21")
agent.ASK("W12")
wumpus_env.render()