How to create a fairly basic game using Python w/ Turtle Graphics
Hello! If you're reading this, I can only assume you want to learn about how to make a fairly basic game using Python with Turtle Graphics. If that's not the case, why are you here? Go do something else.
If you're still here, I'm going to assume you have some basic knowledge of Python (syntax, how to use variables, etc.) and some more in-depth of Turtle Graphics. If you don't, please at least read the first 4 chapters or so of this resource. If you need to find more about turtle functions (which you likely will), see these docs.
I know this is long, but if you want to do this right, it's VERY IMPORTANT to read through each section!!! If you miss even one thing, your code may not work!
OK, so the first thing you need to do is create a Python with Turtle Graphics Repl. If you already have an account, you can do this by clicking the big red plus button, click Search All Languages, searching for Python, and selecting Python (with Turtle). Alternatively, (and anyone can do it this way), you can just click on this link! Now, the next thing you should do is import the turtle graphics module. To import the turtle graphics module, include the following lines at the top of your code:
#Import statements import turtle
It's as simple as that! The first line a comment - it is very useful in keeping your code organized and readable. The second line is called an import statement, and it does just what it sounds like it does.
Now that we have imported the turtle module, we can get started. To make a Python w/ Turtle repl work, we need to have something for the turtle to draw on. Now, technically this isn't necessary at this stage, it will be if you want to set a background color, and it will be for later things. To create a window, add a blank line, then the following code:
#Creating the window window = turtle.Screen()
If you would like to have a background color for the window, also include this line after the following lines:
window.bgcolor("#UVWXYZ"), where UVWXYZ is the hexadecimal color code of the color you want for the background color. (To get a hex color code, just Google 'color code picker'.) The hex color code has to be surrounded in quotes because it is a string and must be read as such. So, just to recap, your code should now look like this (I have included my own hex color code, feel free to use it or choose your own):
#Import statements import turtle #Creating the window window = turtle.Screen() window.bgcolor("#444444")
Now comes the fun part - you can press the green 'Run' button and see something visibly happen! Up in the top left corner of the right side of your screen (under the section labeled 'result'), you should see the word "center". If you choose a background color, you should also see that area turn that color. If this worked, great! Continue to step 3. If not - double-check you have entered the code correctly.
Now, we're going to draw the area that the game will be played in. This will also define the grid we're going to use for the game. For this example, I'm going to be using a box size of 500x500 (-250 to 250 on each axis), with a grid size of 50. To do this, we need to do a few things. First, in a new section (separated from the previous sections by an empty line), we need to create a turtle to use to create the box. So, we're going to create a comment detailing what this section of code does, then we're going to create our first turtle. Let's add the following lines of code:
#Initializing turtles outer = turtle.Turtle()
The second line,
outer = turtle.Turtle(), creates a new turtle object and calls it
outer. We will be doing a lot more of this later, so remember this.
Now, we're going to create our play area. First of all, let's make a new section. The first line we're going to do is set
outer's speed. To set the speed of a turtle, you use the function
turtle.speed(x), where turtle is the name of the turtle object you're using and x is a number from 0 to 10. Speed increases from 1 to 10, and 0 is the maximum speed - going as fast as python can handle. This is what we want to use, because it will keep the user from having to wait as long to start. So, to set the speed of
outer to the fastest possible, we're going to use
Next, let's set a border color and fill color for the area. This uses the
turtle.color() function, which takes up to 2 arguments (the things in the parenthesis). The first is the pen color, or the color that it will draw with. The second is the fill color, which is exactly what it sounds like - the color that is filled in when using fill. For this example, let's use #000000 (black) for the pen color and #FFFFFF (white) for the fill color. So, underneath the outer.speed, we're going to have the following line:
DISCLAIMER: From this point on, I won't be explaining in as much detail how each command works, though I will continue giving examples. Please see the docs if you want more info.
The first thing to do when drawing the area of the game is to decide how big you want the area to be. In this example, I will be using 500x500, but you can easily change that. Since your turtle's pen defaults to down, if you want to move it without drawing, you have to send
Next, you need to go to the coordinates of whatever point you want to be one of the corners of your area - the easiest way to get this is to have your turtle go to (x,y), where x is half of the width of your area and y is half of the height. Since our area is 500x500, use
outer.goto(250,250). Next, do start being able to draw, we need to use
outer.down(). To begin filling the play area, we need to add
outer.begin_fill(), which will continue tracking the inner area of what you have drawn until you tell it to end the fill.
Now, we're finally ready to start drawing. While you can do this with individual turns and forwards, it's much better to use a loop. To make a loop that loops 4 times that will draw us a square of side length 500, we need to use this code:
for i in range(4): outer.rt(90) outer.fd(500)
This will tell the turtle to make a 90-degree right turn, then go forward 500 pixels 4 times, creating a square. Finally, we need to end the fill by sending
outer.end_fill(). If you press run now, you should get a full square drawn and filled in. If it doesn't work, check your code against this:
#Making the outer border of the game outer.speed(0) outer.color("#000000","#FFFFFF") outer.up() outer.goto(250,250) outer.down() outer.begin_fill() for z in range(4): outer.rt(90) outer.fd(500) outer.end_fill()
When you press run, you'll see it the play area, but you'll also see the turtles - the little arrows pointing towards the right. Well, if we're making a game, we can't have that! Let's hide those turtles so that they aren't visible to the user. To do this, we need to make a new section - preferably as close to the top as possible, while still below the section where you initialized the turtles. To hide a turtle, you use
turtle.ht(), where turtle is the name of the turtle. So, let's quickly make a section and hide the
outer turtle. Now, if you run it, you won't see the little arrow after the drawing is finished!
Now, this whole section is OPTIONAL! You do NOT need to include this section, but I find it helpful especially when creating the game. Now that that's out of the way...
To create a grid for the game, we need to do a few things. First, we need to keep track of each gridline's position. We can do that fairly easily with an array. This array should go in its own section, since we're going to be using more arrays later. The increments and decrements from 0 for each value in the array depend on how big you want each grid box to be - for this example, I'm going to use 50x50. So, we're going to create an array - let's call it
gridpos - by doing the following:
gridpos = . We now have an empty array. Now, to get the values for each grid position, we're going to go from the x position of the bottom left corner of your box - if you're using my numbers, it will be -250 - and add the side length of a grid square - in my case, 50 - to it. This is our first value. Continue until you reach the positive version of the first number. Mine looks like this:
gridpos = [-200, -150, -100, -50, 0, 50, 100, 150, 200]. Now, we can start to draw it.
We're going to need another turtle for the grid, so let's call it... wait for it...
grid. We're going to want to hide this turtle, so in the hiding turtles section, let's add a line to hide it. Then, set its speed to 0 and its color to whatever you like - for this example, I used #888888. Then, we're going to need to make a loop, and have it loop for 1 fewer times than the number of grid squares you want for the height or the length. So, if you want a 10x10 grid, then it needs to loop 9 times. Inside that loop, we want it to do the following: Go to the leftmost edge of the box and draw a horizontal line for each value in
gridpos, and go to the bottommost edge of the box and draw a vertical line for each value in
gridpos. The easiest way to do this is with a large loop. You'll want to loop as many times as items you have in
gridpos - in my case, 9 times. Let's break this down into 2 parts - Horizontal Lines and Vertical Lines.
Part 2A - Horizontal lines
Since your turtle will be starting at (0,0), and we want it to draw a horizontal line at each y position in
gridpos, we need to get it to go to each without drawing off the lines. We can do this by telling it to pick up the pen, then goto the far-left x value and the y value corresponding to the iterator's value in
gridpos. This is a little hard to explain, so let me show it:
for p in range(9): grid.up() grid.goto(-250,gridpos[p])
What this will do is go to (-250, the value of the p'th item in
gridpos), whatever that may be. From there, it's as simple as putting the pen down and going forward the side length of the area, then turning left 90 degrees. So, your current code for the grid should look like this (Initializing of the grid turtle and gridpos array not shown):
#Making the grid grid.speed(0) grid.color("#888888") for p in range(9): grid.up() grid.goto(-250,gridpos[p]) grid.down() grid.fd(500) grid.lt(90)
Part 2B - Vertical lines
Now, it's time to make the vertical lines. Luckily, this part is much easier since we have the first part. All you need to do is copy/paste the code from
grid.fd(500) to immediately below itself, switch -250 and gridpos[p] in the
grid.goto() statement, and switch the left turn to a right turn. Now, you should have this inside the loop:
grid.up() grid.goto(-250,gridpos[p]) grid.down() grid.fd(500) grid.lt(90) grid.up() grid.goto(gridpos[p],-250) grid.down() grid.fd(500) grid.rt(90)
If you run it now, you should see it create a complete grid in your play area.
Now, the whole point of this game is to collect boxes - you can decide how many. In the example I used 7, but I only am going to detail 3 because it's a very simple, repetitive process to make more.
First, we're going to need to create a turtle for each box. Let's go back up to the turtle initialization section, and create 3 turtles -
x = turtle.Turtle() and hide them. Now, we probably want some margin between each box and the gridlines - for this example, I'm using a margin of 5 on each side. To make this easier, let's make 2 arrays - an
xlist and a
ylist. Each x value is going to be 5 above the 50 - so -45, 5, 55, 105, etc. Each y value is going to be 5 below the 50 - so -55, -5, 45, 95, etc. These arrays should go in the same section as the
gridpos array, for organization's sake.
We also need to declare a whole ton of variables - 4 for each box - which will be used later. For each box, we need
boxZgridy, where Z is the number of each box. We can do that just below the array initialization section. So, to make it simple, for
box1 we can just use
box1x = box1y = box1gridx = box1gridy = 0, so that each of those variables has been initialized but is equal to 0. This saves line space and some time.
This is the part where things start getting repetitive. Each section to place a box is almost exactly the same as the last. First, make a new section for
box1. First, we want to set its speed to 0 so that it draws as fast as possible. Then, we're going to set its color - in this example, I'm using #8B4513, because it's a nice brownish color. Then, we're going to go up to pick up the pen and go to a set coordinate. For now, let's just go to the top right grid square. To do this, we need to tell
box1 to goto
xlist,ylist. Next, we're going to put down the pen and begin the fill. After that, we need to draw a square of side length 40, because our grid squares are side 50 and we have a margin of 5 on each side. This can be done with the same loop we used to draw the area, but instead of going forward 500 we go forward 40. Finally, we need to end the fill. If you run it now, you should see a brown box appear in the top right corner. That means it's worked! If it hasn't, see this code:
#Making the first box box1.speed(0) box1.color("#8B4513") box1.up() box1.goto(xlist,ylist) box1.down() box1.begin_fill() for z in range(4): box1.fd(40) box1.rt(90) box1.end_fill()
Now, you just need to do the same thing for boxes 2 and 3, but with a different value in the xlist and ylist. See what experimenting with it does to the position!
Part 1 - Basic randomization
Now, I know what you're thinking. "We already placed the boxes!", you'll say. "Why do we need to do another section on placing boxes?!" Well, you don't want to have it be the same position every single game, do you? That takes away some of the fun! Let's make a randomization aspect. First, up in the import section, we need to
import random. This will let us use a very important function -
random.randint(). Now, we need to create a new section above the drawing of box1 - let's do it directly below the grid section. This is where we're going to start using those variables we created. First, we need to assign
box1gridx. So, we're going to use
box1gridx = random.randint(0,9). This will tell
box1gridx to become a random integer between 0 and 9, so that it encompasses the whole of the array. Then, do the same for
box1gridy. Now, we need to assign
box1x to a number in
xlist based on
box1gridx, and the same for
box1y. This is simple, based on what we know about arrays. Just use
box1x = xlist[box1gridx], and do the same for
box1y. Now, just for debug purposes, let's print the x and y positions to the console using
print(box1x,box1y). Finally, we need to change the goto positions when drawing box1 from
box1x,box1y. If you did the randomization right, it should look like this:
#Getting coordinates for box1 box1gridx = random.randint(0,9) box1gridy = random.randint(0,9) box1x = xlist[box1gridx] box1y = ylist[box1gridy] print(box1x,box1y)
If you run it now, it should go to a completely different spot each time.
Part 2 - Specialized randomization
Something you may see come up - very rarely, but it could happen - is multiple boxes in the same spot. Now, while this is technically OK, we don't want it because it limits the game's potential. So, for
box2, we're going to use the same code as for
box1 (slightly modified to apply to
box2), but we're going to add something to it. We need to check to see if
box2x is the same as
box1x and if
box2y is the same as
box1y. If they are the same, then we need to give
box2 a new position. We can do this by putting the whole of the randomization code for
box2 in a
while True: loop (There are better ways to do this, but this is a simple way - you do NOT want to run this until you have put a
break into it), then putting in an if statement. Since we need to check if
if (box2x == box1x and box2y == box1y): should do the trick. Note that we need to check for both, because just one being the same is OK. Then, under that if it doesn't really matter what we put, so I just put a print statement saying that the placement of this box failed. What is very important is the else. We need to make sure we have an else after the if that contains the line
break. Otherwise, we will be stuck in this loop forever. So, before running, double check that you have this code for
#Getting coordinates for box2 while True: box2gridx = random.randint(0,9) box2gridy = random.randint(0,9) box2x = xlist[box2gridx] box2y = ylist[box2gridy] print(box2x,box2y) if (box2x == box1x and box2y == box1y): print("Box 2 placement failed, retrying...") else: break
Finally, let's fix
box2's goto so that it goes to
box2y. Now, to do this for more boxes it's very similar, but you need to check the position against every single previous box's position. So, for box3, you would need the conditional
(box3x == box1x and box3y == box1y) or (box3x == box2x and box3y == box2y). Fill in that randomization and its corresponding goto, and then press run. You should see all 3 boxes go to completely random spots each time!
Now, it's time to draw our player. This will eventually be moving around on the grid and collecting the boxes, but for now let's just try and draw it. To make it simple and easily distinguishable from the boxes, we're going to choose a different color and shape. In this tutorial, I'm using a circle of diameter 40 and color #ABCDEF (a nice light blue.) To draw the circle, you could use
turtle.circle(), but I'm going to use the more obscure
turtle.dot(), which draws a dot based on the turtle's position as the center of the circle. Because of this, we need a new array called
playerposlist. The values in this will be 25 away from each value in
gridpos, because our grid boxes are 50x50. So, we'll start at -225, then -175, -125, etc.
Next, we need to actually create our
player turtle and hide it, both in their respective sections. Now, we need to create 2 variables to track
player's x position and y position in relation to the grid -
playery. Let's set both of them to 4 for now.
Since we're going to need to draw the character a LOT, let's put the drawing code into a function -
drawplayer(). Since this will be called multiple times, we want our first line to clear any previous drawings using
player.clear(). Then, set the speed to 0 and have the turtle pick up the pen. Now, we're going to
player.goto(playerposlist[playerx],playerposlist[playery]). Finally, we need a dot of radius 20 and color #ABCDEF. We can do this with
player.dot(20,"#ABCDEF"). It should now look like this:
player.clear() player.speed(0) player.up() player.goto(playerposlist[playerx],playerposlist[playery]) player.dot(20,"#ABCDEF")
Now, just put a call to drawplayer() below the drawplayer() function. If you press play, the boxes should draw and the player should go to the spot that is 4 up and 4 left from the bottom left corner.
Now, if our player has spawned on or moves onto a box, we want it to be collected and disappear. To do that, we're going to need to create a variable tracking the number of
boxes. Since we have 3 boxes in this example,
boxes should equal 3. Now, it's time to create a function for checking the boxes called
boxcheck. The very first line needs to declare that we are using the global versions of
boxes and of each box's grid coordinate variables (
boxZgridy). Then, we just need to repeat 5 lines of code for each box (replacing Z with the proper box number):
if playerx == boxZgridx and playery == boxZgridy: boxZ.clear() boxes -= 1 boxZgridx = boxZgridy = -1
What this will do is check the player's position against the box's position, and if it's the same it will remove the box and decrement the number of boxes remaining, and then 'put' the box somewhere it can't be counted again. We need to put a call for
boxcheck() in the beginning of the
After you have this code block for each box, we just need to check
if boxes == 0, and if it does, then we need to call a new function:
Now, to make our win function, we need one more global variable -
haswon. Declare it to be 0. Then, create a new turtle called
winner and hide it. When the player wins, we're going to tell them they won and move their player out of bounds so they stop moving. So, now create the
Inside this function, we need to access the global variables
playery. Then, we should print something to the console telling the player they have won - something like
"You won!!". Then, we need an if to check if the win() function has already run. We do this by checking
if haswon == 0.
Inside this if, we need to first set
haswon to 1. This prevents it from running this section of the code again if
win() is called again. Next, we need to set
winner's speed to 0 and have it pick up the pen. Then, we're going to draw our 'YOU WIN!' message.
To do that, we're going to need an array called
winlist. In it, we need to have one string for each character (we can ignore the leading or ending spaces). Since we want to say 'YOU WIN!', we need 8 strings, each with a character from that in it (so "Y", "O", "U", etc.). Then, we will need a loop that loops 8 times, and each iteration will draw one character. For my loop, I used o as the iterator.
Now, let's create the code to draw each letter. We're going to have
xlist[o+1] (because it's 8 characters on a 10 square grid) for the x position, and
xlist for the y position. Then, we need to draw each character using
winner.write(). This function recieves 4 arguments - what to write (
winlist[o]), whether or not to move (leave this on
False), the alignment of the text (
align="left"), and the font information (
font=("Arial",40,"Normal")). So, the final write command should look like this:
winner.write(winlist[o], False, align="left", font=("Arial",40,"
Finally, out of the loop, we need to set
playery to 10 and redraw the player. Now, if you run this, if you won, you'd see that Y and W are not centered. The simple fix to this is to put the
winner.goto() statement inside an else, and have 2 if statements before: one
if o == 0:, and one
if o == 4:. Inside the first, we're going to use
winner.goto(xlist[o+1]+2,xlist), and inside the second, we're going to use
winner.goto(xlist[o+1]-5,xlist). Now, if you run it and won, you'd see that the Y and W ARE centered. However, we should probably make it so that you can actually... play the game.
To get user input, we're going to need to create 4 basic functions. Each one will be very similar, except for a few number changes. The first is
up(). This will need to declare the
global playery. Then, you just have a simple if:
if playery < 9:, which will check to make sure the player isn't at the top of the map. Inside the if, we just need to add one to
playery and call
The same will be done for all three other methods, with slight changes.
down() will still
global playery, but will instead check
if playery > 0 and playery < 10:. This is because the player gets put at playerx and playery position (10,10) at the end of the game, and we want them to no longer be able to move. Inside the if, we're going to decrement instead of increment the
playery value, and we're still going to
left(), we need to
global playerx instead of
playery, and the if will reflect this change. In fact,
left() is the same as
down() except you have to replace each occurance of
playerx and the name is changed. The same can be done for
right(), where you can just copy
up() and replace every occurance with
playerx and change the name.
After you have done this, you still need a way to intercept the user input. We need to add keylisteners for 8 keys: Up, Down, Left, Right, W, A, S, and D. This will be done using the
window.onkey() function, which takes 2 parameters: the function to call, and the key to listen for. The latter has to be passed to the function as a string. So, we're going to have 8 different onkey statements. To save time explaining, I will just paste them here:
window.onkey(up, "Up") window.onkey(up, "W") window.onkey(down, "Down") window.onkey(down, "S") window.onkey(left, "Left") window.onkey(left, "A") window.onkey(right, "Right") window.onkey(right, "D")
This will call the appropriate function for each keypress. Finally, to make this work, we just need to add 2 lines to the bottom of the code:
And with that, you should be done! If you press play, you should now have a functioning game. Now, if you want to, you can apply the principle learned in X -
win() to add in an introduction to the game, like in my example, but that is not necesary. If you enjoyed this tutorial, be sure to upvote it. If you want to make a fairly simple random spiral generator with python, go here. If you want to check out my crash course on LOLCODE, go and click on this cat head (or here) 🐱