Tic-Tac-Toe and BFS Implementation
Tic-Tac-Toe and BFS Implementation
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
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
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:
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
# 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))
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
6
210280107107 AI-SEM7-BE-CE-LDCE
def __str__(self):
string = "----\n"
string += "{:s}\n"
string += "attempted with search method {:s}\n"
if len([Link]) > 0:
return string
• Output:
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
# 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
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")
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:
return string
• Output:
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
return moves
11
210280107107 AI-SEM7-BE-CE-LDCE
def heuristic(self):
return [Link]([Link] != self.goal_state) - ([Link][2, 2] == 0)
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)
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:
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
return moves
13
210280107107 AI-SEM7-BE-CE-LDCE
def heuristic(self):
return [Link]([Link] != self.goal_state) - ([Link][2, 2] == 0)
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.
"""
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)
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:
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 = [
[' ', ' ', ' '],
[' ', ' ', ' '],
[' ', ' ', ' ']
]
16
210280107107 AI-SEM7-BE-CE-LDCE
# 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
# Maximizer's move
if is_maximizing:
best = -float('inf')
return best
# Minimizer's move
else:
best = float('inf')
17
210280107107 AI-SEM7-BE-CE-LDCE
return best
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
20
210280107107 AI-SEM7-BE-CE-LDCE
• 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).
• Output:
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).
can_swim(penguin).
can_swim(duck).
23
210280107107 AI-SEM7-BE-CE-LDCE
• Output:
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
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.
• Output:
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
• Output:
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:
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:
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:
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).
• Output:
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.
• Output:
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) :-
length(Queens, N),
board(Queens, Board, 0, N, _, _),
queens(Board, 0, Queens).
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:
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).
len([], 0).
len([H|T], N):- len(T, X), N is X+1 .
37
210280107107 AI-SEM7-BE-CE-LDCE
/*This adds the stopping location to the visited list, adds the distance and
then calls recursive
to the next stopping location along the path */
/*When we find a path back to the starting point, make that the total distance
and make
sure the graph has touch every node*/
/*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).
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:
38