I'm still a relative beginner to coding and I would like some feedback on my Minesweeper clone written in Python using Pygame for the graphics. I'm wondering if it would be beneficial to use classes anywhere, or if there's simply a better way of doing what I'm trying to do. Thank you very much—any help is much appreciated.
import random, sys
import pygame as pg
import numpy as np
import collections
WIDTH = 200
GREY = (128,128,128) #rgb value
DARKGREY = (105,105,105)
WHITE = (200,200,200)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 128, 0)
PURPLE = (128, 0, 128)
MAGENTA = (255,0,255)
ORANGE = (255, 165, 0)
YELLOW = (255,255,0)
BLACK = (0,0,0)
BOARD_SIZE = 20
WIDTH = HEIGHT = BOARD_SIZE * 20 #window dimensions
NUM_BOMBS = 40
colors = [RED, BLUE, GREEN, PURPLE, MAGENTA, ORANGE, YELLOW, BLACK]
#random.seed(1)
def init_board():
board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)]
for i in range(NUM_BOMBS):
#while count(board) < 40:
row = random.randint(1, BOARD_SIZE-1)
column = random.randint(1, BOARD_SIZE-1)
board[row][column] = 9
# print(np.array(board))
# print("\n")
return board
def count(board):
ret = 0
for row in board[0]:
for col in board:
if board[row][col]==9:
print(board[row][j])
ret+=1
return ret
def create_grid():
board = init_board()
pg.init()
SQUARE_SIZE = 20
win = pg.display.set_mode((HEIGHT, WIDTH))
win.fill(GREY)
pg.display.set_caption("Minesweeper")
font = pg.font.SysFont('Arial', 25)
BOMB_IMG = pg.image.load("bomb.png")
FLAG_IMG = pg.image.load("flag.png")
rect2 = BOMB_IMG.get_rect()
rect3 = FLAG_IMG.get_rect()
moveable = True
done = False
lost = False
called = [[]]
found = 0
for x in range(BOARD_SIZE):
for y in range(BOARD_SIZE):
# draw grid
rectColor = WHITE
rect = pg.Rect(y * SQUARE_SIZE, x * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
pg.draw.rect(win, rectColor, rect, 1)
while not done:
for event in pg.event.get():
if moveable and event.type == pg.MOUSEBUTTONDOWN:
Mouse_x, Mouse_y = pg.mouse.get_pos()
print(np.array(board))
if event.button == 1:
if board[Mouse_y // 20][Mouse_x // 20] != 9:
board = uncover_squares(board, [Mouse_y // 20, Mouse_x // 20])
# print(np.array(board))
for row in range(len(board)):
for col in range(len(board)):
if board[row][col] != 0 and board[row][col] != 9:
if board[row][col]==-1:
rect4 = pg.Rect(col * SQUARE_SIZE, row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
pg.draw.rect(win, WHITE, rect4, 0)
else:
rect4 = pg.Rect(col * SQUARE_SIZE, row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
pg.draw.rect(win, WHITE, rect4, 0)
render_num(win, font, board[row][col],
colors[board[row][col]], col*20, row*20)
if board[Mouse_y // 20][Mouse_x // 20] != -1:
rect6 = pg.Rect(Mouse_x//20 * 20, Mouse_y//20 * 20, SQUARE_SIZE, SQUARE_SIZE)
render_num(win,font, board[Mouse_y //20 ][Mouse_x//20], colors[board[Mouse_y //20][Mouse_x//20]], Mouse_x, Mouse_y)
#pg.draw.rect(win, DARKGREY, rect6, 0)
if board[Mouse_y //20 ][Mouse_x//20] == 9:
display_img(win, BOMB_IMG, Mouse_x, Mouse_y, rect2)
moveable= False
pg.display.set_caption("You lost!")
lost = True
if event.button == 3 and board[Mouse_y // 20][Mouse_x // 20] not in range(len(colors)):
if board[Mouse_y // 20][Mouse_x // 20] == 9:
found+=1
if found == NUM_BOMBS and not any(0 in x for x in board):
moveable = False
pg.display.set_caption("You won!")
if event.type == pg.QUIT:
done = True
pg.display.update() #update display
pg.quit()
sys.exit()
def render_num(screen, font, num, color, mouse_x, mouse_y):
screen.blit(font.render(str(num), True, color), (mouse_x // 20 * 20, mouse_y // 20 * 20))
def display_img(screen, img, mouse_x, mouse_y, rect):
screen.blit(img, (mouse_x//20 * 20, mouse_y//20 * 20), rect)
def uncover_squares(board, move):
neighbors = [(-1, 0), (-1, 1), (0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1)]
uncover(move[0], move[1], len(board), len(board[0]), board, neighbors)
return board
def uncover(i, j, m, n, board, neighbors):
if board[i][j] != 0:
return
mine_count = 0
for cell in neighbors:
#make sure the search is in bounds of array, then check if neighbor is a bomb
if 0 <= i + cell[0] < m and 0 <= j + cell[1] < n and board[i + cell[0]][j + cell[1]] == 9:
mine_count += 1
if mine_count == 0:
board[i][j] = -1
else:
board[i][j] = mine_count
return
for cell in neighbors:
if 0 <= i + cell[0] < m and 0 <= j + cell[1] < n:
uncover(i + cell[0], j + cell[1], m, n, board, neighbors) #call neighbors
if __name__ == "__main__":
create_grid()
2 Answers 2
Nomenclature
create_grid
does not just create the grid - it runs the entire game and then kills the process. It should be divided up into multiple functions, but that aside, it does not do what's on the tin.
Forced-exit
Your use of sys.exit()
is not a great idea. What if a unit tester (who would have a difficult time anyway, given the size of your function) wanted to test multiple runs of create_grid
? Or what if a different caller wanted to run your game, and then perform some other cleanup after?
Round-to-multiple
This:
mouse_x // 20 * 20, mouse_y // 20 * 20
is a little awkward. An alternative is
mouse_x - mouse_x%20, mouse_y - mouse_y%20
Early-return
I would find
if mine_count == 0:
board[i][j] = -1
else:
board[i][j] = mine_count
return
for cell in neighbors:
if 0 <= i + cell[0] < m and 0 <= j + cell[1] < n:
uncover(i + cell[0], j + cell[1], m, n, board, neighbors) #call neighbors
more legible as
if mine_count == 0:
board[i][j] = -1
for cell in neighbors:
if 0 <= i + cell[0] < m and 0 <= j + cell[1] < n:
uncover(i + cell[0], j + cell[1], m, n, board, neighbors) #call neighbors
else:
board[i][j] = mine_count
Remove commented out code
All the code that has been commented out distracts from the actual code. You commented it out for a reason: you don't need it anymore.
Type Hints
These help the user see what is accepted by a function, and what is returned by a function. It's helpful when you need to remember what functions accept what types of values.
from typing import List
def count(board: List[List[int]]) -> int:
....
create_grid
To be frank, this function is really messy. You indent nine times. I would break this into smaller functions that handle different parts of creating the grid.
-
\$\begingroup\$ The commented-out code removal is particularly a good idea if you're using source control, which everyone should anyway. \$\endgroup\$Reinderien– Reinderien2020年07月07日 17:01:40 +00:00Commented Jul 7, 2020 at 17:01
-
\$\begingroup\$ The type hints suggestion is a good one but should have either a PEP reference or an example for the OP to go off of. \$\endgroup\$Reinderien– Reinderien2020年07月07日 17:02:19 +00:00Commented Jul 7, 2020 at 17:02