Skip to content

Commit f34dd4d

Browse files
authored
Merge pull request #8 from ripexz/retry
Restart, timer, refactoring
2 parents a2b4862 + cec439c commit f34dd4d

File tree

2 files changed

+161
-140
lines changed

2 files changed

+161
-140
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,5 @@ Contents:
1313
To Do:
1414
----------
1515
- Have specific number of mines, rather than random
16-
- Time counter
1716
- Highscore table
1817
- Adjustable grid and mine count via UI

minesweeper.py

Lines changed: 161 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import random
88
import platform
99
import time
10+
from datetime import time, date, datetime
1011

1112
SIZE_X = 10
1213
SIZE_Y = 10
@@ -18,216 +19,237 @@
1819
BTN_CLICK = "<Button-1>"
1920
BTN_FLAG = "<Button-2>" if platform.system() == 'Darwin' else "<Button-3>"
2021

22+
window = None
23+
2124
class Minesweeper:
2225

23-
def __init__(self, master):
26+
def __init__(self, tk):
2427

2528
# import images
26-
self.tiles = {
29+
self.images = {
2730
"plain": PhotoImage(file = "images/tile_plain.gif"),
2831
"clicked": PhotoImage(file = "images/tile_clicked.gif"),
2932
"mine": PhotoImage(file = "images/tile_mine.gif"),
3033
"flag": PhotoImage(file = "images/tile_flag.gif"),
3134
"wrong": PhotoImage(file = "images/tile_wrong.gif"),
3235
"numbers": []
3336
}
34-
for x in range(1, 9):
35-
self.tiles["numbers"].append(PhotoImage(file = "images/tile_"+str(x)+".gif"))
37+
for i in range(1, 9):
38+
self.images["numbers"].append(PhotoImage(file = "images/tile_"+str(i)+".gif"))
3639

3740
# set up frame
38-
frame = Frame(master)
39-
frame.pack()
41+
self.tk = tk
42+
self.frame = Frame(self.tk)
43+
self.frame.pack()
44+
45+
# set up labels/UI
46+
self.labels = {
47+
"time": Label(self.frame, text = "00:00:00"),
48+
"mines": Label(self.frame, text = "Mines: 0"),
49+
"flags": Label(self.frame, text = "Flags: 0")
50+
}
51+
self.labels["time"].grid(row = 0, column = 0, columnspan = SIZE_Y) # top full width
52+
self.labels["mines"].grid(row = SIZE_X+1, column = 0, columnspan = SIZE_Y/2) # bottom left
53+
self.labels["flags"].grid(row = SIZE_X+1, column = SIZE_Y/2-1, columnspan = SIZE_Y/2) # bottom right
4054

41-
# show "Minesweeper" at the top
42-
self.label1 = Label(frame, text="Minesweeper")
43-
self.label1.grid(row = 0, column = 0, columnspan = 10)
55+
self.restart() # start game
56+
self.updateTimer() # init timer
4457

58+
def setup(self):
4559
# create flag and clicked tile variables
46-
self.flags = 0
47-
self.correct_flags = 0
48-
self.clicked = 0
60+
self.flagCount = 0
61+
self.correctFlagCount = 0
62+
self.clickedCount = 0
63+
self.startTime = None
4964

5065
# create buttons
51-
self.buttons = dict({})
66+
self.tiles = dict({})
5267
self.mines = 0
5368
for x in range(0, SIZE_X):
5469
for y in range(0, SIZE_Y):
5570
if y == 0:
56-
self.buttons[x] = {}
71+
self.tiles[x] = {}
5772

5873
id = str(x) + "_" + str(y)
5974
isMine = False
6075

6176
# tile image changeable for debug reasons:
62-
gfx = self.tiles["plain"]
77+
gfx = self.images["plain"]
6378

6479
# currently random amount of mines
6580
if random.uniform(0.0, 1.0) < 0.1:
6681
isMine = True
6782
self.mines += 1
6883

69-
self.buttons[x][y] = {
84+
tile = {
7085
"id": id,
7186
"isMine": isMine,
7287
"state": STATE_DEFAULT,
7388
"coords": {
7489
"x": x,
7590
"y": y
7691
},
77-
"widget": Button(frame, image = gfx),
92+
"button": Button(self.frame, image = gfx),
7893
"mines": 0 # calculated after grid is built
7994
}
8095

81-
self.buttons[x][y]["widget"].bind(BTN_CLICK, self.lclicked_wrapper(x, y))
82-
self.buttons[x][y]["widget"].bind(BTN_FLAG, self.rclicked_wrapper(x, y))
96+
tile["button"].bind(BTN_CLICK, self.onClickWrapper(x, y))
97+
tile["button"].bind(BTN_FLAG, self.onRightClickWrapper(x, y))
98+
tile["button"].grid( row = x+1, column = y ) # offset by 1 row for timer
8399

84-
# lay buttons in grid
85-
self.buttons[x][y]["widget"].grid( row = x, column = y )
100+
self.tiles[x][y] = tile
86101

87102
# loop again to find nearby mines and display number on tile
88103
for x in range(0, SIZE_X):
89104
for y in range(0, SIZE_Y):
90-
nearby_mines = 0
91-
nearby_mines += 1 if self.check_for_mines(x-1, y-1) else 0 #top right
92-
nearby_mines += 1 if self.check_for_mines(x-1, y) else 0 #top middle
93-
nearby_mines += 1 if self.check_for_mines(x-1, y+1) else 0 #top left
94-
nearby_mines += 1 if self.check_for_mines(x, y-1) else 0 #left
95-
nearby_mines += 1 if self.check_for_mines(x, y+1) else 0 #right
96-
nearby_mines += 1 if self.check_for_mines(x+1, y-1) else 0 #bottom right
97-
nearby_mines += 1 if self.check_for_mines(x+1, y) else 0 #bottom middle
98-
nearby_mines += 1 if self.check_for_mines(x+1, y+1) else 0 #bottom left
99-
100-
self.buttons[x][y]["mines"] = nearby_mines
101-
102-
#add mine and count at the end
103-
self.label2 = Label(frame, text = "Mines: "+str(self.mines))
104-
self.label2.grid(row = SIZE_X+1, column = 0, columnspan = SIZE_Y/2)
105-
106-
self.label3 = Label(frame, text = "Flags: "+str(self.flags))
107-
self.label3.grid(row = SIZE_X+1, column = SIZE_Y/2-1, columnspan = SIZE_Y/2)
105+
mc = 0
106+
for n in self.getNeighbors(x, y):
107+
mc += 1 if n["isMine"] else 0
108+
self.tiles[x][y]["mines"] = mc
108109

109-
## End of __init__
110+
def restart(self):
111+
self.setup()
112+
self.refreshLabels()
110113

111-
def check_for_mines(self, x, y):
112-
try:
113-
return self.buttons[x][y]["isMine"]
114-
except KeyError:
115-
pass
114+
def refreshLabels(self):
115+
self.labels["flags"].config(text = "Flags: "+str(self.flagCount))
116+
self.labels["mines"].config(text = "Mines: "+str(self.mines))
116117

117-
def lclicked_wrapper(self, x, y):
118-
return lambda Button: self.lclicked(self.buttons[x][y])
118+
def gameOver(self, won):
119+
for x in range(0, SIZE_X):
120+
for y in range(0, SIZE_Y):
121+
if self.tiles[x][y]["isMine"] == False and self.tiles[x][y]["state"] == STATE_FLAGGED:
122+
self.tiles[x][y]["button"].config(image = self.images["wrong"])
123+
if self.tiles[x][y]["isMine"] == True and self.tiles[x][y]["state"] != STATE_FLAGGED:
124+
self.tiles[x][y]["button"].config(image = self.images["mine"])
119125

120-
def rclicked_wrapper(self, x, y):
121-
return lambda Button: self.rclicked(self.buttons[x][y])
126+
self.tk.update()
122127

123-
def lclicked(self, button_data):
124-
if button_data["isMine"] == True:
128+
msg = "You Win! Play again?" if won else "You Lose! Play again?"
129+
res = tkMessageBox.askyesno("Game Over", msg)
130+
if res:
131+
self.restart()
132+
else:
133+
self.tk.quit()
134+
135+
def updateTimer(self):
136+
ts = "00:00:00"
137+
if self.startTime != None:
138+
delta = datetime.now() - self.startTime
139+
ts = str(delta).split('.')[0] # drop ms
140+
if delta.total_seconds() < 36000:
141+
ts = "0" + ts # zero-pad
142+
self.labels["time"].config(text = ts)
143+
self.frame.after(100, self.updateTimer)
144+
145+
def getNeighbors(self, x, y):
146+
neighbors = []
147+
coords = [
148+
{"x": x-1, "y": y-1}, #top right
149+
{"x": x-1, "y": y}, #top middle
150+
{"x": x-1, "y": y+1}, #top left
151+
{"x": x, "y": y-1}, #left
152+
{"x": x, "y": y+1}, #right
153+
{"x": x+1, "y": y-1}, #bottom right
154+
{"x": x+1, "y": y}, #bottom middle
155+
{"x": x+1, "y": y+1}, #bottom left
156+
]
157+
for n in coords:
158+
try:
159+
neighbors.append(self.tiles[n["x"]][n["y"]])
160+
except KeyError:
161+
pass
162+
return neighbors
163+
164+
def onClickWrapper(self, x, y):
165+
return lambda Button: self.onClick(self.tiles[x][y])
166+
167+
def onRightClickWrapper(self, x, y):
168+
return lambda Button: self.onRightClick(self.tiles[x][y])
169+
170+
def onClick(self, tile):
171+
if self.startTime == None:
172+
self.startTime = datetime.now()
173+
174+
if tile["isMine"] == True:
125175
# end game
126-
self.gameover()
176+
self.gameOver(False)
177+
return
178+
179+
# change image
180+
if tile["mines"] == 0:
181+
tile["button"].config(image = self.images["clicked"])
182+
self.clearSurroundingTiles(tile["id"])
127183
else:
128-
# change image
129-
if button_data["mines"] == 0:
130-
button_data["widget"].config(image = self.tiles["clicked"])
131-
self.clear_empty_tiles(button_data["id"])
132-
else:
133-
button_data["widget"].config(image = self.tiles["numbers"][button_data["mines"]-1])
134-
# if not already set as clicked, change state and count
135-
if button_data["state"] != STATE_CLICKED:
136-
button_data["state"] = STATE_CLICKED
137-
self.clicked += 1
138-
if self.clicked == 100 - self.mines:
139-
self.victory()
140-
141-
def rclicked(self, button_data):
184+
tile["button"].config(image = self.images["numbers"][tile["mines"]-1])
185+
# if not already set as clicked, change state and count
186+
if tile["state"] != STATE_CLICKED:
187+
tile["state"] = STATE_CLICKED
188+
self.clickedCount += 1
189+
if self.clickedCount == (SIZE_X * SIZE_Y) - self.mines:
190+
self.gameOver(True)
191+
192+
def onRightClick(self, tile):
193+
if self.startTime == None:
194+
self.startTime = datetime.now()
195+
142196
# if not clicked
143-
if button_data["state"] == STATE_DEFAULT:
144-
button_data["widget"].config(image = self.tiles["flag"])
145-
button_data["state"] = STATE_FLAGGED
146-
button_data["widget"].unbind(BTN_CLICK)
197+
if tile["state"] == STATE_DEFAULT:
198+
tile["button"].config(image = self.images["flag"])
199+
tile["state"] = STATE_FLAGGED
200+
tile["button"].unbind(BTN_CLICK)
147201
# if a mine
148-
if button_data["isMine"] == True:
149-
self.correct_flags += 1
150-
self.flags += 1
151-
self.update_flags()
202+
if tile["isMine"] == True:
203+
self.correctFlagCount += 1
204+
self.flagCount += 1
205+
self.refreshLabels()
152206
# if flagged, unflag
153-
elif button_data["state"] == 2:
154-
button_data["widget"].config(image = self.tiles["plain"])
155-
button_data["state"] = 0
156-
button_data["widget"].bind(BTN_CLICK, self.lclicked_wrapper(button_data["coords"]["x"], button_data["coords"]["y"]))
207+
elif tile["state"] == 2:
208+
tile["button"].config(image = self.images["plain"])
209+
tile["state"] = 0
210+
tile["button"].bind(BTN_CLICK, self.onClickWrapper(tile["coords"]["x"], tile["coords"]["y"]))
157211
# if a mine
158-
if button_data["isMine"] == True:
159-
self.correct_flags -= 1
160-
self.flags -= 1
161-
self.update_flags()
162-
163-
def check_tile(self, x, y, queue):
164-
try:
165-
if self.buttons[x][y]["state"] == STATE_DEFAULT:
166-
if self.buttons[x][y]["mines"] == 0:
167-
self.buttons[x][y]["widget"].config(image = self.tiles["clicked"])
168-
queue.append(self.buttons[x][y]["id"])
169-
else:
170-
self.buttons[x][y]["widget"].config(image = self.tiles["numbers"][self.buttons[x][y]["mines"]-1])
171-
self.buttons[x][y]["state"] = STATE_CLICKED
172-
self.clicked += 1
173-
except KeyError:
174-
pass
175-
176-
def clear_empty_tiles(self, id):
212+
if tile["isMine"] == True:
213+
self.correctFlagCount -= 1
214+
self.flagCount -= 1
215+
self.refreshLabels()
216+
217+
def clearSurroundingTiles(self, id):
177218
queue = deque([id])
178219

179220
while len(queue) != 0:
180221
key = queue.popleft()
181222
parts = key.split("_")
182-
source_x = int(parts[0])
183-
source_y = int(parts[1])
184-
185-
self.check_tile(source_x-1, source_y-1, queue) #top right
186-
self.check_tile(source_x-1, source_y, queue) #top middle
187-
self.check_tile(source_x-1, source_y+1, queue) #top left
188-
self.check_tile(source_x, source_y-1, queue) #left
189-
self.check_tile(source_x, source_y+1, queue) #right
190-
self.check_tile(source_x+1, source_y-1, queue) #bottom right
191-
self.check_tile(source_x+1, source_y, queue) #bottom middle
192-
self.check_tile(source_x+1, source_y+1, queue) #bottom left
193-
194-
def reveal(self):
195-
for x in range(0, SIZE_X):
196-
for y in range(0, SIZE_Y):
197-
if self.buttons[x][y]["isMine"] == False and self.buttons[x][y]["state"] == STATE_FLAGGED:
198-
self.buttons[x][y]["widget"].config(image = self.tiles["wrong"])
199-
if self.buttons[x][y]["isMine"] == True and self.buttons[x][y]["state"] != STATE_FLAGGED:
200-
self.buttons[x][y]["widget"].config(image = self.tiles["mine"])
201-
global root
202-
root.update()
203-
204-
def gameover(self):
205-
self.reveal()
206-
tkMessageBox.showinfo("Game Over", "You Lose!")
207-
global root
208-
root.destroy()
209-
210-
def victory(self):
211-
self.reveal()
212-
tkMessageBox.showinfo("Game Over", "You Win!")
213-
global root
214-
root.destroy()
215-
216-
def update_flags(self):
217-
self.label3.config(text = "Flags: "+str(self.flags))
223+
x = int(parts[0])
224+
y = int(parts[1])
225+
226+
for tile in self.getNeighbors(x, y):
227+
self.clearTile(tile, queue)
228+
229+
def clearTile(self, tile, queue):
230+
if tile["state"] != STATE_DEFAULT:
231+
return
232+
233+
if tile["mines"] == 0:
234+
tile["button"].config(image = self.images["clicked"])
235+
queue.append(tile["id"])
236+
else:
237+
tile["button"].config(image = self.images["numbers"][tile["mines"]-1])
238+
239+
tile["state"] = STATE_CLICKED
240+
self.clickedCount += 1
218241

219242
### END OF CLASSES ###
220243

221244
def main():
222-
global root
223-
# create Tk widget
224-
root = Tk()
245+
# create Tk instance
246+
window = Tk()
225247
# set program title
226-
root.title("Minesweeper")
248+
window.title("Minesweeper")
227249
# create game instance
228-
minesweeper = Minesweeper(root)
250+
minesweeper = Minesweeper(window)
229251
# run event loop
230-
root.mainloop()
252+
window.mainloop()
231253

232254
if __name__ == "__main__":
233255
main()

0 commit comments

Comments
 (0)