TOWER OF HANOI
CASE STUDY ON
ARTIFICIAL INTELLIGENCE
CODE : 2031
BY
TEAM-4
MRIDUL DAS VU22CSEN0102221
SATVIK KATYAYAN VU22CSEN0101577
M.L.V.N.S MANJUNATH VU22CSEN0100581
K.KARTHIKEYA VARMA VU22CSEN0100G27
S. Kishore VU22CSEN0400292
UNDER THE GUIDANCE OF:
Dr. D. Chandrika
Mr. V.S.V.S Murthy
Introduction:
The Tower of Hanoi (also called the problem of Benares Temple, Tower of
Brahma or Lucas Tower, and sometimes pluralized as Towers, or simply
pyramid puzzle), is a mathematical game or puzzle consisting of three rods
and a number of disks of various diameters, which can slide onto any rod.
The puzzle begins with the disks stacked on one rod in order of decreasing
size, the smallest at the top, thus approximating a conical shape. The
objective of the puzzle is to move the entire stack to one of the other rods,
obeying the following rules:
Only one disk may be moved at a time.
Each move consists of taking the upper disk from one of the stacks and
placing it on top of another stack or on an empty rod.
No disk may be placed on top of a disk that is smaller than it.
With three disks, the puzzle can be solved in seven moves. The minimal
number of moves required to solve a Tower of Hanoi puzzle is 2n − 1, where
n is the number of disks.
Problem Description:
The puzzle consists of:
• Three pegs (rods): These are labeled as Source (A),
Auxiliary (B), and Destination (C).
• N disks: These are of varying sizes, stacked in descending
order (largest at the bottom and smallest at the top) on the
source peg.
The objective is to move all the disks from the Source peg to the
Destination peg while adhering to the following rules:
1. Only one disk can be moved at a time.
2. A larger disk cannot be placed on top of a smaller disk.
3. All disks must be moved using the auxiliary peg.
Here’s a Towers of Hanoi-themed case study with 14 creative
subtasks aligned with AI concepts. These tasks explore problem-
solving techniques while maintaining the constraints of moving
disks between three pegs. Each subtask includes innovative
extensions to enhance engagement and learning.
The Towers of Hanoi-themed AI case study is a classic yet
innovative choice for BTech students, offering a blend of
foundational learning and advanced AI applications. Here's why it
is an excellent case study:
1. Timeless and Engaging Problem:
o The Towers of Hanoi is a well-known and engaging
puzzle, making it approachable for students while still
being intellectually challenging.
o The problem’s simple rules and clear objectives
provide an excellent starting point for exploring
complex AI concepts.
2. Comprehensive Use of AI Techniques:
o The case study explores a wide range of AI
methodologies, including uninformed search (BFS,
DFS, UCS), informed search (A*, Greedy), local search
(hill climbing, simulated annealing), and optimization
(genetic algorithms).
o Advanced topics like adversarial search, constraint
satisfaction problems (CSP), and first-order logic are
incorporated, broadening the scope of learning.
3. Dynamic and Realistic Scenarios:
o Introduces extensions such as unavailable pegs, time
constraints, and competitive settings, adding depth
and making the scenarios dynamic and realistic.
o Encourages adaptability and creativity through evolving
constraints and adversarial scenarios.
4. Scalability and Flexibility:
o Tasks range from beginner-friendly implementations
(e.g., BFS for shortest moves) to advanced challenges
(e.g., genetic algorithms for solution evolution),
catering to various expertise levels.
o The inclusion of dynamic extensions allows for
increased complexity and further exploration.
5. Interdisciplinary Learning:
o Combines AI with logic, optimization, and game theory,
showcasing interdisciplinary problem-solving
applications.
o Encourages students to apply theoretical knowledge in
practical, creative ways.
PEAS (Performance Measure, Environment, Actuators,
Sensors) for Tower of Hanoi:
Elements Description
Performance Minimum number of moves to transfer all
disks while following the rules
Environment Three rods (A, B, C) and a set of disks in
increasing order on the initial rod
Actuators The ability to pick up a disk and place it on
another rod
Sensors Ability to detect the current arrangement of
disks on the rods
Task Environment Properties:
Observable: Fully observable since the agent has complete
knowledge of the state.
Deterministic: Yes, the result of each action is known.
Episodic or Sequential: Sequential, as each action affects future
actions.
Static or Dynamic: Static; no changes occur in the environment
except those caused by the agent.
Discrete or Continuous: Discrete; there are a limited number of
states and moves.
Single-Agent or Multi-Agent: Single-agent.
Problem Definition:
Initial State: All disks on Rod A in increasing order, Rods B and C
empty.
Action: Move the top disk from one rod to another valid rod.
Transition Model: Describes the effect of an action on the state.
A disk moves from source rod to destination rod, updating the
state.
Goal State: All disks are moved to the target rod in the same
order.
Path Cost: The cost is the number of moves taken to reach the
goal. The number of moves required, optimally 2^n−1.
3. Algorithm Implementation:
A. Depth-First Search (DFS) Approach
DFS explores the solution tree by going as deep as possible into
one path before backtracking. It is implemented using recursion
or a stack.
Steps for DFS in Tower of Hanoi:
1. Move (n-1) disks from source to auxiliary rod.
2. Move the nth (largest) disk to the destination rod.
3. Move (n-1) disks from the auxiliary rod to the destination
rod.
Complexity:
• DFS is not optimal since it may explore unnecessary paths.
• Time Complexity: O(2n)O(2^n)O(2n) (Exponential)
• Space Complexity: O(n)O(n)O(n) (Recursion depth)
Code Implementation:
def tower_of_hanoi(n, source, auxiliary, target, moves):
if n == 1:
moves.append((source, target))
return
tower_of_hanoi(n - 1, source, target, auxiliary, moves)
moves.append((source, target))
tower_of_hanoi(n - 1, auxiliary, source, target, moves)
def solve_hanoi(n):
moves = []
tower_of_hanoi(n, 'A', 'B', 'C', moves)
return moves
n=3
result = solve_hanoi(n)
for move in result:
print(f"Move disk from {move[0]} to {move[1]}")
B. Greedy Algorithm Approach
A greedy algorithm makes the locally optimal choice at each step
with the hope of reaching the global solution.
Greedy Approach for Tower of Hanoi:
• Always move the smallest available disk to the next best
position.
• Move alternately between the smallest disk and another
valid move.
Limitations of Greedy Approach in Tower of Hanoi:
• It may not always be optimal, as it does not backtrack
when needed.
• Can lead to unnecessary moves compared to DFS.
Code Implementation:
def iterative_hanoi(n):
source, auxiliary, target = 'A', 'B', 'C'
moves = []
total_moves = 2 ** n - 1
rods = {source: list(range(n, 0, -1)), auxiliary: [], target: []}
if n % 2 == 0:
target, auxiliary = auxiliary, target
for move in range(1, total_moves + 1):
from_rod, to_rod = None, None
if move % 3 == 1:
from_rod, to_rod = source, target
elif move % 3 == 2:
from_rod, to_rod = source, auxiliary
else:
from_rod, to_rod = auxiliary, target
if rods[from_rod] and (not rods[to_rod] or rods[from_rod][-1] < rods[to_rod][-1]):
rods[to_rod].append(rods[from_rod].pop())
moves.append((from_rod, to_rod))
else:
rods[from_rod].append(rods[to_rod].pop())
moves.append((to_rod, from_rod))
return moves
n=3
result = iterative_hanoi(n)
for move in result:
print(f"Move disk from {move[0]} to {move[1]}")
C. Depth-Limited Search
import matplotlib.pyplot as plt
import random
def draw_towers(state, title):
plt.clf()
plt.title(title)
pegs = [0, 1, 2]
peg_width = 0.2
peg_height = max(len(state[0]), len(state[1]), len(state[2])) + 2
for peg in pegs:
plt.bar(peg, peg_height, peg_width, color='black')
for peg_index, peg in enumerate(state):
for disk_index, disk in enumerate(peg):
plt.bar(peg_index, 1, 0.1 + disk * 0.1, bottom=disk_index + 1, color="blue")
plt.xticks([0, 1, 2], ["A", "B", "C"])
plt.show()
plt.pause(1)
def get_user_input():
while True:
try:
n = int(input("Enter number of disks: "))
if n > 0:
return n
else:
print("Please enter a positive integer.")
except ValueError:
print("Invalid input! Please enter a number.")
def generate_random_state(n):
state = [[], [], []]
for disk in range(n, 0, -1):
random.choice(state).append(disk)
return state
def get_possible_moves(state):
moves = []
for from_peg in range(3):
if state[from_peg]:
disk = state[from_peg][-1]
for to_peg in range(3):
if from_peg != to_peg and (not state[to_peg] or state[to_peg][-1] > disk):
new_state = [list(peg) for peg in state]
new_state[from_peg].pop()
new_state[to_peg].append(disk)
moves.append(new_state)
return moves
def depth_limited_search(state, goal, depth_limit):
if state == goal:
return True, state
if depth_limit == 0:
return False, None
for move in get_possible_moves(state):
found, final_state = depth_limited_search(move, goal, depth_limit - 1)
if found:
return True, final_state
return False, None
def solve_with_dls(start, goal, depth_limit):
draw_towers(start, "Initial State")
found, final_state = depth_limited_search(start, goal, depth_limit)
if found:
draw_towers(final_state, "Final State")
return final_state
else:
return "Solution not found within depth limit."
n = get_user_input()
start_state = generate_random_state(n)
goal_state = [[], [], list(range(n, 0, -1))]
print("Initial State:", start_state)
final_state = solve_with_dls(start_state, goal_state, depth_limit=2 ** n)
print("Final State:", final_state)
D. Hill Climbing Algorithm
import matplotlib.pyplot as plt
import random
def draw_towers(state, title):
plt.clf()
plt.title(title)
pegs = [0, 1, 2]
peg_width = 0.2
peg_height = max(len(state[0]), len(state[1]), len(state[2])) + 2
for peg in pegs:
plt.bar(peg, peg_height, peg_width, color='black')
for peg_index, peg in enumerate(state):
for disk_index, disk in enumerate(peg):
plt.bar(peg_index, 1, 0.1 + disk * 0.1, bottom=disk_index + 1, color="blue")
plt.xticks([0, 1, 2], ["A", "B", "C"])
plt.show()
plt.pause(1)
def get_user_input():
while True:
try:
n = int(input("Enter number of disks: "))
if n > 0:
return n
else:
print("Please enter a positive integer.")
except ValueError:
print("Invalid input! Please enter a number.")
def generate_random_state(n):
state = [[], [], []]
for disk in range(n, 0, -1):
random.choice(state).append(disk)
return state
def get_possible_moves(state):
moves = []
for from_peg in range(3):
if state[from_peg]:
disk = state[from_peg][-1]
for to_peg in range(3):
if from_peg != to_peg and (not state[to_peg] or state[to_peg][-1] > disk):
new_state = [list(peg) for peg in state]
new_state[from_peg].pop()
new_state[to_peg].append(disk)
moves.append(new_state)
return moves
def evaluate(state, goal):
score = 0
for peg_index in range(3):
for i, disk in enumerate(state[peg_index]):
if peg_index == 2 and goal[peg_index] and disk == goal[peg_index][i]:
score += 1
return score
def hill_climb(state, goal, max_iterations=100):
draw_towers(state, "Initial State")
for _ in range(max_iterations):
if state == goal:
draw_towers(state, "Final State")
return state
next_moves = get_possible_moves(state)
best_move = max(next_moves, key=lambda move: evaluate(move, goal),
default=None)
if best_move and evaluate(best_move, goal) > evaluate(state, goal):
state = best_move
draw_towers(state, f"Current State - Score: {evaluate(state, goal)}")
else:
break
draw_towers(state, "Final State")
return state
n = get_user_input()
start_state = generate_random_state(n)
goal_state = [[], [], list(range(n, 0, -1))]
print("Initial State:", start_state)
final_state = hill_climb(start_state, goal_state)
print("Final State:", final_state)
E. Uniform Cost Search
import heapq
# Function to represent the state of the Tower of Hanoi
def get_state_key(pegs):
return tuple(tuple(peg) for peg in pegs)
# Check if the current state is the goal state
def is_goal_state(pegs, num_disks):
return len(pegs[2]) == num_disks
# Get the valid moves from the current state
def get_valid_moves(pegs):
moves = []
# Try moving from each peg to every other peg
for i in range(3): # source peg
if pegs[i]:
for j in range(3): # target peg
if i != j: # can't move to the same peg
# Check if the move is valid (either target peg is empty or larger disk)
if not pegs[j] or pegs[i][-1] < pegs[j][-1]:
# Generate new state by moving the disk
new_pegs = [peg[:] for peg in pegs] # Copy of the pegs
disk = new_pegs[i].pop() # Remove disk from source peg
new_pegs[j].append(disk) # Add disk to target peg
moves.append((new_pegs, i, j)) # Append new state and source and target
pegs
return moves
# Uniform Cost Search (UCS)
def uniform_cost_search(initial_pegs, num_disks):
start_state = get_state_key(initial_pegs)
goal_state = get_state_key([[], [], list(range(num_disks, 0, -1))])
# Priority queue (min-heap) for UCS. Stores (cost, state, move_sequence)
frontier = []
heapq.heappush(frontier, (0, start_state, initial_pegs, [])) # Start with initial state
# Set to track visited states
visited = set()
visited.add(start_state)
# Perform UCS
while frontier:
cost, state_key, pegs, move_sequence = heapq.heappop(frontier)
# Check if we reached the goal
if is_goal_state(pegs, num_disks):
return move_sequence, cost
# Get all valid moves from the current state
for next_pegs, source, target in get_valid_moves(pegs):
next_state_key = get_state_key(next_pegs)
# If the new state hasn't been visited, add it to the frontier
if next_state_key not in visited:
visited.add(next_state_key)
new_cost = cost + 1
new_move_sequence = move_sequence + [(source, target)] # Append the move
to sequence
heapq.heappush(frontier, (new_cost, next_state_key, next_pegs,
new_move_sequence))
return None # If no solution is found
# Function to print the solution
def print_solution(move_sequence, total_cost):
print(f"Total cost: {total_cost}")
rods = ['A', 'B', 'C']
print("Solution:")
for move in move_sequence:
source, target = move
print(f"Move disk from rod {rods[source]} to rod {rods[target]}.")
# Main driver
if name == " main ":
# Define the number of disks and initialize the pegs
num_disks = int(input("Enter the number of disks: "))
pegs = [[i for i in range(num_disks, 0, -1)], [], []] # Initial state: All disks on peg A
# Perform Uniform Cost Search to solve the Tower of Hanoi
move_sequence, total_cost = uniform_cost_search(pegs, num_disks)
# Print the solution
if move_sequence:
print_solution(move_sequence, total_cost)
else:
print("No solution found!")
F. Breadth First Search
from collections import deque
# Function to print the current state of the pegs
def print_state(pegs):
for i, peg in enumerate(pegs):
print(f"Peg {i+1}: {peg}")
# Function to check if the current state is the goal state (all disks on the destination peg)
def is_goal_state(pegs, num_disks):
return len(pegs[2]) == num_disks
# Function to get possible valid moves from the current state
def get_valid_moves(pegs):
valid_moves = []
for i in range(3):
if len(pegs[i]) == 0:
continue
# Try moving the top disk of peg[i] to the other two pegs
for j in range(3):
if i != j:
if len(pegs[j]) == 0 or pegs[i][-1] < pegs[j][-1]: # Move is valid if the top disk of
peg[i] is smaller
new_pegs = [peg.copy() for peg in pegs]
disk = new_pegs[i].pop()
new_pegs[j].append(disk)
valid_moves.append((new_pegs, (i, j))) # Store the new pegs and the move
return valid_moves
# Function to find the shortest sequence of moves using BFS
def bfs_solution(num_disks):
# Initial state of the pegs (source peg with all disks, destination peg is empty)
initial_state = [[i for i in range(num_disks, 0, -1)], [], []]
# BFS queue stores tuples (pegs, path)
queue = deque([(initial_state, [])])
# Set to keep track of visited states
visited = set()
visited.add(tuple(tuple(peg) for peg in initial_state))
while queue:
current_pegs, path = queue.popleft()
# Check if we reached the goal state
if is_goal_state(current_pegs, num_disks):
return path
# Get all valid moves from the current state
for new_pegs, move in get_valid_moves(current_pegs):
# Convert the new pegs into a tuple of tuples for immutability
state_tuple = tuple(tuple(peg) for peg in new_pegs)
# If the new state hasn't been visited, add it to the queue
if state_tuple not in visited:
visited.add(state_tuple)
queue.append((new_pegs, path + [move])) # Store the path with the new move
return None # If no solution found
# Function to print the sequence of moves
def print_solution(path):
for i, (start, end) in enumerate(path):
print(f"Move disk from Peg {start+1} to Peg {end+1}")
# Main function
def solve_towers_of_hanoi(num_disks):
print(f"Solving Towers of Hanoi with {num_disks} disks using BFS...\n")
# Get the solution path
solution_path = bfs_solution(num_disks)
if solution_path is None:
print("No solution found.")
else:
print("Solution steps:")
print_solution(solution_path)
print("\nSolved!")
# Driver code to test the BFS solution for Towers of Hanoi
if name == " main ":
num_disks = int(input("Enter the number of disks: "))
solve_towers_of_hanoi(num_disks)
G. Depth First Search
class TowerOfHanoi:
def init (self, num_disks):
self.num_disks = num_disks
self.start_state = (tuple(range(num_disks, 0, -1)), (), ()) # Pegs as tuples
self.visited = set()
self.solutions = []
def is_goal(self, state):
return len(state[0]) == 0 and len(state[1]) == 0 # All disks moved to peg 3
def get_neighbors(self, state):
neighbors = []
for i in range(3):
if state[i]:
for j in range(3):
if i != j and (not state[j] or state[i][-1] < state[j][-1]):
new_state = list(map(list, state))
new_state[j].append(new_state[i].pop())
neighbors.append(tuple(map(tuple, new_state)))
return neighbors
def dfs(self, state, path):
if state in self.visited:
return
self.visited.add(state)
path.append(state)
if self.is_goal(state):
self.solutions.append(path[:]) # Store the solution path
else:
for neighbor in self.get_neighbors(state):
self.dfs(neighbor, path)
path.pop()
def solve(self):
self.dfs(self.start_state, [])
return self.solutions
# Example usage
num_disks = 3
toh = TowerOfHanoi(num_disks)
solutions = toh.solve()
for idx, solution in enumerate(solutions):
print(f"Solution {idx + 1}:")
for state in solution:
print(state)
print("-" * 30)
H. Greedy Best First Search
import heapq
class TowerOfHanoiGBFS:
def init (self, num_disks):
self.num_disks = num_disks
self.start_state = (tuple(range(num_disks, 0, -1)), (), ())
self.goal_state = ((), (), tuple(range(num_disks, 0, -1)))
self.visited = set()
def heuristic(self, state):
return sum(1 for i, disk in enumerate(state[2]) if disk == self.num_disks - i)
def get_neighbors(self, state):
neighbors = []
for i in range(3):
if state[i]:
for j in range(3):
if i != j and (not state[j] or state[i][-1] < state[j][-1]):
new_state = list(map(list, state))
new_state[j].append(new_state[i].pop())
neighbors.append(tuple(map(tuple, new_state)))
return neighbors
def gbfs(self):
pq = []
heapq.heappush(pq, (self.heuristic(self.start_state), self.start_state, []))
while pq:
_, current_state, path = heapq.heappop(pq)
if current_state == self.goal_state:
return path
if current_state in self.visited:
continue
self.visited.add(current_state)
for neighbor in self.get_neighbors(current_state):
heapq.heappush(pq, (self.heuristic(neighbor), neighbor, path + [neighbor]))
return None
# Example usage
num_disks = 3
toh_gbfs = TowerOfHanoiGBFS(num_disks)
solution = toh_gbfs.gbfs()
if solution:
print("Solution found:")
for state in solution:
print(state)
else:
print("No solution found.")
Visualizing the output:
Step-by-step movement
Move Disk 1 from A → C
Move Disk 2 from A → B
Move Disk 1 from C → B
Move Disk 3 from A → C
Move Disk 1 from B → A
Move Disk 2 from B → C
Move Disk 1 from A → C
Iterative Solution:
Recursive solution:
Task Interaction
Each action directly affects the future available moves.
Smaller disks need to be freed first before larger disks can move.
DFS ensures that all possible moves are explored, while Greedy
may take an inefficient path due to local optimizations.
The number of disks affects complexity exponentially (moves
double for every extra disk).
Graphical Visualization:
A graphical representation can make understanding the
problem easier. The steps of execution can be animated as disks
moving from one rod to another.
Ways to visualize graphically:
• Using Python s Matplotlib
o Draw the rods and disks using bars.
o Animate the movement of disks between rods.
o Example libraries: Matplotlib, Pygame, Tkinter
• Using Online Simulators
o Websites like
https://www.mathsisfun.com/games/towerofhanoi.
html
o Animated solutions for different numbers of disks.
• Using Blocks in a Grid (ASCII Animation)
o Represent rods as columns and disks as different-
sized symbols.
Recursive Best-First Search (RBFS):
import math
import heapq
class State:
def __init__(self, pegs, moves=0, h=0, parent=None):
self.pegs = pegs # Pegs represented as a list of stacks
self.moves = moves # Number of moves made
self.h = h # Heuristic value (estimated cost)
self.parent = parent # Parent state for backtracking
def __lt__(self, other):
return (self.moves + self.h) < (other.moves + other.h)
def __eq__(self, other):
return self.pegs == other.pegs
def __hash__(self):
return hash(tuple(tuple(peg) for peg in self.pegs))
def heuristic(state, goal_state):
"""
A simple heuristic: Count the number of disks not in the goal position.
"""
misplaced_disks = sum(1 for i in range(len(state.pegs)) if state.pegs[i] != goal_state[i])
return misplaced_disks
def generate_successors(state, constraints):
"""
Generate all possible valid moves while considering constraints.
"""
successors = []
num_pegs = len(state.pegs)
for i in range(num_pegs):
if state.pegs[i]: # Ensure peg is not empty
disk = state.pegs[i][-1] # Take the top disk
for j in range(num_pegs):
if i != j and (not state.pegs[j] or state.pegs[j][-1] > disk): # Valid move
if disk in constraints and constraints[disk] != j:
continue # Enforce constraint (some disks must stay on certain pegs)
new_pegs = [list(peg) for peg in state.pegs] # Deep copy
new_pegs[j].append(new_pegs[i].pop()) # Move disk
new_state = State(new_pegs, state.moves + 1, 0, state)
new_state.h = heuristic(new_state, constraints['goal'])
successors.append(new_state)
return successors
def rbfs(state, goal_state, constraints, f_limit):
"""
Recursive Best-First Search (RBFS) for dynamically constrained Towers of Hanoi.
"""
if state.pegs == goal_state:
return state, 0 # Solution found
successors = generate_successors(state, constraints)
if not successors:
return None, math.inf # No feasible moves
for s in successors:
s.h = max(s.h, state.h) # Ensure non-decreasing f-values
heapq.heapify(successors) # Priority queue (best move first)
while successors:
best = heapq.heappop(successors) # Best move
if best.h > f_limit:
return None, best.h # Prune if exceeding limit
# Second-best move (alternative path)
alternative = successors[0].h if successors else math.inf
result, best.h = rbfs(best, goal_state, constraints, min(f_limit, alternative))
if result:
return result, best.h # Solution found
heapq.heappush(successors, best) # Restore if needed
return None, math.inf
def solve_towers_of_hanoi(start_state, goal_state, constraints):
"""
Wrapper function to solve Towers of Hanoi using RBFS.
"""
initial_h = heuristic(start_state, constraints['goal'])
result, _ = rbfs(start_state, goal_state, constraints, initial_h)
# Reconstruct the path if a solution was found
if result:
path = []
while result:
path.append(result.pegs)
result = result.parent
return path[::-1] # Return the path from start to goal
return None
# Example usage:
initial_pegs = [[3, 2, 1], [], []] # Start state: All disks on first peg
goal_pegs = [[], [], [3, 2, 1]] # Goal state: All disks on the third peg
# Constraints (e.g., disk 2 must stay on peg 1 for some time)
constraints = {
2: 1, # Disk 2 must stay on peg 1
'goal': goal_pegs # The final goal state
}
start_state = State(initial_pegs)
solution_path = solve_towers_of_hanoi(start_state, goal_pegs, constraints)
if solution_path:
print("\nSolution Found:")
for step, state in enumerate(solution_path):
print(f"Step {step}:", state)
else:
print("No solution found.")
Out put:
Solution Found:
Step 0: [[3, 2, 1], [], []]
Step 1: [[3, 2], [], [1]]
Step 2: [[3], [2], [1]]
Step 3: [[3], [2, 1], []]
Step 4: [[], [2, 1], [3]]
Step 5: [[], [2], [3, 1]]
Step 6: [[], [], [3, 2, 1]]
Constraint Satisfaction Problems (CSP)
class TowersOfHanoiCSP:
def __init__(self, num_disks, constraints):
self.num_disks = num_disks
self.pegs = {i: [] for i in range(3)} # Pegs as dictionary {0: [], 1: [], 2: []}
self.constraints = constraints # Constraints dict {disk: peg}
self.solution = []
for disk in range(num_disks, 0, -1):
self.pegs[0].append(disk) # Start with all disks on peg 0
def is_valid_move(self, from_peg, to_peg):
""" Check if a move is valid under CSP constraints """
if not self.pegs[from_peg]: # No disk to move
return False
disk = self.pegs[from_peg][-1]
# Constraint check: Disk must stay on a particular peg
if disk in self.constraints and self.constraints[disk] != to_peg:
return False
# Standard Hanoi rules: A larger disk cannot be placed on a smaller one
if self.pegs[to_peg] and self.pegs[to_peg][-1] < disk:
return False
return True
def move_disk(self, from_peg, to_peg):
""" Move the top disk from one peg to another """
disk = self.pegs[from_peg].pop()
self.pegs[to_peg].append(disk)
self.solution.append((disk, from_peg, to_peg)) # Log the move
def backtrack(self, num_moves=0):
""" Recursive backtracking search with forward checking """
if self.pegs[2] == list(range(self.num_disks, 0, -1)): # Goal state check
return True
for from_peg in range(3):
if self.pegs[from_peg]: # If peg is not empty
for to_peg in range(3):
if from_peg != to_peg and self.is_valid_move(from_peg, to_peg):
self.move_disk(from_peg, to_peg)
if self.backtrack(num_moves + 1): # Recursive call
return True # Solution found
# Backtrack (undo move)
self.move_disk(to_peg, from_peg)
return False # No valid moves found
def solve(self):
""" Solve the CSP-based Towers of Hanoi problem """
if self.backtrack():
return self.solution
else:
return None # No solution found
# Example Usage:
num_disks = 3
constraints = {2: 1} # Example constraint: Disk 2 must stay on Peg 1 temporarily
hanoi_csp = TowersOfHanoiCSP(num_disks, constraints)
solution = hanoi_csp.solve()
if solution:
print("\nSolution Found:")
for move in solution:
print(f"Move Disk {move[0]} from Peg {move[1]} to Peg {move[2]}")
else:
print("No solution found.")
Output
Move Disk 1 from Peg 0 to Peg 2
Move Disk 2 from Peg 0 to Peg 1
Move Disk 1 from Peg 2 to Peg 1
Move Disk 3 from Peg 0 to Peg 2
Move Disk 1 from Peg 1 to Peg 0
Move Disk 2 from Peg 1 to Peg 2
Move Disk 1 from Peg 0 to Peg 2