AI Lab File
AI Lab File
LAB FILE
INDEX
S. Title Date Signature
No.
1. Write down two intelligent programs for TIC-TAC-TOE
Problem.
EXPERIMENT 1
EXPERIMENT OBJECTIVE
Write down two intelligent programs for TIC-TAC-TOE Problem.
THEORY
Tic Tac Toe is a classic turn-based game where two players take alternate turns on a 3×3
board. It is often used in AI as an introduction to game tree exploration and decision-making
strategies.
1. Minimax Algorithm:
• A recursive algorithm used for decision making in two-player games.
• It simulates all possible future moves and chooses the move that minimizes the
possible loss, assuming the opponent plays optimally.
2. Rule-Based (Heuristic) Strategy:
• A simpler AI that doesn't look ahead.
• It follows predefined rules: win if possible, block the opponent if needed, take
center, then corners, then sides.
PROGRAM CODE
Minimax Algorithm:
import math
def print_board(board):
for row in board:
print("|".join(row))
print("-" * 5)
def check_winner(board):
# Rows, Columns and Diagonals
lines = board + list(map(list, zip(*board))) + [
[board[i][i] for i in range(3)],
[board[i][2 - i] for i in range(3)]
]
for line in lines:
if line == ['X'] * 3:
return 'X'
if line == ['O'] * 3:
return 'O'
if all(cell != ' ' for row in board for cell in row):
return 'Tie'
return None
if is_maximizing:
best_score = -math.inf
for i in range(3):
for j in range(3):
if board[i][j] == ' ':
board[i][j] = 'X'
score = minimax(board, False)
board[i][j] = ' '
best_score = max(score, best_score)
return best_score
else:
best_score = math.inf
for i in range(3):
for j in range(3):
if board[i][j] == ' ':
board[i][j] = 'O'
score = minimax(board, True)
board[i][j] = ' '
best_score = min(score, best_score)
return best_score
def best_move(board):
best_score = -math.inf
move = (0, 0)
for i in range(3):
for j in range(3):
if board[i][j] == ' ':
board[i][j] = 'X'
score = minimax(board, False)
board[i][j] = ' '
if score > best_score:
best_score = score
move = (i, j)
return move
def play_game():
board = [[' ' for _ in range(3)] for _ in range(3)]
print("Tic Tac Toe: You (O) vs AI (X)")
print_board(board)
while True:
# Human Turn
row = int(input("Enter Row (0-2): "))
col = int(input("Enter Col (0-2): "))
if board[row][col] != ' ':
print("Cell Taken! Try Again.")
continue
board[row][col] = 'O'
print_board(board)
if check_winner(board):
break
# AI Turn
ai_row, ai_col = best_move(board)
board[ai_row][ai_col] = 'X'
print("AI Played:")
print_board(board)
if check_winner(board):
break
result = check_winner(board)
print(f"Game Over! Result: {result}")
if __name__ == "__main__":
play_game()
Rule-Based Strategy:
def print_board(board):
for row in board:
print("|".join(row))
print("-" * 5)
def check_winner(board):
lines = board + list(map(list, zip(*board))) + [
[board[i][i] for i in range(3)],
[board[i][2 - i] for i in range(3)]
]
for line in lines:
if line == ['X'] * 3:
return 'X'
if line == ['O'] * 3:
return 'O'
if all(cell != ' ' for row in board for cell in row):
return 'Tie'
return None
def rule_based_move(board):
# Check for Win or Block
for symbol in ['X', 'O']:
for i in range(3):
for j in range(3):
if board[i][j] == ' ':
board[i][j] = symbol
if check_winner(board) == symbol:
if symbol == 'X': # Win
return (i, j)
elif symbol == 'O': # Block
board[i][j] = ' '
return (i, j)
board[i][j] = ' '
# Center
if board[1][1] == ' ':
return (1, 1)
# Corners
for i, j in [(0, 0), (0, 2), (2, 0), (2, 2)]:
if board[i][j] == ' ':
return (i, j)
# Sides
for i, j in [(0, 1), (1, 0), (1, 2), (2, 1)]:
if board[i][j] == ' ':
return (i, j)
return None
def play_game():
board = [[' ' for _ in range(3)] for _ in range(3)]
print("Tic Tac Toe: You (O) vs AI (X)")
print_board(board)
while True:
# Human turn
row = int(input("Enter Row (0-2): "))
col = int(input("Enter Col (0-2): "))
if board[row][col] != ' ':
print("Cell Eaken! Try Again.")
continue
board[row][col] = 'O'
print_board(board)
if check_winner(board):
break
# AI turn
move = rule_based_move(board)
if move:
board[move[0]][move[1]] = 'X'
print("AI Played:")
print_board(board)
if check_winner(board):
break
result = check_winner(board)
print(f"Game Over! Result: {result}")
if __name__ == "__main__":
play_game()
OUTPUT FILE
Minimax Algorithm:
Rule-Based Strategy:
EXPERIMENT 2
EXPERIMENT OBJECTIVE
Write down program to implement Breadth First Search and Depth First Search for Water Jug
Problem.
THEORY
Water Jug Problem:
Given two jugs with capacities A and B, and the goal is to measure exactly C Liters of water
using these two jugs. We can:
• Fill a jug completely.
• Empty a jug completely.
• Pour water from one jug to another until one is either full or empty.
This problem can be solved using state space search techniques like BFS and DFS.
1. Breadth First Search (BFS):
• Explores all neighbours at the present depth prior to moving on to nodes at the next
depth level.
• Suitable for finding the shortest path.
2. Depth First Search (DFS):
• Explores as far as possible along each branch before backtracking.
• Can be implemented using recursion or a stack.
PROGRAM CODE
from collections import deque
while queue:
x, y = queue.popleft()
if (x, y) in visited:
continue
visited.add((x, y))
next_states = [
(capacity1, y), # Fill Jug 1
(x, capacity2), # Fill Jug 2
(0, y), # Empty Jug 1
(x, 0), # Empty Jug 2
(min(x + y, capacity1), y - (min(x + y, capacity1) - x)), # Jug2 → Jug1
(x - (min(x + y, capacity2) - y), min(x + y, capacity2)) # Jug1 → Jug2
]
dfs_util(0, 0)
# Example Usage
jug1_capacity = 4
jug2_capacity = 3
target = 2
OUTPUT FILE
EXPERIMENT 3
EXPERIMENT OBJECTIVE
Implement Best First Search and Least Cost Search for 8-Puzzle Problem.
THEORY
The 8-Puzzle Problem consists of a 3×3 grid with 8 numbered tiles and a blank space. The
objective is to move the blank (represented as 0) to arrange the tiles into the goal
configuration.
1. Best First Search (Greedy Search):
• Uses a heuristic (e.g., number of misplaced tiles).
• Selects the next node based on the best heuristic value.
• Not guaranteed to find the shortest path but is faster.
2. Least Cost Search (Uniform Cost Search):
• Considers the cost from the start node.
• Selects nodes based on the cumulative path cost (g(n)).
• Guaranteed to find the optimal (least-cost) solution.
PROGRAM CODE
import heapq
def flatten(state):
return tuple(sum(state, [])) # Makes 2D list Hashable
def manhattan_distance(state):
distance = 0
for i in range(3):
for j in range(3):
value = state[i][j]
if value == 0:
continue
target_x = (value - 1) // 3
target_y = (value - 1) % 3
distance += abs(target_x - i) + abs(target_y - j)
return distance
def find_zero(state):
for i in range(3):
for j in range(3):
if state[i][j] == 0:
return i, j
def get_neighbors(state):
x, y = find_zero(state)
directions = [(-1,0), (1,0), (0,-1), (0,1)]
neighbors = []
def print_state(state):
for row in state:
print(row)
print()
while pq:
h, current = heapq.heappop(pq)
print(f"h(n) = {h}")
print_state(current)
if current == goal_state:
print("Reached Goal State using Best First Search!\n")
return
visited.add(flatten(current))
for neighbor in get_neighbors(current):
if flatten(neighbor) not in visited:
heapq.heappush(pq, (manhattan_distance(neighbor), neighbor))
while pq:
cost, current = heapq.heappop(pq)
print(f"g(n) = {cost}")
print_state(current)
if current == goal_state:
print("Reached Goal State using Least Cost Search!\n")
return
visited.add(flatten(current))
for neighbor in get_neighbors(current):
if flatten(neighbor) not in visited:
heapq.heappush(pq, (cost + 1, neighbor))
OUTPUT FILE
EXPERIMENT 4
EXPERIMENT OBJECTIVE
Implement steps of Hill Climbing for Travelling Salesman Problem.
THEORY
Travelling Salesman Problem (TSP):
Given a set of cities and the cost to travel between each pair, the goal is to find the shortest
possible route that visits each city exactly once and returns to the starting city.
Hill Climbing Algorithm:
Hill climbing is a local search algorithm that starts with an arbitrary solution and iteratively
makes small changes (neighbours) to the solution. If the neighbour has a lower cost (i.e.,
better), it moves to the neighbour. The process continues until no better neighbour is found
— this is a local optimum.
• Pros: Simple and fast for small input.
• Cons: Can get stuck in local optima.
PROGRAM CODE
import random
def get_neighbors(path):
neighbors = []
for i in range(1, len(path)):
for j in range(i + 1, len(path)):
neighbor = path[:]
neighbor[i], neighbor[j] = neighbor[j], neighbor[i]
neighbors.append(neighbor)
return neighbors
def hill_climbing(start_path):
current_path = start_path
current_distance = total_distance(current_path)
while True:
neighbors = get_neighbors(current_path)
best_neighbor = current_path
best_distance = current_distance
OUTPUT FILE
EXPERIMENT 5
EXPERIMENT OBJECTIVE
Implement the steps of A* Algorithms for 8-Puzzle Problem.
THEORY
8-Puzzle Problem:
• A sliding puzzle consisting of a 3×3 grid with tiles numbered 1 to 8 and one blank
space (0).
• Objective: Move tiles to reach the goal configuration.
A* Algorithm:
• A graph traversal algorithm that finds the shortest path.
• Uses a cost function f(n) = g(n) + h(n):
▪ g(n) is the cost from the start node to current node.
▪ h(n) is the heuristic estimate from current node to goal (e.g., Manhattan
distance).
Heuristic (h(n)) – Manhattan Distance:
▪ The sum of the distances of each tile from its goal position.
▪ More accurate than "misplaced tiles" heuristic.
PROGRAM CODE
import heapq
def flatten(state):
return tuple(sum(state, [])) # Converts 2D sState to a Flat, Hashable Tuple
def manhattan_distance(state):
distance = 0
for i in range(3):
for j in range(3):
value = state[i][j]
if value == 0:
continue
target_x = (value - 1) // 3
target_y = (value - 1) % 3
distance += abs(target_x - i) + abs(target_y - j)
return distance
def find_zero(state):
for i in range(3):
for j in range(3):
if state[i][j] == 0:
return i, j
def get_neighbors(state):
x, y = find_zero(state)
directions = [(-1,0), (1,0), (0,-1), (0,1)]
neighbors = []
def print_state(state):
for row in state:
print(row)
print()
def a_star(start):
print("A* Search Algorithm\n")
visited = set()
pq = []
heapq.heappush(pq, (manhattan_distance(start), 0, start)) # (f(n), g(n), State)
while pq:
f, g, current = heapq.heappop(pq)
print(f"g(n) = {g}, h(n) = {f - g}, f(n) = {f}")
print_state(current)
if current == goal_state:
print("Reached Goal State with A* Search!")
return
visited.add(flatten(current))
# Example Usage
start_state = [[1, 2, 3],
[4, 5, 6],
[7, 0, 8]]
a_star(start_state)
OUTPUT FILE
EXPERIMENT 6
EXPERIMENT OBJECTIVE
Work out the steps to solve Crypt Arithmetical Problems.
THEORY
Cryptarithmatic Problem:
▪ A Cryptarithm is a mathematical puzzle where digits are replaced by letters.
▪ Each letter represents a unique digit (0–9).
▪ The goal is to find a combination of digits that satisfies the given arithmetic equation.
Example:
SEND
+MORE
---------------
MONEY
---------------
To solve:
• No two letters can have the same digit.
• Leading letters (S, M in this case) cannot be zero.
• All possible permutations of digits for the letters are checked.
Algorithm Steps:
1. Extract unique letters from the words.
2. Generate all permutations of digits for those letters.
3. Check each permutation for:
• Unique digits.
• No leading zeros.
• Equation validity.
4. Print valid solution(s).
PROGRAM CODE
from itertools import permutations
import re
def solve_cryptarithm(equation):
print(f"Solving: {equation.replace('==', '=')}")
if eval(equation_eval):
print("Solution Found: ")
for word in words:
print(f" {word} = {word_values[word]}")
print(f" Mapping: {letter_to_digit}\n")
return
# Example runs
solve_cryptarithm("SEND + MORE == MONEY")
solve_cryptarithm("BASE + BALL == GAMES")
OUTPUT FILE
EXPERIMENT 7
EXPERIMENT OBJECTIVE
Exercises to convert the given WFF into Clause Form.
THEORY
A Well-Formed Formula (WFF) is a syntactically correct expression in logic. To perform
automated reasoning (like resolution), we must convert the WFF into Clause Form, which is
a conjunction of disjunctions of literals (i.e., CNF).
Steps to Convert WFF to Clause Form:
1. Eliminate biconditional (↔) and implication (→):
• P ↔ Q becomes (P → Q) ∧ (Q → P)
• P → Q becomes ¬P ∨ Q
2. Move negation (¬) inward using De Morgan’s Laws:
• ¬(P ∧ Q) becomes ¬P ∨ ¬Q
• ¬(P ∨ Q) becomes ¬P ∧ ¬Q
• ¬¬P becomes P
3. Standardize variables:
• Rename variables so that each quantifier uses a unique variable.
4. Move quantifiers left (Prenex form):
• Put all quantifiers at the front of the expression.
5. Skolemization:
• Remove existential quantifiers by introducing Skolem functions or constants.
6. Drop universal quantifiers:
• All remaining variables are universally quantified by default.
7. Distribute ∨ over ∧ (to get CNF):
• Apply distributive laws to achieve a conjunction of disjunctions.
8. Split conjunctions into separate clauses
PROGRAM CODE
from sympy import symbols
from sympy.logic.boolalg import Implies, Equivalent, And, Or, Not, to_cnf
# Example WFF: (P → Q) ∧ (Q → R)
expr = And(Implies(P, Q), Implies(Q, R))
# Convert to CNF
cnf_expr = to_cnf(expr, simplify = True)
print("Original WFF:")
print(expr)
print("\nConverted CNF:")
print(cnf_expr)
OUTPUT FILE
EXPERIMENT 8
EXPERIMENT OBJECTIVE
Exercises on Formal Algorithm for Unification Procedure to combine two clauses.
THEORY
Unification is the process of finding a substitution that makes two logical expressions
syntactically identical.
A substitution replaces a variable with a constant, another variable, or a function.
Example:
• Given: P(x, a) and P(b, y)
• Unifier: {x/b, y/a}
Unification Algorithm Steps:
1. Compare corresponding parts of both expressions (function, predicate, constants,
variables).
2. If both are constants and equal: success.
3. If both are variables: unify them.
4. If one is a variable and the other is any term: substitute and continue.
5. If both are compound (e.g., P(x, a), P(b, y)), unify the functor and recursively unify
arguments.
6. If conflict occurs (e.g., a ≠ b): fail.
PROGRAM CODE
def unify(x, y, subst = None):
if subst is None:
subst = {}
if x == y:
return subst
elif is_variable(x):
return unify_var(x, y, subst)
elif is_variable(y):
return unify_var(y, x, subst)
elif isinstance(x, (list, tuple)) and isinstance(y, (list, tuple)) and len(x) == len(y):
return unify(x[1:], y[1:], unify(x[0], y[0], subst))
else:
return None
def is_variable(term):
return isinstance(term, str) and term[0].islower()
OUTPUT FILE
EXPERIMENT 9
EXPERIMENT OBJECTIVE
Represent the given facts as a set of clauses and predict the result for new statement using
resolution.
THEORY
Resolution is a rule of inference used for propositional and first-order logic, especially in
proof by contradiction. It works by:
• Converting all facts and the negation of the query into clause form (CNF).
• Resolving pairs of clauses until either the empty clause is produced (meaning the query
is entailed) or no new clauses can be inferred.
Resolution Steps:
1. Express all known facts in CNF.
2. Negate the statement to be proved and add it to the set.
3. Apply resolution between pairs of clauses.
4. If an empty clause is derived, the original statement is true.
5. If not, the statement cannot be inferred.
PROGRAM CODE
def resolve(ci, cj):
resolvents = set()
for di in ci:
for dj in cj:
if di == f"~{dj}" or f"~{di}" == dj:
new_clause = (ci - {di}) | (cj - {dj})
print(f"Resolving {set(ci)} and {set(cj)} on {di} and {dj} → {set(new_clause) if
new_clause else '∅'}")
resolvents.add(frozenset(new_clause))
return resolvents
new = set()
while True:
n = len(clauses)
pairs = [(clauses[i], clauses[j]) for i in range(n) for j in range(i + 1, n)]
if new.issubset(set(clauses)):
print("\nNo New Clauses. Query Cannot be Proven.")
return False
for c in new:
if c not in clauses:
clauses.append(c)
# Example
# KB: A ∨ B, ¬B ∨ C, ¬C
# Query: A
knowledge_base = [
{"A", "B"},
{"~B", "C"},
{"~C"}
]
query = "A"
resolution(knowledge_base, query)
OUTPUT FILE
EXPERIMENT 10
EXPERIMENT OBJECTIVE
Write a Prolog program to implement the following, assuming the suitable structure and
attributes:
d) Infer procedure for semantic nets for suitable domain
e) Infer procedure for frames using University domain.
f) Inference procedure for probabilistic reasoning for diagnostic of male functioning of
computer system.
THEORY
Semantic Nets:
Semantic nets are a graphical knowledge representation with nodes (objects/concepts) and
edges (relationships). In Prolog, we use facts and rules to represent these relations.
Frames:
Frames are structured representations for stereotyped situations. They use slots to hold
attributes. In Prolog, these are modeled using facts with attribute-value pairs.
Probabilistic Reasoning:
Used to infer the likelihood of an event (like system failure) given uncertain or incomplete
information. In Prolog, a simplified version is done using confidence values or probabilistic
facts.
PROGRAM CODE
Semantic Nets (Domain: Animals):
% Facts Representing a Semantic Network
is_a(tiger, mammal).
is_a(mammal, animal).
has(tiger, stripes).
can(tiger, roar).
% Inheritance Rule
property(X, P) :- has(X, P).
property(X, P) :- is_a(X, Y), property(Y, P).
% Queries:
% ?- property(tiger, stripes).
% ?- property(tiger, roar).
% ?- property(tiger, animal).
Frames (Domain: University):
% University Domain Frame Representation
% Frame Declarations
frame(student1, student).
frame(prof1, professor).
% Slot Declarations
slot(student1, name, 'Ayush').
slot(student1, course, 'COE').
slot(student1, year, 3).
slot(student1, university, 'DTU').
% Query:
% ?- diagnose(0.5).
OUTPUT FILE
Semantic Nets (Domain: Animals):
Frames (Domain: University):