-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathnpuzzle.py
104 lines (82 loc) · 3.72 KB
/
npuzzle.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import math
import matplotlib.pyplot as plt
class NPuzzleState:
def __init__(self, N=8, tiles=None):
if tiles is None:
self.tiles = tuple(range(N + 1)) # [0, 1, 2, ..., N] and 0 is blank
else:
N = len(tiles) - 1
self.tiles = tuple(tiles[:])
self.N = N
self.grid_size = int(math.sqrt(N + 1)) # for 8-puzzle, this is 3x3
def successors(self):
''' Returns a list of possible actions, their costs and their resulting states.
'''
blank_idx = self.tiles.index(0)
successors = []
# left
if blank_idx % self.grid_size > 0:
tiles = list(self.tiles)
tiles[blank_idx], tiles[blank_idx - 1] = tiles[blank_idx - 1], tiles[blank_idx]
successor = NPuzzleState(tiles=tiles)
successors.append((successor, 'Left', 1))
# up
if blank_idx >= self.grid_size:
tiles = list(self.tiles)
tiles[blank_idx], tiles[blank_idx - self.grid_size] = tiles[blank_idx - self.grid_size], tiles[blank_idx]
successor = NPuzzleState(tiles=tiles)
successors.append((successor, 'Up', 1))
# right
if blank_idx % self.grid_size < self.grid_size - 1:
tiles = list(self.tiles)
tiles[blank_idx], tiles[blank_idx + 1] = tiles[blank_idx + 1], tiles[blank_idx]
successor = NPuzzleState(tiles=tiles)
successors.append((successor, 'Right', 1))
# down
if blank_idx + self.grid_size < len(self.tiles):
tiles = list(self.tiles)
tiles[blank_idx], tiles[blank_idx + self.grid_size] = tiles[blank_idx + self.grid_size], tiles[blank_idx]
successor = NPuzzleState(tiles=tiles)
successors.append((successor, 'Down', 1))
return successors
def is_goal(self, goal_state):
return self == goal_state
def plot(self, ax=None, title=None, fs=20):
if ax is None:
_, ax = plt.subplots(1)
gs = self.grid_size
# draw border
border = plt.Rectangle((0, 0), gs, gs, ec='k', fc='w', lw=3)
ax.add_patch(border)
# draw tiles
for i, tile in enumerate(self.tiles):
if tile == 0: continue
col = self.grid_size - 1 - i // self.grid_size
row = i % self.grid_size
cell = plt.Rectangle((row, col), 1, 1, fc='darkslateblue', ec='k', lw=3, alpha=0.4)
ax.add_patch(cell)
tileSq = plt.Rectangle((row + 0.15, col + 0.15), 0.7, 0.7, fc='darkslateblue', ec='k', lw=1, alpha=0.8)
ax.add_patch(tileSq)
ax.text(row + 0.5, col + 0.5, f"{tile}", color='w', fontsize=fs, va='center', ha='center')
ax.axis('square')
ax.axis('off')
if title:
ax.set_title(title, fontsize=fs)
def __hash__(self):
return hash(self.tiles)
def __eq__(self, other):
if self is other: return True # True object equallity test for efficiency
if other is None: return False
if not isinstance(other, NPuzzleState): return False
return self.tiles == other.tiles
def __str__(self):
""" An string representation of the tiles configuration in 2d format.
"""
result = ''
for i in range(len(self.tiles)):
result += f' {self.tiles[i]:2d} ' if self.tiles[i] != 0 else ' '
if i % self.grid_size == self.grid_size - 1 and i < self.N:
result += '\n'
return result
def __repr__(self):
return f'NPuzzleState(N={self.N}, tiles={self.tiles})'