Pong with Turtle Graphics: Part 1
In the previous post — Games with Turtle Graphics — I proposed a general game plan 😁 for implementing Pong with Turtle graphics. In this post and the next, I explain how to translate that game plan to working code, piece by piece. In this post we’ll put the building blocks in place, and in part 2 we’ll implement the game’s logic.
Note: As we are just beginning to learn how to model a system, the code below is not optimal. It is also verbose to make what is happening very clear, and there are a number of edge-cases that are not handled.
We’ll be making use of Turtle graphics, so let’s begin by importing some things from the
turtle module. We’ll obviously need a screen to begin with, so we must import the
Screen class. We’ll also be using individual turtles, so we need to import the
Turtle class. We’d also like to define a custom shape for the two turtles that will represent our paddles, so we need to import the
Shape class as well:
from turtle import Turtle, Screen, Shape
As we need a screen before we can display anything, it makes sense for it to be the first thing we create:
# Screen screen = Screen() screen.setup(600, 400) # width, height
Instead of using the entire screen as the area where the game is played, let’s designate a smaller area as the playing field so as to leave some space for displaying the score. Whether we use the entire screen or not, it makes sense to create variables that describe the top, bottom, left and right sides of the playing field, as we can then use these later to determine when the ball should bounce off the top and bottom of the area, and when it reaches the left or right. As the origin of the default coordinate system is in the middle of the screen, it divides the screen into halves both horizontally and vertically, with positive x values on the right, negative x values on the left, positive y values in the top half, and negative y values in the bottom half. Let’s make our playing field 200 units less than the screen height (100 less at top, 100 at bottom), and 100 units less than the screen width (50 less on left, 50 on the right):
# Playing field boundaries play_top = screen.window_height() / 2 - 100 # top of screen minus 100 units play_bottom = -screen.window_height() / 2 + 100 # 100 from bottom play_left = -screen.window_width() / 2 + 50 # 50 from left play_right = screen.window_width() / 2 - 50 # 50 from right
If you are struggling to make sense of what these variables represent in relation to the screen’s size, take a good look at the picture below:
Now that we’ve defined the playing field, we can go ahead and draw it. As explained in the previous post, it’s easiest to use a number of different turtles to draw or represent the different elements of the game, so we’ll use a dedicated turtle to draw the playing field:
# Draw playing field area = Turtle() area.hideturtle() area.penup() area.goto(play_right, play_top) area.pendown() area.goto(play_left, play_top) area.goto(play_left, play_bottom) area.goto(play_right, play_bottom) area.goto(play_right, play_top)
We need a turtle to represent the ball. Let’s change its shape to a circle so that it looks like a ball. We’ll simply move it around the screen, so let’s lift up its pen so that it doesn’t draw. The built-in turtle shapes have a 10 unit radius which seems too big for our small playing field, so let’s stretch it to half that size. Saving the ball’s new radius should prove useful for animating collisions later, as we need to work out exactly when to make it look like it has collided with something:
# Ball ball = Turtle() ball.penup() ball.shape("circle") # Use the built-in shape "circle" ball.shapesize(0.5, 0.5) # Stretch it to half default size ball_radius = 10 * 0.5 # Save the new radius for later
Now we’ll create two turtles to represent the two paddles. Let’s decide to use “L” in variable names that relate to the player on the left, and likewise “R” for things that relate to the player on the right. Turtles will simply look like paddles and move around, so they mustn’t draw:
# Paddles L = Turtle() R = Turtle() L.penup() R.penup()
We’d like our paddles to look like black rectangles, so we need to change the L and R turtle’s shapes. We must create the shape by specifying the points, then register the shape with the screen under a name we’ve made up, like “paddle”. We can then change the shape of the turtles to the “paddle” shape. Let’s make the paddles 10 units wide and 40 units tall.
The position of a turtle on the screen is actually the position of the origin of that turtle’s local coordinate system. When we add a new shape, it therefore makes sense to have that shape centred on the origin, so that the position of the turtle is the exact middle of the shape at point (0,0), otherwise there will be a mismatch between the visual shape and the position that the turtle reports. The points that define the shape will consequently have both negative and positive x and y values:
Notice how for our rectangle shape, all of the coordinates of the corner points are defined in terms of half the width and half the height of the paddle, so it makes sense to save these as variables. It should also prove useful for animating collisions later, as we will constantly need to calculate exactly where the corners of each paddle are on the screen, to check whether the ball is overlapping with a paddle.
However, as the turtle’s default mode is “standard” with the forward direction to the right of the screen instead of to the top (see the mode function), the collection of points we pass to the
shape.addcomponent(points, fill_color) function would appear to be rotated by 90 degrees relative to a standard Cartesian coordinate system. If you prefer you can change the turtle’s mode to “logo” after creating the screen and carry on as normal with coordinates as in the image above. Or you can just adjust the x and y values accordingly which is what I’ve done below:
# Paddles shape paddle_w_half = 10 / 2 # 10 units wide paddle_h_half = 40 / 2 # 40 units high paddle_shape = Shape("compound") paddle_points = ((-paddle_h_half, -paddle_w_half), (-paddle_h_half, paddle_w_half), (paddle_h_half, paddle_w_half), (paddle_h_half, -paddle_w_half)) paddle_shape.addcomponent(paddle_points, "black") screen.register_shape("paddle", paddle_shape) L.shape("paddle") R.shape("paddle")
Let’s then move each of the paddles to their starting position:
L.setx(play_left + 10) R.setx(play_right - 10)
Finally we’ll need one more turtle for displaying the score, and variables for keeping track of each player’s score. Let’s create a function which we can call whenever we want our score turtle to update the score’s display. A turtle has a function for drawing text on the screen, and it can be used to display the values of the score variables. The function should clear the previous score drawing, before drawing the new scores:
# Score score_turtle = Turtle() score_turtle.penup() score_turtle.hideturtle() score_L = 0 score_R = 0 def write_scores() : score_turtle.clear() score_turtle.goto(-screen.window_width()/4, screen.window_height()/2 - 80) score_turtle.write(score_L, align="center", font=("Arial", 32, "bold")) score_turtle.goto(screen.window_width()/4, screen.window_height()/2 - 80) score_turtle.write(score_R, align="center", font=("Arial", 32, "bold")) write_scores()
By running all of the code that we’ve written so far, you should have a setup that looks like this:
We’ve created all of the visual elements and the building blocks are in place. We can now begin to implement the game’s logic, which we’ll do in part 2.