Creating a Simple Minesweeper Game with Python

Introduction: Digging into Fun!

Welcome, aspiring Pythonistas and game enthusiasts! Today, we’re going to embark on a fun project: building a simplified version of the classic game Minesweeper using Python. Even if you’re new to programming, don’t worry! We’ll break down each step using simple language and clear explanations.

What is Minesweeper?

Minesweeper is a single-player puzzle game. The goal is to clear a rectangular board containing hidden “mines” without detonating any of them. If you click on a cell with a mine, you lose! If you click on a safe cell, it reveals a number. This number tells you how many mines are in the eight surrounding cells (including diagonals). These numbers are your clues to figure out where the mines are located.

Why Build it in Python?

Python is a fantastic language for beginners because it’s easy to read and write. Creating a game like Minesweeper is an excellent way to practice several core programming concepts:

  • Variables and Data Structures: Storing information like our game board.
  • Loops: Repeating actions, like checking all cells or running the game.
  • Conditional Statements: Making decisions, like “Is this a mine?”
  • Functions: Organizing our code into reusable blocks.
  • User Input: Interacting with the player.

By the end of this tutorial, you’ll have a working text-based Minesweeper game and a better understanding of how these concepts come together. Let’s get started!

The Building Blocks of Our Game

Before we write any code, let’s think about the main parts we need for our game:

  • The Grid (Game Board): This is where all the action happens. We need a way to represent a grid of cells.
  • Mines: These are the hidden dangers. We’ll need to place them randomly on our grid.
  • Numbers (Clues): For every cell that doesn’t have a mine, we need to calculate and store a number indicating how many mines are nearby.
  • Player Actions: The player needs to be able to “click” (or choose) a cell on the board.
  • Display: We need to show the player what the board looks like, hiding unrevealed cells and showing numbers or mines for revealed ones.

Setting Up Your Python Project

The great news is you don’t need to install anything special for this project! Python comes with everything we need. Just make sure you have Python installed on your computer. You can write your code in any text editor and run it from your terminal or command prompt.

Step-by-Step Implementation

We’ll build our game step by step, explaining each piece of code.

1. Representing the Game Board

How do we represent a grid in Python? The easiest way for a game board is using a “list of lists,” also known as a 2D list or nested list.
Imagine a spreadsheet: each row is a list, and all those row lists are put together into one big list.

We’ll need two main grids:
* board: This will store the actual content of each cell (either a mine 'M' or a number 0-8).
* display_board: This is what the player sees. Initially, all cells are hidden (e.g., '-'). When a player reveals a cell, we update display_board with the content from board.

Let’s start by initializing these boards.

import random

def initialize_boards(rows, cols):
    """
    Creates two empty boards: one for game logic and one for display.

    Args:
        rows (int): The number of rows in the board.
        cols (int): The number of columns in the board.

    Returns:
        tuple: A tuple containing (board, display_board).
               'board' holds '0' for empty cells initially.
               'display_board' holds '-' for hidden cells.
    """
    board = [['0' for _ in range(cols)] for _ in range(rows)]
    display_board = [['-' for _ in range(cols)] for _ in range(rows)]
    return board, display_board

2. Placing the Mines

Now, let’s randomly place our mines on the board. We’ll use Python’s built-in random module for this.

Technical Term: random module
The random module in Python provides functions to generate random numbers. random.randint(a, b) will give you a random whole number between a and b (inclusive). This is perfect for picking random row and column numbers.

def place_mines(board, num_mines):
    """
    Randomly places mines ('M') on the game board.

    Args:
        board (list of lists): The game board where mines will be placed.
        num_mines (int): The total number of mines to place.
    """
    rows = len(board)
    cols = len(board[0])
    mines_placed = 0

    while mines_placed < num_mines:
        r = random.randint(0, rows - 1) # Pick a random row
        c = random.randint(0, cols - 1) # Pick a random column

        if board[r][c] != 'M': # If there isn't already a mine here
            board[r][c] = 'M'
            mines_placed += 1

3. Calculating the Clues (Numbers)

After placing mines, we need to calculate the numbers for all the non-mine cells. For each cell that is not a mine, we look at its eight surrounding neighbors (up, down, left, right, and diagonals) and count how many of them contain a mine.

Technical Term: Adjacent Cells
“Adjacent” simply means “next to.” In a grid, a cell typically has 8 adjacent cells: one directly above, below, left, right, and one in each of the four diagonal directions.

def calculate_numbers(board):
    """
    Calculates the number of adjacent mines for each non-mine cell.

    Args:
        board (list of lists): The game board with mines placed.
    """
    rows = len(board)
    cols = len(board[0])

    for r in range(rows):
        for c in range(cols):
            if board[r][c] == 'M':
                continue # Skip if it's a mine

            mine_count = 0
            # Check all 8 adjacent cells
            for dr in [-1, 0, 1]: # Delta row: -1 (up), 0 (same row), 1 (down)
                for dc in [-1, 0, 1]: # Delta col: -1 (left), 0 (same col), 1 (right)
                    if dr == 0 and dc == 0: # Skip the current cell itself
                        continue

                    nr, nc = r + dr, c + dc # Neighbor row, neighbor col

                    # Check if the neighbor is within the board boundaries
                    if 0 <= nr < rows and 0 <= nc < cols:
                        if board[nr][nc] == 'M':
                            mine_count += 1
            board[r][c] = str(mine_count) # Store the count as a string

4. Displaying the Board to the Player

This function will print the display_board to the console, making it readable for the player. We’ll also add row and column numbers to help the player choose cells.

def print_display_board(display_board):
    """
    Prints the current state of the display board to the console.
    """
    rows = len(display_board)
    cols = len(display_board[0])

    # Print column numbers
    print("  ", end="")
    for c in range(cols):
        print(f" {c}", end="")
    print()

    # Print a separator line
    print("  " + "---" * cols)

    # Print row numbers and board content
    for r in range(rows):
        print(f"{r} |", end="")
        for c in range(cols):
            print(f" {display_board[r][c]}", end="")
        print(" |")
    print("  " + "---" * cols)

5. Handling Player Moves

The game needs to ask the player for their desired move (row and column) and make sure it’s a valid choice.

def get_player_move(rows, cols):
    """
    Prompts the player to enter their move (row and column).

    Args:
        rows (int): Total number of rows on the board.
        cols (int): Total number of columns on the board.

    Returns:
        tuple: (row, col) if input is valid, otherwise asks again.
    """
    while True:
        try:
            move_input = input(f"Enter your move (row column, e.g., 0 0): ").split()
            r, c = int(move_input[0]), int(move_input[1])

            if 0 <= r < rows and 0 <= c < cols:
                return r, c
            else:
                print("Invalid input. Row and column must be within board limits.")
        except (ValueError, IndexError):
            print("Invalid input format. Please enter two numbers separated by a space.")

6. Putting It All Together: The Game Loop

This is where the magic happens! The play_game function will bring all our previous functions together, managing the game flow, checking win/loss conditions, and letting the player keep playing until the game ends.

Technical Term: Game Loop
A “game loop” is a fundamental concept in game programming. It’s a while loop that continuously runs the main actions of the game: getting player input, updating the game state, and displaying the game, until a condition (like game over or win) is met.

def play_game():
    """
    Main function to run the Minesweeper game.
    """
    print("Welcome to Simple Minesweeper!")

    # You can change these values to make the board bigger or smaller
    board_rows = 5
    board_cols = 5
    number_of_mines = 4 

    # Initialize the board and display board
    game_board, current_display = initialize_boards(board_rows, board_cols)
    place_mines(game_board, number_of_mines)
    calculate_numbers(game_board)

    game_over = False
    mines_hit = False
    safe_cells_revealed = 0
    total_safe_cells = (board_rows * board_cols) - number_of_mines

    while not game_over:
        print_display_board(current_display)

        # Get player move
        row, col = get_player_move(board_rows, board_cols)

        # Check if the cell is already revealed
        if current_display[row][col] != '-':
            print("This cell is already revealed. Choose another one.")
            continue

        # Reveal the cell
        cell_content = game_board[row][col]
        current_display[row][col] = cell_content # Update what the player sees

        if cell_content == 'M':
            mines_hit = True
            game_over = True
            print("\nBOOM! You hit a mine. Game Over!")
        else:
            safe_cells_revealed += 1
            if safe_cells_revealed == total_safe_cells:
                game_over = True
                print("\nCongratulations! You've cleared all the safe cells. You Win!")

    # After game over, reveal the full board for review
    print("\n--- Game Board Revealed ---")
    # Temporarily copy game_board content to display to show all mines
    final_display = [['0' for _ in range(board_cols)] for _ in range(board_rows)]
    for r in range(board_rows):
        for c in range(board_cols):
            final_display[r][c] = game_board[r][c]
    print_display_board(final_display)

if __name__ == "__main__":
    play_game()

The Complete Simple Minesweeper Code

Here’s the entire code for your simple Minesweeper game:

import random

def initialize_boards(rows, cols):
    """
    Creates two empty boards: one for game logic and one for display.

    Args:
        rows (int): The number of rows in the board.
        cols (int): The number of columns in the board.

    Returns:
        tuple: A tuple containing (board, display_board).
               'board' holds '0' for empty cells initially.
               'display_board' holds '-' for hidden cells.
    """
    board = [['0' for _ in range(cols)] for _ in range(rows)]
    display_board = [['-' for _ in range(cols)] for _ in range(rows)]
    return board, display_board

def place_mines(board, num_mines):
    """
    Randomly places mines ('M') on the game board.

    Args:
        board (list of lists): The game board where mines will be placed.
        num_mines (int): The total number of mines to place.
    """
    rows = len(board)
    cols = len(board[0])
    mines_placed = 0

    while mines_placed < num_mines:
        r = random.randint(0, rows - 1) # Pick a random row
        c = random.randint(0, cols - 1) # Pick a random column

        if board[r][c] != 'M': # If there isn't already a mine here
            board[r][c] = 'M'
            mines_placed += 1

def calculate_numbers(board):
    """
    Calculates the number of adjacent mines for each non-mine cell.

    Args:
        board (list of lists): The game board with mines placed.
    """
    rows = len(board)
    cols = len(board[0])

    for r in range(rows):
        for c in range(cols):
            if board[r][c] == 'M':
                continue # Skip if it's a mine

            mine_count = 0
            # Check all 8 adjacent cells
            for dr in [-1, 0, 1]: # Delta row: -1 (up), 0 (same row), 1 (down)
                for dc in [-1, 0, 1]: # Delta col: -1 (left), 0 (same col), 1 (right)
                    if dr == 0 and dc == 0: # Skip the current cell itself
                        continue

                    nr, nc = r + dr, c + dc # Neighbor row, neighbor col

                    # Check if the neighbor is within the board boundaries
                    if 0 <= nr < rows and 0 <= nc < cols:
                        if board[nr][nc] == 'M':
                            mine_count += 1
            board[r][c] = str(mine_count) # Store the count as a string

def print_display_board(display_board):
    """
    Prints the current state of the display board to the console.
    """
    rows = len(display_board)
    cols = len(display_board[0])

    # Print column numbers
    print("  ", end="")
    for c in range(cols):
        print(f" {c}", end="")
    print()

    # Print a separator line
    print("  " + "---" * cols)

    # Print row numbers and board content
    for r in range(rows):
        print(f"{r} |", end="")
        for c in range(cols):
            print(f" {display_board[r][c]}", end="")
        print(" |")
    print("  " + "---" * cols)

def get_player_move(rows, cols):
    """
    Prompts the player to enter their move (row and column).

    Args:
        rows (int): Total number of rows on the board.
        cols (int): Total number of columns on the board.

    Returns:
        tuple: (row, col) if input is valid, otherwise asks again.
    """
    while True:
        try:
            move_input = input(f"Enter your move (row column, e.g., 0 0): ").split()
            r, c = int(move_input[0]), int(move_input[1])

            if 0 <= r < rows and 0 <= c < cols:
                return r, c
            else:
                print("Invalid input. Row and column must be within board limits.")
        except (ValueError, IndexError):
            print("Invalid input format. Please enter two numbers separated by a space.")

def play_game():
    """
    Main function to run the Minesweeper game.
    """
    print("Welcome to Simple Minesweeper!")

    # You can change these values to make the board bigger or smaller
    board_rows = 5
    board_cols = 5
    number_of_mines = 4 

    # Initialize the board and display board
    game_board, current_display = initialize_boards(board_rows, board_cols)
    place_mines(game_board, number_of_mines)
    calculate_numbers(game_board)

    game_over = False
    safe_cells_revealed = 0
    total_safe_cells = (board_rows * board_cols) - number_of_mines

    while not game_over:
        print_display_board(current_display)

        # Get player move
        row, col = get_player_move(board_rows, board_cols)

        # Check if the cell is already revealed
        if current_display[row][col] != '-':
            print("This cell is already revealed. Choose another one.")
            continue

        # Reveal the cell
        cell_content = game_board[row][col]
        current_display[row][col] = cell_content # Update what the player sees

        if cell_content == 'M':
            game_over = True
            print("\nBOOM! You hit a mine. Game Over!")
        else:
            safe_cells_revealed += 1
            if safe_cells_revealed == total_safe_cells:
                game_over = True
                print("\nCongratulations! You've cleared all the safe cells. You Win!")

    # After game over, reveal the full board for review
    print("\n--- Game Board Revealed ---")
    # Temporarily copy game_board content to display to show all mines
    final_display = [['0' for _ in range(board_cols)] for _ in range(board_rows)]
    for r in range(board_rows):
        for c in range(board_cols):
            final_display[r][c] = game_board[r][c]
    print_display_board(final_display)

if __name__ == "__main__":
    play_game()

How to Play Your Game

  1. Save the Code: Save the entire code block above into a file named minesweeper.py (or any name ending with .py).
  2. Open a Terminal/Command Prompt: Navigate to the directory where you saved your file.
  3. Run the Game: Type python minesweeper.py and press Enter.
  4. Enter Moves: The game will prompt you to enter a row and column number (e.g., 0 0 for the top-left corner).
  5. Try to Win!: Avoid mines and reveal all the safe cells. Good luck!

Conclusion: You’ve Swept the Mines!

Congratulations! You’ve successfully built a basic Minesweeper game using Python. You’ve learned about creating 2D lists, using the random module, implementing loops and conditionals, defining functions, and managing game flow.

This is a great foundation! You can further enhance this game by adding features like:
* Allowing players to “flag” potential mine locations.
* Implementing the automatic revealing of empty cells and their neighbors.
* Adding difficulty levels (changing board size and mine count).
* Creating a graphical user interface (GUI) instead of a text-based one.

Keep experimenting and happy coding!

Comments

Leave a Reply