/
game.py
258 lines (216 loc) · 7.88 KB
/
game.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
import random
import minesweeper_util as u
import minesweeper as mnsw
import math
import time
class MinesweeperGame(object):
def __init__(self, num_mines=None, mine_prob=None):
self.cell_ids = list(self.gen_cells())
self.num_cells = len(self.cell_ids)
assert num_mines is not None or mine_prob is not None
if num_mines is not None:
assert num_mines >= 0 and num_mines <= self.num_cells
mines = [True] * num_mines + [False] * (self.num_cells - num_mines)
random.shuffle(mines)
self.mode = 'minecount'
else:
assert mine_prob >= 0. and mine_prob <= 1.
mines = [random.random() < mine_prob for i in xrange(self.num_cells)]
self.mode = 'mineprob'
self.mine_prob = mine_prob
self.num_mines = len(filter(None, mines))
self.mines = dict((c, m) for c, m in zip(self.cell_ids, mines))
self.cells = dict((c, None) for c in self.cell_ids)
self.yet_to_uncover = self.num_cells - self.num_mines
self.mine_exposed = False
def outcome(self):
if self.mine_exposed:
return 'loss'
elif self.yet_to_uncover == 0:
return 'win'
else:
return None
def can_play_cell(self, cell):
"""whether this cell can be cleared"""
c = self.cells[cell]
return (c is None or c == 'marked')
def is_frontier_cell(self, cell):
"""whether this cell is represented by the 'other' term of the minesweeper solution"""
return self.cells[cell] is None and all(self.cells[neighbor] in (None, 'marked') for neighbor in self.adjacent(cell))
def sweep(self, cell):
if not self.can_play_cell(cell):
return
if self.mines[cell]:
self.mine_exposed = True
return
self.yet_to_uncover -= 1
adj_count = len([c for c in self.adjacent(cell) if self.mines[c]])
self.cells[cell] = adj_count
if adj_count == 0:
for neighbor in self.adjacent(cell):
if self.can_play_cell(neighbor):
self.sweep(neighbor)
def mark(self, cell):
assert self.can_play_cell(cell)
self.cells[cell] = 'marked'
def gen_cells(self):
assert False, 'abstract'
def adjacent(self, cell_id):
assert False, 'abstract'
class GridMinesweeperGame(MinesweeperGame):
def __init__(self, width, height, *args, **kwargs):
self.width = width
self.height = height
super(GridMinesweeperGame, self).__init__(*args, **kwargs)
def gen_cells(self):
for i in xrange(self.width):
for j in xrange(self.height):
yield (i, j)
def adjacent(self, cell):
i, j = cell
for ni in xrange(i - 1, i + 2):
for nj in xrange(j - 1, j + 2):
if (ni >= 0 and ni < self.width and
nj >= 0 and nj < self.height and
(ni, nj) != (i, j)):
yield (ni, nj)
class BoardWrapper(object):
"""convert the gameboard to a form recognizable by the generate_rules() utility function"""
def __init__(self, game):
self.game = game
def toCell(self, cell_id):
cell = self.game.cells[cell_id]
code = {
None: 'x',
'marked': '*',
0: '.',
}.get(cell, str(cell))
return u.BoardCell(code, cell_id)
@property
def cells(self):
return dict((k, self.toCell(k)) for k in self.game.cell_ids)
def total_cells(self):
return self.game.num_cells
def adjacent(self, cell_id):
return dict((k, self.toCell(k)) for k in self.game.adjacent(cell_id))
def autoplay(game, **kwargs):
moves = 0
hopeless = False
while True:
#print game
#print '----'
result = game.outcome()
if result is not None:
return result, moves, hopeless
state = u.generate_rules(BoardWrapper(game), game.num_mines)
if game.mode == 'mineprob':
state[1] = game.mine_prob
solution = mnsw.solve(*state)
def _cells(cells):
for c in cells:
if c is not None:
yield c
else:
for e in game.cell_ids:
if game.is_frontier_cell(e):
yield e
def get_cells(p):
EPSILON = 1e-6
return _cells(k for k, v in solution.iteritems() if abs(v - p) < EPSILON)
mines = get_cells(1.)
safe = list(get_cells(0.))
for c in mines:
game.mark(c)
#print 'marking', c
if safe:
for c in safe:
game.sweep(c)
#print 'clearing', c
else:
# find safest
min_risk = min(solution.values())
if min_risk > .5 - 1e-6:
hopeless = True
safest = list(get_cells(min_risk))
STRATEGY = kwargs.get('strategy')
if STRATEGY:
safest = locpref_strategy(STRATEGY, game, safest)
move = random.choice(safest)
game.sweep(move)
#print 'safest', move
moves += 1
# strategy like:
# [['corner']] -- prefer corners
# [['corner', 'edge']] -- prefer corners/edges
# [['corner'], ['edge']] -- prefer corners, then edges
def locpref_strategy(strategy, game, safest):
def cell_type(cell):
edgex = (cell[0] in (0, game.width - 1))
edgey = (cell[1] in (0, game.height - 1))
if edgex and edgey:
return 'corner'
elif edgex or edgey:
return 'edge'
else:
return 'interior'
filtered_safest = None
for mode in strategy:
filtered_safest = filter(lambda e: cell_type(e) in mode, safest)
if filtered_safest:
break
return filtered_safest or safest
BEGINNER = 'GridMinesweeperGame(8, 8, num_mines=10)'
BEGINNER_NEW = 'GridMinesweeperGame(9, 9, num_mines=10)'
INTERMEDIATE = 'GridMinesweeperGame(16, 16, num_mines=40)'
EXPERT = 'GridMinesweeperGame(16, 30, num_mines=99)'
def run_trial(args):
gamestr, kwargs = args
return autoplay(eval(gamestr), **kwargs)
def trial(new_game_str, tolerance=.5e-3, first_safe=True, threaded=True, **kwargs):
try:
total_games = 0
total_wins = 0
total_hopeless = 0
hopeless_wins = 0
stop = False
if threaded:
import multiprocessing
def gen_trials():
pool = multiprocessing.Pool()
def args():
while not stop:
yield (new_game_str, kwargs)
return pool.imap_unordered(run_trial, args())
else:
def gen_trials():
while not stop:
yield run_trial((new_game_str, kwargs))
start = time.time()
for t in gen_trials():
result, moves, hopeless = t
loss_on_first_move = (result == 'loss' and moves == 1)
if loss_on_first_move and first_safe:
continue
total_games += 1
if result == 'win':
total_wins += 1
if hopeless:
total_hopeless += 1
if result == 'win':
hopeless_wins += 1
p = float(total_wins) / total_games
err = (p * (1-p) / total_games)**.5
if err == 0:
err = 1.
est_trials = -1
else:
est_trials = int(round(p * (1-p) / tolerance**2))
rate = total_games / (time.time() - start)
est_time_left = (est_trials - total_games) / rate
terminate = (err <= tolerance)
if terminate or total_games % 5 == 0:
print '%d/%d %d/%d %.4f+/-%.4f %d %.1f' % (total_wins, total_games, hopeless_wins, total_hopeless, p, err, est_trials, est_time_left)
if terminate:
return (total_games, total_wins, total_hopeless, hopeless_wins)
finally:
stop = True