Create a program for the classic game 'Minesweeper' with Python. Use your knowledge of variables, user input, loops, conditional statements and functions.
The game 'Minesweeper'.
By making this game with code you will learn how to:
Define a world using variables
Render a world using the turtle module and loops
Create objects by asking for user input
Handle events using conditional statements
A computer or tablet capable of accessing the online interactive Python environment called Trinket. You can create a trinket.io account using your google school account; or
If you don’t want to create an account you can use the embedded interactive trinkets below to run code and see what happens; or
You can use a computer that has the Python programme installed.
Sign in to your trinket.io account, click on your name and then select ‘New Trinket’ and ‘Python’ from the drop-down menus.
You will be using the turtle module to draw the game on the screen so type import turtle and from random import randint.
The first step is to create a world for the game to be played in so create the variables MAPWDITH and MAPHEIGHT. These are global variables and so by convention are written in capitals. Setting these to 6 will give you a game world/map/area that is a 6x6 grid. The variable SQUARESIZE is set equal to 50 and determines how big in pixels each square in the grid is.
To see the world you have just created you need to render it to the screen. To do this you need to create a screen object with screen = turtle.Screen(). Calculate the screen size in pixels by creating width = MAPWIDTH * SCREENSIZE and height = MAPHEIGHT * SCREEN SIZE and then set the screen size screen.setup(width, height).
In the turtle module position 0,0 is in the centre of the screen. To change 0,0 to top left type screen.setworldcoordinates(0, -height, width, 0). Finally, to see the screen you have created, set a background colour screen.bgcolor('white').
Create a turtle to draw the minefield with turtle.hideturtle(), turtle.speed(0), and turtle.penup.
Define a function to draw a square.
def draw_square(colour, xpos, ypos):
turtle.color(colour)
turtle.goto(xpos, ypos)
turtle.begin_fill()
for i in range(4):
turtle.forward(SQUARESIZE)
turtle.right(90)
turtle.end_fill()
Finally, define a function to draw a square in each grid space. Create a variable for the fill colour of the square with three values. Use a loop to create 6 rows and another nested loop to create 6 columns. Send the turtle to the top left corner of each grid space. Call the draw_square function, set its colour parameter to a randomly chosen shade of green, and its xpos and ypos parameters to the coordinates of the top left corner of the square. Don't forget to call this function at the end of your code to see the results!
def draw_field():
square_colour = ['green', 'forest green', 'dark green']
for row in range(MAPHEIGHT):
for col in range(MAPWIDTH):
turtle.goto(col * SQUARESIZE, row * -SQUARESIZE)
draw_square(square_colour[randint(0, 2)], col*SQUARESIZE, row*-SQUARESIZE)
The objects in the game minesweeper are the hidden bombs. The more bombs there are, the more difficult the game! Start by creating an empty list for the variable bomb_map = [ ] and also a new variable called squares_to_clear = 0.
Define a function to create a bomb map with the parameter 'bombs'. This parameter can be set when you call the function and will determine (approximately) how many bombs there will be. The code global allows the squares_to_clear variable to be changed inside the function. Use a loop to create 6 rows and a nested loop to create 6 columns. For each grid square in a row, determine whether it will have a bomb or not by using an if statement. If a random integer selected between 1 and the total number of grid squares is less than or equal to the bombs parameter choice, 1 will be added to the row list and that square will have a bomb. Otherwise 0 will be added to the list and it will be a safe square, and the number of squares_to_clear goes up by 1.
def create_bombs(bombs):
global squares_to_clear
for row in range(MAPHEIGHT):
row_list = []
for col in range(MAPWIDTH):
if randint(1, MAPWIDTH*MAPHEIGHT) <= bombs:
row_list.append(1)
else:
row_list.append(0)
squares_to_clear += 1
bomb_map.append(row_list)
print(row_list)
As each row_list is finished, append it to the bomb_map list. At this point it is useful to print the list to check the code is working properly but we will delete it later to avoid cheating in the game!
Finally, call your function at the end of your code and decide how difficult you want the game to be. Choosing a bombs parameter of 4 is a good place to start create_bombs(4). You can increase this number if you find the game too easy. Run the game a few times and notice how there is not always 4 bombs, sometimes it is 3 and sometimes 5. That is because we are generating the bombs using the randint function.
You have game graphics and a list of hidden bombs, next you need the code to register when you click on a square. Insert the screen.onscreenclick(check_bomb) function at the bottom of your code. This function will feed the x,y coordinates of where you click on the screen into the check_bomb function you will create now.
Define the check_bomb function with x and y as parameters. Create two variables called x_grid and y_grid. Import the maths module floor function at the start of your code from math import floor. Divide the x and y coordinates returned from the mouse click by SQUARESIZE. Rounding this result down to the nearest integer using the floor function will give you a grid position in terms of squares rather than pixels. Now your code knows which square of the game your mouse has clicked on, you can use an if statement to check whether the corresponding entry in the bomb_map list is a 1 or 0. If it is 1 and therefore a bomb, call your draw_square function to draw a red square in that grid space. If it is 0 and so a safe square, call your draw_square function to draw a brown square.
def check_bomb(x,y):
x_grid = floor(x/SQUARESIZE)
y_grid = floor(y/-SQUARESIZE)
if bomb_map[y_grid][x_grid] == 1:
draw_square("red", x_grid*SQUARESIZE, y_grid*-SQUARESIZE)
else:
draw_square("saddle brown", x_grid*SQUARESIZE, y_grid*-SQUARESIZE)
A key feature of the game 'minesweeper' is that when you click on a safe square, the game tells you how many bombs that square is surrounded by. You can create this feature using if statements to check for bombs. As there are up to 8 other squares around each square you will need 8 checking if statements. Start by creating the variable total_bombs. Next check whether the square you are on has a square above it. If it does, look up the entry for the square above in the bomb_map list - it will have the same x_grid number and y_grid minus 1 - and check if it is equal to 1. If there is a bomb in the square above, add 1 to the total_bombs variable.
total_bombs = 0
if y_grid > 0:
if bomb_map[y_grid - 1][x_grid] == 1:
total_bombs +=1
Repeat the last three lines of codes for the other 7 surrounding squares. Finally, use the turtle write function to show the result for total bombs in the middle of the square.
turtle.color("black")
turtle.goto(turtle.xcor() + (SQUARESIZE/2), turtle.ycor() - (SQUARESIZE/2) - 6)
turtle.write(str(total_bombs), align="center", font=("Monospace", 12))
The final step before you have a fully functioning game is to create a score variable and a function to tell the player when the game is over. Add the variable score = 0 just before your check_bomb function. Now add global squares_to_clear, score as the first line of the check_bomb function. If the player has clicked in a safe square it will turn brown and display the total surrounding bombs. In addition to this, add code to reduce the number of squares_to_clear -=1 and increase the score +=1.
To tell the player when the game is over you can create a game_over(colour, result) function. This has the input parameters of colour and result so it can be used for winning or losing. The goto function sends the turtle to the middle of the game map and the write function writes out the parameter 'result'. The sety function then moves the turtle one grid square down and the write function writes out the score.
def game_over(colour, result):
turtle.color(colour)
turtle.goto(width/2, -height/2)
turtle.write(result, align="center", font=("Monospace",18))
turtle.sety(turtle.ycor() - SQUARESIZE)
turtle.write("Your score was: " + str(score), align="center", font=("Monospace",18))
You can call this function when a player chooses a square with a bomb. In the check_bomb function under the draw_square("red") code add the line:
game_over("red", str("Game Over!"))
You can also call this function when a player wins by clearing all the safe squares. At the end of the check_bomb function code add the lines:
if squares_to_clear == 0:
game_over("blue", str("Well Done!"))
It is coding convention to define all your functions first in a program, followed by variables, the screen setup and finally calling the functions that run the game. Try rearranging your code in this order.
You can also move your global variables into a separate file so they can be easily changed. Create a new file and call it variables.py copy the code for your global variables here. In your main code under 'import turtle' add the code from variables import *.
To improve the functionality of the game you can let the player decide the level of difficulty. You can implement this feature by asking for user input. Start by defining a function that asks the user whether they want the game to be easy, medium or hard. First create a variable for bombs = 0. Define a function called level_choice and set the bombs variable to global so it can be changed inside the function. Use the print function to ask the user to input e, m or h. Use the input() and lower() functions to record the result as a lowercase letter. Now use if statements to set the variable bombs equal to some global variables that we will define next.
def level_choice():
global bombs
print('Do you want easy(e), medium(m) or hard(h) difficulty?')
letter = input().lower()
if letter == 'e':
bombs = EASY
elif letter == 'm':
bombs = MEDIUM
elif letter == 'h':
bombs = HARD
Just in case the player enters something that is not an e, m or h, add the following print statement and call the level_choice function. Calling a function inside itself is called recursion and can create an infinite loop!
else:
print('That is not an option. Please try again!')
level_choice()
print('Pick a square to start!')
In the variables.py file create the following global variables EASY, MEDIUM and HARD and give them integer values. You can choose how many bombs you think would make the game easy, medium or hard, or you can make the number adjust with the size of the game grid. Use the round function to ensure the result is an integer.
EASY = round(10/100*(MAPWIDTH * MAPHEIGHT)) 10% chance of hitting a bomb
MEDIUM = round(20/100*(MAPWIDTH * MAPHEIGHT)) 20% chance of hitting a bomb
HARD = round(30/100*(MAPWIDTH * MAPHEIGHT)) 30% chance of hitting a bomb
Finally, at the end of your code in your main.py file, change the input parameter for the create_bombs function from 4 to bombs create_bombs(bombs). Enjoy your game!
Try changing your global variables to see how it affects your game, for example, a more challenging game would be a 10x10 grid. You could also personalise it by changing the colours.
If you have found it hard to follow this project, try remixing the game trinket at https://trinket.io/python/d9de1774c7