├── README.md
├── driver.py
├── images
├── 8Puzzle.JPG
├── Astar.gif
├── BFS.gif
└── DFS.gif
└── output.txt
/README.md:
--------------------------------------------------------------------------------
1 | # SEARCH ALGORITHM FOR 8 PUZZLE GAME
2 | By: Guillermo Andres De Mendoza Corrales , memoodm@gmail.com, Colombia, Bogota D.C.
3 | Language: Python 3.6
4 | Description: Some search solutions for 8 puzzle game, implementing these algorithms:
5 | - Breadth-First Search
6 | - Depth-First Search
7 | - A*
8 |
9 | ## 1. 8 Puzzle game
10 |
11 | 
12 |
13 | The 8-puzzle (also called Gem Puzzle, Boss Puzzle, Game of Fifteen, Mystic Square and many others) is a 3x3 sliding puzzle that consists of a frame of eight numbered square tiles in random order with one tile missing
14 |
15 | ## 2. Conceptual background algorrithm
16 |
17 | ### Breadth-First Search
18 |
19 | 
20 |
21 | Is an algorithm for traversing or searching tree or graph data structures. It starts at the tree root (or some arbitrary node of a graph, sometimes referred to as a 'search key), and explores all of the neighbor nodes at the present depth prior to moving on to the nodes at the next depth level.
22 |
23 | It uses the opposite strategy as depth-first search, which instead explores the highest-depth nodes first before being forced to backtrack and expand shallower nodes.
24 |
25 | BFS and its application in finding connected components of graphs were invented in 1945 by Konrad Zuse and Michael Burke, in their (rejected) Ph.D. thesis on the Plankalkül programming language, but this was not published until 1972. It was reinvented in 1959 by Edward F. Moore, who used it to find the shortest path out of a maze, and later developed by C. Y. Lee into a wire routing algorithm (published 1961).
26 |
27 | Link: wikipedia -> https://en.wikipedia.org/wiki/Breadth-first_search
28 |
29 | ### Depth-First Search
30 |
31 | 
32 |
33 | Is an algorithm for traversing or searching tree or graph data structures. The algorithm starts at the root node (selecting some arbitrary node as the root node in the case of a graph) and explores as far as possible along each branch before backtracking.
34 |
35 | A version of depth-first search was investigated in the 19th century by French mathematician Charles Pierre Trémaux as a strategy for solving mazes
36 |
37 | Link: wikipedia -> https://en.wikipedia.org/wiki/Depth-first_search
38 |
39 | ### A*
40 |
41 | 
42 |
43 | A* (pronounced as "A star") is a computer algorithm that is widely used in pathfinding and graph traversal, which is the process of plotting an efficiently directed path between multiple points, called "nodes". It enjoys widespread use due to its performance and accuracy. However, in practical travel-routing systems, it is generally outperformed by algorithms which can pre-process the graph to attain better performance, although other work has found A* to be superior to other approaches.
44 |
45 | Peter Hart, Nils Nilsson and Bertram Raphael of Stanford Research Institute (now SRI International) first published the algorithm in 1968.[3] It is an extension of Edsger Dijkstra's 1959 algorithm. A* achieves better performance by using heuristics to guide its search.
46 |
47 | Link: wikipedia -> https://en.wikipedia.org/wiki/A*_search_algorithm
48 |
49 | ## 3. Heuristic selected
50 | In the A* solution the manhattan distance is implemented as a function of estimating the distance to goal, by assigning negative score in each number for the total number of squares away from its goal possition
51 |
52 | ```sh
53 | values_0 = [0,1,2,1,2,3,2,3,4]
54 | values_1 = [1,0,1,2,1,2,3,2,3]
55 | values_2 = [2,1,0,3,2,1,4,3,2]
56 | values_3 = [1,2,3,0,1,2,1,2,3]
57 | values_4 = [2,1,2,1,0,1,2,1,2]
58 | values_5 = [3,2,1,2,1,0,3,2,1]
59 | values_6 = [2,3,4,1,2,3,0,1,2]
60 | values_7 = [3,2,3,2,1,2,1,0,1]
61 | values_8 = [4,3,2,3,2,1,2,1,0]
62 |
63 | def Heuristic(node):
64 |
65 | global values_0,values_1,values_2,values_3,values_4,values_5,values_6,values_7,values_8
66 | v0=values_0[node.index("0")]
67 | v1=values_1[node.index("1")]
68 | v2=values_2[node.index("2")]
69 | v3=values_3[node.index("3")]
70 | v4=values_4[node.index("4")]
71 | v5=values_5[node.index("5")]
72 | v6=values_6[node.index("6")]
73 | v7=values_7[node.index("7")]
74 | v8=values_8[node.index("8")]
75 | valorTotal = v0+v1+v2+v3+v4+v5+v6+v7+v8
76 | return valorTotal
77 | ```
78 |
79 | ## 4. Project input
80 |
81 | Execute python via comand prompt:
82 | ```sh
83 | python driver.py 'method' 'board state'
84 | ```
85 | Where method is the algorithm selected for finding a solution path, and board state is the initial board of the game to start finding a solvable path
86 |
87 | Example bfs:
88 | ```sh
89 | python driver.py bfs 8,6,4,2,1,3,5,7,0
90 | ```
91 |
92 | Example dfs:
93 | ```sh
94 | python driver.py dfs 1,2,5,3,4,8,6,7,0
95 | ```
96 |
97 | Example ast:
98 | ```sh
99 | python driver.py ast 1,2,5,3,4,8,6,7,0
100 | ```
101 |
102 | ## 5. Project output
103 |
104 | There are two different outputs for the project:
105 |
106 | ### 5.1 Print line in command prompt
107 | Show the variables of the path that best solve the starting board, Example:
108 | ```sh
109 | C:\Users\Admin\Desktop\AI\Search>python driver.py bfs 8,6,4,2,1,3,5,7,0
110 | path: ['Left', 'Up', 'Up', 'Left', 'Down', 'Right', 'Down', 'Left', 'Up', 'Right', 'Right', 'Up', 'Left', 'Left', 'Down', 'Right', 'Right', 'Up', 'Left', 'Down', 'Down', 'Right', 'Up', 'Left', 'Up', 'Left']
111 | cost: 26
112 | nodes expanded: 166786
113 | search_depth: 26
114 | MaxSearchDeep: 27
115 | running_time: 3.97613275
116 | ```
117 |
118 |
119 | ### 5.2 Output plain text file
120 | Generate a plain text file that ilustrate the variables of the path that best solve the starting board, Example:
121 | ```sh
122 | path_to_goal: ['Left', 'Up', 'Up', 'Left', 'Down', 'Right', 'Down', 'Left', 'Up', 'Right', 'Right', 'Up', 'Left', 'Left', 'Down', 'Right', 'Right', 'Up', 'Left', 'Down', 'Down', 'Right', 'Up', 'Left', 'Up', 'Left']
123 | cost_of_path: 26
124 | nodes_expanded: 166786
125 | search_depth: 26
126 | max_search_depth: 27
127 | running_time: 3.97613275
128 | ```
--------------------------------------------------------------------------------
/driver.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import time
3 | import timeit
4 | from collections import deque
5 |
6 |
7 | #Information *****************************************************
8 | class PuzzleState:
9 | def __init__(self, state, parent, move, depth, cost, key):
10 | self.state = state
11 | self.parent = parent
12 | self.move = move
13 | self.depth = depth
14 | self.cost = cost
15 | self.key = key
16 | if self.state:
17 | self.map = ''.join(str(e) for e in self.state)
18 | def __eq__(self, other):
19 | return self.map == other.map
20 | def __lt__(self, other):
21 | return self.map < other.map
22 | def __str__(self):
23 | return str(self.map)
24 |
25 | #Global variables***********************************************
26 | GoalState = [0, 1, 2, 3, 4, 5, 6, 7, 8]
27 | GoalNode = None # at finding solution
28 | NodesExpanded = 0 #total nodes visited
29 | MaxSearchDeep = 0 #max deep
30 | MaxFrontier = 0 #max frontier
31 |
32 |
33 | #BFS**************************************************************
34 | def bfs(startState):
35 |
36 | global MaxFrontier, GoalNode, MaxSearchDeep
37 |
38 | boardVisited= set()
39 | Queue = deque([PuzzleState(startState, None, None, 0, 0, 0)])
40 |
41 | while Queue:
42 | node = Queue.popleft()
43 | boardVisited.add(node.map)
44 | if node.state == GoalState:
45 | GoalNode = node
46 | return Queue
47 | posiblePaths = subNodes(node)
48 | for path in posiblePaths:
49 | if path.map not in boardVisited:
50 | Queue.append(path)
51 | boardVisited.add(path.map)
52 | if path.depth > MaxSearchDeep:
53 | MaxSearchDeep = MaxSearchDeep + 1
54 | if len(Queue) > MaxFrontier:
55 | QueueSize = len(Queue)
56 | MaxFrontier = QueueSize
57 |
58 | #DFS**************************************************************
59 | def dfs(startState):
60 |
61 | global MaxFrontier, GoalNode, MaxSearchDeep
62 |
63 | boardVisited = set()
64 | stack = list([PuzzleState(startState, None, None, 0, 0, 0)])
65 | while stack:
66 | node = stack.pop()
67 | boardVisited.add(node.map)
68 | if node.state == GoalState:
69 | GoalNode = node
70 | return stack
71 | #inverse the order of next paths for execution porpuses
72 | posiblePaths = reversed(subNodes(node))
73 | for path in posiblePaths:
74 | if path.map not in boardVisited:
75 | stack.append(path)
76 | boardVisited.add(path.map)
77 | if path.depth > MaxSearchDeep:
78 | MaxSearchDeep = 1 + MaxSearchDeep
79 | if len(stack) > MaxFrontier:
80 | MaxFrontier = len(stack)
81 |
82 |
83 | #AST**************************************************************
84 | def ast(startState):
85 |
86 | global MaxFrontier, MaxSearchDeep, GoalNode
87 |
88 | #transform initial state to calculate Heuritic
89 | node1 = ""
90 | for poss in startState:
91 | node1 = node1 + str(poss)
92 |
93 | #calculate Heuristic and set initial node
94 | key = Heuristic(node1)
95 | boardVisited= set()
96 | Queue = []
97 | Queue.append(PuzzleState(startState, None, None, 0, 0, key))
98 | boardVisited.add(node1)
99 |
100 | while Queue:
101 | Queue.sort(key=lambda o: o.key)
102 | node = Queue.pop(0)
103 | if node.state == GoalState:
104 | GoalNode = node
105 | return Queue
106 | posiblePaths = subNodes(node)
107 | for path in posiblePaths:
108 | thisPath = path.map[:]
109 | if thisPath not in boardVisited:
110 | key = Heuristic(path.map)
111 | path.key = key + path.depth
112 | Queue.append(path)
113 | boardVisited.add(path.map[:])
114 | if path.depth > MaxSearchDeep:
115 | MaxSearchDeep = 1 + MaxSearchDeep
116 |
117 |
118 | #Heuristic: distance to root numbers
119 | values_0 = [0,1,2,1,2,3,2,3,4]
120 | values_1 = [1,0,1,2,1,2,3,2,3]
121 | values_2 = [2,1,0,3,2,1,4,3,2]
122 | values_3 = [1,2,3,0,1,2,1,2,3]
123 | values_4 = [2,1,2,1,0,1,2,1,2]
124 | values_5 = [3,2,1,2,1,0,3,2,1]
125 | values_6 = [2,3,4,1,2,3,0,1,2]
126 | values_7 = [3,2,3,2,1,2,1,0,1]
127 | values_8 = [4,3,2,3,2,1,2,1,0]
128 |
129 | def Heuristic(node):
130 |
131 | global values_0,values_1,values_2,values_3,values_4,values_5,values_6,values_7,values_8
132 | v0=values_0[node.index("0")]
133 | v1=values_1[node.index("1")]
134 | v2=values_2[node.index("2")]
135 | v3=values_3[node.index("3")]
136 | v4=values_4[node.index("4")]
137 | v5=values_5[node.index("5")]
138 | v6=values_6[node.index("6")]
139 | v7=values_7[node.index("7")]
140 | v8=values_8[node.index("8")]
141 | valorTotal = v0+v1+v2+v3+v4+v5+v6+v7+v8
142 | return valorTotal
143 |
144 |
145 |
146 |
147 | #Obtain Sub Nodes********************************************************
148 | def subNodes(node):
149 |
150 | global NodesExpanded
151 | NodesExpanded = NodesExpanded+1
152 |
153 | nextPaths = []
154 | nextPaths.append(PuzzleState(move(node.state, 1), node, 1, node.depth + 1, node.cost + 1, 0))
155 | nextPaths.append(PuzzleState(move(node.state, 2), node, 2, node.depth + 1, node.cost + 1, 0))
156 | nextPaths.append(PuzzleState(move(node.state, 3), node, 3, node.depth + 1, node.cost + 1, 0))
157 | nextPaths.append(PuzzleState(move(node.state, 4), node, 4, node.depth + 1, node.cost + 1, 0))
158 | nodes=[]
159 | for procPaths in nextPaths:
160 | if(procPaths.state!=None):
161 | nodes.append(procPaths)
162 | return nodes
163 |
164 | #Next step**************************************************************
165 | def move(state, direction):
166 | #generate a copy
167 | newState = state[:]
168 |
169 | #obtain poss of 0
170 | index = newState.index(0)
171 |
172 | if(index==0):
173 | if(direction==1):
174 | return None
175 | if(direction==2):
176 | temp=newState[0]
177 | newState[0]=newState[3]
178 | newState[3]=temp
179 | if(direction==3):
180 | return None
181 | if(direction==4):
182 | temp=newState[0]
183 | newState[0]=newState[1]
184 | newState[1]=temp
185 | return newState
186 | if(index==1):
187 | if(direction==1):
188 | return None
189 | if(direction==2):
190 | temp=newState[1]
191 | newState[1]=newState[4]
192 | newState[4]=temp
193 | if(direction==3):
194 | temp=newState[1]
195 | newState[1]=newState[0]
196 | newState[0]=temp
197 | if(direction==4):
198 | temp=newState[1]
199 | newState[1]=newState[2]
200 | newState[2]=temp
201 | return newState
202 | if(index==2):
203 | if(direction==1):
204 | return None
205 | if(direction==2):
206 | temp=newState[2]
207 | newState[2]=newState[5]
208 | newState[5]=temp
209 | if(direction==3):
210 | temp=newState[2]
211 | newState[2]=newState[1]
212 | newState[1]=temp
213 | if(direction==4):
214 | return None
215 | return newState
216 | if(index==3):
217 | if(direction==1):
218 | temp=newState[3]
219 | newState[3]=newState[0]
220 | newState[0]=temp
221 | if(direction==2):
222 | temp=newState[3]
223 | newState[3]=newState[6]
224 | newState[6]=temp
225 | if(direction==3):
226 | return None
227 | if(direction==4):
228 | temp=newState[3]
229 | newState[3]=newState[4]
230 | newState[4]=temp
231 | return newState
232 | if(index==4):
233 | if(direction==1):
234 | temp=newState[4]
235 | newState[4]=newState[1]
236 | newState[1]=temp
237 | if(direction==2):
238 | temp=newState[4]
239 | newState[4]=newState[7]
240 | newState[7]=temp
241 | if(direction==3):
242 | temp=newState[4]
243 | newState[4]=newState[3]
244 | newState[3]=temp
245 | if(direction==4):
246 | temp=newState[4]
247 | newState[4]=newState[5]
248 | newState[5]=temp
249 | return newState
250 | if(index==5):
251 | if(direction==1):
252 | temp=newState[5]
253 | newState[5]=newState[2]
254 | newState[2]=temp
255 | if(direction==2):
256 | temp=newState[5]
257 | newState[5]=newState[8]
258 | newState[8]=temp
259 | if(direction==3):
260 | temp=newState[5]
261 | newState[5]=newState[4]
262 | newState[4]=temp
263 | if(direction==4):
264 | return None
265 | return newState
266 | if(index==6):
267 | if(direction==1):
268 | temp=newState[6]
269 | newState[6]=newState[3]
270 | newState[3]=temp
271 | if(direction==2):
272 | return None
273 | if(direction==3):
274 | return None
275 | if(direction==4):
276 | temp=newState[6]
277 | newState[6]=newState[7]
278 | newState[7]=temp
279 | return newState
280 | if(index==7):
281 | if(direction==1):
282 | temp=newState[7]
283 | newState[7]=newState[4]
284 | newState[4]=temp
285 | if(direction==2):
286 | return None
287 | if(direction==3):
288 | temp=newState[7]
289 | newState[7]=newState[6]
290 | newState[6]=temp
291 | if(direction==4):
292 | temp=newState[7]
293 | newState[7]=newState[8]
294 | newState[8]=temp
295 | return newState
296 | if(index==8):
297 | if(direction==1):
298 | temp=newState[8]
299 | newState[8]=newState[5]
300 | newState[5]=temp
301 | if(direction==2):
302 | return None
303 | if(direction==3):
304 | temp=newState[8]
305 | newState[8]=newState[7]
306 | newState[7]=temp
307 | if(direction==4):
308 | return None
309 | return newState
310 |
311 | #MAIN**************************************************************
312 | def main():
313 |
314 | global GoalNode
315 |
316 | #a = [1,8,2,3,4,5,6,7,0]
317 | #point=Heuristic(a)
318 | #print(point)
319 | #return
320 |
321 | #info = "6,1,8,4,0,2,7,3,5" #20
322 | #info = "8,6,4,2,1,3,5,7,0" #26
323 |
324 | #Obtain information from calling parameters
325 | parser = argparse.ArgumentParser()
326 | parser.add_argument('method')
327 | parser.add_argument('initialBoard')
328 | args = parser.parse_args()
329 | data = args.initialBoard.split(",")
330 |
331 | #Build initial board state
332 | InitialState = []
333 | InitialState.append(int(data[0]))
334 | InitialState.append(int(data[1]))
335 | InitialState.append(int(data[2]))
336 | InitialState.append(int(data[3]))
337 | InitialState.append(int(data[4]))
338 | InitialState.append(int(data[5]))
339 | InitialState.append(int(data[6]))
340 | InitialState.append(int(data[7]))
341 | InitialState.append(int(data[8]))
342 |
343 | #Start operation
344 | start = timeit.default_timer()
345 |
346 | function = args.method
347 | if(function=="bfs"):
348 | bfs(InitialState)
349 | if(function=="dfs"):
350 | dfs(InitialState)
351 | if(function=="ast"):
352 | ast(InitialState)
353 |
354 | stop = timeit.default_timer()
355 | time = stop-start
356 |
357 | #Save total path result
358 | deep=GoalNode.depth
359 | moves = []
360 | while InitialState != GoalNode.state:
361 | if GoalNode.move == 1:
362 | path = 'Up'
363 | if GoalNode.move == 2:
364 | path = 'Down'
365 | if GoalNode.move == 3:
366 | path = 'Left'
367 | if GoalNode.move == 4:
368 | path = 'Right'
369 | moves.insert(0, path)
370 | GoalNode = GoalNode.parent
371 |
372 | #'''
373 | #Print results
374 | print("path: ",moves)
375 | print("cost: ",len(moves))
376 | print("nodes expanded: ",str(NodesExpanded))
377 | print("search_depth: ",str(deep))
378 | print("MaxSearchDeep: ",str(MaxSearchDeep))
379 | print("running_time: ",format(time, '.8f'))
380 | #'''
381 |
382 | #Generate output document for grade system
383 | #'''
384 | file = open('output.txt', 'w')
385 | file.write("path_to_goal: " + str(moves) + "\n")
386 | file.write("cost_of_path: " + str(len(moves)) + "\n")
387 | file.write("nodes_expanded: " + str(NodesExpanded) + "\n")
388 | file.write("search_depth: " + str(deep) + "\n")
389 | file.write("max_search_depth: " + str(MaxSearchDeep) + "\n")
390 | file.write("running_time: " + format(time, '.8f') + "\n")
391 | file.close()
392 | #'''
393 |
394 | if __name__ == '__main__':
395 | main()
396 |
--------------------------------------------------------------------------------
/images/8Puzzle.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/memoodm/AI-8Puzzle-SearchAlgorithm/749b8481b57f82fc6dbbf598920e1a534825eee5/images/8Puzzle.JPG
--------------------------------------------------------------------------------
/images/Astar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/memoodm/AI-8Puzzle-SearchAlgorithm/749b8481b57f82fc6dbbf598920e1a534825eee5/images/Astar.gif
--------------------------------------------------------------------------------
/images/BFS.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/memoodm/AI-8Puzzle-SearchAlgorithm/749b8481b57f82fc6dbbf598920e1a534825eee5/images/BFS.gif
--------------------------------------------------------------------------------
/images/DFS.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/memoodm/AI-8Puzzle-SearchAlgorithm/749b8481b57f82fc6dbbf598920e1a534825eee5/images/DFS.gif
--------------------------------------------------------------------------------
/output.txt:
--------------------------------------------------------------------------------
1 | path_to_goal: ['Left', 'Up', 'Up', 'Left', 'Down', 'Right', 'Down', 'Left', 'Up', 'Right', 'Right', 'Up', 'Left', 'Left', 'Down', 'Right', 'Right', 'Up', 'Left', 'Down', 'Down', 'Right', 'Up', 'Left', 'Up', 'Left']
2 | cost_of_path: 26
3 | nodes_expanded: 166786
4 | search_depth: 26
5 | max_search_depth: 27
6 | running_time: 3.97492307
7 |
--------------------------------------------------------------------------------