├── README.md ├── LICENSE └── 8-puzzle.py /README.md: -------------------------------------------------------------------------------- 1 | # 8-puzzle-solution 2 | 3 | Solution to the [8 puzzle prooblem](https://www.cs.princeton.edu/courses/archive/spr10/cos226/assignments/8puzzle.html) using BFS and DFS in python. 4 | The solution can easily be used be converted for solving a 15-puzzle problem. 5 | 6 | ![image](https://user-images.githubusercontent.com/54510448/115289408-255a0e00-a170-11eb-91d3-fcfe74045420.png) 7 | ![image](https://user-images.githubusercontent.com/54510448/115289465-373bb100-a170-11eb-8d35-5a65e66cab95.png) 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Saurav Shrivastav 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /8-puzzle.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import numpy as np 3 | 4 | 5 | class Node: 6 | def __init__(self, state, parent, action): 7 | self.state = state 8 | self.parent = parent 9 | self.action = action 10 | 11 | 12 | class StackFrontier: 13 | def __init__(self): 14 | self.frontier = [] 15 | 16 | def add(self, node): 17 | self.frontier.append(node) 18 | 19 | def contains_state(self, state): 20 | return any((node.state[0] == state[0]).all() for node in self.frontier) 21 | 22 | def empty(self): 23 | return len(self.frontier) == 0 24 | 25 | def remove(self): 26 | if self.empty(): 27 | raise Exception("Empty Frontier") 28 | else: 29 | node = self.frontier[-1] 30 | self.frontier = self.frontier[:-1] 31 | return node 32 | 33 | 34 | class QueueFrontier(StackFrontier): 35 | def remove(self): 36 | if self.empty(): 37 | raise Exception("Empty Frontier") 38 | else: 39 | node = self.frontier[0] 40 | self.frontier = self.frontier[1:] 41 | return node 42 | 43 | 44 | class Puzzle: 45 | def __init__(self, start, startIndex, goal, goalIndex): 46 | self.start = [start, startIndex] 47 | self.goal = [goal, goalIndex] 48 | self.solution = None 49 | 50 | def neighbors(self, state): 51 | mat, (row, col) = state 52 | results = [] 53 | 54 | if row > 0: 55 | mat1 = np.copy(mat) 56 | mat1[row][col] = mat1[row - 1][col] 57 | mat1[row - 1][col] = 0 58 | results.append(('up', [mat1, (row - 1, col)])) 59 | if col > 0: 60 | mat1 = np.copy(mat) 61 | mat1[row][col] = mat1[row][col - 1] 62 | mat1[row][col - 1] = 0 63 | results.append(('left', [mat1, (row, col - 1)])) 64 | if row < 2: 65 | mat1 = np.copy(mat) 66 | mat1[row][col] = mat1[row + 1][col] 67 | mat1[row + 1][col] = 0 68 | results.append(('down', [mat1, (row + 1, col)])) 69 | if col < 2: 70 | mat1 = np.copy(mat) 71 | mat1[row][col] = mat1[row][col + 1] 72 | mat1[row][col + 1] = 0 73 | results.append(('right', [mat1, (row, col + 1)])) 74 | 75 | return results 76 | 77 | def print(self): 78 | solution = self.solution if self.solution is not None else None 79 | print("Start State:\n", self.start[0], "\n") 80 | print("Goal State:\n", self.goal[0], "\n") 81 | print("\nStates Explored: ", self.num_explored, "\n") 82 | print("Solution:\n ") 83 | for action, cell in zip(solution[0], solution[1]): 84 | print("action: ", action, "\n", cell[0], "\n") 85 | print("Goal Reached!!") 86 | 87 | def does_not_contain_state(self, state): 88 | for st in self.explored: 89 | if (st[0] == state[0]).all(): 90 | return False 91 | return True 92 | 93 | def solve(self): 94 | self.num_explored = 0 95 | 96 | start = Node(state=self.start, parent=None, action=None) 97 | frontier = QueueFrontier() 98 | frontier.add(start) 99 | 100 | self.explored = [] 101 | 102 | while True: 103 | if frontier.empty(): 104 | raise Exception("No solution") 105 | 106 | node = frontier.remove() 107 | self.num_explored += 1 108 | 109 | if (node.state[0] == self.goal[0]).all(): 110 | actions = [] 111 | cells = [] 112 | while node.parent is not None: 113 | actions.append(node.action) 114 | cells.append(node.state) 115 | node = node.parent 116 | actions.reverse() 117 | cells.reverse() 118 | self.solution = (actions, cells) 119 | return 120 | 121 | self.explored.append(node.state) 122 | 123 | for action, state in self.neighbors(node.state): 124 | if not frontier.contains_state(state) and self.does_not_contain_state(state): 125 | child = Node(state=state, parent=node, action=action) 126 | frontier.add(child) 127 | 128 | 129 | start = np.array([[1, 2, 3], [8, 0, 4], [7, 6, 5]]) 130 | goal = np.array([[2, 8, 1], [0, 4, 3], [7, 6, 5]]) 131 | 132 | 133 | startIndex = (1, 1) 134 | goalIndex = (1, 0) 135 | 136 | 137 | p = Puzzle(start, startIndex, goal, goalIndex) 138 | p.solve() 139 | p.print() 140 | --------------------------------------------------------------------------------