Minesweeper isn’t just a classic puzzle — it’s also a perfect coding project for beginners. In this guide, we’ll build Minesweeper in Python step by step, explaining every line in plain English. You’ll learn how to create the grid, plant bombs, calculate hints, and handle user moves. By the end, you’ll not only have a working game but also a deeper understanding of Python programming.
What you’ll learn
You’ll read a compact Minesweeper game in Python.
We’ll break the script into small parts.
Each line gets a clear, plain-English note.
By the end, you can run it, tweak it, and make it your own.
Tip: if you like AI coding helpers, you may also enjoy our guides on Claude Code, Qwen 3 Coder, and AgentMode in Lovable.
Quick overview of the script
The game:
- Creates a square grid.
- Plants bombs at random spots.
- Fills other cells with numbers 0–8.
- Lets you dig until you win or hit a bomb.
- Prints a clean board after every move.
File header and imports
import random
Use Python’s random number generator to place bombs.
import re
Splits user input like “row,col”. The re module makes this easy.
Why both?
Random handles the game’s uncertainty.
Regex keeps inputs flexible and forgiving.
The Board class — your game’s brain
Class purpose
Encapsulates all board data and behavior.
Track dimension, bombs, revealed cells, and printing.
__init__(self, dim_size, num_bombs)
- Saves the grid size and number of bombs.
- Calls
make_new_board()to create cells and plant bombs. - Calls
assign_values_to_board()to compute neighbor counts. - Creates
self.dug = set()to remember revealed coordinates.
Key idea:
A set makes “already dug?” checks fast.
Making the empty grid and planting bombs
make_new_board(self)
- Create the 2D list
board = [[None for _ in range(self.dim_size)] for _ in range(self.dim_size)]- Builds a square grid filled with
None. - Each inner list is a row.
- Builds a square grid filled with
- Plant bombs
- Tracks
bombs_planted = 0. - While bombs remain:
- Picks a random location from
0todim_size**2 - 1. - Converts that flat index to
(row, col)with:row = loc // dim_sizecol = loc % dim_size
- Skips if a bomb is already there (
"*"). - Otherwise sets
board[row][col] = "*"and increments the count.
- Picks a random location from
- Tracks
- Return the board
Now you have bombs in random cells.
Others are stillNone.
Why flat index math?
It avoids double loops.
You map one number to a row and column cleanly.
Counting bombs around each cell
assign_values_to_board(self)
- Loops through every
(r, c). - Skips bombs.
- Replaces
Nonewith the count fromget_num_neighboring_bombs(r, c).
get_num_neighboring_bombs(self, row, col)
- Looks at up to 8 neighbors:
- Top, bottom, left, right, and diagonals.
- Uses
max()andmin()to stay inside the grid:for r in range(max(0, row-1), min(self.dim_size - 1, row + 1) + 1): for c in range(max(0, col-1), min(self.dim_size - 1, col + 1) + 1): - Skips the center cell itself.
- Counts neighbors where
self.board[r][c] == "*". - Returns the total.
Result:
Every safe cell shows how many bombs touch it.
Zero means a clear patch.
Digging logic (the core gameplay)
dig(self, row, col)
- Adds
(row, col)toself.dug. - If it’s a bomb (
"*"), returnsFalse→ game over. - If it’s
> 0, returnsTrue→ shows that number and stops. - If it’s
0, it reveals neighbors too:- Loops through adjacent cells.
- Skips cells already dug.
- Calls
self.dig(r, c)recursively.
Why recursion?
Zero means “no bombs nearby”.
So you reveal its neighbors automatically, like real Minesweeper.
Pretty-printing the board in the console
__str__(self)
- Builds a
visible_boardfirst:- Shows numbers for dug cells.
- Shows blank space for hidden cells.
- Computes column widths so the grid aligns.
- Prints a header row with column indices.
- Prints each row with
|separators. - Returns a string so
print(board)looks clean.
User experience matters.
A tidy board keeps the game readable.
It feels like a real puzzle.
The play() function — the game loop
Setup
- Creates
board = Board(dim_size, num_bombs). - Sets
safe = True.
Loop condition
- Continues while dug cells
< total safe cells. - Prints the board each turn.
Read input
- Prompts: “Where would you like to dig? Input as row,col:”
- Uses:
user_input = re.split(",(\\s)*", input(...)) row, col = int(user_input[0]), int(user_input[-1]) - Accepts “0,0” or “0, 0”.
- Validates bounds.
- Asks again if out of range.
Dig and check
- Calls
safe = board.dig(row, col). - Breaks if you hit a bomb.
Ending the game
- If
safestays true, prints a victory message. - If not:
- Prints “SORRY GAME OVER :(”.
- Reveals the whole board by setting
board.dugto include all coordinates. - Prints the final grid.
Run guard
if __name__ == "__main__":
- Calls
play()only when the file runs directly. - Keeps the module import‑friendly.
- A small best practice with big benefits.
Function map (cheat sheet)
| Function / Part | What it does | Why it exists |
|---|---|---|
Board.__init__ | Sets size, bombs, board, values, dug set | Centralizes setup |
make_new_board | Builds the grid, plants bombs | Random placement |
assign_values_to_board | Computes numbers for safe cells | Precomputes hints |
get_num_neighboring_bombs | Counts adjacent bombs | Efficient lookups |
dig | Reveals cells and expands zeros | Core gameplay |
__str__ | Renders the board nicely | Good UX |
play | Handles input and win/lose | Game loop |
__main__ guard | Runs game on direct exec | Clean imports |
Try it, then extend it
Add:
- Flags: mark suspected bombs with
F. - Input safety: handle bad input gracefully.
- Timer or score: track speed or moves.
- Difficulty presets: easy, medium, hard.
- Colors: ANSI colors for numbers and bombs.
Want to go further with automation and AI tools around coding? Read our guides on RunAgents and AWS AgentCore.

Conclusion
Now you understand a Python Minesweeper tutorial end to end.
The Minesweeper game code is small, yet complete.
Tweak it. Add flags. Improve the UI.
Then ship your version and have fun.
If you’d like help turning ideas into polished tools, we’re here. Explore our services at Ossels AI and keep building smarter.
Next reads: