This guide explains how to build the Minesweeper game in the Python terminal. We’ll break down the code step by step—from importing modules and defining helper functions to creating the main classes and implementing the game loop. Finally, you’ll learn how to run the game in your terminal.
This program is built for the Python terminal environment using only Python’s standard libraries. It does not depend on any external libraries and is compatible with Python 3.10 and above.
For the complete code, please visit the “oyna” project and then go to the “Minesweeper” section to view the code. Feel free to contribute to its improvement and development.
1. Importing Modules and Helper Function
The code begins by importing required modules and defining a helper function to capture a single character from user input. This function works across different operating systems.
import itertools
import random
import typing
from enum import Enum
def getch() -> str:
"""Gets a single character"""
try:
import msvcrt
return str(msvcrt.getch().decode("utf-8")) # type: ignore
except ImportError:
import sys
import termios
import tty
fd = sys.stdin.fileno()
oldsettings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, oldsettings)
return ch
Imports:
-
itertoolsandrandomfor processing iterators and generating random values. -
typingto support type hints. -
Enumfrom theenummodule is used to define various states and actions.
getch Function:
Captures a single character without waiting for the Enter key.
Uses msvcrt on Windows and termios/tty on Unix-like systems.
2. Defining States and Actions
Two enums are defined to manage cell states and user actions.
class State(Enum):
BLOCK = "🟪"
BOMB = "💣"
DEAD = "💥"
EIGHT = " 8"
FIVE = " 5"
FLAG = "❓"
FOUR = " 4"
NINE = " 9"
ONE = " 1"
PLAYER = "🟦"
SEVEN = " 7"
SIX = " 6"
THREE = " 3"
TWO = " 2"
WALL = "🔹"
WIN = "🏆"
ZERO = " "
@classmethod
def create_from_number(cls, number: int) -> "State":
return {
0: cls.ZERO,
1: cls.ONE,
2: cls.TWO,
3: cls.THREE,
4: cls.FOUR,
5: cls.FIVE,
6: cls.SIX,
7: cls.SEVEN,
8: cls.EIGHT,
9: cls.NINE,
}[number]
class Action(Enum):
CLICK = "click"
MOVE_DOWN = "down"
MOVE_LEFT = "left"
MOVE_RIGHT = "right"
MOVE_UP = "up"
SET_FLAG = "flag"
State Enum:
Contains various visual states for cells, including hidden blocks, bombs, numbers (from ZERO to NINE), flags, walls, and end-game states (WIN and DEAD).
The create_from_number class method maps a numeric value to its corresponding state.
Action Enum:
Lists possible actions such as clicking a cell, moving the player in four directions, and setting a flag.
3. The Cell Class
Each cell on the Minesweeper board is represented by the Cell class. This class holds cell-specific properties and methods to handle actions like clicking and flagging.
class Cell:
def __init__(self, state: State = State.BLOCK) -> None:
self.player_is_here = False # Indicates if the player is on this cell.
self.seen = False # Whether the cell has been revealed.
self.state = state # The display state of the cell.
self.value = 0 # Holds the bomb count (or -1 for a bomb).
self.down: "Cell"
self.up: "Cell"
self.right: "Cell"
self.left: "Cell"
def __str__(self) -> str:
# If the player is here, show the PLAYER icon; otherwise, show the current state.
return State.PLAYER.value if self.player_is_here else self.state.value
def set_neighbors(
self, left: "Cell", right: "Cell", up: "Cell", down: "Cell"
) -> None:
self.down = down
self.up = up
self.right = right
self.left = left
def set_state(self, action: Action) -> "Cell":
match action:
case Action.SET_FLAG:
self._set_flag()
return self
case Action.CLICK:
self._click()
return self
case _:
return self._move_tile(action)
def _move_tile(self, action: Action) -> "Cell":
side_: "Cell" = getattr(self, action.value)
if side_.state == State.WALL:
return self
else:
self.player_is_here = False
side_.player_is_here = True
return side_
def _click(self) -> None:
# Process a click only if the cell hasn't been seen and is not a wall.
if self._is_unseen_and_not_wall():
# If the cell is not a bomb, update its state based on the value.
self.state = (
State.create_from_number(self.value) if self.value > -1 else State.BOMB
)
self.seen = True
# If the cell is empty (no bombs around), automatically reveal adjacent cells.
if self._is_empty():
self._continue(Action.MOVE_DOWN)
self._continue(Action.MOVE_LEFT)
self._continue(Action.MOVE_RIGHT)
self._continue(Action.MOVE_UP)
def _is_unseen_and_not_wall(self) -> bool:
return not self.seen and self.state != State.WALL
def _is_empty(self) -> bool:
return self.value == 0
def _set_flag(self) -> None:
# Toggle flag if the cell has not been revealed.
if not self.seen:
self.state = State.BLOCK if self.state == State.FLAG else State.FLAG
def _continue(self, side: Action) -> None:
side_: typing.Union["Cell", None] = getattr(self, side.value)
if side_ is not None:
side_.set_state(Action.CLICK)
Attributes:
-
player_is_heretracks the player’s current position. -
seenindicates whether the cell has been clicked and revealed. -
stateholds the visual representation (like a hidden block, number, flag, or bomb). -
valuestores the underlying number (bomb count) or -1 if the cell contains a bomb. - Neighbor pointers (
up,down,left,right) are set later.
Methods:
-
__str__: Displays the appropriate icon depending on whether the player is on the cell. -
set_neighbors: Sets the cell’s adjacent neighbors. -
set_state: Dispatches actions (click, flag, or move) based on the user input. -
_click: Reveals the cell; if it is empty, recursively reveals neighboring cells. -
_set_flag: Toggles a flag on the cell. -
_continue: Automatically continues revealing adjacent cells if the current cell is empty.
4. The Board Class
The Board class manages the game grid, sets up walls, places bombs, and assigns neighboring cells.
class Board:
def __init__(self, size: int) -> None:
self.start_player_position = size // 2
self.size = size
self.cells = self._cells()
self.set_initial()
self.player = self.cells[self.start_player_position][self.start_player_position]
def _cells(self) -> list[list[Cell]]:
return [[Cell() for _ in range(self.main_size)] for _ in range(self.main_size)]
@property
def main_size(self) -> int:
# The main board size includes a border (walls) around the game area.
return self.size + 2
def set_initial(self) -> None:
self.set_horizontal_walls()
self.set_vertical_walls()
self.set_cells_neighboring()
self.set_player()
self.set_bombs()
def set_horizontal_walls(self) -> None:
for j in range(self.main_size):
self.cells[0][j].state = State.WALL
self.cells[self.main_size - 1][j].state = State.WALL
def set_vertical_walls(self) -> None:
for i in range(self.main_size):
self.cells[i][0].state = State.WALL
self.cells[i][self.main_size - 1].state = State.WALL
def set_cells_neighboring(self) -> None:
for i in range(1, self.main_size - 1):
for j in range(1, self.main_size - 1):
self.cells[i][j].set_neighbors(
self.cells[i][j - 1],
self.cells[i][j + 1],
self.cells[i - 1][j],
self.cells[i + 1][j],
)
def set_player(self) -> None:
self.cells[self.start_player_position][self.start_player_position].player_is_here = True
def set_bombs(self) -> None:
# Place a certain number of bombs randomly on the board.
for _ in range(self.size + 2):
cell = self.cells[random.randint(2, self.size - 1)][random.randint(2, self.size - 1)]
if cell.value != -1:
cell.value = -1
# Update neighboring cells by increasing their bomb count.
self.increase_value(cell.down)
self.increase_value(cell.down.left)
self.increase_value(cell.down.right)
self.increase_value(cell.up)
self.increase_value(cell.up.left)
self.increase_value(cell.up.right)
self.increase_value(cell.left)
self.increase_value(cell.right)
@staticmethod
def increase_value(cell: Cell) -> None:
# Increment the cell's value if it is not a bomb.
cell.value += 1 if cell.value != -1 else 0
def action(self, ch: str) -> None:
# Map key inputs to actions on the current player cell.
match ch:
case "w":
self.player = self.player.set_state(Action.MOVE_UP)
case "a":
self.player = self.player.set_state(Action.MOVE_LEFT)
case "s":
self.player = self.player.set_state(Action.MOVE_DOWN)
case "d":
self.player = self.player.set_state(Action.MOVE_RIGHT)
case "e":
self.player = self.player.set_state(Action.CLICK)
case "q":
self.player = self.player.set_state(Action.SET_FLAG)
case " ":
self.player.state = State.BOMB
case _:
pass
def __str__(self) -> str:
return "\n".join(["".join([str(cell) for cell in rows]) for rows in self.cells])
def player_win(self) -> bool:
# Determine if the player has won by checking if every non-bomb, non-wall cell has been revealed.
for cell in itertools.chain(*self.cells):
if cell.value >= 0 and cell.state != State.WALL and not cell.seen:
return False
return True
Grid Initialization:
The board is created with an extra border to act as walls.
Walls:
set_horizontal_walls and set_vertical_walls add walls around the game area.
Neighboring Cells:
set_cells_neighboring connects each cell to its adjacent neighbors.
Bomb Placement:
set_bombs randomly places bombs and updates the numbers of adjacent cells.
User Input:
The action method maps key presses (such as “w”, “a”, “s”, “d” for movement and “e” for clicking) to corresponding actions.
Win Condition:
The player_win method checks if all non-bomb cells have been revealed.
5. The Game Class and Game Loop
The Game class encapsulates the main game loop, managing the display and processing user input until the game ends.
class Game:
def __init__(self) -> None:
self.board = Board(15)
def run(self) -> None:
self._bold_font()
while self.allow_continue():
self._print_board()
self.board.action(getch())
self.print_result()
@staticmethod
def _bold_font() -> None:
print("\033[1;10m")
@staticmethod
def clear_screen() -> None:
print("\033[H\033[J", end="")
def _print_board(self) -> None:
self.clear_screen()
print(self.board)
def allow_continue(self) -> bool:
# Continue the game if the player hasn't hit a bomb and hasn't yet won.
return self.board.player.state != State.BOMB and not self.board.player_win()
def print_result(self) -> None:
# Once the game is over, update bomb cells to reflect win or loss.
for cell in filter(lambda c: c.value < 0, itertools.chain(*self.board.cells)):
cell.state = State.WIN if self.board.player_win() else State.DEAD
cell.player_is_here = False
self._print_board()
def run() -> None:
Game().run()
if __name__ == "__main__":
run()
Game Initialization:
A Game object is created with a board of size 15.
Game Loop:
The loop continues while the player hasn't triggered a bomb or met the win condition. Each iteration clears the screen, prints the board, and processes a key input.
Display Settings:
The _bold_font method sets a bold font for improved visual appeal.
Result Handling:
After the game ends, print_result updates bomb cells to indicate whether the player won (WIN state) or lost (DEAD state) and then prints the final board.
6. How to Run the Game
To run this Minesweeper game in your Python terminal:
Prerequisites:
Python 3.10 or above. No external libraries are needed as the game uses only standard Python libraries.
Download the Code:
Save the entire code in a file (for example, minesweeper_game.py) or download the grid_base.py file.
Run the Game:
Open your terminal and navigate to the directory containing the file. Execute the command:
python3 minesweeper_game.py
7. How to Play
Once the game starts, the Minesweeper board will be displayed in your terminal. Use the following controls:
Movement:
-
w: Move Up -
a: Move Left -
s: Move Down -
d: Move Right -
e: Click (reveal a cell) -
q: Set/Toggle Flag (to mark a suspected bomb) -
Space: Exit the game
Objective:
Reveal all cells that do not contain bombs. If you reveal a bomb, the game is over. When all safe cells are revealed, you win!
8. Conclusion
This comprehensive guide provided a step-by-step explanation of the Minesweeper game code in the Python terminal. We covered:
- How to handle user input with a cross-platform
getchfunction. - The use of
Enumclasses to manage cell states and actions. - Detailed explanations of the
CellandBoardclasses, including neighbor assignment, bomb placement, and win condition checking. - The game loop and how the
Gameclass manages the overall flow. - Instructions on running the game and the controls for playing.
Enjoy the game!

Top comments (0)