Skip to content

Commit cb0a567

Browse files
committed
Implement 8 puzzle using simulated annealing
1 parent 7db75e3 commit cb0a567

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
2+
"""
3+
8 Puzzle solver using Simulated Annealing.
4+
"""
5+
6+
# Import required libraries.
7+
import random
8+
import math
9+
import copy
10+
11+
# Setting the random seed for same output.
12+
random.seed(42)
13+
14+
class Solver:
15+
16+
def __init__(self, initial_state, goal_state):
17+
"""
18+
Initialize the object.
19+
"""
20+
21+
# Setup the class with the initial and goal states.
22+
self.initial_state = initial_state
23+
self.goal_state = goal_state
24+
25+
def get_blank_pos(self, state):
26+
"""
27+
Get the position of the blank in the puzzle.
28+
"""
29+
30+
for i in range(3):
31+
for j in range(3):
32+
33+
if state[i][j] == 0:
34+
return [i, j]
35+
36+
37+
def get_neighbors(self, state):
38+
"""
39+
Get all the valid moves.
40+
"""
41+
42+
# Get the blank position.
43+
blank_pos = self.get_blank_pos(state)
44+
45+
# Top, Left, Right, and Bottom moves.
46+
positions = [
47+
[1, 0],
48+
[0, 1],
49+
[-1, 0],
50+
[0, -1]
51+
]
52+
53+
# Initialize the neighbors.
54+
neighbors = []
55+
56+
for pos in positions:
57+
58+
# Get the new x move.
59+
x = pos[0] + blank_pos[0]
60+
61+
# Get the new y move.
62+
y = pos[1] + blank_pos[1]
63+
64+
# If x and y are valid moves then add it to the neighbors list.
65+
if (x >= 0 and x < 3) and \
66+
(y >= 0 and y < 3):
67+
68+
neighbors.append([x, y])
69+
70+
return neighbors
71+
72+
def print_state(self, state):
73+
"""
74+
Print the state of the solution.
75+
"""
76+
77+
78+
output = ''
79+
80+
output += '.-------.\n'
81+
82+
for i in range(3):
83+
84+
output += '| '
85+
86+
for j in range(3):
87+
88+
output += str(state[i][j]) + ' '
89+
90+
output += '|\n'
91+
92+
output += '.-------.'
93+
94+
print(output)
95+
96+
# for i in range(3):
97+
98+
# for j in range(3):
99+
100+
# print(state[i][j], end=' ')
101+
102+
# print('')
103+
104+
def compute_cost(self, state):
105+
"""
106+
Computes the hamming distance i.e; number of cells that are out of place
107+
from the goal state
108+
"""
109+
110+
cost = 0
111+
112+
for i in range(3):
113+
114+
for j in range(3):
115+
116+
if state[i][j] != self.goal_state[i][j]:
117+
118+
# Update the cost.
119+
cost += 1
120+
121+
return cost
122+
123+
def simulated_annealing(self, state):
124+
"""
125+
Use the Simulated Annealing to solve the 8-puzzle.
126+
"""
127+
128+
# Set the initial temperature.
129+
initial_temp = 90
130+
131+
# Set the final temperature.
132+
final_temp = 0.01
133+
134+
# Set the alpha value.
135+
alpha = 0.0001
136+
137+
# Set the current temperature, state, solution, and best solution.
138+
current_temp = initial_temp
139+
current_state = state
140+
solution = state
141+
solution_best = state
142+
143+
while current_temp > final_temp:
144+
145+
# Get the blank position.
146+
blank_pos = self.get_blank_pos(current_state)
147+
148+
# Pick the random move.
149+
neighbor = random.choice(self.get_neighbors(current_state))
150+
151+
# Deep copy the current state.
152+
new_state = copy.deepcopy(current_state)
153+
154+
# Find the blanks in the current state.
155+
bp1 = blank_pos[0]
156+
bp2 = blank_pos[1]
157+
158+
# Get the x and y of the new state.
159+
np1 = neighbor[0]
160+
np2 = neighbor[1]
161+
162+
# Swap the blank with the new move.
163+
new_state[bp1][bp2], new_state[np1][np2] = new_state[np1][np2], new_state[bp1][bp2]
164+
165+
# Find the cost difference with the old state and the new state.
166+
cost_diff = self.compute_cost(new_state) - self.compute_cost(current_state)
167+
168+
if self.compute_cost(solution_best) > self.compute_cost(new_state):
169+
170+
# If the new state is better the global best solution then update it.
171+
solution_best = new_state
172+
173+
# If the new cost difference is greater than or eu the current cost.
174+
#
175+
# Compute the e^(-|old_cost - new_cost| / T) and if it's greater than
176+
# or equal to the the random probability then update the
177+
# current state.
178+
if random.uniform(0, 1) <= math.exp((-1 * cost_diff) / current_temp):
179+
current_state = new_state
180+
181+
# Update the temperature.
182+
current_temp -= alpha
183+
184+
# return the best solution.
185+
return solution_best
186+
187+
if __name__ == '__main__':
188+
189+
# Initial state.
190+
# initial = [
191+
# [5, 7, 3],
192+
# [1, 6, 4],
193+
# [8, 0, 2]
194+
# ]
195+
196+
initial = [
197+
[1, 3, 4],
198+
[8, 0, 5],
199+
[7, 2, 6]
200+
]
201+
202+
# Goal state.
203+
goal = [
204+
[1, 2, 3],
205+
[8, 0, 4],
206+
[7, 6, 5]
207+
]
208+
209+
# Initialze the solver.
210+
solver = Solver(initial, goal)
211+
212+
print('The initial state:')
213+
solver.print_state(initial)
214+
215+
print('Using simulated annealing to solve the 8-puzzle.')
216+
217+
# Get the solution.
218+
sol = solver.simulated_annealing(initial)
219+
220+
print('8-puzzle solved.')
221+
222+
print('Final state:')
223+
224+
# Perform the simulated annealing and print the best solution.
225+
solver.print_state(sol)
226+
227+
# Output
228+
# ---
229+
#
230+
# The initial state:
231+
# .-------.
232+
# | 1 3 4 |
233+
# | 8 0 5 |
234+
# | 7 2 6 |
235+
# .-------.
236+
# Using simulated annealing to solve the 8-puzzle.
237+
# 8-puzzle solved.
238+
# Final state:
239+
# .-------.
240+
# | 1 2 3 |
241+
# | 8 0 4 |
242+
# | 7 6 5 |
243+
# .-------.

0 commit comments

Comments
 (0)