-
Notifications
You must be signed in to change notification settings - Fork 0
/
game.py
141 lines (121 loc) · 3.99 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
import random
import numpy as np
from functools import lru_cache
basic_style = {
0: '0',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: ' ',
'o': 'o',
'x': 'x',
}, lambda x, s: s
default_style = basic_style
try:
from sty import fg, bg
sty_style = {
0: bg.white + ' ' + bg.rs,
1: bg.white + fg.blue + '1' + fg.rs + bg.rs,
2: bg.white + fg.green + '2' + fg.rs + bg.rs,
3: bg.white + fg.red + '3' + fg.rs + bg.rs,
4: bg.white + fg.da_blue + '4' + fg.rs + bg.rs,
5: bg.white + fg.da_red + '5' + fg.rs + bg.rs,
6: bg.white + fg.da_green + '6' + fg.rs + bg.rs,
7: bg.white + fg.magenta + '7' + fg.rs + bg.rs,
8: bg.white + fg.black + '8' + fg.rs + bg.rs,
9: ' ',
'o': fg.black + 'o' + fg.rs,
'x': fg.black + 'x' + fg.rs,
}, lambda x, s: bg(int(x * 255.0), (255 - (int(x * 255.0))), 0) + s + bg.rs
default_style = sty_style
except ImportError:
pass
def neighbors_do(pos, height, width):
'''Find the neighbors of a given position in a grid.'''
i, j = pos
for ii in range(max(i-1, 0), min(i+2, height)):
for jj in range(max(j-1, 0), min(j+2, width)):
yield (ii, jj)
@lru_cache(maxsize=81)
def neighbors(pos, height, width):
return list(neighbors_do(pos, height, width))
def format_move(view, mines, pos, style=None, risk_matrix=None):
if style is None:
style = default_style
result = [[style[0][v] for v in row] for row in view]
if pos is not None:
if pos in mines:
for (i, j) in mines:
result[i][j] = style[0]['x']
result[pos[0]][pos[1]] = style[0]['o']
else:
i, j = pos
result[i][j] = style[0]['o']
if risk_matrix is not None:
for i, row in enumerate(view):
for j, v in enumerate(row):
if v == 9:
r = risk_matrix[i][j]
result[i][j] = style[1](r, result[i][j])
return '\n'.join(''.join(row) for row in result)
class Game:
def __init__(self, height, width, mines):
self.height = height
self.width = width
self.guessed = set()
self.mines = set()
self.add_mines(mines)
# Adds mines randomly.
def add_mines(self, mines):
for _ in range(mines):
while True:
i = random.randint(0, self.height-1)
j = random.randint(0, self.width-1)
pos = (i, j)
if pos in self.mines:
continue
self.mines.add(pos)
break
def __repr__(self):
return format_move(self.view(), self.mines, None)
# Returns True if a mine was hit.
def guess(self, pos):
if not self.guessed and pos in self.mines:
# If the first guess was unlucky then move the mine.
while pos in self.mines:
self.mines.remove(pos)
self.add_mines(1)
if pos in self.mines:
return True
if pos in self.guessed:
return None
self.spread(pos)
return False
def count_nearby_mines(self, pos):
result = 0
for n in neighbors(pos, self.height, self.width):
if n in self.mines:
result += 1
return result
def spread(self, pos):
'''spreads a guess out'''
if pos in self.guessed:
return
self.guessed.add(pos)
if self.count_nearby_mines(pos) > 0:
return
for n in neighbors(pos, self.height, self.width):
self.spread(n)
def is_won(self):
return len(self.guessed) + len(self.mines) == self.height * self.width
def view(self):
'''machine readable representation of what's seen'''
result = np.zeros((self.height, self.width), dtype=np.int8) + 9
for (i, j) in self.guessed:
result[i, j] = self.count_nearby_mines((i, j))
return result