How to make a Minesweeper using Python and pygame

Introduction to flat development of simple video games to begin to understand the logic of the code

Imagen de portada

How to make a Minesweeper using Python and pygame

Introduction to flat development of simple video games to begin to understand the logic of the code

Introduction to flat development of simple video games to begin to understand the logic of the code.

1. Pygame Initial Configuration

Assuming you have correctly installed pygame (pip install pygame), we start by importing the necessary libraries and configuring the basic parameters:



import pygame
import random
import time

# Configuración de Pygame
pygame.init()

# Colores
BLANCO = (255, 255, 255)
NEGRO = (0, 0, 0)
GRIS = (192, 192, 192)
ROJO = (255, 0, 0)
AZUL = (0, 0, 255)
VERDE = (0, 255, 0)

# Parámetros de las celdas
ANCHO_CELDA = 30
ALTO_CELDA = 30

2. Creating the Window and Defining Difficulties

Here we create the game window and configure the difficulty levels. Each difficulty affects the size of the grid and the number of mines:



# Crear la ventana
ANCHO_VENTANA = 600
ALTO_VENTANA = 400
ventana = pygame.display.set_mode((ANCHO_VENTANA, ALTO_VENTANA))
pygame.display.set_caption('Buscaminas')

# Definir dificultades
DIFICULTADES = {
    'Facil': (10, 10, 10),
    'Medio': (16, 16, 40),
    'Dificil': (24, 24, 99)
}

3. Grid Management Functions

We create functions to initialize the grid and place the mines:



def crear_cuadricula(fila, col):
    return [[0 for _ in range(col)] for _ in range(fila)]

def colocar_minas(cuadricula, fila, col, num_minas):
    minas_colocadas = 0
    while minas_colocadas < num_minas:
        x = random.randint(0, fila - 1)
        y = random.randint(0, col - 1)
        if cuadricula[x][y] == 0:
            cuadricula[x][y] = -1
            minas_colocadas += 1
            for i in range(max(0, x - 1), min(fila, x + 2)):
                for j in range(max(0, y - 1), min(col, y + 2)):
                    if cuadricula[i][j] != -1:
                        cuadricula[i][j] += 1

  • crear_cuadricula(fila, col): Creates a row x col grid initialized to 0.
  • colocar_minas(cuadricula, fila, col, num_minas): Places num_mines randomly on the grid and updates the counts of adjacent mines.

4. Drawing and Click Management Features

These functions are responsible for drawing the grid on the window and handling user clicks:



def dibujar_cuadricula(cuadricula, fila, col):
    ventana.fill(NEGRO)
    for x in range(fila):
        for y in range(col):
            rect = pygame.Rect(margen_x + y * ANCHO_CELDA, margen_y + x * ALTO_CELDA, ANCHO_CELDA, ALTO_CELDA)
            if celda_descubierta[x][y]:
                pygame.draw.rect(ventana, BLANCO, rect)
                if cuadricula[x][y] == -1:
                    pygame.draw.circle(ventana, ROJO, rect.center, ANCHO_CELDA // 4)
                elif cuadricula[x][y] > 0:
                    font = pygame.font.Font(None, 24)
                    texto = font.render(str(cuadricula[x][y]), True, NEGRO)
                    ventana.blit(texto, texto.get_rect(center=rect.center))
            else:
                pygame.draw.rect(ventana, GRIS, rect)
                if celda_marcada[x][y]:
                    pygame.draw.line(ventana, AZUL, rect.topleft, rect.bottomright, 3)
                    pygame.draw.line(ventana, AZUL, rect.bottomleft, rect.topright, 3)
            pygame.draw.rect(ventana, NEGRO, rect, 1)
    pygame.display.flip()

def descubrir_celda(cuadricula, fila, col, x, y):
    if x < 0 or x >= fila or y < 0 or y >= col or celda_descubierta[x][y] or celda_marcada[x][y]:
        return
    celda_descubierta[x][y] = True
    if cuadricula[x][y] == 0:
        for i in range(max(0, x - 1), min(fila, x + 2)):
            for j in range(max(0, y - 1), min(col, y + 2)):
                descubrir_celda(cuadricula, fila, col, i, j)

  • dibujar_cuadricula(cuadricula, fila, col): Draws the grid in the window, showing uncovered and marked cells.
  • descubrir_celda(cuadricula, fila, col, x, y): Discovers the cell at position (x, y) and recursively discovers adjacent cells if necessary.

5. Start Screen and End Screen

We added features to show the start screen and end screens for winning or losing:



def pantalla_inicio():
    ventana.fill(NEGRO)
    font = pygame.font.Font(None, 36)
    texto = font.render('Selecciona la Dificultad', True, BLANCO)
    ventana.blit(texto, (ANCHO_VENTANA // 2 - texto.get_width() // 2, ALTO_VENTANA // 4))

    botones = []
    for idx, dificultad in enumerate(DIFICULTADES.keys()):
        rect = pygame.Rect(ANCHO_VENTANA // 2 - 75, ALTO_VENTANA // 2 + idx * 50, 150, 40)
        pygame.draw.rect(ventana, GRIS, rect)
        texto = font.render(dificultad, True, NEGRO)
        ventana.blit(texto, (rect.x + (rect.width - texto.get_width()) // 2, rect.y + (rect.height - texto.get_height()) // 2))
        botones.append((rect, dificultad))

    pygame.display.flip()
    return botones

def pantalla_final(mensaje):
    ventana.fill(NEGRO)
    font = pygame.font.Font(None, 36)
    texto = font.render(mensaje, True, BLANCO)
    ventana.blit(texto, (ANCHO_VENTANA // 2 - texto.get_width() // 2, ALTO_VENTANA // 2))
    pygame.display.flip()
    pygame.time.wait(3000)

  • pantalla_inicio(): Shows the home screen where the player can select the difficulty.
  • pantalla_final(mensaje): Discovers the cell at position (x, y) and recursively discovers adjacent cells if necessary.

6. Game Logic Implementation

Finally, we implement the game logic to manage state and events:



def verificar_victoria(fila, col):
    for x in range(fila):
        for y in range(col):
            if cuadricula[x][y] != -1 and not celda_descubierta[x][y]:
                return False
    return True

def main():
    global cuadricula, celda_descubierta, celda_marcada, FILA, COL, NUM_MINAS, jugando, inicio_tiempo, fin_tiempo, margen_x, margen_y, ventana, ANCHO_VENTANA, ALTO_VENTANA, victoria

    ejecutando = True
    while ejecutando:
        seleccionando_dificultad = True
        while seleccionando_dificultad:
            botones = pantalla_inicio()
            for evento in pygame.event.get():
                if evento.type == pygame.QUIT:
                    pygame.quit()
                    return
                elif evento.type == pygame.MOUSEBUTTONDOWN:
                    for rect, dificultad in botones:
                        if rect.collidepoint(evento.pos):
                            FILA, COL, NUM_MINAS = DIFICULTADES[dificultad]
                            ANCHO_VENTANA = COL * ANCHO_CELDA
                            ALTO_VENTANA = FILA * ALTO_CELDA
                            ventana = pygame.display.set_mode((ANCHO_VENTANA, ALTO_VENTANA))
                            pygame.display.set_caption('Buscaminas')
                            cuadricula = crear_cuadricula(FILA, COL)
                            colocar_minas(cuadricula, FILA, COL, NUM_MINAS)
                            celda_descubierta = [[False] * COL for _ in range(FILA)]
                            celda_marcada = [[False] * COL for _ in range(FILA)]
                            inicio_tiempo = time.time()
                            seleccionando_dificultad = False
                            jugando = True
                            break
        
        while jugando:
            for evento in pygame.event.get():
                if evento.type == pygame.QUIT:
                    jugando = False
                elif evento.type == pygame.MOUSEBUTTONDOWN:
                    x, y = evento.pos
                    fila = (y - margen_y) // ALTO_CELDA
                    col = (x - margen_x) // ANCHO_CELDA

                    if 0 <= fila < FILA and 0 <= col < COL:
                        if evento.button == 1:  # Botón izquierdo
                            if cuadricula[fila][col] == -1:
                                pantalla_final("¡Perdiste!")
                                jugando = False
                            else:
                                descubrir_celda(cuadricula, FILA, COL, fila, col)
                                if verificar_victoria(FILA, COL):
                                    pantalla_final("¡Ganaste!")
                                    jugando = False
                        elif evento.button == 3:  # Botón derecho
                            celda_marcada[fila][col] = not celda_marcada[fila][col]

            ventana.fill(NEGRO)
            dibujar_cuadricula(cuadricula, FILA, COL)
            pygame.display.flip()

        pygame.quit()

if __name__ == "__main__":
    main()

  • verificar_victoria(fila, col): Check if the player has won the game.
  • main(): Main function that handles difficulty selection, game state and click detection. Shows the start screen, manages the game and shows the end screen.

7. Done!!

In this tutorial, we have created a Minesweeper game using Pygame. Yes, extremely simple and forcing us not to use any assets (no sprites, no sounds, no animations...). But at a functional level we have implemented everything from the initial configuration to the game management and the graphical interface, covering all the necessary aspects to create a functional and entertaining game. Now you can experiment with the code, add new features or adjust the difficulty to improve your game. Have fun programming and playing!

Click here to check and download the full code.