6
\$\begingroup\$

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()
Reinderien
70.9k5 gold badges76 silver badges256 bronze badges
asked Jun 28, 2020 at 1:11
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

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
answered Jul 7, 2020 at 17:10
\$\endgroup\$
2
\$\begingroup\$

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.

answered Jul 7, 2020 at 16:43
\$\endgroup\$
2
  • \$\begingroup\$ The commented-out code removal is particularly a good idea if you're using source control, which everyone should anyway. \$\endgroup\$ Commented 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\$ Commented Jul 7, 2020 at 17:02

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.