-
Notifications
You must be signed in to change notification settings - Fork 0
/
puzzle.py
192 lines (159 loc) · 5.92 KB
/
puzzle.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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
from random import randint
from random import choice
class Puzzle: #could be Board
'''
N-Puzzle with changable state, set of acceptable directions\n
methods for shuffling, moving the tiles, checking for the win
'''
# moves
UP = (1, 0)
DOWN = (-1, 0)
LEFT = (0, 1)
RIGHT = (0, -1)
DIRECTIONS = [UP, DOWN, LEFT, RIGHT]
def __init__(self, solved=False, other=None, size=4):
'''
Puzzle constructor, if other Puzzle is provided - a copy cnstr
'''
if other is None:
# Create a new puzzle from passed data
self._size = size
self._state = [[0] * self._size for _ in range(self._size)]
self._blankPos = (self._size - 1, self._size - 1)
self.get_solved_state()
if not solved: self.shuffleMoves()
else:
# Create a copy of the puzzle based on another puzzle object
self._size = other.size
self._state = [row[:] for row in other.state]
self._blankPos = other.blankPos
def __str__(self):
'''
string representation of the puzzle state
'''
return '\n'.join(map(str, self._state))
def __getitem__(self, key):
return self._state[key]
def setState(self, state):
'''
setter for state with correct blankPos change
'''
self._state = state
#ot search for index in python list (not sure but)
for i in range(self._size):
for j in range(self._size):
if self._state[i][j] == 0:
self._blankPos = (i, j)
def get_solved_state(self):
'''
sets state to the win state of the puzzle, updates the blankPos
'''
for i in range(self._size):
for j in range(self._size):
self._state[i][j] = i * self._size + j + 1
self._state[-1][-1] = 0
self._blankPos = (self._size - 1, self._size - 1)
def shuffleMoves(self):
''' shuffles the puzzle using the directions '''
self.get_solved_state()
shufflesCount = 132
for _ in range(shufflesCount):
dir = choice(self.DIRECTIONS)
self.move(dir)
def shuffle(self):
''' Fisher-Yatse shuffle with solvability check'''
n = self._size * self._size
arr = [0]*n
for i in range(n):
arr[i] = i+1
arr[-1] = 0
# Start from the last element and swap one by one. We don't
# need to run for the first element that's why i > 0
for i in range(n-1,0,-1):
j = randint(0,i)# Pick a random index from 0 to i
arr[i],arr[j] = arr[j],arr[i]
index = 0
for i in range(self._size):
for j in range(self._size):
if(arr[index] == 0):
self._blankPos = (i, j)
self._state[i][j] = arr[index]
index+=1
print(self)
self.isSolvable()
def getInvCount(self):
''' returns the number of inversions '''
n = self._size*self._size
arr1=[0]*n
index = 0
for x in range(self._size):
for y in range(self._size):
arr1[index] = self._state[x][y]
index += 1
inv_count = 0
for i in range(self._size * self._size - 1):
for j in range(i + 1, self._size * self._size):
if (arr1[j] and arr1[i] and arr1[i] > arr1[j]):
inv_count+=1
print(inv_count)
return inv_count
def isSolvable(self):
'''
checks if puzzle is solvable by count inversions, getting the blankPos
if the sum of inv_count and blankPos row is even -> then solvable
if not, swap two elements that do not include blank tile
'''
# Count inversions in given puzzle
invCount = self.getInvCount()
# If grid is odd (not Puzzle15 case but better have it), return true if inversion
# count is even.
if (self._size & 1):
solvable = ~(invCount & 1)
else: # grid is even
pos = self._blankPos[0] + 1
print(pos)
# print(pos & 1) # ??? this notation
if (pos & 1): #odd
solvable = (invCount & 1) # yes if invCount is odd
else:
solvable = not (invCount & 1) # yes if invCount is even (will turn to 1)
if not solvable:
i = 0
j = 1
if self._state[0][i] == 0 or self._state[0][j] == 0:
i = 2
j = 3
self._state[0][i], self._state[0][j] =self._state[0][j], self._state[0][i]
def move(self, dir: tuple):
''' moves the tiles only in correct direction '''
if dir not in self.DIRECTIONS:
return False
newBlankPos = (self._blankPos[0] + dir[0], self._blankPos[1] + dir[1])
# check if not moving outside of the board
if newBlankPos[0] < 0 or newBlankPos[0] >= self._size \
or newBlankPos[1] < 0 or newBlankPos[1] >= self._size:
return False
self._state[self._blankPos[0]][self._blankPos[1]] = self._state[newBlankPos[0]][newBlankPos[1]]
self._state[newBlankPos[0]][newBlankPos[1]] = 0
self._blankPos = newBlankPos
return True
def ifWon(self):
''' checking if the state is winning'''
index = 1
for i in range(self._size):
for j in range(self._size):
if index == 16:
if self._state[i][j] == 0: return True
elif self._state[i][j] != index:
return False
index += 1
return True
@property
def size(self):
return self._size
@property
def state(self):
return self._state
@property
def blankPos(self):
return self._blankPos