-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsweeper.py
281 lines (256 loc) · 12.5 KB
/
sweeper.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
import random
import sys
import pygame
from pygame.locals import *
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREY = (220, 220, 220)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)
ONE_Img = pygame.image.load("img/1.bmp")
TWO_Img = pygame.image.load("img/2.bmp")
THREE_Img = pygame.image.load("img/3.bmp")
FOUR_Img = pygame.image.load("img/4.bmp")
FIVE_Img = pygame.image.load("img/5.bmp")
SIX_Img = pygame.image.load("img/6.bmp")
SEVEN_Img = pygame.image.load("img/7.bmp")
EIGHT_Img = pygame.image.load("img/8.bmp")
FLAG_Img = pygame.image.load("img/flag.bmp")
NOTSURE_Img = pygame.image.load("img/notsure.bmp")
IMGS = (ONE_Img, TWO_Img, THREE_Img, FOUR_Img, FIVE_Img, SIX_Img, SEVEN_Img, EIGHT_Img)
def bomber(length, width, bomb, m, n):
"""
Place bombs randomly (position of first click and its surroundings cannot be bombs)
:param length: length of the board
:param width: width of the board
:param bomb: number of bombs
:param m: horizontal position of first click
:param n: vertical position of first click
:return: list of bomb positions
"""
forbidden = ((m - 1, n - 1), (m - 1, n), (m - 1, n + 1), (m, n - 1), (m, n), (m, n + 1), (m + 1, n - 1), (m + 1, n),
(m + 1, n + 1))
candidates = [(x, y) for x in range(length) for y in range(width) if (x, y) not in forbidden]
return random.sample(candidates, bomb)
def get_cal(length, width, bombs):
"""
Get numbers of non-bomb positions
:param length: length of the board
:param width: width of the board
:param bombs: list of bomb positions
:return: matrix of numbers
"""
cals = [[0 for _ in range(width)] for _ in range(length)]
for i in range(length):
for j in range(width):
if (i, j) not in bombs:
tests = [(m, n) for m in (i - 1, i, i + 1) for n in (j - 1, j, j + 1)]
cals[i][j] = sum([t in bombs for t in tests])
return cals
def position(mouse_x, mouse_y, length, width):
"""
Get the position on the board representing the mouse click
:param mouse_x: horizontal position of mouse click
:param mouse_y: vertical position of mouse click
:param length: length of the board
:param width: width of the board
:return: position on the board
"""
x, y = (mouse_x - 20) // 15, (mouse_y - 40) // 15
if 0 <= x < length and 0 <= y < width:
return x, y
return -1, -1
def expand(x, y, cals, shown, length, width):
"""
Expand empty position with no bombs surrounded
:param x: horizontal position
:param y: vertical position
:param cals: matrix of numbers
:param shown: list of positions shown
:param length: length of the board
:param width: width of the board
:return: modified list of positions shown
"""
for m in (x - 1, x, x + 1):
for n in (y - 1, y, y + 1):
if 0 <= m < length and 0 <= n < width:
if (m != x or n != y) and (m, n) not in shown:
shown.append((m, n))
if cals[m][n] == 0:
expand(m, n, cals, shown, length, width)
return shown
def doubleclick(x, y, bombs, cal, flags):
"""
Shortcut for double click
:param x: horizontal position of the click
:param y: vertical position of the click
:param bombs: list of bomb positions
:param cal: number of the position
:param flags: list of flags
:return: status after the double click (0 for wrong judgment, 1 for right judgment, -1 for invalid double click),
list of positions to expand (empty list for status 0 or -1)
"""
tests = [(m, n) for m in (x - 1, x, x + 1) for n in (y - 1, y, y + 1) if (m, n) not in flags]
if cal == 9 - len(tests):
for t in tests:
if t in bombs:
return 0, []
return 1, tests
return -1, []
def main():
length, width, bomb = 9, 9, 10 # length of the board, width of the board, number of bombs
pygame.init()
fps, fps_clock = 30, pygame.time.Clock()
displaysurf = pygame.display.set_mode((length * 15 + 40, width * 15 + 60 + 20), 0, 32)
pygame.display.set_caption("Minesweeper")
font1 = pygame.font.SysFont("simsunnsimsun", 15)
time1 = font1.render("Time: 0", True, BLACK, GREY)
rect1 = time1.get_rect()
rect1.center = (50, 20)
bomb1 = font1.render("Bombs: 0", True, BLACK, GREY)
rect2 = bomb1.get_rect()
rect2.center = (length * 15 - 10, 20)
easy1 = font1.render("Easy", True, GREEN, GREY)
rect3 = easy1.get_rect()
rect3.center = (30, width * 15 + 50)
normal1 = font1.render("Norm", True, YELLOW, GREY)
rect4 = normal1.get_rect()
rect4.center = (80, width * 15 + 50)
hard1 = font1.render("Hard", True, RED, GREY)
rect5 = hard1.get_rect()
rect5.center = (130, width * 15 + 50)
difficulty, game, bombed = 1, 2, False # 1 easy, 2 normal, 3 hard; 0 game ends, 1 game continues, 2 game starts
bombs, shown = [], [] # list of bomb positions, list of positions shown
time, second = 0, 0
cals = [[0 for _ in range(width)] for _ in range(length)] # initialize matrix of numbers
flags, notsure = [], [] # list of flags, list of question marks
red = (-1, -1) # bombing point
while True:
displaysurf.fill(GREY)
for i in range(length + 1):
pygame.draw.line(displaysurf, BLACK, (20 + i * 15, 40), (20 + i * 15, 40 + width * 15), 1)
for i in range(width + 1):
pygame.draw.line(displaysurf, BLACK, (20, 40 + i * 15), (20 + length * 15, 40 + i * 15), 1)
for i, j in shown:
cal = cals[i][j]
if cal == 0:
pygame.draw.rect(displaysurf, WHITE, (21 + i * 15, 41 + j * 15, 14, 14), 0)
else:
displaysurf.blit(IMGS[cal - 1], (22 + i * 15, 42 + j * 15))
for i, j in flags:
displaysurf.blit(FLAG_Img, (22 + i * 15, 42 + j * 15))
for i, j in notsure:
displaysurf.blit(NOTSURE_Img, (22 + i * 15, 42 + j * 15))
if bombed:
for i, j in bombs:
pygame.draw.circle(displaysurf, BLACK, (28 + i * 15, 48 + j * 15), 6)
pygame.draw.circle(displaysurf, RED, (28 + red[0] * 15, 48 + red[1] * 15), 6)
if game == 0 and not bombed: # win
bomb = 0
for i, j in bombs:
pygame.draw.rect(displaysurf, GREEN, (21 + i * 15, 41 + j * 15, 14, 14))
pygame.draw.circle(displaysurf, BLACK, (28 + i * 15, 48 + j * 15), 6)
time1 = font1.render("Time: " + str(second), True, BLACK, GREY)
displaysurf.blit(time1, rect1)
bomb1 = font1.render("Bombs: " + str(bomb), True, BLACK, GREY)
displaysurf.blit(bomb1, rect2)
displaysurf.blit(easy1, rect3)
displaysurf.blit(normal1, rect4)
displaysurf.blit(hard1, rect5)
if game != 0:
time += 1
if time % 30 == 0:
second += 1
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_x, mouse_y = event.pos
x, y = position(mouse_x, mouse_y, length, width)
pressed = pygame.mouse.get_pressed() # indices 0 for left click, 1 for middle click, 2 for right click
if 0 <= x < length and 0 <= y < width:
if pressed[0] and pressed[2]: # shortcut for double click
if game == 1 and cals[x][y] > 0 and (x, y) in shown:
temp, to_expands = doubleclick(x, y, bombs, cals[x][y], flags)
if temp == 0: # wrong judgment, game over
pygame.mixer.music.load("aud/001.wav")
pygame.mixer.music.play()
bombed, game = True, 0
red = (-10, -10) # do not show red
if temp == 1: # right judgment, expand surroundings
for m, n in to_expands:
if 0 <= m < length and 0 <= n < width:
if (m, n) not in bombs and (m, n) not in shown:
shown.append((m, n))
if cals[m][n] == 0:
shown = expand(m, n, cals, shown, length, width)
if length * width - len(shown) == len(bombs): # win if all non-bomb positions are shown
pygame.mixer.music.load("aud/002.mid")
pygame.mixer.music.play()
game = 0
if pressed[0] and not pressed[2]: # left click
if game == 1:
if (x, y) not in shown and (x, y) not in flags and (x, y) not in notsure:
if (x, y) in bombs: # click the bomb
pygame.mixer.music.load("aud/001.wav")
pygame.mixer.music.play()
bombed, game = True, 0
red = (x, y)
else:
shown.append((x, y))
if cals[x][y] == 0: # no bombs surrounded
shown = expand(x, y, cals, shown, length, width)
if length * width - len(shown) == len(bombs): # win if all non-bomb positions are shown
pygame.mixer.music.load("aud/002.mid")
pygame.mixer.music.play()
game = 0
elif game == 2: # new game
game = 1
bombs = bomber(length, width, bomb, x, y)
cals = get_cal(length, width, bombs) # store numbers of non-bomb positions
shown.append((x, y))
shown = expand(x, y, cals, shown, length, width) # expand surroundings
else: # restart
game, bombed = 2, False
if difficulty == 1:
length, width, bomb = 9, 9, 10
elif difficulty == 2:
length, width, bomb = 16, 16, 40
else:
length, width, bomb = 16, 30, 99
bombs, shown = [], []
time, second = 0, 0
flags, notsure = [], []
if pressed[2] and not pressed[0]: # right click
if game == 1 and (x, y) not in shown:
if (x, y) not in flags: # no flag at this position
if (x, y) not in notsure: # empty position
flags.append((x, y))
bomb -= 1
else:
notsure.remove((x, y))
else: # with flag at this position
bomb += 1
flags.remove((x, y))
notsure.append((x, y))
if width * 15 + 42 < mouse_y < width * 15 + 58:
change = False
if 15 < mouse_x < 45 and difficulty != 1: # click "Easy"
difficulty, length, width, bomb, change = 1, 9, 9, 10, True
elif 65 < mouse_x < 95 and difficulty != 2: # click "Norm"
difficulty, length, width, bomb, change = 2, 16, 16, 40, True
elif 115 < mouse_x < 145 and difficulty != 3: # click "Hard"
difficulty, length, width, bomb, change = 3, 16, 30, 99, True
if change: # change difficulty level
game, bombed, time, second = 2, False, 0, 0
bombs, shown, flags, notsure = [], [], [], []
displaysurf = pygame.display.set_mode((length * 15 + 40, width * 15 + 60 + 20), 0, 32)
rect3.center = (30, width * 15 + 50)
rect4.center = (80, width * 15 + 50)
rect5.center = (130, width * 15 + 50)
pygame.display.update()
fps_clock.tick(fps)
if __name__ == "__main__":
main()