import random
from collections import deque
import matplotlib.pyplot as plt
import numpy as np
random.seed(58)
def is_solvable(tiles):
# Count the inversions in the flattened list of tiles (excluding the blank space).
# Assumption: board is square and width is odd.
inversions = 0
for i in range(len(tiles)):
for j in range(i + 1, len(tiles)):
if tiles[i] != 0 and tiles[j] != 0 and tiles[i] > tiles[j]:
inversions += 1
return inversions % 2 == 0
def generate_solvable_board():
# Generate a random board configuration that is guaranteed to be solvable
tiles = list(range(9))
random.shuffle(tiles)
while not is_solvable(tiles):
random.shuffle(tiles)
return tuple(tiles)
def plot_board(board, title, num_pos, position):
ax = plt.subplot(1, num_pos, position)
ax.set_title(title)
ax.set_xticks([])
ax.set_yticks([])
board = np.array(board).reshape(3, 3).tolist() # Reconfigure the grid to be 3x3
# Use a color map to display the numbers
cmap = plt.cm.plasma
norm = plt.Normalize(vmin=-1, vmax=8)
for i in range(3):
for j in range(3):
tile_value = board[i][j]
color = cmap(norm(tile_value))
ax.add_patch(plt.Rectangle((j, 2 - i), 1, 1, facecolor=color, edgecolor='black'))
if tile_value == 0:
ax.add_patch(plt.Rectangle((j, 2 - i), 1, 1, facecolor='white', edgecolor='black'))
else:
ax.text(j + 0.5, 2 - i + 0.5, str(tile_value),
fontsize=16, ha='center', va='center', color='black')
ax.set_xlim(0, 3)
ax.set_ylim(0, 3)
def is_empty(frontier):
"""Checks if the frontier is empty."""
return len(frontier) == 0
def is_goal(state, goal_state):
"""Determines if a given state matches the goal state."""
return state == goal_state
def expand(state):
"""Generates successor states by moving the blank tile in all possible directions."""
size = int(len(state) ** 0.5) # Determine puzzle size (3 for 8-puzzle, 4 for 15-puzzle)
idx = state.index(0) # Find the index of the blank tile represented by 0
x, y = idx % size, idx // size # Convert index to (x, y) coordinates
neighbors = []
# Define possible moves: Left, Right, Up, Down
moves = [(-1, 0), (1, 0), (0, -1), (0, 1)]
for dx, dy in moves:
nx, ny = x + dx, y + dy
# Check if the new position is within the puzzle boundaries
if 0 <= nx < size and 0 <= ny < size:
n_idx = ny * size + nx
new_state = list(state)
# Swap the blank tile with the adjacent tile
new_state[idx], new_state[n_idx] = new_state[n_idx], new_state[idx]
neighbors.append(tuple(new_state))
return neighbors
def print_solution(solution):
"""Prints the sequence of steps from the initial to the goal state."""
size = int(len(solution[0]) ** 0.5)
for step, state in enumerate(solution):
print(f"Step {step}:")
for i in range(size):
row = state[i*size:(i+1)*size]
print(' '.join(str(n) if n != 0 else ' ' for n in row))
print()
def bfs(initial_state, goal_state):
frontier = deque() # Initialize the queue for BFS
frontier.append((initial_state, [])) # A node: (state, preceding states)
reached = {initial_state}
expanded = 0 # Count non-goal nodes whose successors are generated
while not is_empty(frontier):
current_state, path = frontier.popleft()
if is_goal(current_state, goal_state):
print(f"Nodes expanded: {expanded}")
return path + [current_state] # Return the successful path
expanded += 1
for neighbor in expand(current_state):
if neighbor not in reached:
reached.add(neighbor)
frontier.append((neighbor, path + [current_state]))
return None
def dfs(initial_state, goal_state):
frontier = [(initial_state, [])] # A node: (state, preceding states)
reached = {initial_state}
expanded = 0
while not is_empty(frontier):
current_state, path = frontier.pop()
if is_goal(current_state, goal_state):
print(f"Nodes expanded: {expanded}")
return path + [current_state] # Return the successful path
expanded += 1
for neighbor in expand(current_state):
if neighbor not in reached:
reached.add(neighbor)
frontier.append((neighbor, path + [current_state]))
return None