-
Notifications
You must be signed in to change notification settings - Fork 0
/
AI_0.py
284 lines (202 loc) · 10.4 KB
/
AI_0.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
'''
This is the AI-0 class that finds the bomb based on a risk score, knowledge gain
and minimized probing overlap
@authors Andrea Covre
@version 1.8
'''
import json
import copy
import random
class AI0:
def __init__(self, board):
self.board = board
self.rows = board.rows
self.cols = board.cols
self.safe = board.safe
self.numBombs = board.numBombs
self.maxValue = 8
self.knowledge = []
self.untaintedKnowledge = []
self.riskKnowledgeGain = []
self.explorativeKnowledgeGain = []
self.gridOrder = []
self.nextGridOrderOptions = []
self.nextRiskOptions = []
self.nextMove = [self.safe[1], self.safe[0]]
self.bombsHit = 0
self.bombsLocation = []
self.isActive = True
self.generateGridOrder()
for i in range(self.rows):
row = []
rowExpGain = []
rowUntainted = []
for j in range(self.cols):
if (j == self.safe[0] and i == self.safe[1]):
row.append([0, "S"])
rowUntainted.append(0)
else:
row.append([0, "U"])
rowUntainted.append(0)
rowExpGain.append(0)
self.untaintedKnowledge.append(rowUntainted)
self.knowledge.append(row)
self.explorativeKnowledgeGain.append(rowExpGain)
'''
This function generates a list of cells that orderly spaced on the grid
as to decrease the number of isolated unmined squares
'''
def generateGridOrder(self):
for i in range(1, self.rows, 3):
for j in range(1, self.cols, 3):
if (i < self.rows and j < self.cols):
self.gridOrder.append([i, j])
'''
Function called whenever a new square is mined that integrates the new knowledge given by the board
after mining a square, start the procedures needed to update all the knowledge maps and compute the next move.
@param {int} x coordinate of the square mined
@param {int} y coordinate of the square mined
@param {int} mined value of the mined square
'''
def update(self, x, y, mined):
if (mined == 9):
mined = "X"
self.bombsLocation.append([y, x])
self.knowledge[y][x][0] = 9
self.knowledge[y][x][1] = "X"
self.bombsHit = 1 + self.bombsHit
else:
self.untaintedKnowledge[y][x] = mined
self.knowledge[y][x][0] = mined
self.knowledge[y][x][1] = "M"
self.updateProcedure()
'''
Execute the update steps needed
The update routine is:
- offset the mined values -> offsetMinedValues()
- update knowledge -> updateKnowledge()
- update knowledge gain maps -> updateKnowledgeGain()
- compute the next move -> getNextMove()
'''
def updateProcedure(self):
self.offsetMinedValues()
self.updateKnowledge()
self.updateKnowledgeGain()
self.getNextMove()
'''
Offest mined squares based on the mines already identified
'''
def offsetMinedValues(self):
for i in range(self.rows):
for j in range(self.cols):
if (self.knowledge[i][j][1] == "M"):
i_T = None
j_T = None
self.knowledge[i][j][0] = self.untaintedKnowledge[i][j]
#Loop for a 3x3 moving kernel
for k in range(-1, 2):
for w in range(-1, 2):
i_T = i + k
j_T = j + w
#Guarding the boundaries, don't compute the kernel cells that are outside the board
if (i_T >= 0 and i_T < self.rows and j_T >= 0 and j_T < self.cols):
#If the current square was mined and the square we are checking is a bomb, then decrease
#the mined value of the current square
if (self.knowledge[i_T][j_T][1] == "X" or self.knowledge[i_T][j_T][1] == "H"):
self.knowledge[i][j][0] = - 1 + self.knowledge[i][j][0]
'''
Update the knowledge maps related to the square flags and risk scores
'''
def updateKnowledge(self):
newKnowledge = copy.deepcopy(self.knowledge)
for i in range(self.rows):
for j in range(self.cols):
if (self.knowledge[i][j][1] != "M"):
i_T = None
j_T = None
if self.knowledge[i][j][1] == "U":
newKnowledge[i][j][0] = 0
for k in range(-1, 2):
for w in range(-1, 2):
i_T = i + k
j_T = j + w
if (i_T >= 0 and i_T < self.rows and j_T >= 0 and j_T < self.cols):
#If the this nearby square is == 0, then the current square is safe and flag it with 'S'
if (self.knowledge[i_T][j_T][1] == "M"):
if (self.knowledge[i_T][j_T][0] == 0 and newKnowledge[i][j][1] == "U"):
newKnowledge[i][j][1] = "S"
#Increase the risk score of the current square based on the mined value of this nearby square
if (self.knowledge[i_T][j_T][0] != 0 and newKnowledge[i][j][1] == "U" and self.knowledge[i_T][j_T][1] == "M"):
newKnowledge[i][j][0] = self.knowledge[i_T][j_T][0] + newKnowledge[i][j][0]
self.knowledge = copy.deepcopy(newKnowledge)
'''
This function follows the same procedures as update(), but updates the knowledge gain map
which can only be updated once the knowledge map has been updated from the lates mining
'''
def updateKnowledgeGain(self):
#Reset the arrays of next moves
self.nextRiskOptions = []
for i in range(self.rows):
for j in range(self.cols):
self.explorativeKnowledgeGain[i][j] = 0
if (self.knowledge[i][j][1] != "M"):
i_T = None
j_T = None
for k in range(-1, 2):
for w in range(-1, 2):
i_T = i + k
j_T = j + w
if (i_T >= 0 and i_T < self.rows and j_T >= 0 and j_T < self.cols):
#Increase explorative knowledge gain based on how many non-mined squares are around
if (self.knowledge[i_T][j_T][1] == "U"):
self.explorativeKnowledgeGain[i][j] = 1 + self.explorativeKnowledgeGain[i][j]
#Consider only the unsafe squares
if (self.knowledge[i][j][1] == "U"):
#If no option yet, then add the current square to the list
if (len(self.nextRiskOptions) == 0):
self.nextRiskOptions = [[i, j]]
else:
#If the current square has the highest risk knowledge gain so far then make it the new record
if (self.knowledge[i][j][0] > self.knowledge[self.nextRiskOptions[0][0]][self.nextRiskOptions[0][1]][0]):
self.nextRiskOptions = [[i, j]]
#If the current square has the same record high risk then check explorative knowledge gain
elif (self.knowledge[i][j][0] == self.knowledge[self.nextRiskOptions[0][0]][self.nextRiskOptions[0][1]][0]):
#If the current square has the highest explorative knowledge gain then make it the new record
if (self.explorativeKnowledgeGain[i][j] > self.explorativeKnowledgeGain[self.nextRiskOptions[0][0]][self.nextRiskOptions[0][1]]):
self.nextRiskOptions = [[i, j]]
#If the current square has the same record high risk and explorative knowledge gain then add it to the other records
elif (self.explorativeKnowledgeGain[i][j] == self.explorativeKnowledgeGain[self.nextRiskOptions[0][0]][self.nextRiskOptions[0][1]]):
self.nextRiskOptions.append([i, j])
'''
Gets the next move prioritizing risk, and then explorative gain
'''
def getNextMove(self):
#if all bombs have been found or if there is not other smart move then close the game
if (self.bombsHit == self.numBombs or len(self.nextRiskOptions) == 0):
self.isActive = False
return
#updating the next best grid order option including all the grid order options with max explorative gain
self.nextGridOrderOptions = []
for square in self.gridOrder:
if (self.explorativeKnowledgeGain[square[0]][square[1]] == 9):
self.nextGridOrderOptions.append(square)
self.nextGridOrderOptions = []; #uncomment to deactivate nextGridOrder
#If there is no next risk option available with positve risk then pick an option from the grid order
if (self.knowledge[self.nextRiskOptions[0][0]][self.nextRiskOptions[0][1]][0] == 0 and len(self.nextGridOrderOptions) != 0):
#this.nextMove = this.nextUnsafeOptions[Math.floor(Math.random() * this.nextUnsafeOptions.length)];
self.nextMove = self.nextGridOrderOptions[0]
#Otherwise pick randomly one of the highest risk score squares (and then highest explorative gain)
else:
self.nextMove = self.nextRiskOptions[random.randrange(0, len(self.nextRiskOptions))]
'''
Play the next move
'''
def playNext(self):
self.update(self.nextMove[1], self.nextMove[0], self.board.mine(self.nextMove[1], self.nextMove[0]))
'''
Play the whole game
'''
def playGame(self):
while(self.isActive):
self.playNext()
#print("%d/%d bombs found" % (self.bombsHit, self.numBombs))