DEV Community

mihainem
mihainem

Posted on

Develop your own Python-Terminal-Based Minesweeper Game

Hey there fellow gamers and python developers!
I've been taking a Python course on Codecademy, and as this is my first article, I'm a little uneasy about writing it. However, what better way to get over this unease than to show you how I made a simple Python Minesweeper game to be played in the terminal?

this:
Minesweeper Image

turned into this:
minesweeper in terminal

So, grab a cup of coffee, and let's get started!

First things first, let's explain the Matrix class that we'll be using to represent the game board. This class is responsible for creating the two-dimensional matrix that represents the game board. It also handles the logic for adding mines to the board and counting the number of mines around each cell.

The Matrix constructor takes in the number of rows and columns and a value for the initial state of each cell. In this case, the initial value is either an underscore for the front matrix or zero for the back matrix.

class Matrix:
    def __init__(self, rows, cols, val):
        self.rows = rows
        self.cols = cols
        self.matrix = [[val for i in range(cols)] for j in range(rows)]
Enter fullscreen mode Exit fullscreen mode

The str method is overridden to return a string representation of the matrix, with each row on a separate line.

    def __str__(self):
        return "\n".join([str(row) for row in self.matrix])
Enter fullscreen mode Exit fullscreen mode

The add_mines method randomly places mines on the board. It takes in the number of mines to add and uses the randint function from the random module to select a row and column for each mine. It also calls the add_mines_counters method for each mine to update the cells around the mine with the number of adjacent mines.

    def add_mines(self, no_of_mines):
        for i in range(no_of_mines):
            row = randint(0, self.rows - 1)
            col = randint(0, self.cols - 1)
            self.matrix[row][col] = -1
            self.add_mines_counters(row, col)
Enter fullscreen mode Exit fullscreen mode

The add_mines_counters method iterates over the cells around a given mine and updates their values to reflect the number of adjacent mines. If a cell already contains a mine, it skips that cell.

    def add_mines_counters(self, i, j):
        for row in range(i-1, i + 2, 1):
            for col in range(j - 1, j + 2, 1):
                if(row < 0 or row >= self.rows or col < 0 or col >= self.cols):
                    continue
                if self.matrix[row][col] == -1:
                    continue
                self.matrix[row][col] = max(self.matrix[row][col], self.count_mines(row, col))
Enter fullscreen mode Exit fullscreen mode

The count_mines method counts the number of mines around a given cell.

    def count_mines(self, i, j):
        count = 0
        for row in range(i-1,i+2, 1):
            for col in range(j-1,j+2, 1):
                if(row < 0 or row >= self.rows or col < 0 or col >= self.cols):
                    continue
                if self.matrix[row][col] == -1:
                    count += 1
        return count
Enter fullscreen mode Exit fullscreen mode

The add_mines_for_level method calculates the number of mines to add based on the level chosen by the player and calls the add_mines method with that number of mines.

    def add_mines_for_level(self, level):
        no_of_mines = self.rows * self.cols // max(abs((4 - min(level, 4)) % 5), 1)
        self.add_mines(no_of_mines)
Enter fullscreen mode Exit fullscreen mode

Next, let's define our Game class. This class will be responsible for managing the game, including creating the game board, setting up the game difficulty, and accepting user input to reveal cells on the board. Here's what it looks like:

As you can see, the Game class has several methods, including init, which initializes the game board and sets the game difficulty;

from matrix import Matrix

class Game:
     def __init__(self):
        rows = int(input("How many rows?: "))
        cols = int(input("How many columns?: "))
        level = int(input("""Pick your experience level:
0-Begginer
1-Intermediate 
2-Advanced 
3-Expert 
4-Insane:"""))
        self.front_matrix = Matrix(rows, cols, "_")
        self.back_matrix = Matrix(rows, cols, 0)
        self.back_matrix.add_mines_for_level(level)
        self.game_over = False
        self.play()

    def __str__(self):
        return str(self.front_matrix)
Enter fullscreen mode Exit fullscreen mode

play, which is responsible for accepting user input and updating the game board accordingly;

    def play(self):
        while not self.game_over:
            #Uncomment this line to debug back_matrix
            #print(str(self.back_matrix))

            print(self)
            line = input("Enter pairs for rows and cols (e.g. 1 2): ")
            action = input("Enter action (o - open, f - flag): ")
            splits = line.split()
            rows_and_cols = []
            count_numbers = len(splits)
            for i in range(0, count_numbers - (count_numbers % 2), 2):
                rows_and_cols.append((int(splits[i]), int(splits[i + 1])))
            self.set_multiple(rows_and_cols, action)

        retry = input("Do you want to retry? (y/n): ")
        if retry == "y":
            self.__init__()

Enter fullscreen mode Exit fullscreen mode

set_multiple which takes a list of row and column pairs, and an action (either 'o' for open or 'f' for flag), and calls either the open_cell or set_flag method for each pair.

    def set_multiple(self, list, action):
        for row, col in list:
            if action == "o":
                self.open_cell(row, col)
            elif action == "f":
                self.set_flag(row, col)
            else:
                print("Invalid action!")
Enter fullscreen mode Exit fullscreen mode

set_flag, which sets a flag on a particular cell;

    def set_flag(self, row, col):
        self.front_matrix.matrix[row][col] = "F"
Enter fullscreen mode Exit fullscreen mode

open_cell, which reveals a particular cell and updates the game board accordingly;

    def open_cell(self, row, col):
        if self.back_matrix.matrix[row][col] == -1:
            self.game_over = True
            print("You lost")
        else:
            self.front_matrix.matrix[row][col] = self.back_matrix.matrix[row][col]
            self.check_if_won()
Enter fullscreen mode Exit fullscreen mode

and check_if_won, which checks if the player has won the game.

     def check_if_won(self):
        for row in range(self.front_matrix.rows):
            for col in range(self.front_matrix.cols):
                if self.front_matrix.matrix[row][col] == "_" \
                    or self.front_matrix.matrix[row][col] == "F" \
                        and self.back_matrix.matrix[row][col] != -1:
                    return
        self.game_over = True
        print("You won!")
Enter fullscreen mode Exit fullscreen mode

So there you have it, a fully functional Minesweeper game in Python! Of course, this is just a basic implementation, and you can always add more features and customize it to your liking.
Here is a full walkthrough of how I won a minesweeper game in terminal.

minesweeper-terminal game-walkthrough

For the full project check my github Minesweeper Repo

Hope you find it insightful and fun,
Greetings and thanks for reading,

...also many thanks to CodeCademy for making me to start this blog and for teaching me Python and ComputerScience, as well as to ChatGPT and Quillbot for providing better blog post starter drafts, and rephrasing.

Top comments (0)