5
\$\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()
\$\endgroup\$

2 Answers 2

2
\$\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
\$\endgroup\$
1
\$\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.

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.