Skip to content

Instantly share code, notes, and snippets.

@sean-m
Last active September 23, 2020 00:05
Show Gist options
  • Save sean-m/c92df7fe89ad72d4429c5a48936dc117 to your computer and use it in GitHub Desktop.
Save sean-m/c92df7fe89ad72d4429c5a48936dc117 to your computer and use it in GitHub Desktop.
sloppy minesweeper in with python3 tkinter
#! /usr/bin/env python3
import random as rand
import tkinter as tk
from tkinter import ttk
colors = [ "white", "blue", "green", "red", "purple", "brown", "grey", "black" ]
class Tile:
def __init__(self, bomb=False):
self.bomb = bomb
self.flag = False
self.state = "hidden"
#self.state = "cleared"
class Game:
def __init__(self, x, y, w, h, b):
'''
x: board columns
y: board rows
w: canvas width
h: cangas height
'''
#print(f"x:{x}, y:{y}, w:{w}, h:{h}")
self.x = x
self.y = y
self.w = w
self.h = h
self.r = 5
self.b = (x * y / b) / 100
self.size = 20
self.board = []
self.end = False;
self.bomb_count=0
# Add all the tiles to the board
for i in range(0, x, 1):
self.board.append([])
for j in range(0, y, 1):
self.board[i].append(Tile())
while self.bomb_count < b:
rx = rand.randint(0, x-1)
ry = rand.randint(0, y-1)
#print(f"{rx} {ry}")
spot = self.board[rx][ry]
if not spot.bomb:
self.board[rx][ry].bomb = True
self.bomb_count = self.bomb_count + 1
def get_stats(self):
return f"Bombs:{self.bomb_count}"
def draw_board(self, canvas):
canvas.delete("all")
for i in range(0, self.w, int(self.w/self.x)):
canvas.create_line([(i, 0), (i, self.h)], tag='grid_line')
# Creates all horizontal lines at intevals of 100
for i in range(0, self.h, int(self.h/self.y)):
canvas.create_line([(0, i), (self.w, i)], tag='grid_line')
cw = int(self.w / self.x)
ch = int(self.h / self.y)
for i in range(0, self.x, 1):
for j in range(0, self.y, 1):
spot=self.board[i][j]
# coordinates for tile locations
x = i * (self.w/self.x)
y = j * (self.h/self.y)
# coordinates for bomb/number locations, center of tile
cx = x + (self.w/self.x/2)
cy = y + (self.h/self.y/2)
if spot.state == "exploded":
canvas.create_oval(cx-self.r, cy-self.r, cx+self.r, cy+self.r, fill="red", outline="#DDD", width=1)
elif spot.state == "hidden":
canvas.create_rectangle(x, y, x+cw, y+ch, fill="grey")
elif spot.state == "cleared":
if spot.bomb:
#print(f"i:{i} j:{j} x:{x} y:{y}")
canvas.create_oval(cx-self.r, cy-self.r, cx+self.r, cy+self.r, fill="black", outline="#DDD", width=1)
else:
count = self.find_bombs(i, j)
canvas.create_text(cx, cy, text=str(count), fill=colors[count])
elif spot.state == "flagged":
r = self.r # vertical unit
xoff = cx-(2 * r)
canvas.create_line(xoff, cy - (r * 2.5), xoff, cy + (r * 2.5), fill="black", width=2) # flagpole
canvas.create_rectangle(xoff+1, cy - (r * 2.5), cx + (r * 1.5), cy, fill="red")
#print(f"draw flag at {x} {y}")
def find_bombs(self, x, y):
'''
Walk around the x, y value and count the bombs found
'''
count = 0
for i in range(-1,2,1):
for j in range(-1,2,1):
x1 = x + i
y1 = y + j
#print(f"x:{x} y:{y} i:{i} j:{j} x1:{x1} y1:{y1} {count}")
if 0 <= x1 < self.x and 0 <= y1 < self.y:
try:
if self.test_bomb(x1, y1):
count += 1
except Exception as e:
pass
#print("")
return count
def test_bomb(self, x, y):
return self.board[x][y].bomb
def flag_tile(self, x, y):
if self.end:
return
cw = int(self.w / self.x)
ch = int(self.h / self.y)
tx = int(x / cw)
ty = int(y / ch)
#print(f"{tx} {ty}")
if self.board[tx][ty].state != "flagged" \
and self.board[tx][ty].state != "hidden":
return
if self.board[tx][ty].state == "flagged":
self.board[tx][ty].state = "hidden"
else:
self.board[tx][ty].state = "flagged"
def test_tile(self, x, y):
if self.end:
return
cw = int(self.w / self.x)
ch = int(self.h / self.y)
tx = int(x / cw)
ty = int(y / ch)
#print(f"{tx} {ty}")
if self.test_bomb(tx, ty):
self.board[tx][ty].state = "exploded"
self.end = True
else:
self.flood_clear(tx, ty)
def flood_clear(self, x, y):
queue = []
queue.append([x, y, self.find_bombs(x, y)])
while queue:
px, py, count = queue.pop()
if self.board[px][py].state == "cleared":
# already cleared, skip
continue
#print(f"px:{px} py:{py} count:{count}")
self.board[px][py].state = "cleared"
if count != 0:
# found a number, stop walking
continue;
for i in range(-1,2,1):
for j in range(-1,2,1):
nx, ny = px-i, py+j
if 0 <= nx < self.x and 0 <= ny < self.y and not self.test_bomb(nx, ny):
nc = self.find_bombs(nx, ny)
if nc == 0:
queue.append([nx, ny, nc])
else:
self.board[nx][ny].state = "cleared"
class Application(tk.Frame):
def __init__(self, game, master=None):
super().__init__(master)
self.master = master
self.game = game
self.pack()
self.create_widgets()
def create_widgets(self):
h = self.game.h
w = self.game.w
self.canvas = tk.Canvas(self, width=w, height=h, bg="white")
self.canvas.bind("<Button-1>", self.left_clicked)
self.canvas.bind("<Button-2>", self.right_clicked)
self.canvas.bind("<Button-3>", self.right_clicked)
self.canvas.pack(side="top")
self.hi_there = ttk.Button(self)
self.hi_there["text"] = "New Game"
self.hi_there["command"] = self.new_game
self.hi_there.pack(side="top")
self.stats = tk.Label(self, text=self.game.get_stats())
self.stats["textvariable"] = self.game.get_stats()
self.stats.pack(side="left")
self.game.draw_board(self.canvas)
def left_clicked(self, event):
#print(event)
self.game.test_tile(event.x, event.y)
self.game.draw_board(self.canvas)
def right_clicked(self, event):
#print(event)
self.game.flag_tile(event.x, event.y)
self.game.draw_board(self.canvas)
def new_game(self):
game = Game(10,10,480,480, 15)
self.canvas.delete("all")
self.game = game
self.game.draw_board(self.canvas)
self.stats = self.game.get_stats()
game = Game(20,20,480,480, 45)
root = tk.Tk()
def main():
app = Application(master=root, game=game)
root.mainloop()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment