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