25.9. Gameplay Functions Review (Optional)¶
When you first cloned the Minesweeper project from GitHub, the repository included several complete functions. The video tutorials in the chapter mention these functions, but they skip a detailed analysis of the code. Well, now is a good time to take a deeper look!
This page describes how the ready-made functions work. However, the code shows ONE way to solve the gameplay tasks. There are many OTHER ways to achieve the same results. Feel free to create a new branch in your Git repository and experiment with your own ideas. You can try to streamline the functions, or replace any of them with a completely new set of statements. Remember, we learn to code by coding, and this includes examining other programmers’ work.
25.9.1. The game_logic.py
Module¶
This file manages the nitty-gritty details of running Minesweeper. It resets the conditions for each new game, initializes session variables, populates lists, hides mines, formats SQL query strings, and checks if the player makes a safe choice on the board.
The module begins by importing three other Python modules:
1 2 3 | import random
import string
from crud import *
|
random
is used to choose where to hide the mines. string
helps build
the row labels and cell coordinates. The functions from crud
run the SQL
queries.
Now let’s examine four of the functions included in game_logic.py
.
25.9.1.1. The make_columns()
Function¶
make_columns()
is the shortest function in the module. It’s job is to
fill a list with the column headings for the game board. It is called from the
reset_board()
function.
20 21 22 23 24 | def make_columns():
headings = ['']
for label in range(10):
headings.append(label+1)
return headings.copy()
|
Here’s a breakdown of the code:
Line 21: Instead of an empty list,
headings
begins with a single entry. That string value keeps the upper left cell on the board blank. Since the first column contains row letters instead of active spaces, no heading is necessary.Line 22: This sets up a basic
for
loop. Each time it repeats,label
is assigned a new integer (0, 1, 2, … 9).Line 23: This appends the value
label + 1
to the end of theheadings
list.Line 24: This returns an independent copy of the column headings, which is assigned to
session['columns']
.
Note
Since the loop just builds a list of 10 numbers, you might wonder why we don’t hard-code the result.
headings = ['', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Using a loop keeps our code flexible. If we add a parameter to
make_columns()
, we can adapt the for
loop to generate a different
number of headings. This opens up the possibility for other board layouts.
def make_columns(num_headings = 10):
headings = ['']
for label in range(num_headings):
headings.append(label+1)
return headings.copy()
25.9.1.2. The make_rows()
Function¶
make_rows()
is also called from the reset_board()
function. Its job is
to build a list with all the string values needed for the row labels and button
text.
There are 10 rows on the game board (A - J), and each one contains 11 columns.
To fit this structure, make_rows()
returns a two-dimensional
list of lists.
rows = [ [row_A_entries], [row_B_entries], ... [row_J_entries] ]
Each entry in rows
is a list with 11 elements. Each element is the string
value for a row label (A - J
) or button text (like B7
).
To generate the 2-dimensional list, make_rows()
uses a pair of nested
loops.
26 27 28 29 30 31 32 33 34 35 36 37 | def make_rows():
rows = []
for row in range(10):
letter = string.ascii_uppercase[row]
cells = []
for column in range(11):
if column == 0:
cells.append(letter)
else:
cells.append(letter + str(column))
rows.append(cells)
return rows
|
Here’s a breakdown of the code:
Line 27: Assigns an empty list to
rows
.Lines 28 - 30: Line 28 starts the outer loop. Each time it repeats,
row
is assigned a new value in the range 0 - 9. Line 29 uses this integer to assign an uppercase character (A - J
) toletter
. Line 30 assigns an empty list to the accumulator variablecells
.Lines 31 - 35: Line 31 begins the inner loop. Each time it repeats,
column
is assigned an integer in the range 0 - 10. Whencolumn == 0
, we are dealing with the first cell in the row. Line 33 appendsletter
tocells
.When
column
is not0
, the space on the board will contain a button. Line 35 appends a letter/number combination tocells
. This string will be used as the text inside the button.After the inner loop finishes, the
cells
list contains 11 entries.Line 36: This statement is part of the outer loop. It appends the completed
cells
list torows
.Line 37: This returns the completed
rows
list, which is assigned tosession['rows']
.
25.9.1.3. The place_mines()
Function¶
place_mines()
is called from the index()
function in main.py
. Its
job is to randomly assign mines to locations on the game board. It accepts a
single parameter, which is the number of mines to hide.
39 40 41 42 43 44 45 46 47 48 49 50 | def place_mines(amount):
mines = []
while len(mines) < amount:
row = random.choice(string.ascii_uppercase[0:10])
column = random.randint(1, 10)
location = row + str(column)
if location not in mines:
mines.append(location)
mines.sort()
record_mines(mines)
count_mines()
return mines.copy()
|
Here’s a breakdown of the code:
Line 40: Assigns an empty list to the
mines
variable. This begins yet another example of the accumulator pattern!Line 41: The condition
len(mines) < amount
keeps thewhile
loop running until the number of entries inmines
matches the number assigned toamount
.Line 42:
string.ascii_uppercase[0:10]
returns a slice from the string of uppercase letters. In this case, the index values[0:10]
return the letters'ABCDEFGHIJ'
.random.choice
then selects one letter from the slice.Line 43: This selects a random integer from 1 - 10, including both end points.
Line 44: This combines the row letter with the column number and assigns the string to
location
.Lines 45 & 46: The conditional prevents duplicate choices for the mine locations. If the newly chosen cell is NOT currently in the
mines
list, it is added. Otherwise, the choice is ignored.Lines 47 - 50: These statements alphabetize the
mines
list, call two of thecrud.py
functions, and return an independent copy of the list.
Note
We coded the record_mines()
and count_mines()
functions on the
Database Functions page.
25.9.1.4. The check_guess()
Function¶
check_guess()
is called from the play()
function in main.py
. It
returns True
each time the player chooses a safe cell on the game board.
This happens when the cell does NOT contain a mine, or if the user selects the
Flag Mine option before clicking on the space. check_guess()
returns
False
when the player hits a mine.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | def check_guess(guess, flag):
safe_guess = True
if flag:
session['flags'].append(guess)
session['num_mines'] -= 1
if guess in session['mines']:
session['mines'].remove(guess)
else:
sql_query = f"SELECT * FROM board WHERE coordinates = '{guess}' AND mine_id IS NULL"
no_mine = execute_query(sql_query)
if no_mine:
session['guesses'].append(guess)
if guess in session['flags']:
session['flags'].remove(guess)
session['num_mines'] += 1
else:
safe_guess = False
session.modified = True
sql_query = f"UPDATE board SET guessed = True WHERE coordinates = '{guess}'"
execute_query(sql_query)
return safe_guess
|
Given the size of the function, it’s easier to review it with a video!
25.9.2. The crud.py
Module¶
This file manages the nitty-gritty details of interacting with the game’s database. We reviewed or coded all but one of these functions earlier in this chapter. Now it’s time to complete that work.
25.9.2.1. The check_surroundings()
Function¶
check_surroundings()
is called at the start of each new round of
Minesweeper. When the player submits a number of mines to hide:
The
index()
function callsplace_mines()
.The
place_mines()
function picks random locations on the board to hide the new set of mines. Then it calls thecount_mines()
function.count_mines()
iterates through all of the cells in theboard
table, and it passes each location tocheck_surroundings()
.
For a given cell on the table, check_surroundings()
counts the number of
mines hidden in the surrounding spaces. This total is stored in both the
session cookie and the database.
The code for check_surroundings()
is probably the most involved. To help
break up the discussion, we’ve split the explanation into two parts. The first
describes how to perform a 2-dimensional search, which checks the 8 cells
above, below, and to each side of the selected location. The second video
examines the Python code used to perform that search.
25.9.2.2. How to Check Surrounding Cells¶
25.9.2.3. The check_surroundings()
Code¶
25.9.2.4. Video Summary¶
To check all of the spaces surrounding a given cell, we can use a pair of nested for loops. The outer loop sets a new row value. The inner loop iterates over the possible columns in each row.
To simplify our search algorithm, add extra cells to the board!
Place the cells along the outer edge of the game board.
The cells remain hidden from the player, and they will never contain a mine.
The cost of storing data for the hidden cells is well worth it. We gain a lot more by decreasing the complexity of the search.