-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathsolver.py
355 lines (320 loc) · 13.5 KB
/
solver.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
import collections
import numpy as np
import time
gameState = ''
posWalls = ''
posGoals = ''
class PriorityQueue:
"""Define a PriorityQueue data structure that will be used"""
def __init__(self):
self.Heap = []
self.Count = 0
def push(self, item, priority):
entry = (priority, self.Count, item)
PriorityQueue.heappush(self.Heap, entry)
self.Count += 1
def pop(self):
(_, _, item) = PriorityQueue.heappop(self.Heap)
return item
def isEmpty(self):
return len(self.Heap) == 0
# Code taken from heapq
@staticmethod
def heappush(heap, item):
"""Push item onto heap, maintaining the heap invariant."""
heap.append(item)
PriorityQueue._siftdown(heap, 0, len(heap)-1)
@staticmethod
def heappop(heap):
"""Pop the smallest item off the heap, maintaining the heap invariant."""
lastelt = heap.pop() # raises appropriate IndexError if heap is empty
if heap:
returnitem = heap[0]
heap[0] = lastelt
PriorityQueue._siftup(heap, 0)
return returnitem
return lastelt
@staticmethod
def _siftup(heap, pos):
endpos = len(heap)
startpos = pos
newitem = heap[pos]
# Bubble up the smaller child until hitting a leaf.
childpos = 2*pos + 1 # leftmost child position
while childpos < endpos:
# Set childpos to index of smaller child.
rightpos = childpos + 1
if rightpos < endpos and not heap[childpos] < heap[rightpos]:
childpos = rightpos
# Move the smaller child up.
heap[pos] = heap[childpos]
pos = childpos
childpos = 2*pos + 1
# The leaf at pos is empty now. Put newitem there, and bubble it up
# to its final resting place (by sifting its parents down).
heap[pos] = newitem
PriorityQueue._siftdown(heap, startpos, pos)
@staticmethod
def _siftdown(heap, startpos, pos):
newitem = heap[pos]
# Follow the path to the root, moving parents down until finding a place
# newitem fits.
while pos > startpos:
parentpos = (pos - 1) >> 1
parent = heap[parentpos]
if newitem < parent:
heap[pos] = parent
pos = parentpos
continue
break
heap[pos] = newitem
"""Load puzzles and define the rules of sokoban"""
def PosOfPlayer(gameState):
"""Return the position of agent"""
return tuple(np.argwhere(gameState == 2)[0]) # e.g. (2, 2)
def PosOfBoxes(gameState):
"""Return the positions of boxes"""
return tuple(tuple(x) for x in np.argwhere((gameState == 3) | (gameState == 5))) # e.g. ((2, 3), (3, 4), (4, 4), (6, 1), (6, 4), (6, 5))
def PosOfWalls(gameState):
"""Return the positions of walls"""
return tuple(tuple(x) for x in np.argwhere(gameState == 1)) # e.g. like those above
def PosOfGoals(gameState):
"""Return the positions of goals"""
return tuple(tuple(x) for x in np.argwhere((gameState == 4) | (gameState == 5))) # e.g. like those above
def isEndState(posBox):
"""Check if all boxes are on the goals (i.e. pass the game)"""
return sorted(posBox) == sorted(posGoals)
def isLegalAction(action, posPlayer, posBox):
"""Check if the given action is legal"""
xPlayer, yPlayer = posPlayer
if action[-1].isupper(): # the move was a push
x1, y1 = xPlayer + 2 * action[0], yPlayer + 2 * action[1]
else:
x1, y1 = xPlayer + action[0], yPlayer + action[1]
return (x1, y1) not in posBox + posWalls
def legalActions(posPlayer, posBox):
"""Return all legal actions for the agent in the current game state"""
allActions = [[-1,0,'u','U'],[1,0,'d','D'],[0,-1,'l','L'],[0,1,'r','R']]
xPlayer, yPlayer = posPlayer
legalActions = []
for action in allActions:
x1, y1 = xPlayer + action[0], yPlayer + action[1]
if (x1, y1) in posBox: # the move was a push
action.pop(2) # drop the little letter
else:
action.pop(3) # drop the upper letter
if isLegalAction(action, posPlayer, posBox):
legalActions.append(action)
else:
continue
return tuple(tuple(x) for x in legalActions) # e.g. ((0, -1, 'l'), (0, 1, 'R'))
def updateState(posPlayer, posBox, action):
"""Return updated game state after an action is taken"""
xPlayer, yPlayer = posPlayer # the previous position of player
newPosPlayer = [xPlayer + action[0], yPlayer + action[1]] # the current position of player
posBox = [list(x) for x in posBox]
if action[-1].isupper(): # if pushing, update the position of box
posBox.remove(newPosPlayer)
posBox.append([xPlayer + 2 * action[0], yPlayer + 2 * action[1]])
posBox = tuple(tuple(x) for x in posBox)
newPosPlayer = tuple(newPosPlayer)
return newPosPlayer, posBox
def isFailed(posBox):
"""This function used to observe if the state is potentially failed, then prune the search"""
rotatePattern = [[0,1,2,3,4,5,6,7,8],
[2,5,8,1,4,7,0,3,6],
[0,1,2,3,4,5,6,7,8][::-1],
[2,5,8,1,4,7,0,3,6][::-1]]
flipPattern = [[2,1,0,5,4,3,8,7,6],
[0,3,6,1,4,7,2,5,8],
[2,1,0,5,4,3,8,7,6][::-1],
[0,3,6,1,4,7,2,5,8][::-1]]
allPattern = rotatePattern + flipPattern
for box in posBox:
if box not in posGoals:
board = [(box[0] - 1, box[1] - 1), (box[0] - 1, box[1]), (box[0] - 1, box[1] + 1),
(box[0], box[1] - 1), (box[0], box[1]), (box[0], box[1] + 1),
(box[0] + 1, box[1] - 1), (box[0] + 1, box[1]), (box[0] + 1, box[1] + 1)]
for pattern in allPattern:
newBoard = [board[i] for i in pattern]
if newBoard[1] in posWalls and newBoard[5] in posWalls: return True
elif newBoard[1] in posBox and newBoard[2] in posWalls and newBoard[5] in posWalls: return True
elif newBoard[1] in posBox and newBoard[2] in posWalls and newBoard[5] in posBox: return True
elif newBoard[1] in posBox and newBoard[2] in posBox and newBoard[5] in posBox: return True
elif newBoard[1] in posBox and newBoard[6] in posBox and newBoard[2] in posWalls and newBoard[3] in posWalls and newBoard[8] in posWalls: return True
return False
"""Implement all approcahes"""
def breadthFirstSearch():
"""Implement breadthFirstSearch approach"""
beginBox = PosOfBoxes(gameState)
beginPlayer = PosOfPlayer(gameState)
startState = (beginPlayer, beginBox) # e.g. ((2, 2), ((2, 3), (3, 4), (4, 4), (6, 1), (6, 4), (6, 5)))
frontier = collections.deque([[startState]]) # store states
actions = collections.deque([[0]]) # store actions
exploredSet = set()
count = 0
while frontier:
node = frontier.popleft()
node_action = actions.popleft()
if isEndState(node[-1][-1]):
# print(','.join(node_action[1:]).replace(',',''))
solution = ','.join(node_action[1:]).replace(',','')
print(count)
return solution
# break
if node[-1] not in exploredSet:
exploredSet.add(node[-1])
for action in legalActions(node[-1][0], node[-1][1]):
count = count + 1
newPosPlayer, newPosBox = updateState(node[-1][0], node[-1][1], action)
if isFailed(newPosBox):
continue
frontier.append(node + [(newPosPlayer, newPosBox)])
actions.append(node_action + [action[-1]])
def depthFirstSearch():
"""Implement depthFirstSearch approach"""
beginBox = PosOfBoxes(gameState)
beginPlayer = PosOfPlayer(gameState)
startState = (beginPlayer, beginBox)
frontier = collections.deque([[startState]])
exploredSet = set()
actions = [[0]]
count = 0
while frontier:
node = frontier.pop()
node_action = actions.pop()
if isEndState(node[-1][-1]):
# print(','.join(node_action[1:]).replace(',',''))
solution = ','.join(node_action[1:]).replace(',','')
print(count)
return solution
# break
if node[-1] not in exploredSet:
exploredSet.add(node[-1])
for action in legalActions(node[-1][0], node[-1][1]):
count = count + 1
newPosPlayer, newPosBox = updateState(node[-1][0], node[-1][1], action)
if isFailed(newPosBox):
continue
frontier.append(node + [(newPosPlayer, newPosBox)])
actions.append(node_action + [action[-1]])
def heuristic(posPlayer, posBox):
"""A heuristic function to calculate the overall distance between the else boxes and the else goals"""
distance = 0
completes = set(posGoals) & set(posBox)
sortposBox = list(set(posBox).difference(completes))
sortposGoals = list(set(posGoals).difference(completes))
for i in range(len(sortposBox)):
distance += (abs(sortposBox[i][0] - sortposGoals[i][0])) + (abs(sortposBox[i][1] - sortposGoals[i][1]))
return distance
def cost(actions):
"""A cost function"""
return len([x for x in actions if x.islower()])
def uniformCostSearch():
"""Implement uniformCostSearch approach"""
beginBox = PosOfBoxes(gameState)
beginPlayer = PosOfPlayer(gameState)
startState = (beginPlayer, beginBox)
frontier = PriorityQueue()
frontier.push([startState], 0)
exploredSet = set()
actions = PriorityQueue()
actions.push([0], 0)
count = 0
while frontier:
node = frontier.pop()
node_action = actions.pop()
if isEndState(node[-1][-1]):
# print(','.join(node_action[1:]).replace(',',''))
solution = ','.join(node_action[1:]).replace(',','')
print(count)
return solution
# break
if node[-1] not in exploredSet:
exploredSet.add(node[-1])
Cost = cost(node_action[1:])
for action in legalActions(node[-1][0], node[-1][1]):
count = count + 1
newPosPlayer, newPosBox = updateState(node[-1][0], node[-1][1], action)
if isFailed(newPosBox):
continue
frontier.push(node + [(newPosPlayer, newPosBox)], Cost)
actions.push(node_action + [action[-1]], Cost)
def aStarSearch():
"""Implement aStarSearch approach"""
beginBox = PosOfBoxes(gameState)
beginPlayer = PosOfPlayer(gameState)
start_state = (beginPlayer, beginBox)
frontier = PriorityQueue()
frontier.push([start_state], heuristic(beginPlayer, beginBox))
exploredSet = set()
actions = PriorityQueue()
actions.push([0], heuristic(beginPlayer, start_state[1]))
count = 0
while frontier:
# count = count+1
# print('frontier',frontier)
if frontier.isEmpty():
return 'x'
node = frontier.pop()
node_action = actions.pop()
if isEndState(node[-1][-1]):
solution = ','.join(node_action[1:]).replace(',','')
print(solution)
print(count)
return solution
# break
if node[-1] not in exploredSet:
exploredSet.add(node[-1])
Cost = cost(node_action[1:])
for action in legalActions(node[-1][0], node[-1][1]):
newPosPlayer, newPosBox = updateState(node[-1][0], node[-1][1], action)
if isFailed(newPosBox):
continue
count = count + 1
Heuristic = heuristic(newPosPlayer, newPosBox)
frontier.push(node + [(newPosPlayer, newPosBox)], Heuristic + Cost)
actions.push(node_action + [action[-1]], Heuristic + Cost)
def transferToGameState(layout):
"""Transfer the layout of initial puzzle"""
layout = [','.join(layout[i]) for i in range(len(layout))]
layout = [x.split(",") for x in layout]
maxColsNum = max([len(x) for x in layout])
for irow in range(len(layout)):
for icol in range(len(layout[irow])):
if layout[irow][icol] == ' ': layout[irow][icol] = 0 # free space
elif layout[irow][icol] == '#': layout[irow][icol] = 1 # wall
elif layout[irow][icol] == '&': layout[irow][icol] = 2 # player
elif layout[irow][icol] == 'B': layout[irow][icol] = 3 # box
elif layout[irow][icol] == '.': layout[irow][icol] = 4 # goal
elif layout[irow][icol] == 'X': layout[irow][icol] = 5 # box on goal
colsNum = len(layout[irow])
if colsNum < maxColsNum:
layout[irow].extend([1 for _ in range(maxColsNum-colsNum)])
return np.array(layout)
def solveSokodan(method, layout):
print('solve')
time_start = time.time()
global gameState
global posWalls
global posGoals
gameState = transferToGameState(layout)
posWalls = PosOfWalls(gameState)
posGoals = PosOfGoals(gameState)
solution = ''
if method == 'astar':
solution = aStarSearch()
elif method == 'dfs':
solution = depthFirstSearch()
elif method == 'bfs':
solution = breadthFirstSearch()
elif method == 'ucs':
solution = uniformCostSearch()
else:
raise ValueError('Invalid method.')
time_end=time.time()
time_str = '%.2f seconds.' %(time_end-time_start)
print(solution)
print('Runtime of %s: %.2f second.' %(method, time_end-time_start))
return solution, time_str
solveSokodan