[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky]
[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Enhance the minesweeper game made with Python and tkinter

[An enhanced minesweeper game built with Python and tkinter]

In my earlier post Make a minesweeper game with Python and tkinter, I mentioned some possible enhancements that you could make to the first Minesweeper program.

  • It's usually not worth really starting a game (particularly an advanced one) until you randomly left-click a few cells and hopefully expose a large safe area. You could add a tool that automatically exposes some number (perhaps 10?) or non-mine cells to get you started.
  • Sometimes you know that a cell's neighbors are safe. For example, it might be a 1 cell and you have already flagged its adjacent mine. You could provide a method to let the player expose all of its neighbors, perhaps by double-clicking.
  • You could play sounds for left-click, right-click, exploded mine, and winning.
  • You could keep score. I mostly care about whether I finish or not, but other people's versions give a score that counts the number of clicks, total time, or some combination of those.

Of course it didn't take me long before I had to give them a try. In this post, I explain how the current example implements those enhancements. They're actually delightfully simple.

Initial Guesses

The new version of the program has a Move menu that contains a Move 10 command. When you select that command, the program makes 10 random guesses to get you started. Here's the code that executes when you invoke that command.

def mnu_move10(self): '''Make 10 random moves.''' # Make sure the menu item is enabled. # (Ctrl+M will try to run it anyway.) if self.move_menu.entrycget('Move 10', 'state') != 'normal': return # Make 10 random moves. num_moves_made = 0 while num_moves_made < 10: # Pick a random cell. r = random.randrange(self.num_rows) c = random.randrange(self.num_rows) if not self.board[r][c].is_mine: self.board[r][c].clicked() num_moves_made += 1

This code first checks that the menu item is enabled and returns if it is not. You cannot select the menu item if it is disabled, but you can press the accelerator Ctrl+M so this code prevents that from doing anything. Otherwise you could just keep pressing Ctrl+M until the puzzle was solved and where's the fun in that?

Assuming the Make 10 command is enabled, the code enters a loop that executes until num_moves_made equals 10. Within the loop the code picks a random row and column. If the corresponding cell isn't a mine, the code calls the cell's clicked method, which is the method that the program uses to respond to mouse clicks on the cell. The code also increments num_moves_made.

This command lets you quickly guess 10 random cells so you can try to start with an open area. If you would rather start from scratch, don't use the tool. If the Move 10 command doesn't give you enough open space to be worth continuing, press Ctrl+B, Ctrl+I, or Ctrl+A, to start a new beginner, intermediate, or advanced level game and then press Ctrl+M again.

Clearing Neighbors

If you have identified a safe cell, then you know that its neighbors are also safe. For example, suppose you have uncovered a 2 cell and you already have two mines flagged next to it. Then you know that the cell's other six neighboring cells are not mines. If you double-click the 2 cell, the following code automatically uncovers the six neighbors.

def double_clicked(self): '''If this cell is visible, click its neighbors.''' if not self.is_shown: # Do nothing if we are not already shown. return # Click neighbors except flags. for neighbor in self.neighbors(): if not neighbor.is_flagged and not neighbor.is_shown: neighbor.clicked()

First, the code checks that the cell you double-clicked has been exposed. That prevents you from accidentally double-clicking a completely unknown cell, which is definitely unsafe. (I uncovered this little issue during testing.)

If the double-clicked cell is uncovered, the code loops through the cell's neighbors. The first version of the program defined the neighbors method so it's easy to loop through them.

If a neighbor is not flagged (you marked it as a mine) and not yet shown, then the code calls it's clicked method to expose it. If your mine placement is wrong and the neighbor is a mine, it explodes as if you had clicked it manually.

Sounds

The program uses the methods described in the post Control sounds with Pygame in Python to manage its sounds. See that post for details.

The program plays a sound in the following circumstances.

EventSound
Click on a mineExplosion
Click on a non-minePing
Expose the last non-mineVictory fanfare
Flag a cell as a mineSplat
Unflag a cellWoosh

The pinging sound got old really fast so I commented out its call to play_sound. You can turn it back on if you like.

The other sounds are okay, and I like the other two enhancements: the Move 10 command and double-clicking.

Conclusion

These changes were quite easy to implement. I didn't implement a scoring system because I don't care very much. As I mentioned in the earlier post, I care more about whether I finish at all without exploding.

Download the example to experiment with it and to see additional details.

© 2025 Rocky Mountain Computer Consulting, Inc. All rights reserved.