├── 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 | ![8puzzle](https://github.com/memoodm/AI-Search-8Puzzle/blob/master/images/8Puzzle.JPG) 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 | ![BFS](https://github.com/memoodm/AI-Search-8Puzzle/blob/master/images/BFS.gif) 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 | ![DFS](https://github.com/memoodm/AI-Search-8Puzzle/blob/master/images/DFS.gif) 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 | ![Astart](https://github.com/memoodm/AI-Search-8Puzzle/blob/master/images/Astar.gif) 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 | --------------------------------------------------------------------------------