-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsolver.py
274 lines (251 loc) · 12.8 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
import sys
import collections
import numpy as np
import heapq
import time
import numpy as np
global posWalls, posGoals
class PriorityQueue:
"""Define a PriorityQueue data structure that will be used"""
def __init__(self):
self.Heap = []
self.Count = 0
self.len = 0
def push(self, item, priority):
entry = (priority, self.Count, item)
heapq.heappush(self.Heap, entry)
self.Count += 1
def pop(self):
(_, _, item) = heapq.heappop(self.Heap)
return item
def isEmpty(self):
return len(self.Heap) == 0
"""Load puzzles and define the rules of sokoban"""
def transferToGameState(layout):
"""Transfer the layout of initial puzzle"""
layout = [x.replace('\n','') for x in layout]
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)])
# print(layout)
return np.array(layout)
def transferToGameState2(layout, player_pos):
"""Transfer the layout of initial puzzle"""
maxColsNum = max([len(x) for x in layout])
temp = np.ones((len(layout), maxColsNum))
for i, row in enumerate(layout):
for j, val in enumerate(row):
temp[i][j] = layout[i][j]
temp[player_pos[1]][player_pos[0]] = 2
return temp
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 cost(actions):
return len([x for x in actions if x.islower()])
def states(actions):
return len([x for x in actions ])
def depthFirstSearch(gameState):
"""Implement depthFirstSearch approach"""
beginBox = PosOfBoxes(gameState)
beginPlayer = PosOfPlayer(gameState)
startState = (beginPlayer, beginBox)
frontier = collections.deque([[startState]])
exploredSet = set()
actions = [[0]]
temp = []
while frontier:
node = frontier.pop()
node_action = actions.pop()
if isEndState(node[-1][-1]):
temp += node_action[1:]
print("cost of dfs: ", states(temp))
break
if node[-1] not in exploredSet:
exploredSet.add(node[-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
frontier.append(node + [(newPosPlayer, newPosBox)])
actions.append(node_action + [action[-1]])
return temp
def breadthFirstSearch(gameState):
"""Implement breadthFirstSearch approach"""
beginBox = PosOfBoxes(gameState) #Initial the coordinates of the box on the screen
beginPlayer = PosOfPlayer(gameState) #Initial the coordinates of the player on the screen
# The data structure of frontier and actions is double queue (The queue which we can ...
# ... pop or append from both the left side and the right side)
startState = (beginPlayer, beginBox) # e.g. ((2, 2), ((2, 3), (3, 4), (4, 4), (6, 1), (6, 4), (6, 5)))
frontier = collections.deque([[startState]]) # A queue which stores states
actions = collections.deque([[0]]) # A queue which stores actions
exploredSet = set() # Close set
temp = [] # The list that store the path from begining to solution
### Implement breadthFirstSearch here
while frontier: # Iterate through the queue that stores the states
node = frontier.popleft() # Pop the leftside node from the frontier
node_action = actions.popleft() # Pop the leftside node from the actions
if isEndState(node[-1][-1]): # Check if node is the end state or not
temp += node_action[1:] # if the node is end state, temp will save the path from beginning to solution
print("Cost of bfs: ", states(temp)) # print cost of the way to solution
break # Exit the frontier
if node[-1] not in exploredSet: #check if the node is explored or not
exploredSet.add(node[-1]) # if not, add it to explored set
for action in legalActions(node[-1][0], node[-1][1]): # Loop through the set of legal action
newPosPlayer, newPosBox = updateState(node[-1][0], node[-1][-1], action)
#Update the new Player and Box Position corresponding with the action
if isFailed(newPosBox): #check if the new position of box is pottentially failed or not
continue # if yes, skip this loop and go to another loop
frontier.append(node + [(newPosPlayer, newPosBox)]) # add new position to the frontier
actions.append(node_action + [action[-1]]) # add new action to the actions
return temp # return the path to solution
def uniformCostSearch(gameState):
"""Implement uniformCostSearch approach"""
beginBox = PosOfBoxes(gameState) #Initial the coordinates of the box on the screen
beginPlayer = PosOfPlayer(gameState) #Initial the coordinates of the player on the screen
# The data structure of frontier and actions ...
# ... is priority queue (the queue that can pop or push the node by the priority)
startState = (beginPlayer, beginBox) #Initial the startState
frontier = PriorityQueue() #Initial frontier as Priority queue
frontier.push([startState], 0) #Push the startState to Frontier with Priority = 0
exploredSet = set() # Close Set
actions = PriorityQueue() # Initial actions as Priority queue
actions.push([0], 0) #Push the first item to actions with Priority = 0
temp = [] # A set that store the path to solution
### Implement uniform cost search here
while frontier: #Iterating through the frontier
node = frontier.pop() # Pop the node from the frontier
node_action = actions.pop() # Pop the actions from the frontier
if isEndState(node[-1][-1]): # Check if node is the end state or not
temp += node_action[1:] # if the node is end state, temp will save the path from beginning to solution
print("Cost of ucs: ", cost(temp)) # print cost of the way to solution
break # Exit the frontier
if node[-1] not in exploredSet: #check if the node is explored or not
exploredSet.add(node[-1]) # if not, add it to explored set
Cost_Action = cost(node_action[1:]) # calculate the cost of node_action
for action in legalActions(node[-1][0], node[-1][1]): # Loop through the set of legal action
newPosPlayer, newPosBox = updateState(node[-1][0], node[-1][1], action)
#Update the new Player and Box Position corresponding with the action
if isFailed(newPosBox): #check if the new position of box is pottentially failed or not
continue # if yes, skip this loop and go to another loop
frontier.push(node + [(newPosPlayer, newPosBox)],Cost_Action)# push the new node to the frontier with the priority of cost action
actions.push(node_action + [action[-1]],Cost_Action)
# push the new node to the actions with the priority of cost action
return temp # return the path to solution
"""Read command"""
def readCommand(argv):
from optparse import OptionParser
parser = OptionParser()
parser.add_option('-l', '--level', dest='sokobanLevels',
help='level of game to play', default='level1.txt')
parser.add_option('-m', '--method', dest='agentMethod',
help='research method', default='bfs')
args = dict()
options, _ = parser.parse_args(argv)
with open('assets/levels/' + options.sokobanLevels,"r") as f:
layout = f.readlines()
args['layout'] = layout
args['method'] = options.agentMethod
return args
def get_move(layout, player_pos, method):
time_start = time.time()
global posWalls, posGoals
# layout, method = readCommand(sys.argv[1:]).values()
gameState = transferToGameState2(layout, player_pos)
posWalls = PosOfWalls(gameState)
posGoals = PosOfGoals(gameState)
if method == 'dfs':
result = depthFirstSearch(gameState)
elif method == 'bfs':
result = breadthFirstSearch(gameState)
elif method == 'ucs':
result = uniformCostSearch(gameState)
else:
raise ValueError('Invalid method.')
time_end=time.time()
print('Runtime of %s: %.2f second.' %(method, time_end-time_start))
print(result)
return result