In this article, we will be going through the steps of creating our own terminal-based Minesweeper using Python Language.

## About the game

Minesweeper is a single-player game in which the player has to clear a square grid containing mines and numbers. The player has to prevent himself from landing on a mine with the help of numbers in the neighbouring tiles.

## Gameplay Demo

Aftermath of few hours of creating a game of Minesweeper.

## Designing Minesweeper Using Python

Before creating the game logic, we need to design the basic layout of the game. A square grid is rather easy to create using Python by:

# Printing the Minesweeper Layout def print_mines_layout(): global mine_values global n print() print("\t\t\tMINESWEEPER\n") st = " " for i in range(n): st = st + " " + str(i + 1) print(st) for r in range(n): st = " " if r == 0: for col in range(n): st = st + "______" print(st) st = " " for col in range(n): st = st + "| " print(st + "|") st = " " + str(r + 1) + " " for col in range(n): st = st + "| " + str(mine_values[r][col]) + " " print(st + "|") st = " " for col in range(n): st = st + "|_____" print(st + '|') print()

The grid displayed in each iteration resembles the following figure:

The `'M'`

symbol denotes the presence of a ‘mine’ in that cell. As we can see clearly, any number on the grid denotes the number of mines present in the neighbouring ‘eight’ cells.

The use of variables like, `mine_values`

will be explained further in the tutorial.

## Input system

One of the most important parts of any game is sustaining the input method. In our version of Minesweeper, we will be using the row and column numbers for our input technique.

Before starting the game, the script must provide a set of instructions for the player. Our game prints the following.

The row and column numbers displayed along with the grid are helpful for our input system. As we know, keeping track of mines without any indicator can be difficult. Therefore, Minesweeper has a provision of using ‘flag’ to mark the cells, which we know contains a mine.

## Data Storage

For a single game of Minesweeper, we need to keep track of the following information:

- The
**size**of the grid. - The
**number of mines**. **The ‘actual’ grid values**– At the start of the game, we need a container for storing the real values for the game, unknown to the player. For instance, the location of mines.**The ‘apparent’ grid values**– After each move, we need to update all the values that must be shown to the player.**The flagged positions**– The cells which have been flagged.

These values are stored using the following data structures

if __name__ == "__main__": # Size of grid n = 8 # Number of mines mines_no = 8 # The actual values of the grid numbers = [[0 for y in range(n)] for x in range(n)] # The apparent values of the grid mine_values = [[' ' for y in range(n)] for x in range(n)] # The positions that have been flagged flags = []

There is not much in the game-logic of Minesweeper. All the effort is to be done in setting up the Minesweeper layout.

## Setting up the Mines

We need to set up the positions of the mines randomly, so that the player might not predict their positions. This can be done by:

# Function for setting up Mines def set_mines(): global numbers global mines_no global n # Track of number of mines already set up count = 0 while count < mines_no: # Random number from all possible grid positions val = random.randint(0, n*n-1) # Generating row and column from the number r = val // n col = val % n # Place the mine, if it doesn't already have one if numbers[r][col] != -1: count = count + 1 numbers[r][col] = -1

In the code, we choose a random number from all possible cells in the grid. We keep doing this until we get the said number of mines.

Note:The actual value for a mine is stored as -1, whereas the values stored for display, denote the mine as`'M'`

.

Note:The ‘randint’ function can only be used after importing the random library. It is done by writing`'import random'`

at the start of the program.

## Setting up the grid numbers

For each cell in the grid, we have to check all adjacent neighbours whether there is a mine present or not. This is done by:

# Function for setting up the other grid values def set_values(): global numbers global n # Loop for counting each cell value for r in range(n): for col in range(n): # Skip, if it contains a mine if numbers[r][col] == -1: continue # Check up if r > 0 and numbers[r-1][col] == -1: numbers[r][col] = numbers[r][col] + 1 # Check down if r < n-1 and numbers[r+1][col] == -1: numbers[r][col] = numbers[r][col] + 1 # Check left if col > 0 and numbers[r][col-1] == -1: numbers[r] = numbers[r] + 1 # Check right if col < n-1 and numbers[r][col+1] == -1: numbers[r][col] = numbers[r][col] + 1 # Check top-left if r > 0 and col > 0 and numbers[r-1][col-1] == -1: numbers[r][col] = numbers[r][col] + 1 # Check top-right if r > 0 and col < n-1 and numbers[r-1][col+1]== -1: numbers[r][col] = numbers[r][col] + 1 # Check below-left if r < n-1 and col > 0 and numbers[r+1][col-1]== -1: numbers[r][col] = numbers[r][col] + 1 # Check below-right if r < n-1 and col< n-1 and numbers[r+1][col+1]==-1: numbers[r][col] = numbers[r][col] + 1

These values are to be hidden from the player, therefore they are stored in `numbers`

variable.

## Game Loop

Game Loop is a very crucial part of the game. It is needed to update every move of the player as well as the conclusion of the game.

# Set the mines set_mines() # Set the values set_values() # Display the instructions instructions() # Variable for maintaining Game Loop over = False # The GAME LOOP while not over: print_mines_layout()

In each iteration of the loop, the Minesweeper grid must be displayed as well as the player’s move must be handled.

## Handle the player input

As we mentioned before, there are two kinds of player input :

# Input from the user inp = input("Enter row number followed by space and column number = ").split()

### Standard input

In a normal kind of move, the row and column number are mentioned. The player’s motive behind this move is to unlock a cell that does not contain a mine.

# Standard Move if len(inp) == 2: # Try block to handle errant input try: val = list(map(int, inp)) except ValueError: clear() print("Wrong input!") instructions() continue

### Flag input

In a flagging move, three values are sent in by the gamer. The first two values denote cell location, while the last one denotes flagging.

# Flag Input elif len(inp) == 3: if inp[2] != 'F' and inp[2] != 'f': clear() print("Wrong Input!") instructions() continue # Try block to handle errant input try: val = list(map(int, inp[:2])) except ValueError: clear() print("Wrong input!") instructions() continue

## Sanitize the input

After storing the input, we have to do some sanity checks, for the smooth functioning of the game.

# Sanity checks if val[0] > n or val[0] < 1 or val[1] > n or val[1] < 1: clear() print("Wrong Input!") instructions() continue # Get row and column numbers r = val[0]-1 col = val[1]-1

On the completion of input process, the row and column numbers are to be extracted and stored in `'r'`

and `'c'`

.

## Handle the flag input

Managing the flag input is not a big issue. It requires checking for some pre-requisites before flagging the cell for a mine.

The following checks must be made:

- The cell has already been flagged or not.
- Whether the cell to be flagged is already displayed to the player.
- The number of flags does not exceed the number of mines.

After taking care of these issues, the cell is flagged for a mine.

# If cell already been flagged if [r, col] in flags: clear() print("Flag already set") continue # If cell already been displayed if mine_values[r][col] != ' ': clear() print("Value already known") continue # Check the number for flags if len(flags) < mines_no: clear() print("Flag set") # Adding flag to the list flags.append([r, col]) # Set the flag for display mine_values[r][col] = 'F' continue else: clear() print("Flags finished") continue

## Handle the standard input

The standard input involves the overall functioning of the game. There are three different scenarios:

### Anchoring on a mine

The game is finished as soon as the player selects a cell having a mine. It can happen out of bad luck or poor judgment.

# If landing on a mine --- GAME OVER if numbers[r][col] == -1: mine_values[r][col] = 'M' show_mines() print_mines_layout() print("Landed on a mine. GAME OVER!!!!!") over = True continue

After we land on a cell with mine, we need to display all the mines in the game and alter the variable behind the game loop.

The function `'show_mines()'`

is responsible for it.

def show_mines(): global mine_values global numbers global n for r in range(n): for col in range(n): if numbers[r][col] == -1: mine_values[r][col] = 'M'

### Visiting a ‘0’-valued cell.

The trickiest part of creating the game is managing this scenario. Whenever a gamer, visits a ‘0’-valued cell, all the neighboring elements must be displayed until a non-zero-valued cell is reached.

# If landing on a cell with 0 mines in neighboring cells elif numbers[r][n] == 0: vis = [] mine_values[r][n] = '0' neighbours(r, col)

This objective is achieved using **Recursion**. Recursion is a programming tool in which the function calls itself until the base case is satisfied. The `neighbours`

function is a recursive one, solving our problem.

def neighbours(r, col): global mine_values global numbers global vis # If the cell already not visited if [r,col] not in vis: # Mark the cell visited vis.append([r,col]) # If the cell is zero-valued if numbers[r][col] == 0: # Display it to the user mine_values[r][col] = numbers[r][col] # Recursive calls for the neighbouring cells if r > 0: neighbours(r-1, col) if r < n-1: neighbours(r+1, col) if col > 0: neighbours(r, col-1) if col < n-1: neighbours(r, col+1) if r > 0 and col > 0: neighbours(r-1, col-1) if r > 0 and col < n-1: neighbours(r-1, col+1) if r < n-1 and col > 0: neighbours(r+1, col-1) if r < n-1 and col < n-1: neighbours(r+1, col+1) # If the cell is not zero-valued if numbers[r][col] != 0: mine_values[r][col] = numbers[r][col]

For this particular concept of the game, a new data structure is used, namely, `vis`

. The role of `vis`

to keep track of already visited cells during recursion. Without this information, the recursion will continue perpetually.

After all the cells with zero value and their neighbours are displayed, we can move on to the last scenario.

### Choosing a non zero-valued cell

No effort is needed to handle this case, as all we need to do is alter the displaying value.

# If selecting a cell with atleast 1 mine in neighboring cells else: mine_values[r][col] = numbers[r][col]

## End game

There is a requirement to check for completion of the game, each time a move is made. This is done by:

# Check for game completion if(check_over()): show_mines() print_mines_layout() print("Congratulations!!! YOU WIN") over = True continue

The function `check_over()`

, is responsible for checking the completion of the game.

# Function to check for completion of the game def check_over(): global mine_values global n global mines_no # Count of all numbered values count = 0 # Loop for checking each cell in the grid for r in range(n): for col in range(n): # If cell not empty or flagged if mine_values[r][col] != ' ' and mine_values[r][col] != 'F': count = count + 1 # Count comparison if count == n * n - mines_no: return True else: return False

We count the number of cells, that are not empty or flagged. When this count is equal to the total cells, except those containing mines, then the game is regarded as over.

## Clearing output after each move

The terminal becomes crowded as we keep on printing stuff on it. Therefore, there must be provision for clearing it constantly. This can be done by:

# Function for clearing the terminal def clear(): os.system("clear")

Note:There is a need to import the`os`

library, before using this feature. It can be done by`'import os'`

at the start of the program.

## The complete code

Below is the complete code of the Minesweeper game:

# Importing packages import random import os # Printing the Minesweeper Layout def print_mines_layout(): global mine_values global n print() print("\t\t\tMINESWEEPER\n") st = " " for i in range(n): st = st + " " + str(i + 1) print(st) for r in range(n): st = " " if r == 0: for col in range(n): st = st + "______" print(st) st = " " for col in range(n): st = st + "| " print(st + "|") st = " " + str(r + 1) + " " for col in range(n): st = st + "| " + str(mine_values[r][col]) + " " print(st + "|") st = " " for col in range(n): st = st + "|_____" print(st + '|') print() # Function for setting up Mines def set_mines(): global numbers global mines_no global n # Track of number of mines already set up count = 0 while count < mines_no: # Random number from all possible grid positions val = random.randint(0, n*n-1) # Generating row and column from the number r = val // n col = val % n # Place the mine, if it doesn't already have one if numbers[r][col] != -1: count = count + 1 numbers[r][col] = -1 # Function for setting up the other grid values def set_values(): global numbers global n # Loop for counting each cell value for r in range(n): for col in range(n): # Skip, if it contains a mine if numbers[r][col] == -1: continue # Check up if r > 0 and numbers[r-1][col] == -1: numbers[r][col] = numbers[r][col] + 1 # Check down if r < n-1 and numbers[r+1][col] == -1: numbers[r][col] = numbers[r][col] + 1 # Check left if col > 0 and numbers[r][col-1] == -1: numbers[r][col] = numbers[r][col] + 1 # Check right if col < n-1 and numbers[r][col+1] == -1: numbers[r][col] = numbers[r][col] + 1 # Check top-left if r > 0 and col > 0 and numbers[r-1][col-1] == -1: numbers[r][col] = numbers[r][col] + 1 # Check top-right if r > 0 and col < n-1 and numbers[r-1][col+1] == -1: numbers[r][col] = numbers[r][col] + 1 # Check below-left if r < n-1 and col > 0 and numbers[r+1][col-1] == -1: numbers[r][col] = numbers[r][col] + 1 # Check below-right if r < n-1 and col < n-1 and numbers[r+1][col+1] == -1: numbers[r][col] = numbers[r][col] + 1 # Recursive function to display all zero-valued neighbours def neighbours(r, col): global mine_values global numbers global vis # If the cell already not visited if [r,col] not in vis: # Mark the cell visited vis.append([r,col]) # If the cell is zero-valued if numbers[r][col] == 0: # Display it to the user mine_values[r][col] = numbers[r][col] # Recursive calls for the neighbouring cells if r > 0: neighbours(r-1, col) if r < n-1: neighbours(r+1, col) if col > 0: neighbours(r, col-1) if col < n-1: neighbours(r, col+1) if r > 0 and col > 0: neighbours(r-1, col-1) if r > 0 and col < n-1: neighbours(r-1, col+1) if r < n-1 and col > 0: neighbours(r+1, col-1) if r < n-1 and col < n-1: neighbours(r+1, col+1) # If the cell is not zero-valued if numbers[r][col] != 0: mine_values[r][col] = numbers[r][col] # Function for clearing the terminal def clear(): os.system("clear") # Function to display the instructions def instructions(): print("Instructions:") print("1. Enter row and column number to select a cell, Example \"2 3\"") print("2. In order to flag a mine, enter F after row and column numbers, Example \"2 3 F\"") # Function to check for completion of the game def check_over(): global mine_values global n global mines_no # Count of all numbered values count = 0 # Loop for checking each cell in the grid for r in range(n): for col in range(n): # If cell not empty or flagged if mine_values[r][col] != ' ' and mine_values[r][col] != 'F': count = count + 1 # Count comparison if count == n * n - mines_no: return True else: return False # Display all the mine locations def show_mines(): global mine_values global numbers global n for r in range(n): for col in range(n): if numbers[r][col] == -1: mine_values[r][col] = 'M' if __name__ == "__main__": # Size of grid n = 8 # Number of mines mines_no = 8 # The actual values of the grid numbers = [[0 for y in range(n)] for x in range(n)] # The apparent values of the grid mine_values = [[' ' for y in range(n)] for x in range(n)] # The positions that have been flagged flags = [] # Set the mines set_mines() # Set the values set_values() # Display the instructions instructions() # Variable for maintaining Game Loop over = False # The GAME LOOP while not over: print_mines_layout() # Input from the user inp = input("Enter row number followed by space and column number = ").split() # Standard input if len(inp) == 2: # Try block to handle errant input try: val = list(map(int, inp)) except ValueError: clear() print("Wrong input!") instructions() continue # Flag input elif len(inp) == 3: if inp[2] != 'F' and inp[2] != 'f': clear() print("Wrong Input!") instructions() continue # Try block to handle errant input try: val = list(map(int, inp[:2])) except ValueError: clear() print("Wrong input!") instructions() continue # Sanity checks if val[0] > n or val[0] < 1 or val[1] > n or val[1] < 1: clear() print("Wrong input!") instructions() continue # Get row and column numbers r = val[0]-1 col = val[1]-1 # If cell already been flagged if [r, col] in flags: clear() print("Flag already set") continue # If cell already been displayed if mine_values[r][col] != ' ': clear() print("Value already known") continue # Check the number for flags if len(flags) < mines_no: clear() print("Flag set") # Adding flag to the list flags.append([r, col]) # Set the flag for display mine_values[r][col] = 'F' continue else: clear() print("Flags finished") continue else: clear() print("Wrong input!") instructions() continue # Sanity checks if val[0] > n or val[0] < 1 or val[1] > n or val[1] < 1: clear() print("Wrong Input!") instructions() continue # Get row and column number r = val[0]-1 col = val[1]-1 # Unflag the cell if already flagged if [r, col] in flags: flags.remove([r, col]) # If landing on a mine --- GAME OVER if numbers[r][col] == -1: mine_values[r][col] = 'M' show_mines() print_mines_layout() print("Landed on a mine. GAME OVER!!!!!") over = True continue # If landing on a cell with 0 mines in neighboring cells elif numbers[r][col] == 0: vis = [] mine_values[r][col] = '0' neighbours(r, col) # If selecting a cell with atleast 1 mine in neighboring cells else: mine_values[r][col] = numbers[r][col] # Check for game completion if(check_over()): show_mines() print_mines_layout() print("Congratulations!!! YOU WIN") over = True continue clear()

## Conclusion

We hope that this tutorial on creating our own Minesweeper game was understandable as well as fun. For any queries, feel free to comment below. The complete code is also available on my Github account.