0% found this document useful (0 votes)
32 views38 pages

Tic-Tac-Toe and BFS Implementation

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
32 views38 pages

Tic-Tac-Toe and BFS Implementation

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

210280107107 AI-SEM7-BE-CE-LDCE

Part-A: AI Programs
Practical – 1
• AIM: Write a program to implement Tic-Tac-Toe game problem.

• Theory:
o One of the players chooses ‘O’ and the other ‘X’ to mark their respective cells. The
game starts with one of the players and the game ends when one of the players
has one whole row/ column/ diagonal filled with his/her respective character (‘O’
or ‘X’). If no one wins, then the game is said to be draw.
o Winning Strategy – An Interesting Fact If both the players play optimally then it is
destined that you will never lose (“although the match can still be drawn”). It
doesn’t matter whether you play first or second. In another ways – “Two expert
players will always draw”. To represent the tic-tac-toe board we use a Prolog list.

• Implementation:
import random

map = {i: " " for i in range(1, 10)}


availble = list([Link]())
coms = []
players = []

wins = {
1: {
5: [[1,9],[3,7],[2,8],[4,6]],
},
2: {
1: [[2,3],[4,7],[5,9]],
3: [[1,2],[5,7],[6,9]],
7: [[1,4],[3,5],[8,9]],
9: [[1,5],[3,6],[7,8]]
},
3: {
2: [[1,3],[5,8]],
4: [[1,7],[5,6]],
6: [[3,9],[4,5]],
8: [[7,9],[2,5]]
}
}

def print_map():
print(f'\n{map[1]} | {map[2]} | {map[3]}')
print("----------")
print(f'{map[4]} | {map[5]} | {map[6]}')
print("----------")
print(f'{map[7]} | {map[8]} | {map[9]}')

def check_x(pos):
return True if map[pos]=='1' else False

1
210280107107 AI-SEM7-BE-CE-LDCE

def check_o(pos):
return True if map[pos]=='0' else False

def check_o_arr(arr):
for a in arr:
if check_o(a[0]) and check_o(a[1]):
return True
return False

def check_empty(pos):
return True if map[pos]==' ' else False

def computer_input():
if(len(availble)>0):
while True:
c = None

for i,item_i in [Link]():


for j, item_j in item_i.items():
if(check_empty(j) and check_o_arr(item_j)):
c = j
break
if c != None:
break

c = [Link](availble) if c == None else c


if check_empty(c):
[Link](c)
map[c] = '1'
print(f"\n** Computer played at {c} **")
break
else:
#DO SOMETHING
raise IndexError

def user_input():
while True:
try:
x = int(input("\nEnter position to enter O : "))
if(x<1 or x>9):
raise ValueError
if(not check_empty(x)):
print("\nAleardy Occupied")
continue
else:
[Link](x)
map[x] = '0'
[Link](x)
break
except ValueError:
print("\nPLEASE ENTER POSITION BTW 1 - 9")
continue
except KeyboardInterrupt:
print("\nERROR..")
continue

# HORIZONTAL
def check_h1():
return map[1] if map[1] == map [2] == map[3] else None

2
210280107107 AI-SEM7-BE-CE-LDCE

def check_h2():
return map[4] if map[4] == map [5] == map[6] else None
def check_h3():
return map[7] if map[7] == map [8] == map[9] else None

# VERTICAL
def check_v1():
return map[1] if map[1] == map [4] == map[7] else None
def check_v2():
return map[2] if map[2] == map [5] == map[8] else None
def check_v3():
return map[3] if map[3] == map [6] == map[9] else None

# DIAGONAL
def check_d1():
return map[1] if map[1] == map [5] == map[9] else None
def check_d2():
return map[3] if map[3] == map [5] == map[7] else None

def check_win1():
return True if check_h1() == '1' or check_h2() == '1' or check_h3() == '1'
or check_v1() == '1' or check_v2() == '1' or check_v3() == '1' or check_d1()
== '1' or check_d2() == '1' else False

def check_win0():
return True if check_h1() == '0' or check_h2() == '0' or check_h3() == '0'
or check_v1() == '0' or check_v2() == '0' or check_v3() == '0' or check_d1()
== '0' or check_d2() == '0' else False

def main():
try:
while True:
print_map()
print(f'\n{availble}\n')
user_input()
if(check_win0()):
print_map()
print("\nYOU WON")
break
computer_input()
if(check_win1()):
print_map()
print("\nCOMPUTER WON")
break
except IndexError:
print("TIE")

main()

3
210280107107 AI-SEM7-BE-CE-LDCE

• Output:

Signature of Faculty: Grade:

4
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 2
• AIM: Write a program to implement BFS (for 8 puzzle problem or Water Jug problem or
any AI search problem).

• Theory:
o BFS is a graph traversal algorithm that explores all nodes at a given depth before
moving on to the next depth level. It starts from a source node and visits all its
neighbors first, then moves to the neighbors of those neighbors, and so on.
o BFS is often used to find the shortest path between two nodes in an unweighted
graph. It also has applications in various areas, such as network routing, searching
for connected components, and solving puzzles like the maze problem.
o Water Jug Problem is also known as Water Pouring Puzzles, measuring puzzles
and decanting problems. These belong to a class of puzzles, in which there are a
finite and specific number of water jugs having predefined integral capacities, in
terms of gallons or liters.
o The prime challenge in the water jug problem is that these water jugs do not have
any calibrations for measuring the intermediate water levels. In order to solve
these puzzles, you are given a required measurement that you have to achieve by
transferring waters from one jug to another, you can iterate multiple times until
the final goal is reached, but in order to get the best results, the final solution
should have a minimum cost and a minimum number of transfers.

• Implementation:
[Link]
from uninformed_search import bfs_search

class WaterJugProblem:
def
__init__(self,initial_state=(0,0),jug0=4,jug1=3,finaljug0=2,finaljug1=0):
self.start_state = initial_state
self.goal_state = (finaljug0, finaljug1)
self.jug0 = jug0
self.jug1 = jug1
self.finaljug0 = finaljug0
self.finaljug1 = finaljug1

def get_successors(self, state):


possible = set()
final = set()

# Empty jug0
if state[0] > 0:
[Link]((0, state[1]))
# Empty jug1
if state[1] > 0:
[Link]((state[0], 0))
# Fill jug0
if state[0] < self.jug0:

5
210280107107 AI-SEM7-BE-CE-LDCE

[Link]((self.jug0, state[1]))
# Fill jug1
if state[1] < self.jug1:
[Link]((state[0], self.jug1))
# Pour jug0 -> jug1, until it is full
if state[1] < self.jug1 and self.jug1 - state[1] <= state[0]:
[Link]((state[0]-(self.jug1 - state[1]), self.jug1))
# Pour jug1 -> jug0, until it is full
if state[0] < self.jug0 and self.jug0 - state[0] <= state[1]:
[Link]((self.jug0, state[1] - (self.jug0 - state[0])))
# Pour jug0 -> jug1, until it is empty
if state[0] > 0 and self.jug1 - state[1] >= state[0]:
[Link]((0, state[1] + state[0]))
# Pour jug1 -> jug0, until it is empty
if state[1] > 0 and self.jug0 - state[0] >= state[1]:
[Link]((state[0] + state[1], 0))

for successor in possible:


if self.is_legal(successor):
[Link](successor)
return final

def is_legal(self, state):


if 0 <= state[0] <= self.jug0 and 0 <= state[1] <= self.jug1:
return True
return False

def goal_test(self, state):


return state == self.goal_state

def __str__(self):
return f"Water Jug Problem with {self.jug0} L and {self.jug1} L to
reach {self.finaljug0} L and {self.finaljug1} L in respective jugs"

if __name__ == "__main__":
problem4320 = WaterJugProblem()
print(bfs_search(problem4320))
uninformed_search.py
def bfs_search(search_problem):
start_node = SearchNode(search_problem.start_state) # assumes
search_problem has start_state
visited_from = {start_node.state}
queue = deque([start_node])
count = 0

while queue: # while there are items in queue


count += 1
curr_node = [Link]()
#print(curr_node.state)

# if goal is found, we are done!


if search_problem.goal_test(curr_node.state): # assumes search_problem
has goal_test(state)
return backchain(search_problem, curr_node, "BFS", count)

for successor in search_problem.get_successors(curr_node.state):


# ^assumes search_problem has get_successors(state)
if successor not in visited_from:
visited_from.add(successor)

6
210280107107 AI-SEM7-BE-CE-LDCE

[Link](0, SearchNode(successor, curr_node))


#count += 1

# no solution found if control reaches this point


return no_solution(search_problem, "BFS", count)
[Link]
class SearchSolution:
def __init__(self, problem, search_method):
self.problem_name = str(problem)
self.search_method = search_method
[Link] = []
self.nodes_visited = 0

def __str__(self):
string = "----\n"
string += "{:s}\n"
string += "attempted with search method {:s}\n"

if len([Link]) > 0:

string += "number of nodes visited: {:d}\n"


string += "solution length: {:d}\n"
string += "path: {:s}\n"

string = [Link](self.problem_name, self.search_method,


self.nodes_visited, len([Link]),
str([Link]))
else:
string += "no solution found after visiting {:d} nodes\n"
string = [Link](self.problem_name, self.search_method,
self.nodes_visited)

return string

• Output:

Signature of Faculty: Grade:

7
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 3
• AIM: Write a program to implement DFS (for 8 puzzle problem or Water Jug problem or
any AI search problem).

• Theory:
o DFS is another graph traversal algorithm that explores as deeply as possible along
each branch before backtracking. It starts from a source node and follows a path
until it reaches a dead end or a previously visited node. Then it backtracks to the
last node with unexplored neighbors and continues the process.
o DFS is often used to find cycles in a graph, check if a graph is connected, and solve
problems like topological sorting. It can also be used to find the shortest path in
certain types of graphs, but it might not be the most efficient choice compared to
BFS in unweighted graphs.

• Implementation:
[Link]
from uninformed_search import dfs_search

class WaterJugProblem:
def
__init__(self,initial_state=(0,0),jug0=4,jug1=3,finaljug0=2,finaljug1=0):
self.start_state = initial_state
self.goal_state = (finaljug0, finaljug1)
self.jug0 = jug0
self.jug1 = jug1
self.finaljug0 = finaljug0
self.finaljug1 = finaljug1

def get_successors(self, state):


possible = set()
final = set()

# Empty jug0
if state[0] > 0:
[Link]((0, state[1]))
# Empty jug1
if state[1] > 0:
[Link]((state[0], 0))
# Fill jug0
if state[0] < self.jug0:
[Link]((self.jug0, state[1]))
# Fill jug1
if state[1] < self.jug1:
[Link]((state[0], self.jug1))
# Pour jug0 -> jug1, until it is full
if state[1] < self.jug1 and self.jug1 - state[1] <= state[0]:
[Link]((state[0]-(self.jug1 - state[1]), self.jug1))
# Pour jug1 -> jug0, until it is full
if state[0] < self.jug0 and self.jug0 - state[0] <= state[1]:
[Link]((self.jug0, state[1] - (self.jug0 - state[0])))
# Pour jug0 -> jug1, until it is empty

8
210280107107 AI-SEM7-BE-CE-LDCE

if state[0] > 0 and self.jug1 - state[1] >= state[0]:


[Link]((0, state[1] + state[0]))
# Pour jug1 -> jug0, until it is empty
if state[1] > 0 and self.jug0 - state[0] >= state[1]:
[Link]((state[0] + state[1], 0))

for successor in possible:


if self.is_legal(successor):
[Link](successor)
return final

def is_legal(self, state):


if 0 <= state[0] <= self.jug0 and 0 <= state[1] <= self.jug1:
return True
return False

def goal_test(self, state):


return state == self.goal_state

def __str__(self):
return f"Water Jug Problem with {self.jug0} L and {self.jug1} L to
reach {self.finaljug0} L and {self.finaljug1} L in respective jugs"

if __name__ == "__main__":
problem4320 = WaterJugProblem()
print(dfs_search(problem4320))
uninformed_search.py
def dfs_search(search_problem, depth_limit=100, node=None, solution=None):
# if no node object given, create a new search from starting state
if node == None:
node = SearchNode(search_problem.start_state)
solution = SearchSolution(search_problem, "DFS")

solution.nodes_visited += 1 # increment nodes visited count


# base case: test to see if goal is current node
if search_problem.goal_test([Link]):
return backchain(search_problem, node, "DFS", solution.nodes_visited)
else:
# base case: depth limit has been exceeded, handle accordingly
if depth_limit-1 <= 0: # -1 for logic purposes
if [Link] == search_problem.start_state:
return no_solution(search_problem, "DFS",
solution.nodes_visited)
else:
return None

# recursive case: continue recursing down successors, searching for


goal node
for successor in search_problem.get_successors([Link]):
if not_in(node, successor):
result = dfs_search(search_problem, depth_limit-1,
SearchNode(successor, node), solution)
if result: # ensure that result is not none
if not len([Link]) == 0: # ensure that result is not
no_result
return result
# none of the successors had any promise, return no_solution
return no_solution(search_problem, "DFS", solution.nodes_visited)

9
210280107107 AI-SEM7-BE-CE-LDCE

[Link]
class SearchSolution:
def __init__(self, problem, search_method):
self.problem_name = str(problem)
self.search_method = search_method
[Link] = []
self.nodes_visited = 0

def __str__(self):
string = "----\n"
string += "{:s}\n"
string += "attempted with search method {:s}\n"

if len([Link]) > 0:

string += "number of nodes visited: {:d}\n"


string += "solution length: {:d}\n"
string += "path: {:s}\n"

string = [Link](self.problem_name, self.search_method,


self.nodes_visited, len([Link]),
str([Link]))
else:
string += "no solution found after visiting {:d} nodes\n"
string = [Link](self.problem_name, self.search_method,
self.nodes_visited)

return string

• Output:

Signature of Faculty: Grade:

10
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 4
• AIM: Write a program to implement Single Player Game (Using any Heuristic Function).

• Theory:
o Heuristics are problem-solving techniques that employ rules of thumb or
strategies to guide a search towards a solution. They are often used when finding
an exact solution is computationally expensive or impossible. Heuristics are not
guaranteed to find the optimal solution but can provide good approximations in
many cases.
o Heuristic Functions are specific mathematical functions used to estimate the
distance or cost between a current state and the desired goal state. They provide
a measure of how close a solution is. A well-chosen heuristic function can
significantly improve the efficiency of a search algorithm.
o Heuristic Algorithms are search algorithms that incorporate heuristic functions to
guide their search. They often use these functions to prioritize the exploration of
nodes that are more likely to lead to a solution.

• Implementation:
import numpy as np
import heapq

class Puzzle:
def __init__(self, board):
[Link] = [Link](board)
[Link] = 3
self.blank_tile = (2, 2) # Starting position of the blank tile (0)
self.goal_state = [Link]([[1, 2, 3], [4, 5, 6], [7, 8, 0]])

def is_goal(self):
return np.array_equal([Link], self.goal_state)

def get_blank_position(self):
return [Link]([Link] == 0)[0]

def get_possible_moves(self):
row, col = self.get_blank_position()
moves = []
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] # Right, Left, Down,
Up

for dr, dc in directions:


new_row, new_col = row + dr, col + dc
if 0 <= new_row < [Link] and 0 <= new_col < [Link]:
[Link]((new_row, new_col))

return moves

def move(self, new_row, new_col):


row, col = self.get_blank_position()
new_board = [Link]()

11
210280107107 AI-SEM7-BE-CE-LDCE

new_board[row, col], new_board[new_row, new_col] = new_board[new_row,


new_col], new_board[row, col]
return Puzzle(new_board)

def heuristic(self):
return [Link]([Link] != self.goal_state) - ([Link][2, 2] == 0)

def __lt__(self, other):


return True

def best_first_search(puzzle):
open_list = [([Link](), puzzle, [])]
closed_set = set()
nodes_visited = 0
while open_list:
_, current_puzzle, moves = [Link](open_list)
nodes_visited += 1
if current_puzzle.is_goal():
return nodes_visited, len(moves), moves

closed_set.add(current_puzzle)

for move in current_puzzle.get_possible_moves():


new_puzzle = current_puzzle.move(*move)
if new_puzzle not in closed_set:
new_f = new_puzzle.heuristic() + len(moves) + 1 # Adjust
heuristic as needed
[Link](open_list, (new_f, new_puzzle, moves + [move]))

return None

if __name__ == "__main__":
initial_state = [[4, 1, 3], [7, 2, 6], [0, 5, 8]]
puzzle = Puzzle(initial_state)
result = best_first_search(puzzle)

if result:
nodes_visited, path_length, path = result
print('Best-first search Result')
print("Nodes visited:", nodes_visited)
print("Actual length of path:", path_length)
print("Path:")
for move in path:
print(f"{[Link](move)},",end=" ")
else:
print("No solution found.")

• Output:

Signature of Faculty: Grade:


12
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 5
• AIM: Write a program to Implement A* Algorithm.

• Theory:
o A* search is a popular informed search algorithm that combines the best features
of breadth-first search (BFS) and depth-first search (DFS). It uses a heuristic
function to estimate the cost to reach the goal state from a given node. The
algorithm prioritizes nodes based on their f-value, which is the sum of the actual
cost to reach the node (g-value) and the estimated cost to reach the goal (h-value).
o A* search is guaranteed to find the optimal solution if the heuristic function is
admissible (never overestimates the actual cost) and consistent (the difference in
estimated costs between two nodes is a lower bound on the actual cost). This
makes it a powerful tool for solving various pathfinding and problem-solving
tasks.

• Implementation:
import heapq
import numpy as np

class Puzzle:
def __init__(self, board):
[Link] = [Link](board)
[Link] = 3
self.blank_tile = (2, 2) # Starting position of the blank tile (0)
self.goal_state = [Link]([[1, 2, 3], [4, 5, 6], [7, 8, 0]])

def is_goal(self):
return np.array_equal([Link], self.goal_state)

def get_blank_position(self):
return [Link]([Link] == 0)[0]

def get_possible_moves(self):
row, col = self.get_blank_position()
moves = []
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] # Right, Left, Down,
Up

for dr, dc in directions:


new_row, new_col = row + dr, col + dc
if 0 <= new_row < [Link] and 0 <= new_col < [Link]:
[Link]((new_row, new_col))

return moves

def move(self, new_row, new_col):


row, col = self.get_blank_position()
new_board = [Link]()
new_board[row, col], new_board[new_row, new_col] = new_board[new_row,
new_col], new_board[row, col]
return Puzzle(new_board)

13
210280107107 AI-SEM7-BE-CE-LDCE

def heuristic(self):
return [Link]([Link] != self.goal_state) - ([Link][2, 2] == 0)

def __lt__(self, other):


return True

def a_star_search(puzzle):
"""Solves the 8-puzzle using A* search.

Args:
puzzle: The initial puzzle state.

Returns:
A tuple containing the number of nodes visited, the actual length of
the path, and the path.
"""

open_list = [([Link](), puzzle, [])]


closed_set = set()
nodes_visited = 0

while open_list:
_, current_puzzle, moves = [Link](open_list)
nodes_visited += 1

if current_puzzle.is_goal():
return nodes_visited, len(moves), moves

closed_set.add(current_puzzle)

for move in current_puzzle.get_possible_moves():


new_puzzle = current_puzzle.move(*move)

if new_puzzle not in closed_set:


new_g = len(moves) + 1
new_h = new_puzzle.heuristic()
new_f = new_g + new_h
[Link](open_list, (new_f, new_puzzle, moves + [move]))

return None

if __name__ == "__main__":
initial_state = [[4, 1, 3], [7, 2, 6], [0, 5, 8]]
puzzle = Puzzle(initial_state)
result = a_star_search(puzzle)

if result:
nodes_visited, path_length, path = result
print("A* Result")
print("Nodes visited:", nodes_visited)
print("Actual length of path:", path_length)
print("Path:")
for move in path:
print([Link](move),end=", ")
else:
print("No solution found.")

14
210280107107 AI-SEM7-BE-CE-LDCE

• Output:

Signature of Faculty: Grade:

15
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 6
• AIM: Write a program to implement mini-max algorithm for any game development.

• Theory:
o The Minimax algorithm is a decision-making algorithm used in game theory and
artificial intelligence. It is a recursive algorithm that searches for the optimal move
in a two-player game assuming that both players play optimally. The algorithm
assumes that the opponent will make the best possible move at each step, and it
tries to maximize its own utility (or minimize the opponent's utility) by choosing
the move that leads to the best possible outcome.
o Minimax is often used in games like chess, checkers, and tic-tac-toe, where players
have opposing goals. By considering the potential moves of both players and
evaluating their outcomes, the Minimax algorithm can help a player make
informed decisions and increase their chances of winning.

• Implementation:
board = [
[' ', ' ', ' '],
[' ', ' ', ' '],
[' ', ' ', ' ']
]

# Function to print the board


def print_board(board):
for row in board:
print("|".join(row))
print("-" * 5)

# Check if there are moves left on the board


def is_moves_left(board):
for row in board:
if ' ' in row:
return True
return False

# Evaluate the board to check the winner


def evaluate(board):
# Check rows for a win
for row in board:
if row[0] == row[1] == row[2] != ' ':
return 10 if row[0] == 'X' else -10

# Check columns for a win


for col in range(3):
if board[0][col] == board[1][col] == board[2][col] != ' ':
return 10 if board[0][col] == 'X' else -10

# Check diagonals for a win


if board[0][0] == board[1][1] == board[2][2] != ' ':
return 10 if board[0][0] == 'X' else -10

16
210280107107 AI-SEM7-BE-CE-LDCE

if board[0][2] == board[1][1] == board[2][0] != ' ':


return 10 if board[0][2] == 'X' else -10

# No winner: return 0
return 0

# Mini-Max Algorithm
def minimax(board, depth, is_maximizing):
score = evaluate(board)

# If maximizer (X) has won the game, return the evaluated score
if score == 10:
return score - depth # Favor faster wins

# If minimizer (O) has won the game, return the evaluated score
if score == -10:
return score + depth # Favor slower losses

# If no more moves and no winner, return 0 (draw)


if not is_moves_left(board):
return 0

# Maximizer's move
if is_maximizing:
best = -float('inf')

# Traverse all cells


for i in range(3):
for j in range(3):
# Check if the cell is empty
if board[i][j] == ' ':
# Make the move
board[i][j] = 'X'

# Call minimax recursively and choose the maximum value


best = max(best, minimax(board, depth + 1, False))

# Undo the move


board[i][j] = ' '

return best

# Minimizer's move
else:
best = float('inf')

# Traverse all cells


for i in range(3):
for j in range(3):
# Check if the cell is empty
if board[i][j] == ' ':
# Make the move
board[i][j] = 'O'

# Call minimax recursively and choose the minimum value


best = min(best, minimax(board, depth + 1, True))

# Undo the move


board[i][j] = ' '

17
210280107107 AI-SEM7-BE-CE-LDCE

return best

# Find the best move for the AI (X)


def find_best_move(board):
best_val = -float('inf')
best_move = (-1, -1)

# Traverse all cells


for i in range(3):
for j in range(3):
# Check if the cell is empty
if board[i][j] == ' ':
# Make the move
board[i][j] = 'X'

# Compute evaluation function for this move


move_val = minimax(board, 0, False)

# Undo the move


board[i][j] = ' '

# If the value of the current move is more than the best


value, update best move
if move_val > best_val:
best_move = (i, j)
best_val = move_val

return best_move

def main():
player = 'O' # Start with minimizer (player O)
while is_moves_left(board):
if player == 'O':
print("Player O's turn")
row, col = map(int, input("Enter row and column (0, 1, 2):
").split())
if board[row][col] == ' ':
board[row][col] = 'O'
player = 'X'
else:
print("Invalid move. Try again.")
else:
print("AI (X) is making a move...")
row, col = find_best_move(board)
board[row][col] = 'X'
player = 'O'

print_board(board)

score = evaluate(board)
if score == 10:
print("AI (X) wins!")
break
elif score == -10:
print("Player O wins!")
break
elif not is_moves_left(board):
print("It's a draw!")

18
210280107107 AI-SEM7-BE-CE-LDCE

break

if __name__ == "__main__":
main()

• Output:

19
210280107107 AI-SEM7-BE-CE-LDCE

Signature of Faculty: Grade:

20
210280107107 AI-SEM7-BE-CE-LDCE

Part-B: Prolog Programs


Practical – 1
• AIM: Write a PROLOG program to represent Facts. Make some queries based on the facts.

• Theory:
o Prolog (short for PROgramming in LOGic) is a declarative programming language
primarily used in artificial intelligence and computational linguistics. It is based
on formal logic and allows developers to define relationships between objects and
make inferences based on those relationships. In Prolog, the programmer focuses
on defining "what" the problem is, rather than detailing "how" to solve it. The
Prolog engine uses a process called backtracking to search for solutions to queries
by exploring all possible options until it finds a match or exhausts the search space.
Its unique strength lies in solving logical problems, making it ideal for domains
like natural language processing, knowledge representation, and expert systems.
o In Prolog, facts are the simplest form of knowledge representation. They describe
basic assertions about the world, like the relationship between two objects or a
property of an object, which are unconditionally true. A fact consists of a predicate
followed by one or more arguments, enclosed in parentheses. For example,
parent(ravi, arjun) states that "Ravi is the parent of Arjun." Facts are foundational
building blocks in Prolog, and complex logical relationships can be derived from
them through rules. By defining various facts, a knowledge base can be built,
allowing Prolog to infer further truths through queries.
o Queries in Prolog are used to ask questions about the knowledge base. These
questions can be aimed at verifying whether certain facts are true or retrieving
data that matches certain conditions. For example, the query ?- parent(ravi, arjun)
checks if "Ravi is the parent of Arjun," returning true if the fact exists. Queries can
also involve variables, such as ?- parent(ravi, X), which asks, "Who are Ravi's
children?" and will return all possible answers. Prolog uses its inference engine
and backtracking mechanism to find solutions to queries, exploring all possible
matches in the knowledge base.

• Implementation:
% Defining parent-child relationships
parent(ravi, arjun). % Ravi is the parent of Arjun
parent(ravi, nisha). % Ravi is the parent of Nisha
parent(sita, arjun). % Sita is the parent of Arjun
parent(sita, nisha). % Sita is the parent of Nisha
parent(arjun, rohit). % Arjun is the parent of Rohit
parent(meera, rohit). % Meera is the parent of Rohit
parent(nisha, ananya). % Nisha is the parent of Ananya
parent(sameer, ananya). % Sameer is the parent of Ananya

% Defining gender

21
210280107107 AI-SEM7-BE-CE-LDCE

male(ravi).
male(arjun).
male(rohit).
male(sameer).

female(sita).
female(nisha).
female(meera).
female(ananya).

% Defining spouse relationships


spouse(ravi, sita). % Ravi is the spouse of Sita
spouse(sita, ravi). % Sita is the spouse of Ravi
spouse(arjun, meera). % Arjun is the spouse of Meera
spouse(meera, arjun). % Meera is the spouse of Arjun
spouse(nisha, sameer). % Nisha is the spouse of Sameer
spouse(sameer, nisha). % Sameer is the spouse of Nisha

• Output:

Signature of Faculty: Grade:

22
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 2
• AIM: Write a PROLOG program to represent Rules. Make some queries based on the facts
and rules.

• Theory:
o In Prolog, rules define more complex logical relationships by combining facts and
other rules. A rule consists of a head and a body, separated by ":-", which can be
read as "if." The head of the rule represents the conclusion, while the body consists
of conditions that must be satisfied for the head to be true. For example, the rule
grandparent(X, Y) :- parent(X, Z), parent(Z, Y) means that "X is a grandparent of Y
if X is the parent of Z and Z is the parent of Y." Rules allow Prolog to deduce new
facts based on existing ones, making it possible to infer relationships indirectly
through logical reasoning.

• Implementation:
% Facts: basic features of birds
has_feathers(sparrow).
has_feathers(penguin).
has_feathers(ostrich).
has_feathers(eagle).
has_feathers(duck).

has_two_legs(sparrow).
has_two_legs(penguin).
has_two_legs(ostrich).
has_two_legs(eagle).
has_two_legs(duck).

has_wings(sparrow).
has_wings(penguin).
has_wings(ostrich).
has_wings(eagle).
has_wings(duck).

% Facts: special abilities of some birds


can_fly(sparrow).
can_fly(eagle).
can_fly(duck).

can_swim(penguin).
can_swim(duck).

% Rule: X is a bird if X has feathers, wings, and two legs


is_bird(X) :- has_feathers(X), has_wings(X), has_two_legs(X).

% Rule: X is a flightless bird if X is a bird and cannot fly


flightless_bird(X) :- is_bird(X), \+ can_fly(X).

23
210280107107 AI-SEM7-BE-CE-LDCE

• Output:

Signature of Faculty: Grade:

24
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 3
• AIM: Write a PROLOG program to define the relations using following predicates:
a) Define a predicate brother(X,Y) which holds iff X and Y are brothers.
b) Define a predicate cousin(X,Y) which holds iff X and Y are cousins.
c) Define a predicate grandson(X,Y) which holds iff X is a grandson of Y.
d) Define a predicate descendent(X,Y) which holds iff X is a descendent of Y.

• Implementation:
Consider the following genealogical tree:
father(a,b).
father(a,c).
father(b,d).
father(b,e).
father(c,f).

Say which answers, and in which order, are generated by your definitions for the
following queries in Prolog:
?- brother(X, Y).
?- cousin(X, Y).
?- grandson(X, Y).
?- descendent(X, Y).

% RULES
brother(X,Y):- father(Z,X), father(Z,Y), X\=Y.
grandson(X,Y):- father(Z,X), father(Y,Z).
descendent(X,Y):- father(Y,X).
descendent(X, Y):- father(Z, X), descendent(Z, Y).
cousin(X,Y):-father(Z,X),father(W,Y),brother(Z,W).

% FACTS
father(a,b).
father(a,c).
father(b,d).
father(b,e).
father(c,f).

• Output:

25
210280107107 AI-SEM7-BE-CE-LDCE

Signature of Faculty: Grade:

26
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 4
• AIM: Write a PROLOG program to perform addition, subtraction, multiplication and
division of two numbers using arithmetic operators.

• Theory:
o In Prolog, arithmetic operations such as addition, subtraction, multiplication, and
division are performed using built-in operators like +, -, *, and /. These operations
are handled through the is predicate, which is used to evaluate arithmetic
expressions. The is predicate takes two arguments: the left-hand side (a variable)
and the right-hand side (an arithmetic expression). The right-hand side is
evaluated, and the result is unified with the left-hand side.
o For example, in a rule like Result is X + Y, Prolog computes the sum of X and Y and
binds the result to the variable Result. In addition to simple operations, Prolog also
supports checks to handle special cases like division by zero using logical tests
(=\=) to prevent invalid operations.

• Implementation:
% Addition rule
add(X, Y, Result) :- Result is X + Y.

% Subtraction rule
subtract(X, Y, Result) :- Result is X - Y.

% Multiplication rule
multiply(X, Y, Result) :- Result is X * Y.

% Division rule (with a check to prevent division by zero)


divide(X, Y, Result) :- Y =\= 0, Result is X / Y.
divide(_, 0, 'undefined') :- write('Division by zero is not allowed.').

• Output:

Signature of Faculty: Grade:

27
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 5
• AIM: Write a PROLOG program to display the numbers from 1 to 10 by simulating the
loop using recursion.

• Theory:
o In Prolog, looping constructs commonly found in other programming languages,
such as for or while, are simulated using recursion. Recursion allows a predicate
to call itself repeatedly, with a base case that eventually terminates the recursion.
In the example of displaying numbers from 1 to 10, a recursive predicate is defined
with two cases: one to stop the recursion when a certain condition is met (the base
case) and another to continue the process by incrementing the value and calling
the predicate again (the recursive case). This approach is fundamental to how
Prolog handles iterative tasks, relying on recursive logic rather than explicit loops.

• Implementation:
% Base case: when N is greater than 10, stop the recursion
display_numbers(N) :-
N > 10, !. % Cut operator to prevent backtracking

% Recursive case: display the current number and call the predicate with the
next number
display_numbers(N) :-
write(N), nl, % Print the current number and move to a new line
N1 is N + 1, % Increment N by 1
display_numbers(N1). % Recursive call with the incremented value

% Start the display from 1


print_numbers :-
display_numbers(1).

• Output:

Signature of Faculty: Grade:

28
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 6
• AIM: Write a PROLOG program to count number of elements in a list.

• Theory:
o In Prolog, a list is a fundamental data structure used to store sequences of
elements. Lists in Prolog are enclosed in square brackets (`[]`), with elements
separated by commas, such as `[1, 2, 3]`. Lists can contain various data types like
numbers, atoms, or even other lists. The most important characteristics of lists in
Prolog are that they are recursive structures: each list is either an empty list (`[]`)
or consists of a head (the first element) and a tail (the rest of the list). Prolog's
ability to manipulate lists is based on pattern matching, allowing developers to
extract the head and tail of a list easily, which is a key feature for writing recursive
predicates.
o Prolog offers several built-in predicates for operating on lists. The `length/2`
predicate calculates the number of elements in a list, while `member/2` checks if
a particular element exists within a list. The `append/3` predicate is used to
concatenate two lists, and `reverse/2` reverses a list. In addition to these,
developers can define custom predicates to perform operations such as counting
elements, summing them, or searching through the list. Recursive techniques are
commonly employed for these tasks, leveraging Prolog’s natural affinity for
recursion to traverse and process each element of the list. These list-manipulation
methods provide Prolog with flexible and powerful tools for data handling and
reasoning.

• Implementation:
length([1,2,3,5,8,11,45,88],X).

• Output:

Signature of Faculty: Grade:


29
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 7
• AIM: Write a PROLOG program to perform following operations on a list:
a. To insert an element into the list.
b. To replace an element from the list.
c. To delete an element from the list.

• Theory:
o In Prolog, list manipulation is a common task, and operations such as inserting,
replacing, and deleting elements are typically done through recursive predicates.
Lists in Prolog are recursive data structures consisting of a head (the first element)
and a tail (the rest of the list). To perform operations like insertion, replacement,
or deletion, Prolog exploits pattern matching to decompose and reassemble lists.
o For insertion, an element can be added to the front of a list using the list
construction operator (`|`), which creates a new list with the element prepended.
For replacement and deletion, Prolog recursively traverses the list, checking each
element until the target is found. When the target is located, it is either replaced
or skipped over, depending on the operation. This recursive approach ensures
that Prolog can handle list manipulation flexibly while maintaining the logical
structure and declarative nature of the language.

• Implementation:
%insert an element
insert(E, [], [E]).
insert(E, [H | T], [H | NT]):-insert(E, T, NT).

%replace an element
replace(_, _, [], []):- write('Element to be replaced not found'), nl.
replace(R, F, [F | T], [R | T]).
replace(R, F, [H | T], [H | NT]) :-
H \= F,
replace(R, F, T, NT).

%delete an element
delete(_, [], _):- write('Element not found'), nl.
delete(D, [D | T], T).
delete(D, [H | T], [H | NT]) :-
H \= D,
delete(D, T, NT).

30
210280107107 AI-SEM7-BE-CE-LDCE

• Output:

Signature of Faculty: Grade:

31
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 8
• AIM: Write a PROLOG program to reverse the list.

• Theory:
o In Prolog, reversing a list is a common operation that can be achieved through
recursion and list manipulation. A list consists of a head (the first element) and a
tail (the rest of the list). To reverse a list, we can recursively process each element,
placing it at the end of a new list.
o Prolog naturally lends itself to recursive solutions, making it a powerful tool for
such tasks. The base case in reversing is when the list is empty, where the reverse
of an empty list is also an empty list. The recursive step takes the head of the list
and appends it to the reversed tail. This can be implemented using an auxiliary
predicate or an accumulator for efficiency.

• Implementation:
reverse([1,3,5,9,45,855,20],X).

• Output:

Signature of Faculty: Grade:

32
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 9
• AIM: Write a PROLOG program to display the numbers from 1 to 10 by simulating the
loop using recursion.

• Theory:
o In Prolog, the CUT (!) and FAIL predicates are used to control the flow of execution
and backtracking. The CUT predicate is used to prevent Prolog from exploring
alternative solutions once a certain point in the program is reached. When Prolog
encounters a (!), it "commits" to the choices made up to that point and discards
any other potential solutions or rules that might otherwise have been tried during
backtracking. This can improve efficiency but should be used with caution, as it
changes the natural behavior of Prolog's backtracking mechanism.
o The FAIL predicate, on the other hand, is used to force failure deliberately. It
always fails when called, ensuring that any predicates that use fail will not
succeed. Together, CUT and FAIL can be used for tasks like filtering or defining
conditions where certain branches should never succeed, enforcing strict control
over Prolog's behavior.

• Implementation:
% Example 1: Using CUT to stop backtracking
check_member(X, [X|_]) :- !.
check_member(X, [_|T]) :- check_member(X, T).

% Example 2: Using CUT and FAIL for conditions


check_adult(Age) :- Age >= 18, !.
check_adult(_) :- fail.

• Output:

Signature of Faculty: Grade:


33
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 10
• AIM: Write a PROLOG program to solve the Tower of Hanoi problem.

• Theory:
o The Tower of Hanoi is a classic problem in recursion, involving three pegs (A, B,
and C) and a number of disks of varying sizes. The goal is to move all the disks
from one peg (source) to another (destination) using an auxiliary peg, following
these rules:
• Only one disk can be moved at a time.
• A disk can only be placed on top of a larger disk.
• You must use the auxiliary peg to transfer disks between the source and
destination.
o In Prolog, recursion is a natural fit for solving the Tower of Hanoi problem. The
base case is moving one disk directly from the source to the destination. The
recursive case involves moving the top N-1 disks to the auxiliary peg, then moving
the largest disk to the destination, and finally moving the N-1 disks from the
auxiliary peg to the destination.

• Implementation:
% Base case: Move a single disk directly from the source to the destination
move(1, Source, Destination, _) :-
write('Move disk from '), write(Source), write(' to '),
write(Destination), nl.

% Recursive case: Move N disks using the Tower of Hanoi rules


move(N, Source, Destination, Auxiliary) :-
N > 1,
M is N - 1,
move(M, Source, Auxiliary, Destination),
move(1, Source, Destination, _),
move(M, Auxiliary, Destination, Source).

• Output:

Signature of Faculty: Grade:


34
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 11
• AIM: Write a PROLOG program to solve the N-Queens problem..

• Theory:
o The is a classic backtracking problem in combinatorial optimization. The
objective is to place N queens on an N x N chessboard such that no two queens
threaten each other. This means that no two queens can occupy the same row,
column, or diagonal. The challenge lies in finding a configuration that meets these
constraints.
o In Prolog, the N-Queens problem can be solved using recursion and backtracking.
We can represent the board as a list of positions where each element denotes the
column of the queen in that row. For example, the list [2, 4, 1, 3] represents a
configuration where a queen is placed in the second column of the first row, the
fourth column of the second row, and so on. The algorithm will attempt to place a
queen in each row and check for conflicts with previously placed queens. If a
conflict arises, it will backtrack and try the next possible column.

• Implementation:
% render solutions nicely.
:- use_rendering(chess).

%% queens(+N, -Queens) is nondet.


% @param Queens is a list of column numbers for placing the queens.
% @author Richard A. O'Keefe (The Craft of Prolog)

queens(N, Queens) :-
length(Queens, N),
board(Queens, Board, 0, N, _, _),
queens(Board, 0, Queens).

board([], [], N, N, _, _).


board([_|Queens], [Col-Vars|Board], Col0, N, [_|VR], VC) :-
Col is Col0+1,
functor(Vars, f, N),
constraints(N, Vars, VR, VC),
board(Queens, Board, Col, N, VR, [_|VC]).

constraints(0, _, _, _) :- !.
constraints(N, Row, [R|Rs], [C|Cs]) :-
arg(N, Row, R-C),
M is N-1,
constraints(M, Row, Rs, Cs).

queens([], _, []).
queens([C|Cs], Row0, [Col|Solution]) :-
Row is Row0+1,
select(Col-Vars, [C|Cs], Board),
arg(Row, Vars, Row-Row),
queens(Board, Row, Solution).

35
210280107107 AI-SEM7-BE-CE-LDCE

• Output:

Signature of Faculty: Grade:

36
210280107107 AI-SEM7-BE-CE-LDCE

Practical – 12
• AIM: Write a PROLOG program to solve the Travelling Salesman problem..

• Theory:
o In Prolog, looping constructs commonly found in other programming languages,
such as for or while, are simulated using recursion. Recursion allows a predicate
to call itself repeatedly, with a base case that eventually terminates the recursion.
In the example of displaying numbers from 1 to 10, a recursive predicate is defined
with two cases: one to stop the recursion when a certain condition is met (the base
case) and another to continue the process by incrementing the value and calling
the predicate again (the recursive case). This approach is fundamental to how
Prolog handles iterative tasks, relying on recursive logic rather than explicit loops.

• Implementation:
edge(a, b, 3).
edge(a, c, 4).
edge(a, d, 2).
edge(a, e, 7).
edge(b, c, 4).
edge(b, d, 6).
edge(b, e, 3).
edge(c, d, 5).
edge(c, e, 8).
edge(d, e, 6).
edge(b, a, 3).
edge(c, a, 4).
edge(d, a, 2).
edge(e, a, 7).
edge(c, b, 4).
edge(d, b, 6).
edge(e, b, 3).
edge(d, c, 5).
edge(e, c, 8).
edge(e, d, 6).
edge(a, h, 2).
edge(h, d, 1).

/* Finds the length of a list, while there is something in the list it


increments N
when there is nothing left it returns.*/

len([], 0).
len([H|T], N):- len(T, X), N is X+1 .

/*Best path, is called by shortest_path. It sends it the paths found in a


path, distance format*/

best_path(Visited, Total):- path(a, a, Visited, Total).

/*Path is expanded to take in distance so far and the nodes visited */

37
210280107107 AI-SEM7-BE-CE-LDCE

path(Start, Fin, Visited, Total) :- path(Start, Fin, [Start], Visited, 0,


Total).

/*This adds the stopping location to the visited list, adds the distance and
then calls recursive
to the next stopping location along the path */

path(Start, Fin, CurrentLoc, Visited, Costn, Total) :-


edge(Start, StopLoc, Distance), NewCostn is Costn + Distance, \+
member(StopLoc, CurrentLoc),
path(StopLoc, Fin, [StopLoc|CurrentLoc], Visited, NewCostn, Total).

/*When we find a path back to the starting point, make that the total distance
and make
sure the graph has touch every node*/

path(Start, Fin, CurrentLoc, Visited, Costn, Total) :-


edge(Start, Fin, Distance), reverse([Fin|CurrentLoc], Visited),
len(Visited, Q),
(Q\=7 -> Total is 100000; Total is Costn + Distance).

/*This is called to find the shortest path, takes all the paths, collects them
in holder.
Then calls pick on that holder which picks the shortest path and returns
it*/

shortest_path(Path):-setof(Cost-Path, best_path(Path,Cost),
Holder),pick(Holder,Path).

/* Is called, compares 2 distances. If cost is smaller than bcost, no need to


go on. Cut it.*/

best(Cost-Holder,Bcost-_,Cost-Holder):- Cost<Bcost,!.
best(_,X,X).

/*Takes the top path and distance off of the holder and recursively calls
it.*/

pick([Cost-Holder|R],X):- pick(R,Bcost-Bholder),best(Cost-Holder,Bcost-
Bholder,X),!.
pick([X],X).

• Output:

Signature of Faculty: Grade:

38

You might also like