[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: Flag cells that must be mines in the minesweeper game made with Python and tkinter

[A minesweeper game built with Python and tkinter that flags cells that must be mines]

My earlier post Enhance the minesweeper game made with Python and tkinter shows how to make an enhanced Minesweeper game with tkinter and Python. After spending way too much time playing this game, I couldn't resist tweaking the code. This version provides a mnu_flag_required_mines method that identifies cells that must be mines and flags them.

This program has four main new parts: the main app class's mnu_flag_required_mines method, the Cell class's flagged_required_mines method, and two new Cell class helper methods.

mnu_flag_required_mines

When you invoke the Solve menu's Flag Required Mines method, the following code executes.

def mnu_flag_required_mines(self): '''Repeatedly flag cells that must be mines.''' # Loop over the board looking for cells that require mines. any_change = False made_change = True while made_change: made_change = False for row in self.board: for cell in row: if cell.flagged_required_mines(): made_change = True any_change = True return any_change

This code uses a while loop to repeatedly look for cells that must be mines. The made_change variable controls the loop.

Inside the while loop, the program loops through the board's rows and then loops through each row's cells. The code calls each cell's flagged_required_mines method (described shortly) to flag its neighbor cells if appropriate. If that method returns True, the code sets made_change and any_change both to True to indicate that there was a change.

As I said, the made_change variable controls the while loop and makes that loop repeat until there are no more cells that can be flagged as mines by the flagged_required_mines method.

The any_change variable keeps track of whether this call to mnu_flag_required_mines flagged any cells during one or more trips through the while loop. After the loop ends, the method returns any_change so the calling code knows whether any changes happened.

flagged_required_mines

[The circled cell has two unflagged mines, so the two hidden neighbors must be mines] One of the two main pieces of logic that you use to solve the puzzle looks for hidden cells that must be mines. Suppose you know that a cell has M adjacent mines. Now suppose you have flagged F of them and the cell has M - F unknown neighbors. Then you know that all of those unknown neighbors must be mines.

For example, consider the circled cell in the picture on the right. That cell has 2 neighboring mines and it has only 2 unknown neighbors (neither flagged as mines nor revealed) so those neighbors (marked with little Xs) must be mines.

The following flagged_required_mines method implements this logic.

def flagged_required_mines(self): ''' If this cell is visible, has N blank neighbors, and has N unflagged adjacent mines, then flag those blank neighbors as mines and return True. ''' if not self.is_shown: return False blank_neighbors = self.blank_neighbors() if len(blank_neighbors) < 1: return False flagged_neighbors = self.flagged_neighbors() if self.num_adjacent - len(flagged_neighbors) == len(blank_neighbors): # The number of blank neighbors matches the number of # unflagged mines. Flag the blank neighbors. for neighbor in blank_neighbors: neighbor.right_clicked() return True return False

First, if the current cell is not shown, so the player doesn't know how many mines are adjacent to this cell, the method returns False.

Next the code gets a list of the cell's blank neighbors. (The blank_neighbors method is one of the new helper methods and it's described a bit later.) If the blank neighbors list is empty, we cannot flag any neighbors as mines so the method returns False to indicate that it did not flag any cells.

The code then gets the cell's flagged neighbors list. (The flagged_neighbors method is the other helper method and it's also described a bit later.) If the cell's number of adjacent mines minus the number of flagged mines equals the number of blank neighbors, then those neighbors are also mines. The method loops through the blank neighbors and calls their right_clicked methods to flag them. It then returns True to indicate that we flagged some cells as mines.

If we could not flag any adjacent cells, the method returns false.

Helper Methods

The flagged_required_mines method uses two new helper methods: blank_neighbors and flagged_neighbors. Both of these are simply wrappers for relatively straightforward list comprehensions.

def blank_neighbors(self): '''Return a list holding this cell's blank neighbors.''' return [neighbor for neighbor in self.neighbors() if not neighbor.is_shown and not neighbor.is_flagged] def flagged_neighbors(self): '''Return a list holding this cell's flagged neighbors.''' return [neighbor for neighbor in self.neighbors() if neighbor.is_flagged]

The blank_neighbors method calls neighbors to get a list of the cell's neighbors. It uses a comprehension to loop through the neighbors and selects those that are hidden (is_shown is False) and that are not flagged.

The flagged_neighbors method also loops through the cell's neighbors, this time selecting those that are flagged.

Conclusion

Adding this tool wasn't too hard; understanding how the logic works is the hardest part.

In my next post, I'll add another puzzle-solving method

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

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