-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbasic_agent.py
345 lines (226 loc) · 10.2 KB
/
basic_agent.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
import numpy as np
import board as board
from time import sleep
class Cell():
covered = True
mine = None
mine_count = None
flag = None
safe = None
total_neighbors = 8
hidden_neighbors = 8
safe_neighbors_identified = 0
def __str__(self):
if self.flag:
return "F"
if self.covered:
return "?"
if self.mine_count is not None:
return str(self.mine_count)
if self.mine:
return 'M'
else:
return "err"
def __repr__(self):
return self.__str__()
class BasicAgent():
"""Basic agent for solving minesweeper.
"""
def __init__(self, board):
# Environment/board attribute of agent.
self._board = board
# The only allowed interfaces to this object are:
# .user_select(i, j)
# .user_flag(i, j)
# .check_gameover_conditions()
# .score
# .dim
# .num_mines
# Other attribute or functions accesses are illegal and cheating
# e.g The agent cannot access board.cells or board.excavated
# store board metadata locally
self.dim = self._board.dim
self.num_mines = self._board.num_mines
# Initialize agent's internal model of the board
self.cells = np.zeros((self.dim, self.dim), dtype=Cell)
for i in range(self.dim):
for j in range(self.dim):
self.cells[i, j] = Cell()
# Edge cells have 5 neighbors
if (i == 0) or (j == 0) or (i == self.dim-1) or (j == self.dim-1):
self.cells[i, j].hidden_neighbors = 5
self.cells[i, j].total_neighbors = 5
# Corner cells have 3 neighbors
if (i == 0 and j == 0) or \
(i == self.dim-1 and j == 0) or \
(i == 0 and j == self.dim-1) or \
(i == self.dim-1 and j == self.dim-1):
self.cells[i, j].hidden_neighbors = 3
self.cells[i, j].total_neighbors = 3
# Metric for random clicks done
self.random_clicks = 0
def toggle_flag(self, i, j):
""" Places flag on cell at (i, j).
Sends user_flag() command to board and updates internal structures
"""
self._board.user_flag(i, j)
# Flag currently present
if self.cells[i, j].flag:
self.cells[i, j].flag = False
# Flag currently not present
else:
self.cells[i, j].flag = True
def excavate_cell(self, i, j, log=False):
"""Digs up the cell at (i, j).
Sends user_select() command to board and updates internal structures with
what is returned.
Returns true on successful excavation
"""
# If flagged, cannot excavate
if self.cells[i, j].flag:
return False
value = self._board.user_select(i, j)
self.cells[i, j].covered = False
# Hit a mine
if value == -1:
self.cells[i, j].mine = True
self.cells[i, j].safe = False
if log: print("Excavated a mine at ({}, {})".format(i, j))
else:
# Did not hit a mine
self.cells[i, j].mine_count = value
if log: print("Excavated ({}, {})".format(i, j))
# Decrement hidden_neighbors count for all neighbors
# and increment safe_neighbors count for all neighbors
neighbors = [(i+1, j+1), (i+1, j), (i, j+1), (i-1, j+1), \
(i-1, j-1), (i-1, j), (i, j-1), (i+1, j-1)]
for neighbor in neighbors:
# The neighbor is within bounds of the board
if (0 <= neighbor[0] < self.dim) and (0 <= neighbor[1] < self.dim):
self.cells[neighbor].hidden_neighbors -= 1
self.cells[neighbor].safe_neighbors_identified += 1
return True
def surrounding_safe(self, i, j, log=False):
"""If, for a given cell, the total number of safe neighbors (8 - clue)
minus the number of revealed safe neighbors is the number of hidden
neighbors, every hidden neighbor is safe.
In this case, all hidden_neighbors of (i, j) are marked .safe=True
Returns True if all surrounding hidden cells can be marked as safe.
False otherwise.
"""
cell = self.cells[i, j]
if cell.flag:
return False
if cell.mine:
return False
made_progress = False
total_safe_neighbors = cell.total_neighbors - cell.mine_count
remaining_safe_neighbors = total_safe_neighbors - cell.safe_neighbors_identified
# If the remaining uncovered neighbor count == number of safe neighbrs left
# then all remaining uncovered neighbors are safe.
if remaining_safe_neighbors == cell.hidden_neighbors:
neighbors = [(i+1, j+1), (i+1, j), (i, j+1), (i-1, j+1), \
(i-1, j-1), (i-1, j), (i, j-1), (i+1, j-1)]
for neighbor in neighbors:
# The neighbor is within bounds of the board
if (0 <= neighbor[0] < self.dim) and (0 <= neighbor[1] < self.dim):
# The neighbor is covered
if self.cells[neighbor].covered == True:
# This cell is safe. Mark it as safe
self.cells[neighbor].safe = True
if log: print("Cell ({}, {}) deemed safe using ({}, {}).".format(neighbor[0], neighbor[1], i, j))
made_progress = True
return made_progress
return False
def surrounding_mines(self, i, j, log=False):
"""If, for a given cell, the total number of mines (the clue) minus the
number of revealed mines is the number of hidden neighbors, every
hidden neighbor is a mine
In this case, all hidden_neighbors of (i, j) are flagged.
Returns True if all surrounding hidden cells are identified as mines.
False otherwise.
"""
cell = self.cells[i, j]
made_progress = False
if cell.hidden_neighbors == cell.mine_count:
neighbors = [(i+1, j+1), (i+1, j), (i, j+1), (i-1, j+1), \
(i-1, j-1), (i-1, j), (i, j-1), (i+1, j-1)]
for neighbor in neighbors:
# The neighbor is within bounds of the board
if (0 <= neighbor[0] < self.dim) and (0 <= neighbor[1] < self.dim):
# The neighbor is covered
if self.cells[neighbor].covered == True:
# This is a mine.
# Set it to flagged in agent's data structures
self.cells[neighbor].flag = True
self.cells[neighbor].covered = False
# Set it to a mine in the game board
self._board.user_flag(neighbor[0], neighbor[1])
if log: print("Cell ({}, {}) deduced to be a mine using ({}, {})".format(neighbor[0], neighbor[1], i, j))
made_progress = True
return made_progress
return False
def uncover_all_safe_cells(self, log=False):
""" Loop through all cells on the board and excavate any uncovered
cell marked safe
Returns True if any of the excavate_cell calls was successful
"""
success = False
for i in range(self.dim):
for j in range(self.dim):
if self.cells[i, j].covered and self.cells[i, j].safe:
ret = self.excavate_cell(i, j, log)
if ret:
success = True
return success
def mark_safe_cells(self, log=False):
""" Loop through all uncovered cells and mark their neighbors as safe
if all neighbors are safe
Returns True if any of the surrounding_safe calls was successful
"""
success = False
for i in range(self.dim):
for j in range(self.dim):
if not self.cells[i, j].covered:
ret = self.surrounding_safe(i, j, log)
if ret:
success = True
return success
def mark_mine_cells(self, log=False):
""" Loop through all uncovered cells and flag their neighbors
if all neighbors are identified as mines
Returns True if any of the surrounding_mines calls was successful
"""
success = False
for i in range(self.dim):
for j in range(self.dim):
if not self.cells[i, j].covered:
ret = self.surrounding_mines(i, j, log)
if ret:
success = True
return success
def solve(self, interactive=False, log=False, delay=0):
self.excavate_cell(0, 0)
while(True):
if interactive:
input("Press Enter to continue...")
safe_check = self.mark_safe_cells(log)
mine_check = self.mark_mine_cells(log)
uncover_try = self.uncover_all_safe_cells(log)
if(self._board.check_gameover_conditions()):
return self._board.score
# If nothing was accomplished on this iteration, reveal some
# random cell
if not safe_check and not mine_check and not uncover_try:
done = False
while (not done):
i, j = np.random.randint(0, self.dim, size=2)
if self.cells[i, j].covered:
if log: print("\tRandomly selected ({}, {}) to uncover.".format(i, j))
self.excavate_cell(i, j)
self.random_clicks += 1
done = True
self._board.fig.canvas.draw()
# delay so that we can watch on the GUI
sleep(delay)
return self._board.score