Skip to content

Commit

Permalink
Merge pull request #8 from ripexz/retry
Browse files Browse the repository at this point in the history
Restart, timer, refactoring
  • Loading branch information
ripexz authored Feb 25, 2020
2 parents a2b4862 + cec439c commit f34dd4d
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 140 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,5 @@ Contents:
To Do:
----------
- Have specific number of mines, rather than random
- Time counter
- Highscore table
- Adjustable grid and mine count via UI
300 changes: 161 additions & 139 deletions minesweeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import random
import platform
import time
from datetime import time, date, datetime

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

window = None

class Minesweeper:

def __init__(self, master):
def __init__(self, tk):

# import images
self.tiles = {
self.images = {
"plain": PhotoImage(file = "images/tile_plain.gif"),
"clicked": PhotoImage(file = "images/tile_clicked.gif"),
"mine": PhotoImage(file = "images/tile_mine.gif"),
"flag": PhotoImage(file = "images/tile_flag.gif"),
"wrong": PhotoImage(file = "images/tile_wrong.gif"),
"numbers": []
}
for x in range(1, 9):
self.tiles["numbers"].append(PhotoImage(file = "images/tile_"+str(x)+".gif"))
for i in range(1, 9):
self.images["numbers"].append(PhotoImage(file = "images/tile_"+str(i)+".gif"))

# set up frame
frame = Frame(master)
frame.pack()
self.tk = tk
self.frame = Frame(self.tk)
self.frame.pack()

# set up labels/UI
self.labels = {
"time": Label(self.frame, text = "00:00:00"),
"mines": Label(self.frame, text = "Mines: 0"),
"flags": Label(self.frame, text = "Flags: 0")
}
self.labels["time"].grid(row = 0, column = 0, columnspan = SIZE_Y) # top full width
self.labels["mines"].grid(row = SIZE_X+1, column = 0, columnspan = SIZE_Y/2) # bottom left
self.labels["flags"].grid(row = SIZE_X+1, column = SIZE_Y/2-1, columnspan = SIZE_Y/2) # bottom right

# show "Minesweeper" at the top
self.label1 = Label(frame, text="Minesweeper")
self.label1.grid(row = 0, column = 0, columnspan = 10)
self.restart() # start game
self.updateTimer() # init timer

def setup(self):
# create flag and clicked tile variables
self.flags = 0
self.correct_flags = 0
self.clicked = 0
self.flagCount = 0
self.correctFlagCount = 0
self.clickedCount = 0
self.startTime = None

# create buttons
self.buttons = dict({})
self.tiles = dict({})
self.mines = 0
for x in range(0, SIZE_X):
for y in range(0, SIZE_Y):
if y == 0:
self.buttons[x] = {}
self.tiles[x] = {}

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

# tile image changeable for debug reasons:
gfx = self.tiles["plain"]
gfx = self.images["plain"]

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

self.buttons[x][y] = {
tile = {
"id": id,
"isMine": isMine,
"state": STATE_DEFAULT,
"coords": {
"x": x,
"y": y
},
"widget": Button(frame, image = gfx),
"button": Button(self.frame, image = gfx),
"mines": 0 # calculated after grid is built
}

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

# lay buttons in grid
self.buttons[x][y]["widget"].grid( row = x, column = y )
self.tiles[x][y] = tile

# loop again to find nearby mines and display number on tile
for x in range(0, SIZE_X):
for y in range(0, SIZE_Y):
nearby_mines = 0
nearby_mines += 1 if self.check_for_mines(x-1, y-1) else 0 #top right
nearby_mines += 1 if self.check_for_mines(x-1, y) else 0 #top middle
nearby_mines += 1 if self.check_for_mines(x-1, y+1) else 0 #top left
nearby_mines += 1 if self.check_for_mines(x, y-1) else 0 #left
nearby_mines += 1 if self.check_for_mines(x, y+1) else 0 #right
nearby_mines += 1 if self.check_for_mines(x+1, y-1) else 0 #bottom right
nearby_mines += 1 if self.check_for_mines(x+1, y) else 0 #bottom middle
nearby_mines += 1 if self.check_for_mines(x+1, y+1) else 0 #bottom left

self.buttons[x][y]["mines"] = nearby_mines

#add mine and count at the end
self.label2 = Label(frame, text = "Mines: "+str(self.mines))
self.label2.grid(row = SIZE_X+1, column = 0, columnspan = SIZE_Y/2)

self.label3 = Label(frame, text = "Flags: "+str(self.flags))
self.label3.grid(row = SIZE_X+1, column = SIZE_Y/2-1, columnspan = SIZE_Y/2)
mc = 0
for n in self.getNeighbors(x, y):
mc += 1 if n["isMine"] else 0
self.tiles[x][y]["mines"] = mc

## End of __init__
def restart(self):
self.setup()
self.refreshLabels()

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

def lclicked_wrapper(self, x, y):
return lambda Button: self.lclicked(self.buttons[x][y])
def gameOver(self, won):
for x in range(0, SIZE_X):
for y in range(0, SIZE_Y):
if self.tiles[x][y]["isMine"] == False and self.tiles[x][y]["state"] == STATE_FLAGGED:
self.tiles[x][y]["button"].config(image = self.images["wrong"])
if self.tiles[x][y]["isMine"] == True and self.tiles[x][y]["state"] != STATE_FLAGGED:
self.tiles[x][y]["button"].config(image = self.images["mine"])

def rclicked_wrapper(self, x, y):
return lambda Button: self.rclicked(self.buttons[x][y])
self.tk.update()

def lclicked(self, button_data):
if button_data["isMine"] == True:
msg = "You Win! Play again?" if won else "You Lose! Play again?"
res = tkMessageBox.askyesno("Game Over", msg)
if res:
self.restart()
else:
self.tk.quit()

def updateTimer(self):
ts = "00:00:00"
if self.startTime != None:
delta = datetime.now() - self.startTime
ts = str(delta).split('.')[0] # drop ms
if delta.total_seconds() < 36000:
ts = "0" + ts # zero-pad
self.labels["time"].config(text = ts)
self.frame.after(100, self.updateTimer)

def getNeighbors(self, x, y):
neighbors = []
coords = [
{"x": x-1, "y": y-1}, #top right
{"x": x-1, "y": y}, #top middle
{"x": x-1, "y": y+1}, #top left
{"x": x, "y": y-1}, #left
{"x": x, "y": y+1}, #right
{"x": x+1, "y": y-1}, #bottom right
{"x": x+1, "y": y}, #bottom middle
{"x": x+1, "y": y+1}, #bottom left
]
for n in coords:
try:
neighbors.append(self.tiles[n["x"]][n["y"]])
except KeyError:
pass
return neighbors

def onClickWrapper(self, x, y):
return lambda Button: self.onClick(self.tiles[x][y])

def onRightClickWrapper(self, x, y):
return lambda Button: self.onRightClick(self.tiles[x][y])

def onClick(self, tile):
if self.startTime == None:
self.startTime = datetime.now()

if tile["isMine"] == True:
# end game
self.gameover()
self.gameOver(False)
return

# change image
if tile["mines"] == 0:
tile["button"].config(image = self.images["clicked"])
self.clearSurroundingTiles(tile["id"])
else:
# change image
if button_data["mines"] == 0:
button_data["widget"].config(image = self.tiles["clicked"])
self.clear_empty_tiles(button_data["id"])
else:
button_data["widget"].config(image = self.tiles["numbers"][button_data["mines"]-1])
# if not already set as clicked, change state and count
if button_data["state"] != STATE_CLICKED:
button_data["state"] = STATE_CLICKED
self.clicked += 1
if self.clicked == 100 - self.mines:
self.victory()

def rclicked(self, button_data):
tile["button"].config(image = self.images["numbers"][tile["mines"]-1])
# if not already set as clicked, change state and count
if tile["state"] != STATE_CLICKED:
tile["state"] = STATE_CLICKED
self.clickedCount += 1
if self.clickedCount == (SIZE_X * SIZE_Y) - self.mines:
self.gameOver(True)

def onRightClick(self, tile):
if self.startTime == None:
self.startTime = datetime.now()

# if not clicked
if button_data["state"] == STATE_DEFAULT:
button_data["widget"].config(image = self.tiles["flag"])
button_data["state"] = STATE_FLAGGED
button_data["widget"].unbind(BTN_CLICK)
if tile["state"] == STATE_DEFAULT:
tile["button"].config(image = self.images["flag"])
tile["state"] = STATE_FLAGGED
tile["button"].unbind(BTN_CLICK)
# if a mine
if button_data["isMine"] == True:
self.correct_flags += 1
self.flags += 1
self.update_flags()
if tile["isMine"] == True:
self.correctFlagCount += 1
self.flagCount += 1
self.refreshLabels()
# if flagged, unflag
elif button_data["state"] == 2:
button_data["widget"].config(image = self.tiles["plain"])
button_data["state"] = 0
button_data["widget"].bind(BTN_CLICK, self.lclicked_wrapper(button_data["coords"]["x"], button_data["coords"]["y"]))
elif tile["state"] == 2:
tile["button"].config(image = self.images["plain"])
tile["state"] = 0
tile["button"].bind(BTN_CLICK, self.onClickWrapper(tile["coords"]["x"], tile["coords"]["y"]))
# if a mine
if button_data["isMine"] == True:
self.correct_flags -= 1
self.flags -= 1
self.update_flags()

def check_tile(self, x, y, queue):
try:
if self.buttons[x][y]["state"] == STATE_DEFAULT:
if self.buttons[x][y]["mines"] == 0:
self.buttons[x][y]["widget"].config(image = self.tiles["clicked"])
queue.append(self.buttons[x][y]["id"])
else:
self.buttons[x][y]["widget"].config(image = self.tiles["numbers"][self.buttons[x][y]["mines"]-1])
self.buttons[x][y]["state"] = STATE_CLICKED
self.clicked += 1
except KeyError:
pass

def clear_empty_tiles(self, id):
if tile["isMine"] == True:
self.correctFlagCount -= 1
self.flagCount -= 1
self.refreshLabels()

def clearSurroundingTiles(self, id):
queue = deque([id])

while len(queue) != 0:
key = queue.popleft()
parts = key.split("_")
source_x = int(parts[0])
source_y = int(parts[1])

self.check_tile(source_x-1, source_y-1, queue) #top right
self.check_tile(source_x-1, source_y, queue) #top middle
self.check_tile(source_x-1, source_y+1, queue) #top left
self.check_tile(source_x, source_y-1, queue) #left
self.check_tile(source_x, source_y+1, queue) #right
self.check_tile(source_x+1, source_y-1, queue) #bottom right
self.check_tile(source_x+1, source_y, queue) #bottom middle
self.check_tile(source_x+1, source_y+1, queue) #bottom left

def reveal(self):
for x in range(0, SIZE_X):
for y in range(0, SIZE_Y):
if self.buttons[x][y]["isMine"] == False and self.buttons[x][y]["state"] == STATE_FLAGGED:
self.buttons[x][y]["widget"].config(image = self.tiles["wrong"])
if self.buttons[x][y]["isMine"] == True and self.buttons[x][y]["state"] != STATE_FLAGGED:
self.buttons[x][y]["widget"].config(image = self.tiles["mine"])
global root
root.update()

def gameover(self):
self.reveal()
tkMessageBox.showinfo("Game Over", "You Lose!")
global root
root.destroy()

def victory(self):
self.reveal()
tkMessageBox.showinfo("Game Over", "You Win!")
global root
root.destroy()

def update_flags(self):
self.label3.config(text = "Flags: "+str(self.flags))
x = int(parts[0])
y = int(parts[1])

for tile in self.getNeighbors(x, y):
self.clearTile(tile, queue)

def clearTile(self, tile, queue):
if tile["state"] != STATE_DEFAULT:
return

if tile["mines"] == 0:
tile["button"].config(image = self.images["clicked"])
queue.append(tile["id"])
else:
tile["button"].config(image = self.images["numbers"][tile["mines"]-1])

tile["state"] = STATE_CLICKED
self.clickedCount += 1

### END OF CLASSES ###

def main():
global root
# create Tk widget
root = Tk()
# create Tk instance
window = Tk()
# set program title
root.title("Minesweeper")
window.title("Minesweeper")
# create game instance
minesweeper = Minesweeper(root)
minesweeper = Minesweeper(window)
# run event loop
root.mainloop()
window.mainloop()

if __name__ == "__main__":
main()

0 comments on commit f34dd4d

Please sign in to comment.