|
1 | | -import heapq # For priority queue |
2 | | -from colorama import Style # For coloring the terminal |
| 1 | +import heapq # For priority queue |
| 2 | +from colorama import Style # For coloring the terminal |
| 3 | + |
3 | 4 |
|
4 | 5 | # Macros: |
5 | | -class BackgroundColors: # Colors for the terminal |
6 | | - CYAN = "\033[96m" # Cyan |
7 | | - GREEN = "\033[92m" # Green |
8 | | - YELLOW = "\033[93m" # Yellow |
9 | | - RED = "\033[91m" # Red |
10 | | - BOLD = "\033[1m" # Bold |
11 | | - UNDERLINE = "\033[4m" # Underline |
12 | | - CLEAR_TERMINAL = "\033[H\033[J" # Clear the terminal |
| 6 | +class BackgroundColors: # Colors for the terminal |
| 7 | + CYAN = "\033[96m" # Cyan |
| 8 | + GREEN = "\033[92m" # Green |
| 9 | + YELLOW = "\033[93m" # Yellow |
| 10 | + RED = "\033[91m" # Red |
| 11 | + BOLD = "\033[1m" # Bold |
| 12 | + UNDERLINE = "\033[4m" # Underline |
| 13 | + CLEAR_TERMINAL = "\033[H\033[J" # Clear the terminal |
| 14 | + |
13 | 15 |
|
14 | 16 | # Constants: |
15 | | -GOAL_STATE = [[1, 2, 3], [4, 5, 6], [7, 8, 0]] # Goal state. 0 represents the empty tile |
16 | | -INITIAL_STATE = [[1, 0, 3], [4, 2, 5], [7, 8, 6]] # Initial state |
17 | | -MOVES = [(0, 1), (1, 0), (0, -1), (-1, 0)] # All of the possible moves |
18 | | -MOVE_NAMES = ["right", "down", "left", "up"] # Names of the moves |
| 17 | +GOAL_STATE = [[1, 2, 3], [4, 5, 6], [7, 8, 0]] # Goal state. 0 represents the empty tile |
| 18 | +INITIAL_STATE = [[1, 0, 3], [4, 2, 5], [7, 8, 6]] # Initial state |
| 19 | +MOVES = [(0, 1), (1, 0), (0, -1), (-1, 0)] # All of the possible moves |
| 20 | +MOVE_NAMES = ["right", "down", "left", "up"] # Names of the moves |
| 21 | + |
19 | 22 |
|
20 | 23 | # Define a class to represent the puzzle state |
21 | 24 | class PuzzleState: |
22 | | - # Initialize the class |
23 | | - def __init__(self, state, parent=None, move=""): |
24 | | - self.state = state # Current state of the puzzle |
25 | | - self.parent = parent # Parent state |
26 | | - self.move = move # Move that led to the current state |
27 | | - self.g = 0 # Cost from start node to current node |
28 | | - self.h = self.calculate_heuristic() # Heuristic (estimated cost to goal) |
29 | | - self.f = self.g + self.h # Total estimated cost |
30 | | - |
31 | | - # Define a function to calculate the heuristic |
32 | | - def calculate_heuristic(self): |
33 | | - h = 0 # Number of misplaced tiles |
34 | | - for i in range(3): # For each row |
35 | | - for j in range(3): # For each column |
36 | | - if self.state[i][j] != 0: # If the tile is not empty |
37 | | - goal_row, goal_col = divmod(self.state[i][j] - 1, 3) # Find the goal position of the tile |
38 | | - h += abs(i - goal_row) + abs(j - goal_col) # Add the Manhattan distance to the heuristic |
39 | | - return h # Return the heuristic |
40 | | - |
41 | | - # Define a function to compare two states |
42 | | - def __lt__(self, other): |
43 | | - return self.f < other.f # Compare the f values of the states |
| 25 | + # Initialize the class |
| 26 | + def __init__(self, state, parent=None, move=""): |
| 27 | + self.state = state # Current state of the puzzle |
| 28 | + self.parent = parent # Parent state |
| 29 | + self.move = move # Move that led to the current state |
| 30 | + self.g = 0 # Cost from start node to current node |
| 31 | + self.h = self.calculate_heuristic() # Heuristic (estimated cost to goal) |
| 32 | + self.f = self.g + self.h # Total estimated cost |
| 33 | + |
| 34 | + # Define a function to calculate the heuristic |
| 35 | + def calculate_heuristic(self): |
| 36 | + h = 0 # Number of misplaced tiles |
| 37 | + for i in range(3): # For each row |
| 38 | + for j in range(3): # For each column |
| 39 | + if self.state[i][j] != 0: # If the tile is not empty |
| 40 | + goal_row, goal_col = divmod(self.state[i][j] - 1, 3) # Find the goal position of the tile |
| 41 | + h += abs(i - goal_row) + abs(j - goal_col) # Add the Manhattan distance to the heuristic |
| 42 | + return h # Return the heuristic |
| 43 | + |
| 44 | + # Define a function to compare two states |
| 45 | + def __lt__(self, other): |
| 46 | + return self.f < other.f # Compare the f values of the states |
| 47 | + |
44 | 48 |
|
45 | 49 | # Define a function to find possible moves from a given state |
46 | 50 | # Input: state (2D list) |
47 | 51 | # Returns a list of possible moves |
48 | 52 | def find_possible_moves(state): |
49 | | - empty_row, empty_col = None, None |
50 | | - for i in range(3): # For each row |
51 | | - for j in range(3): # For each column |
52 | | - if state[i][j] == 0: # If the tile is empty |
53 | | - empty_row, empty_col = i, j # Store the row and column of the empty tile |
54 | | - break # Break out of the loop |
55 | | - |
56 | | - possible_moves = [] # List of possible moves |
57 | | - for move, move_name in zip(MOVES, MOVE_NAMES): # For each move |
58 | | - new_row, new_col = empty_row + move[0], empty_col + move[1] # Find the new row and column of the empty tile |
59 | | - if 0 <= new_row < 3 and 0 <= new_col < 3: # If the new row and column are valid |
60 | | - possible_moves.append(move_name) # Add the move to the list of possible moves |
61 | | - |
62 | | - return possible_moves # Return the list of possible moves |
| 53 | + empty_row, empty_col = None, None |
| 54 | + for i in range(3): # For each row |
| 55 | + for j in range(3): # For each column |
| 56 | + if state[i][j] == 0: # If the tile is empty |
| 57 | + empty_row, empty_col = i, j # Store the row and column of the empty tile |
| 58 | + break # Break out of the loop |
| 59 | + |
| 60 | + possible_moves = [] # List of possible moves |
| 61 | + for move, move_name in zip(MOVES, MOVE_NAMES): # For each move |
| 62 | + new_row, new_col = empty_row + move[0], empty_col + move[1] # Find the new row and column of the empty tile |
| 63 | + if 0 <= new_row < 3 and 0 <= new_col < 3: # If the new row and column are valid |
| 64 | + possible_moves.append(move_name) # Add the move to the list of possible moves |
| 65 | + |
| 66 | + return possible_moves # Return the list of possible moves |
| 67 | + |
63 | 68 |
|
64 | 69 | # Define a function to perform a move and generate a new state |
65 | 70 | # Input: state (2D list), move (string) |
66 | 71 | # Returns a new state (2D list) |
67 | 72 | def perform_move(state, move): |
68 | | - empty_row, empty_col = None, None |
69 | | - for i in range(3): # For each row |
70 | | - for j in range(3): # For each column |
71 | | - if state[i][j] == 0: # If the tile is empty |
72 | | - empty_row, empty_col = i, j # Store the row and column of the empty tile |
73 | | - break # Break out of the loop |
74 | | - |
75 | | - # Create a copy of the state |
76 | | - new_state = [row[:] for row in state] |
77 | | - if move == "right": # If the move is right |
78 | | - new_state[empty_row][empty_col], new_state[empty_row][empty_col + 1] = new_state[empty_row][empty_col + 1], new_state[empty_row][empty_col] |
79 | | - elif move == "down": # If the move is down |
80 | | - new_state[empty_row][empty_col], new_state[empty_row + 1][empty_col] = new_state[empty_row + 1][empty_col], new_state[empty_row][empty_col] |
81 | | - elif move == "left": # If the move is left |
82 | | - new_state[empty_row][empty_col], new_state[empty_row][empty_col - 1] = new_state[empty_row][empty_col - 1], new_state[empty_row][empty_col] |
83 | | - elif move == "up": # If the move is up |
84 | | - new_state[empty_row][empty_col], new_state[empty_row - 1][empty_col] = new_state[empty_row - 1][empty_col], new_state[empty_row][empty_col] |
85 | | - |
86 | | - return new_state # Return the new state |
| 73 | + empty_row, empty_col = None, None |
| 74 | + for i in range(3): # For each row |
| 75 | + for j in range(3): # For each column |
| 76 | + if state[i][j] == 0: # If the tile is empty |
| 77 | + empty_row, empty_col = i, j # Store the row and column of the empty tile |
| 78 | + break # Break out of the loop |
| 79 | + |
| 80 | + # Create a copy of the state |
| 81 | + new_state = [row[:] for row in state] |
| 82 | + if move == "right": # If the move is right |
| 83 | + new_state[empty_row][empty_col], new_state[empty_row][empty_col + 1] = ( |
| 84 | + new_state[empty_row][empty_col + 1], |
| 85 | + new_state[empty_row][empty_col], |
| 86 | + ) |
| 87 | + elif move == "down": # If the move is down |
| 88 | + new_state[empty_row][empty_col], new_state[empty_row + 1][empty_col] = ( |
| 89 | + new_state[empty_row + 1][empty_col], |
| 90 | + new_state[empty_row][empty_col], |
| 91 | + ) |
| 92 | + elif move == "left": # If the move is left |
| 93 | + new_state[empty_row][empty_col], new_state[empty_row][empty_col - 1] = ( |
| 94 | + new_state[empty_row][empty_col - 1], |
| 95 | + new_state[empty_row][empty_col], |
| 96 | + ) |
| 97 | + elif move == "up": # If the move is up |
| 98 | + new_state[empty_row][empty_col], new_state[empty_row - 1][empty_col] = ( |
| 99 | + new_state[empty_row - 1][empty_col], |
| 100 | + new_state[empty_row][empty_col], |
| 101 | + ) |
| 102 | + |
| 103 | + return new_state # Return the new state |
| 104 | + |
87 | 105 |
|
88 | 106 | # Define the A* search algorithm |
89 | 107 | # Input: initial_state (2D list) |
90 | 108 | # Returns the solution node |
91 | 109 | def solve_8_puzzle(initial_state): |
92 | | - open_list = [] # Priority queue |
93 | | - closed_set = set() # Set of visited states |
94 | | - initial_node = PuzzleState(initial_state) # Initial state |
95 | | - heapq.heappush(open_list, initial_node) # Add the initial state to the priority queue |
96 | | - |
97 | | - # While the priority queue is not empty |
98 | | - while open_list: |
99 | | - current_node = heapq.heappop(open_list) # Pop the node with the lowest f value |
100 | | - if current_node.state == GOAL_STATE: # If the current state is the goal state |
101 | | - return current_node # Return the current node |
102 | | - |
103 | | - # Print the current state |
104 | | - closed_set.add(tuple(map(tuple, current_node.state))) |
105 | | - |
106 | | - # Print the current state |
107 | | - possible_moves = find_possible_moves(current_node.state) |
108 | | - for move in possible_moves: # For each possible move |
109 | | - new_state = perform_move(current_node.state, move) # Generate a new state |
110 | | - if tuple(map(tuple, new_state)) not in closed_set: # If the new state has not been visited |
111 | | - child_node = PuzzleState(new_state, current_node, move) # Create a child node |
112 | | - child_node.g = current_node.g + 1 # Update the cost from start node to current node |
113 | | - child_node.f = child_node.g + child_node.h # Update the total estimated cost |
114 | | - heapq.heappush(open_list, child_node) # Add the child node to the priority queue |
| 110 | + open_list = [] # Priority queue |
| 111 | + closed_set = set() # Set of visited states |
| 112 | + initial_node = PuzzleState(initial_state) # Initial state |
| 113 | + heapq.heappush(open_list, initial_node) # Add the initial state to the priority queue |
| 114 | + |
| 115 | + # While the priority queue is not empty |
| 116 | + while open_list: |
| 117 | + current_node = heapq.heappop(open_list) # Pop the node with the lowest f value |
| 118 | + if current_node.state == GOAL_STATE: # If the current state is the goal state |
| 119 | + return current_node # Return the current node |
| 120 | + |
| 121 | + # Print the current state |
| 122 | + closed_set.add(tuple(map(tuple, current_node.state))) |
| 123 | + |
| 124 | + # Print the current state |
| 125 | + possible_moves = find_possible_moves(current_node.state) |
| 126 | + for move in possible_moves: # For each possible move |
| 127 | + new_state = perform_move(current_node.state, move) # Generate a new state |
| 128 | + if tuple(map(tuple, new_state)) not in closed_set: # If the new state has not been visited |
| 129 | + child_node = PuzzleState(new_state, current_node, move) # Create a child node |
| 130 | + child_node.g = current_node.g + 1 # Update the cost from start node to current node |
| 131 | + child_node.f = child_node.g + child_node.h # Update the total estimated cost |
| 132 | + heapq.heappush(open_list, child_node) # Add the child node to the priority queue |
| 133 | + |
115 | 134 |
|
116 | 135 | # Function to print the solution path |
117 | 136 | # Input: solution_node (PuzzleState) |
118 | 137 | # Returns nothing |
119 | 138 | def print_solution(solution_node): |
120 | | - path = [] # List of states and moves in the solution path |
121 | | - |
122 | | - # Print the solution path |
123 | | - while solution_node: |
124 | | - path.append((solution_node.state, solution_node.move)) # Add the state and move to the list |
125 | | - solution_node = solution_node.parent # Go to the parent node |
126 | | - path.reverse() # Reverse the list |
127 | | - |
128 | | - # Print the solution path |
129 | | - for state, move in path: # For each state and move |
130 | | - for row in state: # For each row |
131 | | - # change the background color of the terminal |
132 | | - print(f"{BackgroundColors.CYAN}{' '.join(map(str, row))}{Style.RESET_ALL}") |
133 | | - print(f"{BackgroundColors.GREEN}Move: {move}{Style.RESET_ALL}") # Print the move |
134 | | - print(f"{BackgroundColors.YELLOW}------------------{Style.RESET_ALL}") |
| 139 | + path = [] # List of states and moves in the solution path |
| 140 | + |
| 141 | + # Print the solution path |
| 142 | + while solution_node: |
| 143 | + path.append((solution_node.state, solution_node.move)) # Add the state and move to the list |
| 144 | + solution_node = solution_node.parent # Go to the parent node |
| 145 | + path.reverse() # Reverse the list |
| 146 | + |
| 147 | + # Print the solution path |
| 148 | + for state, move in path: # For each state and move |
| 149 | + for row in state: # For each row |
| 150 | + # change the background color of the terminal |
| 151 | + print(f"{BackgroundColors.CYAN}{' '.join(map(str, row))}{Style.RESET_ALL}") |
| 152 | + print(f"{BackgroundColors.GREEN}Move: {move}{Style.RESET_ALL}") # Print the move |
| 153 | + print(f"{BackgroundColors.YELLOW}------------------{Style.RESET_ALL}") |
| 154 | + |
135 | 155 |
|
136 | 156 | # This is the main function |
137 | 157 | def main(): |
138 | | - print(f"{BackgroundColors.GREEN}Welcome to the {BackgroundColors.CYAN}8-Puzzle Solver{BackgroundColors.GREEN}!{Style.RESET_ALL}") # Print a welcome message |
| 158 | + print( |
| 159 | + f"{BackgroundColors.GREEN}Welcome to the {BackgroundColors.CYAN}8-Puzzle Solver{BackgroundColors.GREEN}!{Style.RESET_ALL}" |
| 160 | + ) # Print a welcome message |
| 161 | + |
| 162 | + # Solve the 8-puzzle problem |
| 163 | + solution_node = solve_8_puzzle(INITIAL_STATE) |
139 | 164 |
|
140 | | - # Solve the 8-puzzle problem |
141 | | - solution_node = solve_8_puzzle(INITIAL_STATE) |
| 165 | + # Print the solution |
| 166 | + if solution_node: |
| 167 | + print(f"{BackgroundColors.GREEN}Solution found in {solution_node.g} moves!{Style.RESET_ALL}") |
| 168 | + print(f"{BackgroundColors.GREEN}Initial state: {Style.RESET_ALL}") |
| 169 | + print_solution(solution_node) |
| 170 | + else: |
| 171 | + print(f"{BackgroundColors.RED}Solution not found!{Style.RESET_ALL}") |
142 | 172 |
|
143 | | - # Print the solution |
144 | | - if solution_node: |
145 | | - print(f"{BackgroundColors.GREEN}Solution found in {solution_node.g} moves!{Style.RESET_ALL}") |
146 | | - print(f"{BackgroundColors.GREEN}Initial state: {Style.RESET_ALL}") |
147 | | - print_solution(solution_node) |
148 | | - else: |
149 | | - print(f"{BackgroundColors.RED}Solution not found!{Style.RESET_ALL}") |
150 | 173 |
|
151 | 174 | # This is the standard boilerplate that calls the main() function. |
152 | 175 | if __name__ == "__main__": |
153 | | - main() # Call the main function |
| 176 | + main() # Call the main function |
0 commit comments