-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpuzzle.py
389 lines (327 loc) · 13.3 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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
import random
import time
import copy
SIDES = 6
# state object
class State:
def __init__(self, size=3, c=None):
self.size = size
self.actions = ['front', 'back', 'left', 'right', 'top', 'bottom',
"afront", "aback", "aleft", "aright", "atop", "abottom"]
if c:
self.d = c
self.__front__ = c["front"]
self.__back__ = c["back"]
self.__left__ = c["left"]
self.__right__ = c["right"]
self.__top__ = c["top"]
self.__bottom__ = c["bottom"]
self.__sides__ = [self.front(), self.back(), self.left(
), self.right(), self.top(), self.bottom()]
return
# create array of values 1-6 for different colors
# and multiply by number of pieces per size to get
# equal amount of each color (white. black, red, orange, green, yellow)
'''nums = ['W','B','R','O','G','Y']*(size**2)
# shuffle numbers
shuffle(nums)
front, nums = nums[0:size**2],nums[size**2:]
self.__front__ = [front[i:i + size] for i in range(0,len(front), size)]
back, nums = nums[0:size**2],nums[size**2:]
self.__back__ = [back[i:i + size] for i in range(0,len(front), size)]
left, nums = nums[0:size**2],nums[size**2:]
self.__left__ = [left[i:i + size] for i in range(0,len(front), size)]
right, nums = nums[0:size**2],nums[size**2:]
self.__right__ = [right[i:i + size] for i in range(0,len(front), size)]
top, nums = nums[0:size**2],nums[size**2:]
self.__top__ = [top[i:i + size] for i in range(0,len(front), size)]
bottom, nums = nums[0:size**2],nums[size**2:]
self.__bottom__ = [bottom[i:i + size] for i in range(0,len(front), size)]'''
self.__front__ = [['W', 'W', 'W'], ['W', 'W', 'W'], ['W', 'W', 'W']]
self.__back__ = [['Y', 'Y', 'Y'], ['Y', 'Y', 'Y'], ['Y', 'Y', 'Y']]
self.__top__ = [['R', 'R', 'R'], ['R', 'R', 'R'], ['R', 'R', 'R']]
self.__bottom__ = [['O', 'O', 'O'], ['O', 'O', 'O'], ['O', 'O', 'O']]
self.__left__ = [['B', 'B', 'B'], ['B', 'B', 'B'], ['B', 'B', 'B']]
self.__right__ = [['G', 'G', 'G'], ['G', 'G', 'G'], ['G', 'G', 'G']]
self.__sides__ = [self.front(), self.back(), self.left(),
self.right(), self.top(), self.bottom()]
self.d = {"front": self.front(), "back": self.back(), "left": self.left(),
"right": self.right(), "top": self.top(), "bottom": self.bottom()}
# return new copy of State
def copy(self):
#d = copy.deepcopy(self.d)
new_s = copy.deepcopy(self)
return new_s
# equality tested for cube
def eq(self, other):
return self.__left__ == other.left() and self.__right__ == other.right()\
and self.__top__ == other.top() and self.__bottom__ == other.bottom()\
and self.__front__ == other.front() and self.__back__ == other.back()
# getters and setters for cube sides
def left(self):
return self.__left__
def set_left(self, l):
self.__left__ = l
def right(self):
return self.__right__
def set_right(self, r):
self.__right__ = r
def top(self):
return self.__top__
def set_top(self, t):
self.__top__ = t
def bottom(self):
return self.__bottom__
def set_bottom(self, b):
self.__bottom__ = b
def front(self):
return self.__front__
def set_front(self, f):
self.__front__ = f
def back(self):
return self.__back__
def set_back(self, b):
self.__back__ = b
# randomly shuffle cube (applies n moves)
# stringify a cube
def __str__(self):
return "\nFRONT" + str(self.__front__) + "\nBACK" + str(self.__back__) + "\nLEFT" \
+ str(self.__left__) + "\nRIGHT" + str(self.__right__) + \
"\nTOP" + str(self.__top__) + "\nBOTTOM" + str(self.__bottom__)
def __hash__(self):
return hash(self.__str__())
# execute a 180 degreee rotation of a given side
def rotate_side(self, side):
new_side = [[], [], []]
for i in reversed(range(self.size)):
for y in range(self.size):
new_side[self.size - 1 - i].append(side[i][self.size - 1 - y])
return new_side
# modify a side, rotating its rows to columns, either in left to right
# or right to left order, depending on the reverse parameter
def columns_to_rows(self, side, reverse=False):
new_side = []
for i in range(self.size):
row = []
for j in range(self.size):
# left to right order
if not reverse:
row.append(side[self.size - 1 - j][i])
# right to left order
else:
row.append(side[j][self.size - 1 - i])
new_side.append(row)
return new_side
# rotate the cube 90 degrees counter clockwise
def rotate_cube(self):
left_side = self.__left__
self.__left__ = self.replace_side(self.__back__)
front_side = self.__front__
self.__front__ = self.replace_side(left_side)
right_side = self.__right__
self.__right__ = self.replace_side(front_side)
self.__back__ = self.replace_side(right_side)
self.__top__ = self.columns_to_rows(self.__top__, reverse=True)
self.__bottom__ = self.columns_to_rows(self.__bottom__)
# swap the first row of two given sides, in place
def swap_first_row(self, side1, side2):
s1_1 = side1[0]
s2_1 = side2[0]
# get rest of rows of side1
new_side1 = [s2_1] + list(side for side in side1[1:])
# get rest of rows of side2
new_side2 = [s1_1] + list(side for side in side2[1:])
return new_side1, new_side2
# take the last element of each row of side 1, swap in place with
# first element of reach row of side 2
def swap_first_last_col(self, side1, side2):
for i in range(len(side1)):
side1[i][self.size - 1], side2[i][0] = side2[i][0], side1[i][self.size - 1]
return side1, side2
# given a new side, return a copy of this side,
# to replace a given side of a cube
def replace_side(self, side):
new_side = []
for row in side:
new_side.append(row)
return new_side
# flip cube forward, from perspective of user looking at front
# flipping such that front goes to bottom and top comes to front
def flip_forward(self):
front = self.__front__
self.__front__ = self.replace_side(self.__top__)
bottom = self.__bottom__
self.__bottom__ = self.replace_side(front)
back = self.__back__
self.__back__ = self.replace_side(bottom)
self.__top__ = self.replace_side(back)
# flip cube backward, from perspective of user looking at front
# flipping such that front goes to top and bottom goes to front
def flip_backward(self):
front = self.__front__
self.__front__ = self.replace_side(self.__bottom__)
top = self.__top__
self.__top__ = self.replace_side(front)
back = self.__back__
self.__back__ = self.replace_side(top)
self.__bottom__ = self.replace_side(back)
# flip cube, either forward or backward, and invert
# sides that must be inverted as a result
def flip_cube(self, forward=False):
# if flipping forward, we set front to be top
# top to be back, back to be bottom, and bottom
# to be front, we must then invert the back and front
# if flipping backward, we set front to be bottom
# top to be front, back to be top, and bottom to be
# back, we must then invert the top and bottom
if forward:
self.flip_forward()
self.__front__ = self.rotate_side(self.__front__)
self.__back__ = self.rotate_side(self.__back__)
self.__left__ = self.columns_to_rows(self.__left__)
self.__right__ = self.columns_to_rows(self.__right__, reverse=True)
else:
self.flip_backward()
self.__top__ = self.rotate_side(self.__top__)
self.__bottom__ = self.rotate_side(self.__bottom__)
self.__left__ = self.columns_to_rows(self.__left__, reverse=True)
self.__right__ = self.columns_to_rows(self.__right__)
# rotates the front face 90 degrees clockwise
def _turn_front(self):
# rotate the face clockwise
self.__front__ = self.columns_to_rows(self.__front__)
_top = copy.deepcopy(self.__top__)
self.__top__[0] = [row[2] for row in self.__left__]
self.__left__ = [[row[0], row[1], bot]
for row, bot in zip(self.__left__, self.__bottom__[0])]
self.__bottom__[0] = list(reversed([row[0] for row in self.__right__]))
self.__right__ = [[top, row[1], row[2]]
for row, top in zip(self.__right__, reversed(_top[0]))]
def turn_front(self, ccw):
self._turn_front()
if ccw:
self._turn_front()
self._turn_front()
def turn_back(self, ccw):
# swap the last row of the left/right sides, and the first
# row of the top/bottom sides
# must rotate 90 degress twice
self.rotate_cube()
self.rotate_cube()
self.turn_front(ccw)
self.rotate_cube()
self.rotate_cube()
def turn_left(self, ccw):
# left become front, front becomes right, right becomes back, back becomes left
# top gets rotated 90 degrees counter clockwise
# (3 6 9 -> 1 2 3) (2 5 8 -> 4 5 6) (1 4 7 -> 7 8 9)
# must turn the cube 90 degrees counter clockwise to face the
self.rotate_cube()
self.turn_front(ccw)
# left side of the cube, now as the front, then turn_front
self.rotate_cube()
self.rotate_cube()
self.rotate_cube()
def turn_right(self, ccw):
# must make 3 90 degree rotations of the cube for the right
# side to face front
self.rotate_cube()
self.rotate_cube()
self.rotate_cube()
self.turn_front(ccw)
self.rotate_cube()
def turn_top(self, ccw):
self.flip_cube(forward=True)
self.turn_front(ccw)
self.flip_cube()
def turn_bottom(self, ccw):
self.flip_cube()
self.turn_front(ccw)
self.flip_cube(forward=True)
def isGoalState(self):
# check if all 3 lists that make up a side are equal
# for every side, return false if this is not the case
# e.g. side = [[1,1,1], [1,1,1], [1,1,2]]
for side in self.__sides__:
char = side[0][0]
# check if all values in each row are equal
# to the first value
for row in side:
if not char == row[0] == row[1] == row[2]:
return False
return True
def move(self, action):
ccw = action[0] == 'a'
act = action[1:] if ccw else action
if act == 'left':
self.turn_left(ccw)
elif act == 'right':
self.turn_right(ccw)
elif act == 'front':
self.turn_front(ccw)
elif act == 'back':
self.turn_back(ccw)
elif act == 'top':
self.turn_top(ccw)
elif act == 'bottom':
self.turn_bottom(ccw)
self.__sides__ = [self.front(), self.back(), self.left(),
self.right(), self.top(), self.bottom()]
# check number of pieces on each side of cube that match color
# of the middle piece of that side
def num_pieces_correct_side(state):
correct = 0
for side in state.__sides__:
# get middle piece
color = side[int(state.size / 2)][int(state.size / 2)]
# subtract 1 to ignore middle cube
correct -= 1
for row in side:
# filter items in each row that equal middle color
# and add length of filtered list to total sum
correct += row.count(color)
return correct
def num_solved_sides(state):
solved = 0
for side in state.__sides__:
color = side[0][0]
# if number of pieces on this side equal to first square
# is number of total pieces, side is solved
if sum(row.count(color) for row in side) == state.size**2:
solved += 1
return solved
def num_crosses(state):
crosses = 0
for side in state.__sides__:
color = side[1][1]
if side[0][1] == color and side[1][0] == color and side[1][2] == color and side[2][1] == color:
crosses += 1
return crosses
def num_xs(state):
xs = 0
for side in state.__sides__:
color = side[1][1]
if side[0][0] == color and side[0][2] == color and side[2][0] == color and side[2][2] == color:
xs += 1
return xs
def n_move_state(n=5):
c = State()
return shuffle(c, n=n)
def one_move_state():
c = State()
c.move(c.actions[0])
return c
def shuffle(cube, n=5):
new_cube = cube.copy()
for _ in range(n):
new_cube = random_move(new_cube)
return new_cube
def random_move(cube):
action = random.choice(cube.actions)
# print("executing " + action + " 90 rotation")
cube = move(cube, action)
return cube
def move(s, action):
new_state = s.copy()
new_state.move(action)
return new_state