Thursday 17 April 2014

Writing Pong using Python and Pygame

Ok so imagine the year is 1973. All the talk is about a new arcade game which has been released by Atari. That game is called Pong.



Now it might not seem much of a game by today's standards, but it was a massive hit in its day... or so I am told.

But don't be deceived, although a simple game, Pong covers a wide range of aspects of computer game programming. There is movement, control, collision detection, scoring, artificial intelligence. Its all in there!

Being able to program Pong is a doorway to being able to program a lot of other games.

However once you start playing Pong you might find less time to program, as it is quite addictive!

We are going to program pong using Python and Pygame.

I will be using Python 2.7. For those programming on a Raspberry Pi this will already be installed. Just ensure you click on the IDLE icon and not the IDLE3 icon. If Python 2.7 is not installed on your system you may have to install it from the Python website, just follow the link below.


Pygame is a basically a set of modules which are designed to help you write computer games. We will be using some of these modules throughout this tutorial. You will need to install Pygame, which is free, and runs on Windows, Linux and Mac OSX (plus many more operating systems!)

If you are programming on a Raspberry Pi, again this is already installed, if not to download Pygame go to the Pygame website.


One final comment before we get into the programming, on the Raspberry Pi desktop there is a Python Games icon. This links you to a website by Al Sweigart who has written several Python books including one on Pygame. If you are new to Python then check out his books.


I cannot rate them highly enough! After several false starts with other books, it was these resources that taught me Python.

Ok that's enough pre-amble, lets get on with it.

I am going to break this game down into stages, which reflect how I developed it. I hope this will show you that when you look at the game as a whole it can seem daunting, but when broken down it is just made up of many easy parts.

So the stages we will follow are:

Stage 1 - Create a blank screen
Stage 2 - Draw the arena, the paddles and the ball
Stage 3 - Move the ball around
Stage 4 - Check for a collision with all edges
Stage 5 - Move the players paddle
Stage 6 - Move the computers paddle with Artificial Intelligence
Stage 7 - Check for a collision with the paddles
Stage 8 - Add a scoring system
Stage 9 - Finally we will look at methods to increase the speed for slower computers

As we go through this tutorial I will provide the whole of the code at the beginning. To help you isolate each stage I will also provide the complete code for that stage as we get to it. I will also tell you where to type each line and include the code you need to type. Where I feel it necessary I will add additional lines in with the code to help you understand where you should type the code. You can always refer to the source code of the complete program or the source code of that stage for further guidance.

First of all, as promised, I will show you the whole code. At this stage don't worry if you don't understand it all. I would suggest having a read through it and seeing what parts you understand and which you don't.

import pygame, sys
from pygame.locals import *

# Number of frames per second
# Change this value to speed up or slow down your game
FPS = 200

#Global Variables to be used through our program

WINDOWWIDTH = 400
WINDOWHEIGHT = 300
LINETHICKNESS = 10
PADDLESIZE = 50
PADDLEOFFSET = 20

# Set up the colours
BLACK     = (0  ,0  ,0  )
WHITE     = (255,255,255)

#Draws the arena the game will be played in. 
def drawArena():
    DISPLAYSURF.fill((0,0,0))
    #Draw outline of arena
    pygame.draw.rect(DISPLAYSURF, WHITE, ((0,0),(WINDOWWIDTH,WINDOWHEIGHT)), LINETHICKNESS*2)
    #Draw centre line
    pygame.draw.line(DISPLAYSURF, WHITE, ((WINDOWWIDTH/2),0),((WINDOWWIDTH/2),WINDOWHEIGHT), (LINETHICKNESS/4))


#Draws the paddle
def drawPaddle(paddle):
    #Stops paddle moving too low
    if paddle.bottom > WINDOWHEIGHT - LINETHICKNESS:
        paddle.bottom = WINDOWHEIGHT - LINETHICKNESS
    #Stops paddle moving too high
    elif paddle.top < LINETHICKNESS:
        paddle.top = LINETHICKNESS
    #Draws paddle
    pygame.draw.rect(DISPLAYSURF, WHITE, paddle)


#draws the ball
def drawBall(ball):
    pygame.draw.rect(DISPLAYSURF, WHITE, ball)

#moves the ball returns new position
def moveBall(ball, ballDirX, ballDirY):
    ball.x += ballDirX
    ball.y += ballDirY
    return ball

#Checks for a collision with a wall, and 'bounces' ball off it.
#Returns new direction
def checkEdgeCollision(ball, ballDirX, ballDirY):
    if ball.top == (LINETHICKNESS) or ball.bottom == (WINDOWHEIGHT - LINETHICKNESS):
        ballDirY = ballDirY * -1
    if ball.left == (LINETHICKNESS) or ball.right == (WINDOWWIDTH - LINETHICKNESS):
        ballDirX = ballDirX * -1
    return ballDirX, ballDirY

#Checks is the ball has hit a paddle, and 'bounces' ball off it.     
def checkHitBall(ball, paddle1, paddle2, ballDirX):
    if ballDirX == -1 and paddle1.right == ball.left and paddle1.top < ball.top and paddle1.bottom > ball.bottom:
        return -1
    elif ballDirX == 1 and paddle2.left == ball.right and paddle2.top < ball.top and paddle2.bottom > ball.bottom:
        return -1
    else: return 1

#Checks to see if a point has been scored returns new score
def checkPointScored(paddle1, ball, score, ballDirX):
    #reset points if left wall is hit
    if ball.left == LINETHICKNESS: 
        return 0
    #1 point for hitting the ball
    elif ballDirX == -1 and paddle1.right == ball.left and paddle1.top < ball.top and paddle1.bottom > ball.bottom:
        score += 1
        return score
    #5 points for beating the other paddle
    elif ball.right == WINDOWWIDTH - LINETHICKNESS:
        score += 5
        return score
    #if no points scored, return score unchanged
    else: return score

#Artificial Intelligence of computer player 
def artificialIntelligence(ball, ballDirX, paddle2):
    #If ball is moving away from paddle, center bat
    if ballDirX == -1:
        if paddle2.centery < (WINDOWHEIGHT/2):
            paddle2.y += 1
        elif paddle2.centery > (WINDOWHEIGHT/2):
            paddle2.y -= 1
    #if ball moving towards bat, track its movement. 
    elif ballDirX == 1:
        if paddle2.centery < ball.centery:
            paddle2.y += 1
        else:
            paddle2.y -=1
    return paddle2

#Displays the current score on the screen
def displayScore(score):
    resultSurf = BASICFONT.render('Score = %s' %(score), True, WHITE)
    resultRect = resultSurf.get_rect()
    resultRect.topleft = (WINDOWWIDTH - 150, 25)
    DISPLAYSURF.blit(resultSurf, resultRect)

#Main function
def main():
    pygame.init()
    global DISPLAYSURF
    ##Font information
    global BASICFONT, BASICFONTSIZE
    BASICFONTSIZE = 20
    BASICFONT = pygame.font.Font('freesansbold.ttf', BASICFONTSIZE)

    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT)) 
    pygame.display.set_caption('Pong')

    #Initiate variable and set starting positions
    #any future changes made within rectangles
    ballX = WINDOWWIDTH/2 - LINETHICKNESS/2
    ballY = WINDOWHEIGHT/2 - LINETHICKNESS/2
    playerOnePosition = (WINDOWHEIGHT - PADDLESIZE) /2
    playerTwoPosition = (WINDOWHEIGHT - PADDLESIZE) /2
    score = 0

    #Keeps track of ball direction
    ballDirX = -1 ## -1 = left 1 = right
    ballDirY = -1 ## -1 = up 1 = down

    #Creates Rectangles for ball and paddles.
    paddle1 = pygame.Rect(PADDLEOFFSET,playerOnePosition, LINETHICKNESS,PADDLESIZE)
    paddle2 = pygame.Rect(WINDOWWIDTH - PADDLEOFFSET - LINETHICKNESS, playerTwoPosition, LINETHICKNESS,PADDLESIZE)
    ball = pygame.Rect(ballX, ballY, LINETHICKNESS, LINETHICKNESS)

    #Draws the starting position of the Arena
    drawArena()
    drawPaddle(paddle1)
    drawPaddle(paddle2)
    drawBall(ball)

    pygame.mouse.set_visible(0) # make cursor invisible

    while True: #main game loop
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            # mouse movement commands
            elif event.type == MOUSEMOTION:
                mousex, mousey = event.pos
                paddle1.y = mousey

        drawArena()
        drawPaddle(paddle1)
        drawPaddle(paddle2)
        drawBall(ball)

        ball = moveBall(ball, ballDirX, ballDirY)
        ballDirX, ballDirY = checkEdgeCollision(ball, ballDirX, ballDirY)
        score = checkPointScored(paddle1, ball, score, ballDirX)
        ballDirX = ballDirX * checkHitBall(ball, paddle1, paddle2, ballDirX)
        paddle2 = artificialIntelligence (ball, ballDirX, paddle2)

        displayScore(score)

        pygame.display.update()
        FPSCLOCK.tick(FPS)

if __name__=='__main__':
    main()

Most importantly don't be daunted! All will be revealed throughout this tutorial.

Stage 1 - Create a blank screen

import pygame, sys
from pygame.locals import *

# Number of frames per second
# Change this value to speed up or slow down your game
FPS = 200

#Global Variables to be used through our program
WINDOWWIDTH = 400
WINDOWHEIGHT = 300

#Main function
def main():
    pygame.init()
    global DISPLAYSURF

    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT)) 
    pygame.display.set_caption('Pong')

    while True: #main game loop
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        pygame.display.update()
        FPSCLOCK.tick(FPS)

if __name__=='__main__':
    main()

Ok so where do we start? Well when programming in Pygame I always start with creating a blank screen.

The first thing we need to do is to import the pygame libraries into our program so we have access to them.
We are also importing the sys libraries which we will use later in this section to exit our game.

import pygame, sys
from pygame.locals import *

We will then set a global variable which will control the speed of the program. We do this by varying the number of Frame Per Second or FPS for short. For now we will set this to 200, but can vary this later.

# Number of frames per second
# Change this value to speed up or slow down your game
FPS = 200

Remember any line which starts with a # is not seen by the program when it is running. We use this to allow us to write notes about our code. You will see I do this a lot throughout this program. Comments are very useful when coming back to read your code at a later date.

We also need some global variables for our window size. It is much easier when reading your program at a later date to remember what WINDOWHEIGHT means rather than a value such as 400 spread throughout your program. We will be able to call these variables at any time during our program.

#Global Variables to be used through our program
WINDOWWIDTH = 400
WINDOWHEIGHT = 300

Now we get into writing the main function of the program.

As with any function we have to define the function before calling it.

def main():

The next line is needed to initialise pygame.

    pygame.init()

Pygame works by drawing onto surfaces. This next line creates the main surface we will use throughout our program. We have made it a global variable so we can access it later. Why did we have to use the word global on this variable and not on the previous variables? Well adding global allows us to modify the value later on. We will be changing our surface, so it's important we can modify it when we need to.

    global DISPLAYSURF

The next line relates to the fact we want to set the frame rate ourselves, rather than allowing the program to run as fast as it wants.

    FPSCLOCK = pygame.time.Clock()

Now we assign some information to our surface, which sets the display width and height.

    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT)) 

We set the height and the width to 400 and 300 respectively using the variables we created earlier. Notice how reading WINDOWHEIGHT and WINDOWWIDTH makes it a lot easier to understand what this line means rather than reading 400,300?

Next we set the title of our window. You can put anything you want here, but I am going to call my window 'Pong'

    pygame.display.set_caption('Pong')

Now we get into the business end of our program with

    while True: #main game loop

This is an eternal loop that will keep running until the game is quit.

The first thing we should do is ensure we can quit! This is achieved during the next four lines of code.

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

For now we are only creating a blank screen so our program is not going to do anything. This will of course change later.

Therefore we can just ask the screen to update.

        pygame.display.update()

We need the next line to tell our program to set the Frames Per Second (FPS) rate to use the FPS variable we defined earlier.

        FPSCLOCK.tick(FPS)

That is the end of our main function.

There are a couple of lines we need to type in to call our main function.

if __name__=='__main__':
    main()

You should now save your game and press F5. Hopefully you see a window similar to this?



Remember to save your work often as you go through the program. It's not much fun typing the same thing in twice!

Stage 2 - Draw the Arena, the paddles and the ball.

import pygame, sys
from pygame.locals import *

# Number of frames per second
# Change this value to speed up or slow down your game
FPS = 200

#Global Variables to be used through our program
WINDOWWIDTH = 400
WINDOWHEIGHT = 300
LINETHICKNESS = 10
PADDLESIZE = 50
PADDLEOFFSET = 20

# Set up the colours
BLACK     = (0  ,0  ,0  )
WHITE     = (255,255,255)

#Draws the arena the game will be played in. 
def drawArena():
    DISPLAYSURF.fill((0,0,0))
    #Draw outline of arena
    pygame.draw.rect(DISPLAYSURF, WHITE, ((0,0),(WINDOWWIDTH,WINDOWHEIGHT)), LINETHICKNESS*2)
    #Draw centre line
    pygame.draw.line(DISPLAYSURF, WHITE, ((WINDOWWIDTH/2),0),((WINDOWWIDTH/2),WINDOWHEIGHT), (LINETHICKNESS/4))

#Draws the paddle
def drawPaddle(paddle):
    #Stops paddle moving too low
    if paddle.bottom > WINDOWHEIGHT - LINETHICKNESS:
        paddle.bottom = WINDOWHEIGHT - LINETHICKNESS
    #Stops paddle moving too high
    elif paddle.top < LINETHICKNESS:
        paddle.top = LINETHICKNESS
    #Draws paddle
    pygame.draw.rect(DISPLAYSURF, WHITE, paddle)
  
#draws the ball
def drawBall(ball):
    pygame.draw.rect(DISPLAYSURF, WHITE, ball)

#Main function
def main():
    pygame.init()
    global DISPLAYSURF

    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT)) 
    pygame.display.set_caption('Pong')

    #Initiate variable and set starting positions
    #any future changes made within rectangles
    ballX = WINDOWWIDTH/2 - LINETHICKNESS/2
    ballY = WINDOWHEIGHT/2 - LINETHICKNESS/2
    playerOnePosition = (WINDOWHEIGHT - PADDLESIZE) /2
    playerTwoPosition = (WINDOWHEIGHT - PADDLESIZE) /2

    #Creates Rectangles for ball and paddles.
    paddle1 = pygame.Rect(PADDLEOFFSET,playerOnePosition, LINETHICKNESS,PADDLESIZE)
    paddle2 = pygame.Rect(WINDOWWIDTH - PADDLEOFFSET - LINETHICKNESS, playerTwoPosition, LINETHICKNESS,PADDLESIZE)
    ball = pygame.Rect(ballX, ballY, LINETHICKNESS, LINETHICKNESS)

    #Draws the starting position of the Arena
    drawArena()
    drawPaddle(paddle1)
    drawPaddle(paddle2)
    drawBall(ball)

    while True: #main game loop
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        drawArena()
        drawPaddle(paddle1)
        drawPaddle(paddle2)
        drawBall(ball)

        pygame.display.update()
        FPSCLOCK.tick(FPS)

if __name__=='__main__':
    main()

Stage 1 was mainly some grunt work to write the bare minimum to get our game to work. The fun starts now. :-)

In stage 2 we will be drawing our arena, the paddles and the ball.

Below the other global variables defining the height and the width we will add a few more variables. Keeping these all grouped together makes it easier to read the code again at a later date.

LINETHICKNESS = 10
PADDLESIZE = 50
PADDLEOFFSET = 20

Again it is easier to read our code if we refer to LINETHICKNESS rather than 10 throughout our code.

LINETHICKNESS will be used to determine the thickness of the lines throughout our program.
PADDLESIZE is the length of the paddle.
PADDLEOFFSET is the distance the paddle is from the arena edges.

You can play around with all these variables later and see what happens.

I also know that I will need my screen to have black and white elements.

For ease I have set up some variables for the colours. The three values refer to the amount of Red, Green or Blue (RGB) in the colour.

# Set up the colours
BLACK     = (0  ,0  ,0  )
WHITE     = (255,255,255)

Lets jump back into our main function.

We know that at some point we will expect our ball to move around the screen i.e. that it will move in X and Y. Our paddles however will move up and down i.e only move in Y.

As a starting point we shall place the ball and the paddles in their central position.

We will be defining our ball and paddles using rectangles. These will be defined by stating the top left co-ordinate of each rectangle and then the length and width of each.

Let us create some variables for the ball and each paddle, and assign them values that will position then in their central positions.

    #Initiate variable and set starting positions
    #any future changes made within rectangles
    ballX = WINDOWWIDTH/2 - LINETHICKNESS/2
    ballY = WINDOWHEIGHT/2 - LINETHICKNESS/2
    playerOnePosition = (WINDOWHEIGHT - PADDLESIZE) /2
    playerTwoPosition = (WINDOWHEIGHT - PADDLESIZE) /2

So if we want to place the ball in the centre, why not use WINDOWWIDTH / 2? Well that would place the top of the ball in the centre of the screen and not the centre of the ball. We need to reduce the position by half the ball size, which is half of the LINETHICKNESS.

You can see we do the same for the paddle positions. This time I have shown the subtraction before dividing the whole value by two.

Now we have our starting co-ordinates, let us create a rectangle for the ball and the paddles.

The format for creating a rectangle is as follows.

pygame.Rect(X co-ordinate, Y co-ordinate, Width of Rectangle, Length of Rectangle)

As we have just defined all this information we can easily create the three rectangles,

    #Creates Rectangles for ball and paddles.
    paddle1 = pygame.Rect(PADDLEOFFSET,playerOnePosition, LINETHICKNESS,PADDLESIZE)
    paddle2 = pygame.Rect(WINDOWWIDTH - PADDLEOFFSET - LINETHICKNESS, playerTwoPosition, LINETHICKNESS,PADDLESIZE)
    ball = pygame.Rect(ballX, ballY, LINETHICKNESS, LINETHICKNESS)

For paddle 1 we use an X co-ordinate of PADDLEOFFSET which is defining the distance from the left hand side. Paddle 2 needs a little more work as this is on the right hand side.

The X co-ordinate for paddle 2 needs to be the width of the window (WINDOWWIDTH) minus the paddle offset (PADDLEOFFSET). However this would take us to the right hand side of the paddle, so we also need to minus off the thickness of the paddle (LINETHICKNESS). Therefore the X co-ordinate of paddle 2 would be WINDOWWIDTH - PADDLEOFFSET - LINETHICKNESS

While we have defined the starting positions, and then the required rectangles, we have not actually drawn anything yet. To make it easier lets create a separate function to draw the arena, the two paddles and the ball.

The code to call these functions is positioned directly below the rectangles we have created, and will set up our starting screen.

    #Draws the starting position of the Arena
    drawArena()
    drawPaddle(paddle1)
    drawPaddle(paddle2)
    drawBall(ball)

You will see that for the two players and the ball we pass the appropriate rectangle into the function, for the arena we don't pass anything into that function. The reason is as we move the paddles and the ball we will be updating the rectangle position to reflect the new position of these items. The arena remains the same throughout the game so the arena function will always draw the same thing regardless of where the ball and the paddles are.

As the game progresses we will be moving the ball and the paddles around, so we should update the screen every tick or FPS.

To do this we will call the same functions but this time within our while loop. This will ensure our game is updated so many times per second to match out FPS rate.

        drawArena()
        drawPaddle(paddle1)
        drawPaddle(paddle2)
        drawBall(ball)


Ok, we should now write these four functions.

Starting with drawArena()

Directly below where we defined the two colours type the following function. I will show you the whole function, then we will look at it line by line.

#Draws the arena the game will be played in. 
def drawArena():
    DISPLAYSURF.fill((0,0,0))
    #Draw outline of arena
    pygame.draw.rect(DISPLAYSURF, WHITE, ((0,0),(WINDOWWIDTH,WINDOWHEIGHT)), LINETHICKNESS*2)
    #Draw centre line
    pygame.draw.line(DISPLAYSURF, WHITE, ((WINDOWWIDTH/2),0),((WINDOWWIDTH/2),WINDOWHEIGHT), (LINETHICKNESS/4))

The first line defines the function name, and the fact the brackets are empty shows we are passing nothing into the function.

 Firstly we fill the screen background so it is all white.

    DISPLAYSURF.fill((0,0,0))

Now we want to draw a border all around the arena. We can do this as a rectangle, but rather than filling the rectangle as we did with the paddles and the ball we will make it hollow. This requires us to add an extra parameter which defines the thickness of the line.

    #Draw outline of arena
    pygame.draw.rect(DISPLAYSURF, WHITE, ((0,0),(WINDOWWIDTH,WINDOWHEIGHT)), LINETHICKNESS*2)
 
Let us analyse this line a little more.

The DISPLAYSURF tells the program which surface we want to draw onto. In our case we will use DISPLAYSURF.
WHITE tells us what colour the rectangle should be.
((0,0),(WINDOWWIDTH,WINDOWHEIGHT)) defines a rectangle. (0,0) are the (left, top) co-ordinates, and (WINDOWWIDTH,WINDOWHEIGHT) are the width and height of the rectangle. Notice these are all within a set of brackets.

Now we need to define the thickness of the line. Why have we used LINETHICKNESS * 2? Well our rectangle is around the edge of our window, and when we give it a thickness, half the line thickness is on the inside of the rectangle and half on the outside. Doubling the thickness means there is a total line of LINETHICKNESS on the inside of the rectangle, which you will see and an equal amount on the outside which you cannot see.

Any good court should have a centre line, so we will draw one in our arena.

    #Draw centre line
    pygame.draw.line(DISPLAYSURF, WHITE, ((WINDOWWIDTH/2),0),((WINDOWWIDTH/2),WINDOWHEIGHT), (LINETHICKNESS/4))

This is very similar syntax to the rectangle but ((WINDOWWIDTH/2),0) are the starting (x,y) co-ordinates of the line and ((WINDOWWIDTH/2),WINDOWHEIGHT) are the end co-ordinates.
We only want this to be a thin line so we use LINETHICKNESS / 4 to indicate its width.

There we go that is all that is needed to draw the arena, so lets move swiftly onwards.

We will create a function to draw the paddle. Now as we know we will want to draw a paddle for Paddle1 and Paddle2, we should be able to use the same function for both.

#Draws the paddle
def drawPaddle(paddle):
    #Stops paddle moving too low
    if paddle.bottom > WINDOWHEIGHT - LINETHICKNESS:
        paddle.bottom = WINDOWHEIGHT - LINETHICKNESS
    #Stops paddle moving too high
    elif paddle.top < LINETHICKNESS:
        paddle.top = LINETHICKNESS
    #Draws paddle
    pygame.draw.rect(DISPLAYSURF, WHITE, paddle)

Again the first line defines the function name and tells us we are accepting paddle as a parameter into the function. Now when we called the function earlier, one time we passed paddle1 into the function, and the second time paddle2. Well to help re-use code whatever we pass into this function will be called paddle. This means we can pass both paddle1 and paddle2 into it and it will refer to them as just paddle. This saves us having to write the same function twice, once for paddle1 and once for paddle2.

Now I want to make sure that the paddle will stay within the arena. If it tries to move out of the bottom of the arena we limit it to its lowest point, then do something similar at the top.

As paddle is a rectangle we can isolate the values that make up the rectangle, such as x and y. However pygame gives us more control than that so we can access the top, the bottom, left or right of the rectangle. In fact there are loads of options all listed on the pygame.rect page. If you would like to explore these yourself have a look at this link.


To ensure the paddle doesn't move off the bottom of the arena, it is easier if we look at the bottom of the paddle. This is done with paddle.bottom.

We don't want our paddle bottom to move beyond the arena walls. The bottom arena wall is the height of the window minus the thickness of the wall. Our code therefore looks like this.

    #Stops paddle moving too low
    if paddle.bottom > WINDOWHEIGHT - LINETHICKNESS:
        paddle.bottom = WINDOWHEIGHT - LINETHICKNESS

 We simply say if it moves too far, make it equal to the furthest point we want it to go to.

 The same is done to stop it moving too high. However this time we look at the top of the paddle using paddle.top

    #Stops paddle moving too high
    elif paddle.top < LINETHICKNESS:
        paddle.top = LINETHICKNESS

Now we draw our rectangle

    #Draws paddle
    pygame.draw.rect(DISPLAYSURF, WHITE, paddle)

This is exactly the same as when we drew the rectangle for the arena. However instead of defining the rectangle inside the brackets, we use our predefined rectangle - paddle. Nice and easy.

Finally we will write the function to draw the ball.

#draws the ball
def drawBall(ball):
    pygame.draw.rect(DISPLAYSURF, WHITE, ball)

We name the function drawBall and show it will accept ball.
Then as we have done with the paddles we simply draw the ball. Very simple.

Saving and running your program should show the arena with 2 paddles and a ball.



Stage 3 - Move the ball around. 

import pygame, sys
from pygame.locals import *

# Number of frames per second
# Change this value to speed up or slow down your game
FPS = 200

#Global Variables to be used through our program
WINDOWWIDTH = 400
WINDOWHEIGHT = 300
LINETHICKNESS = 10
PADDLESIZE = 50
PADDLEOFFSET = 20

# Set up the colours
BLACK     = (0  ,0  ,0  )
WHITE     = (255,255,255)

#Draws the arena the game will be played in. 
def drawArena():
    DISPLAYSURF.fill((0,0,0))
    #Draw outline of arena
    pygame.draw.rect(DISPLAYSURF, WHITE, ((0,0),(WINDOWWIDTH,WINDOWHEIGHT)), LINETHICKNESS*2)
    #Draw centre line
    pygame.draw.line(DISPLAYSURF, WHITE, ((WINDOWWIDTH/2),0),((WINDOWWIDTH/2),WINDOWHEIGHT), (LINETHICKNESS/4))

#Draws the paddle
def drawPaddle(paddle):
    #Stops paddle moving too low
    if paddle.bottom > WINDOWHEIGHT - LINETHICKNESS:
        paddle.bottom = WINDOWHEIGHT - LINETHICKNESS
    #Stops paddle moving too high
    elif paddle.top < LINETHICKNESS:
        paddle.top = LINETHICKNESS
    #Draws paddle
    pygame.draw.rect(DISPLAYSURF, WHITE, paddle)

#draws the ball
def drawBall(ball):
    pygame.draw.rect(DISPLAYSURF, WHITE, ball)

#moves the ball returns new position
def moveBall(ball, ballDirX, ballDirY):
    ball.x += ballDirX
    ball.y += ballDirY
    return ball

#Main function
def main():
    pygame.init()
    global DISPLAYSURF

    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT)) 
    pygame.display.set_caption('Pong')

    #Initiate variable and set starting positions
    #any future changes made within rectangles
    ballX = WINDOWWIDTH/2 - LINETHICKNESS/2
    ballY = WINDOWHEIGHT/2 - LINETHICKNESS/2
    playerOnePosition = (WINDOWHEIGHT - PADDLESIZE) /2
    playerTwoPosition = (WINDOWHEIGHT - PADDLESIZE) /2

    #Keeps track of ball direction
    ballDirX = -1 ## -1 = left 1 = right
    ballDirY = -1 ## -1 = up 1 = down

    #Creates Rectangles for ball and paddles.
    paddle1 = pygame.Rect(PADDLEOFFSET,playerOnePosition, LINETHICKNESS,PADDLESIZE)
    paddle2 = pygame.Rect(WINDOWWIDTH - PADDLEOFFSET - LINETHICKNESS, playerTwoPosition, LINETHICKNESS,PADDLESIZE)
    ball = pygame.Rect(ballX, ballY, LINETHICKNESS, LINETHICKNESS)

    #Draws the starting position of the Arena
    drawArena()
    drawPaddle(paddle1)
    drawPaddle(paddle2)
    drawBall(ball)

    while True: #main game loop
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        drawArena()
        drawPaddle(paddle1)
        drawPaddle(paddle2)
        drawBall(ball)

        ball = moveBall(ball, ballDirX, ballDirY)

        pygame.display.update()
        FPSCLOCK.tick(FPS)

if __name__=='__main__':
    main()

While we can see our game is taking shape, there is not much actually happening. We will change this now by moving the ball around. First lets determine which way the ball is moving.

My method of doing this is to create two variables. One for the direction of the ball in X, and one in Y. This allows us to control the ball in each axis independently. Lets create these variables below our initial ball and player positions.

    #Keeps track of ball direction
    ballDirX = -1 ## -1 = left 1 = right
    ballDirY = -1 ## -1 = up 1 = down

Lets say if the ball is moving left, we will make ballDirX = -1, and if it is moving right we will make it 1
Similarly if it is moving up we will make ballDirY = -1 and down ballDirY = 1.

Every FPS we will want to move the ball. Therefore we will write a function which will move the ball by updating the co-ordinates stored in the ball rectangle. We will need to pass into the function the value for ball, and the X and Y directions, ballDirX and ballDirY. Under where we called the drawBall() function in our main loop add the following line to call the moveBall function.

        ball = moveBall(ball, ballDirX, ballDirY)

Whatever is output from our moveBall function will become the new value of ball.

Outside the main loop with the other functions lets create the function moveBall below the drawBall function.

#moves the ball returns new position
def moveBall(ball, ballDirX, ballDirY):
    ball.x += ballDirX
    ball.y += ballDirY
    return ball

After naming the function listing the parameters being passed into it we simply add to the x value of ball the value from ballDirX.

    ball.x += ballDirX

If you are confused about the meaning of += remember a += b is equal to a = a + b.

This means if the ball should be moving left, we add -1 onto the co-ordinates of the ball, which moves the ball left. If ballDirX is 1, ball.x would increase by 1, moving it to the right.

We then do a similar thing for ball.y

    ball.y += ballDirY

Finally we return the modified ball back into our main function.
 
    return ball

Time to save your file and test out step 3 moving the ball.



Step 4 - Check collision with all edges

import pygame, sys
from pygame.locals import *

# Number of frames per second
# Change this value to speed up or slow down your game
FPS = 200

#Global Variables to be used through our program
WINDOWWIDTH = 400
WINDOWHEIGHT = 300
LINETHICKNESS = 10
PADDLESIZE = 50
PADDLEOFFSET = 20

# Set up the colours
BLACK     = (0  ,0  ,0  )
WHITE     = (255,255,255)

#Draws the arena the game will be played in. 
def drawArena():
    DISPLAYSURF.fill((0,0,0))
    #Draw outline of arena
    pygame.draw.rect(DISPLAYSURF, WHITE, ((0,0),(WINDOWWIDTH,WINDOWHEIGHT)), LINETHICKNESS*2)
    #Draw centre line
    pygame.draw.line(DISPLAYSURF, WHITE, ((WINDOWWIDTH/2),0),((WINDOWWIDTH/2),WINDOWHEIGHT), (LINETHICKNESS/4))

#Draws the paddle
def drawPaddle(paddle):
    #Stops paddle moving too low
    if paddle.bottom > WINDOWHEIGHT - LINETHICKNESS:
        paddle.bottom = WINDOWHEIGHT - LINETHICKNESS
    #Stops paddle moving too high
    elif paddle.top < LINETHICKNESS:
        paddle.top = LINETHICKNESS
    #Draws paddle
    pygame.draw.rect(DISPLAYSURF, WHITE, paddle)

#draws the ball
def drawBall(ball):
    pygame.draw.rect(DISPLAYSURF, WHITE, ball)

#moves the ball returns new position
def moveBall(ball, ballDirX, ballDirY):
    ball.x += ballDirX
    ball.y += ballDirY
    return ball

#Checks for a collision with a wall, and 'bounces' ball off it.
#Returns new direction
def checkEdgeCollision(ball, ballDirX, ballDirY):
    if ball.top == (LINETHICKNESS) or ball.bottom == (WINDOWHEIGHT - LINETHICKNESS):
        ballDirY = ballDirY * -1
    if ball.left == (LINETHICKNESS) or ball.right == (WINDOWWIDTH - LINETHICKNESS):
        ballDirX = ballDirX * -1
    return ballDirX, ballDirY

#Main function
def main():
    pygame.init()
    global DISPLAYSURF

    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT)) 
    pygame.display.set_caption('Pong')

    #Initiate variable and set starting positions
    #any future changes made within rectangles
    ballX = WINDOWWIDTH/2 - LINETHICKNESS/2
    ballY = WINDOWHEIGHT/2 - LINETHICKNESS/2
    playerOnePosition = (WINDOWHEIGHT - PADDLESIZE) /2
    playerTwoPosition = (WINDOWHEIGHT - PADDLESIZE) /2

    #Keeps track of ball direction
    ballDirX = -1 ## -1 = left 1 = right
    ballDirY = -1 ## -1 = up 1 = down

    #Creates Rectangles for ball and paddles.
    paddle1 = pygame.Rect(PADDLEOFFSET,playerOnePosition, LINETHICKNESS,PADDLESIZE)
    paddle2 = pygame.Rect(WINDOWWIDTH - PADDLEOFFSET - LINETHICKNESS, playerTwoPosition, LINETHICKNESS,PADDLESIZE)
    ball = pygame.Rect(ballX, ballY, LINETHICKNESS, LINETHICKNESS)

    #Draws the starting position of the Arena
    drawArena()
    drawPaddle(paddle1)
    drawPaddle(paddle2)
    drawBall(ball)

    while True: #main game loop
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        drawArena()
        drawPaddle(paddle1)
        drawPaddle(paddle2)
        drawBall(ball)

        ball = moveBall(ball, ballDirX, ballDirY)
        ballDirX, ballDirY = checkEdgeCollision(ball, ballDirX, ballDirY)

        pygame.display.update()
        FPSCLOCK.tick(FPS)

if __name__=='__main__':
    main()

So how is that moving the ball function working out for you? Well the ball certainly moves, but it disappears off the side of the arena, and keeps going!

How will we deal with this? Well, if the ball hits one of the edges, we need to reverse the direction it is travelling in. If it hits the top or bottom edge we should reverse the ballDirY, and if it hits the left or right edge we should reverse ballDirX. We will do this in a function, but before that in our main game loop lets add a call to that function. So below ball = moveBall(ball, ballDirX, ballDirY) add the following line.

        ballDirX, ballDirY = checkEdgeCollision(ball, ballDirX, ballDirY)

So we are calling a function checkEdgeCollision. The function will need to know the ball position and the current direction in X and Y, therefore we pass those parameters into the function.

Any collision will need to change ballDirX and ballDirY, so the output of the function will modify these as required.

OK time to write the function.

Outside of the main function underneath the moveBall function we will write our function called checkEdgeCollision

#Checks for a collision with a wall, and 'bounces' ball off it.
#Returns new direction
def checkEdgeCollision(ball, ballDirX, ballDirY):
    if ball.top == (LINETHICKNESS) or ball.bottom == (WINDOWHEIGHT - LINETHICKNESS):
        ballDirY = ballDirY * -1
    if ball.left == (LINETHICKNESS) or ball.right == (WINDOWWIDTH - LINETHICKNESS):
        ballDirX = ballDirX * -1
    return ballDirX, ballDirY

So after naming the function we check to see if the top of the ball has hit the top of the arena, or the bottom of the ball has hit the bottom of the arena.

    if ball.top == (LINETHICKNESS) or ball.bottom == (WINDOWHEIGHT - LINETHICKNESS):
        ballDirY = ballDirY * -1

If that is the case we multiply our Y direction by -1.

For those whose mathematics is not great,

1 x -1 = -1
-1 x -1 = 1

Therefore if the direction was negative, it becomes positive and vice versa.

We do the same thing for the left and right of the ball.

    if ball.left == (LINETHICKNESS) or ball.right == (WINDOWWIDTH - LINETHICKNESS):
        ballDirX = ballDirX * -1

and finally we return the X and Y directions, as our code is expecting us to.

    return ballDirX, ballDirY

Wow that edge detection function was pretty easy huh?

Again save and test your code. Do you see a ball bouncing around the screen?



Stage 5 - Move the players paddle.

import pygame, sys
from pygame.locals import *

# Number of frames per second
# Change this value to speed up or slow down your game
FPS = 200

#Global Variables to be used through our program
WINDOWWIDTH = 400
WINDOWHEIGHT = 300
LINETHICKNESS = 10
PADDLESIZE = 50
PADDLEOFFSET = 20

# Set up the colours
BLACK     = (0  ,0  ,0  )
WHITE     = (255,255,255)

#Draws the arena the game will be played in. 
def drawArena():
    DISPLAYSURF.fill((0,0,0))
    #Draw outline of arena
    pygame.draw.rect(DISPLAYSURF, WHITE, ((0,0),(WINDOWWIDTH,WINDOWHEIGHT)), LINETHICKNESS*2)
    #Draw centre line
    pygame.draw.line(DISPLAYSURF, WHITE, ((WINDOWWIDTH/2),0),((WINDOWWIDTH/2),WINDOWHEIGHT), (LINETHICKNESS/4))

#Draws the paddle
def drawPaddle(paddle):
    #Stops paddle moving too low
    if paddle.bottom > WINDOWHEIGHT - LINETHICKNESS:
        paddle.bottom = WINDOWHEIGHT - LINETHICKNESS
    #Stops paddle moving too high
    elif paddle.top < LINETHICKNESS:
        paddle.top = LINETHICKNESS
    #Draws paddle
    pygame.draw.rect(DISPLAYSURF, WHITE, paddle)

#draws the ball
def drawBall(ball):
    pygame.draw.rect(DISPLAYSURF, WHITE, ball)

#moves the ball returns new position
def moveBall(ball, ballDirX, ballDirY):
    ball.x += ballDirX
    ball.y += ballDirY
    return ball

#Checks for a collision with a wall, and 'bounces' ball off it.
#Returns new direction
def checkEdgeCollision(ball, ballDirX, ballDirY):
    if ball.top == (LINETHICKNESS) or ball.bottom == (WINDOWHEIGHT - LINETHICKNESS):
        ballDirY = ballDirY * -1
    if ball.left == (LINETHICKNESS) or ball.right == (WINDOWWIDTH - LINETHICKNESS):
        ballDirX = ballDirX * -1
    return ballDirX, ballDirY

#Main function
def main():
    pygame.init()
    global DISPLAYSURF

    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT)) 
    pygame.display.set_caption('Pong')

    #Initiate variable and set starting positions
    #any future changes made within rectangles
    ballX = WINDOWWIDTH/2 - LINETHICKNESS/2
    ballY = WINDOWHEIGHT/2 - LINETHICKNESS/2
    playerOnePosition = (WINDOWHEIGHT - PADDLESIZE) /2
    playerTwoPosition = (WINDOWHEIGHT - PADDLESIZE) /2

    #Keeps track of ball direction
    ballDirX = -1 ## -1 = left 1 = right
    ballDirY = -1 ## -1 = up 1 = down

    #Creates Rectangles for ball and paddles.
    paddle1 = pygame.Rect(PADDLEOFFSET,playerOnePosition, LINETHICKNESS,PADDLESIZE)
    paddle2 = pygame.Rect(WINDOWWIDTH - PADDLEOFFSET - LINETHICKNESS, playerTwoPosition, LINETHICKNESS,PADDLESIZE)
    ball = pygame.Rect(ballX, ballY, LINETHICKNESS, LINETHICKNESS)

    #Draws the starting position of the Arena
    drawArena()
    drawPaddle(paddle1)
    drawPaddle(paddle2)
    drawBall(ball)

    pygame.mouse.set_visible(0) # make cursor invisible

    while True: #main game loop
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            # mouse movement commands
            elif event.type == MOUSEMOTION:
                mousex, mousey = event.pos
                paddle1.y = mousey

        drawArena()
        drawPaddle(paddle1)
        drawPaddle(paddle2)
        drawBall(ball)

        ball = moveBall(ball, ballDirX, ballDirY)
        ballDirX, ballDirY = checkEdgeCollision(ball, ballDirX, ballDirY)

        pygame.display.update()
        FPSCLOCK.tick(FPS)

if __name__=='__main__':
    main()

While it is nice to see the ball moving so well, its a little frustrating we cannot hit it! I am sure we can resolve that fairly easily.

Earlier on we created an event.type which was equal to quit. Well there are a few event types in Python and one of those is checking the motion of the mouse.

Underneath sys.exit() which is the previous event type you programmed type the following.

            # mouse movement commands
            elif event.type == MOUSEMOTION:
                mousex, mousey = event.pos
                paddle1.y = mousey

The first event checked for a QUIT and then exited the game.  This was held within an 'if' statement. The second part you have just typed is an 'elif' statement which is checked if the 'if' statement was not true.

            elif event.type == MOUSEMOTION:

Else if (elif) the event type is mouse motion

                mousex, mousey = event.pos

This gives the X and Y co-ordinates of the mouse position. Our game is not really interested in the X position, only the Y. We can therefore make the y position of paddle1 equal to the mousey position.

                paddle1.y = mousey

Wow, Pygame really makes these things easy doesn't it?

If you run your game you will see you have control over your paddle. However the fact you can see the mouse cursor is a little annoying, so lets turn that off.

Just before your while True: loop in the main function type the following.

    pygame.mouse.set_visible(0) # make cursor invisible

This means you cannot see the cursor in your game.

Again run your game and see if that has made it better.



Stage 6 - Move the computers paddle with 'Artificial Intelligence'

import pygame, sys
from pygame.locals import *

# Number of frames per second
# Change this value to speed up or slow down your game
FPS = 200

#Global Variables to be used through our program
WINDOWWIDTH = 400
WINDOWHEIGHT = 300
LINETHICKNESS = 10
PADDLESIZE = 50
PADDLEOFFSET = 20

# Set up the colours
BLACK     = (0  ,0  ,0  )
WHITE     = (255,255,255)

#Draws the arena the game will be played in. 
def drawArena():
    DISPLAYSURF.fill((0,0,0))
    #Draw outline of arena
    pygame.draw.rect(DISPLAYSURF, WHITE, ((0,0),(WINDOWWIDTH,WINDOWHEIGHT)), LINETHICKNESS*2)
    #Draw centre line
    pygame.draw.line(DISPLAYSURF, WHITE, ((WINDOWWIDTH/2),0),((WINDOWWIDTH/2),WINDOWHEIGHT), (LINETHICKNESS/4))

#Draws the paddle
def drawPaddle(paddle):
    #Stops paddle moving too low
    if paddle.bottom > WINDOWHEIGHT - LINETHICKNESS:
        paddle.bottom = WINDOWHEIGHT - LINETHICKNESS
    #Stops paddle moving too high
    elif paddle.top < LINETHICKNESS:
        paddle.top = LINETHICKNESS
    #Draws paddle
    pygame.draw.rect(DISPLAYSURF, WHITE, paddle)


#draws the ball
def drawBall(ball):
    pygame.draw.rect(DISPLAYSURF, WHITE, ball)

#moves the ball returns new position
def moveBall(ball, ballDirX, ballDirY):
    ball.x += ballDirX
    ball.y += ballDirY
    return ball

#Checks for a collision with a wall, and 'bounces' ball off it.
#Returns new direction
def checkEdgeCollision(ball, ballDirX, ballDirY):
    if ball.top == (LINETHICKNESS) or ball.bottom == (WINDOWHEIGHT - LINETHICKNESS):
        ballDirY = ballDirY * -1
    if ball.left == (LINETHICKNESS) or ball.right == (WINDOWWIDTH - LINETHICKNESS):
        ballDirX = ballDirX * -1
    return ballDirX, ballDirY

#Artificial Intelligence of computer player       
def artificialIntelligence(ball, ballDirX, paddle2):
    #If ball is moving away from paddle, center bat
    if ballDirX == -1:
        if paddle2.centery < (WINDOWHEIGHT/2):
            paddle2.y += 1
        elif paddle2.centery > (WINDOWHEIGHT/2):
            paddle2.y -= 1
    #if ball moving towards bat, track its movement. 
    elif ballDirX == 1:
        if paddle2.centery < ball.centery:
            paddle2.y += 1
        else:
            paddle2.y -=1
    return paddle2

#Main function
def main():
    pygame.init()
    global DISPLAYSURF

    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT)) 
    pygame.display.set_caption('Pong')

    #Initiate variable and set starting positions
    #any future changes made within rectangles
    ballX = WINDOWWIDTH/2 - LINETHICKNESS/2
    ballY = WINDOWHEIGHT/2 - LINETHICKNESS/2
    playerOnePosition = (WINDOWHEIGHT - PADDLESIZE) /2
    playerTwoPosition = (WINDOWHEIGHT - PADDLESIZE) /2

    #Keeps track of ball direction
    ballDirX = -1 ## -1 = left 1 = right
    ballDirY = -1 ## -1 = up 1 = down

    #Creates Rectangles for ball and paddles.
    paddle1 = pygame.Rect(PADDLEOFFSET,playerOnePosition, LINETHICKNESS,PADDLESIZE)
    paddle2 = pygame.Rect(WINDOWWIDTH - PADDLEOFFSET - LINETHICKNESS, playerTwoPosition, LINETHICKNESS,PADDLESIZE)
    ball = pygame.Rect(ballX, ballY, LINETHICKNESS, LINETHICKNESS)

    #Draws the starting position of the Arena
    drawArena()
    drawPaddle(paddle1)
    drawPaddle(paddle2)
    drawBall(ball)

    pygame.mouse.set_visible(0) # make cursor invisible

    while True: #main game loop
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            # mouse movement commands
            elif event.type == MOUSEMOTION:
                mousex, mousey = event.pos
                paddle1.y = mousey

        drawArena()
        drawPaddle(paddle1)
        drawPaddle(paddle2)
        drawBall(ball)

        ball = moveBall(ball, ballDirX, ballDirY)
        ballDirX, ballDirY = checkEdgeCollision(ball, ballDirX, ballDirY)
        paddle2 = artificialIntelligence (ball, ballDirX, paddle2)

        pygame.display.update()
        FPSCLOCK.tick(FPS)

if __name__=='__main__':
    main()

While being able to move your paddle is all very good, it's not much fun if the computer doesn't move his paddle. Before allowing you to hit the ball lets add some 'artificial intelligence' into the computer.

The computer is not really thinking for itself, so we are not programming Artificial Intelligence in the true sense of the word. It is just following a predetermined set of instructions.

You can think of your own method of this if you like later. I am going to make the computer player play similar to how I play squash.

  • Once I have hit the ball I move to the centre of the court.
  • When my opponent has hit the ball I start to follow the ball so I can hit it back.

Again we will do all the hard work in a function.

That function will need to know:
The position of the ball so it can follow it.
The direction of the ball so it knows if it is moving away or towards the computers paddle.
The position of the paddle so it can adjust its position depending on what the ball is doing.

The output will be the position of the paddle.

Therefore in our main loop lets create a call to a function called artificialIntelligence to suit our requirements.

        paddle2 = artificialIntelligence (ball, ballDirX, paddle2)

 
Here is the function in its entirety. Have a read through and we will break it down in more detail.

#Artificial Intelligence of computer player       
def artificialIntelligence(ball, ballDirX, paddle2):
    #If ball is moving away from paddle, center bat
    if ballDirX == -1:
        if paddle2.centery < (WINDOWHEIGHT/2):
            paddle2.y += 1
        elif paddle2.centery > (WINDOWHEIGHT/2):
            paddle2.y -= 1
    #if ball moving towards bat, track its movement. 
    elif ballDirX == 1:
        if paddle2.centery < ball.centery:
            paddle2.y += 1
        else:
            paddle2.y -=1
    return paddle2

After naming the function with the correct parameters being passed into it we check to see if the ball is moving away from the paddle.

    if ballDirX == -1:

If it is we do one of two things.

If the center of the paddle in y is higher (remember the top of the screen is position 0) than the center of the screen, we move the paddle down by one.

        if paddle2.centery < (WINDOWHEIGHT/2):
            paddle2.y += 1

Notice we use another of the great options when working with rectangles to find the center of the rectangle in y by using paddle2.centery

If it is lower, we move it up by one.

        elif paddle2.centery > (WINDOWHEIGHT/2):
            paddle2.y -= 1

We have used less than and greater than. This means if the centery is equal to the WINDOWHEIGHT/2 it wont try to move and will sit stable in the central position.

That covers if the ball is moving away from the bat. If it is moving towards the bat we said we would move the bat towards the ball.

 We first define the elif part of the loop to check if the ball is moving towards the paddle.

    #if ball moving towards bat, track its movement. 
    elif ballDirX == 1:

In a similar manner to when the ball was moving away from the paddle we check to see the position of the paddle this time with respect to the ball. If it is higher than the ball we increase the paddle Y co-ordinate.

        if paddle2.centery < ball.centery:
            paddle2.y += 1

 Else, it must be lower so we decrease the paddle Y co-ordinate.

        else:
            paddle2.y -=1

Finally we return paddle2.

    return paddle2

 Right time to save and test your program.



 Stage 7 - Collision with the paddle

import pygame, sys
from pygame.locals import *

# Number of frames per second
# Change this value to speed up or slow down your game
FPS = 200

#Global Variables to be used through our program
WINDOWWIDTH = 400
WINDOWHEIGHT = 300
LINETHICKNESS = 10
PADDLESIZE = 50
PADDLEOFFSET = 20

# Set up the colours
BLACK     = (0  ,0  ,0  )
WHITE     = (255,255,255)

#Draws the arena the game will be played in. 
def drawArena():
    DISPLAYSURF.fill((0,0,0))
    #Draw outline of arena
    pygame.draw.rect(DISPLAYSURF, WHITE, ((0,0),(WINDOWWIDTH,WINDOWHEIGHT)), LINETHICKNESS*2)
    #Draw centre line
    pygame.draw.line(DISPLAYSURF, WHITE, ((WINDOWWIDTH/2),0),((WINDOWWIDTH/2),WINDOWHEIGHT), (LINETHICKNESS/4))

#Draws the paddle
def drawPaddle(paddle):
    #Stops paddle moving too low
    if paddle.bottom > WINDOWHEIGHT - LINETHICKNESS:
        paddle.bottom = WINDOWHEIGHT - LINETHICKNESS
    #Stops paddle moving too high
    elif paddle.top < LINETHICKNESS:
        paddle.top = LINETHICKNESS
    #Draws paddle
    pygame.draw.rect(DISPLAYSURF, WHITE, paddle)   

#draws the ball
def drawBall(ball):
    pygame.draw.rect(DISPLAYSURF, WHITE, ball)

#moves the ball returns new position
def moveBall(ball, ballDirX, ballDirY):
    ball.x += ballDirX
    ball.y += ballDirY
    return ball

#Checks for a collision with a wall, and 'bounces' ball off it.
#Returns new direction
def checkEdgeCollision(ball, ballDirX, ballDirY):
    if ball.top == (LINETHICKNESS) or ball.bottom == (WINDOWHEIGHT - LINETHICKNESS):
        ballDirY = ballDirY * -1
    if ball.left == (LINETHICKNESS) or ball.right == (WINDOWWIDTH - LINETHICKNESS):
        ballDirX = ballDirX * -1
    return ballDirX, ballDirY

#Checks is the ball has hit a paddle, and 'bounces' ball off it.     
def checkHitBall(ball, paddle1, paddle2, ballDirX):
    if ballDirX == -1 and paddle1.right == ball.left and paddle1.top < ball.top and paddle1.bottom > ball.bottom:
        return -1
    elif ballDirX == 1 and paddle2.left == ball.right and paddle2.top < ball.top and paddle2.bottom > ball.bottom:
        return -1
    else: return 1
  
#Artificial Intelligence of computer player 
def artificialIntelligence(ball, ballDirX, paddle2):
    #If ball is moving away from paddle, center bat
    if ballDirX == -1:
        if paddle2.centery < (WINDOWHEIGHT/2):
            paddle2.y += 1
        elif paddle2.centery > (WINDOWHEIGHT/2):
            paddle2.y -= 1
    #if ball moving towards bat, track its movement. 
    elif ballDirX == 1:
        if paddle2.centery < ball.y:
            paddle2.y += 1
        else:
            paddle2.y -=1
    return paddle2

#Main function
def main():
    pygame.init()
    global DISPLAYSURF

    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT)) 
    pygame.display.set_caption('Pong')

    #Initiate variable and set starting positions
    #any future changes made within rectangles
    ballX = WINDOWWIDTH/2 - LINETHICKNESS/2
    ballY = WINDOWHEIGHT/2 - LINETHICKNESS/2
    playerOnePosition = (WINDOWHEIGHT - PADDLESIZE) /2
    playerTwoPosition = (WINDOWHEIGHT - PADDLESIZE) /2

    #Keeps track of ball direction
    ballDirX = -1 ## -1 = left 1 = right
    ballDirY = -1 ## -1 = up 1 = down

    #Creates Rectangles for ball and paddles.
    paddle1 = pygame.Rect(PADDLEOFFSET,playerOnePosition, LINETHICKNESS,PADDLESIZE)
    paddle2 = pygame.Rect(WINDOWWIDTH - PADDLEOFFSET - LINETHICKNESS, playerTwoPosition, LINETHICKNESS,PADDLESIZE)
    ball = pygame.Rect(ballX, ballY, LINETHICKNESS, LINETHICKNESS)

    #Draws the starting position of the Arena
    drawArena()
    drawPaddle(paddle1)
    drawPaddle(paddle2)
    drawBall(ball)

    pygame.mouse.set_visible(0) # make cursor invisible

    while True: #main game loop
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            # mouse movement commands
            elif event.type == MOUSEMOTION:
                mousex, mousey = event.pos
                paddle1.y = mousey

        drawArena()
        drawPaddle(paddle1)
        drawPaddle(paddle2)
        drawBall(ball)

        ball = moveBall(ball, ballDirX, ballDirY)
        ballDirX, ballDirY = checkEdgeCollision(ball, ballDirX, ballDirY)
        ballDirX = ballDirX * checkHitBall(ball, paddle1, paddle2, ballDirX)
        paddle2 = artificialIntelligence (ball, ballDirX, paddle2)

        pygame.display.update()
        FPSCLOCK.tick(FPS)

if __name__=='__main__':
    main()

You should now have your paddle moving around the screen nicely using the mouse and the computers paddle moving using its artificial intelligence.

However we have not programmed the software to allow the ball to collide with the paddles, so let us do that now.

As always lets call the function in our main loop to ensure it is checked every-time the FPS ticks over.

        ballDirX = ballDirX * checkHitBall(ball, paddle1, paddle2, ballDirX)

We know that when the ball hits the paddle we will want to reverse the direction the ball is moving in in X.

If we make our function checkHitBall return a -1 if the ball is hit, and a 1 if it is not, then by multiplying this returned value by the value in ballDirX will give us the new ballDirX. This is similar to how we reversed the direction if the ball hit one of the sides.

So below our checkEdgeCollision function lets add a checkHitBall function.
 
#Checks is the ball has hit a paddle, and 'bounces' ball off it.     
def checkHitBall(ball, paddle1, paddle2, ballDirX):
    if ballDirX == -1 and paddle1.right == ball.left and paddle1.top < ball.top and paddle1.bottom > ball.bottom:
        return -1
    elif ballDirX == 1 and paddle2.left == ball.right and paddle2.top < ball.top and paddle2.bottom > ball.bottom:
        return -1
    else: return 1

As this function checks if the ball has hit either paddle, we pass both paddles and the ball into the function.

def checkHitBall(ball, paddle1, paddle2, ballDirX):

Now the fun bit of seeing if the ball has hit the paddle. Pygame has a few ways of doing this such as checking to see if one rectangle has intersected another. I want to make sure my ball can only be hit from the front and not the back

if ballDirX == -1 and paddle1.right == ball.left and paddle1.top < ball.top and paddle1.bottom > ball.bottom:
   
This line checks four things to see if the ball has been hit.
The first things it checks the direction of the ball. We only want to ball to be classed as being hit if it is hits the paddle from the front. If it is hit from the rear it means you have missed the ball, so we will make the ball pass through the paddle until it is back in play.
The next three things it checks are to see the position of the ball relative to the paddle. It checks if the right hand side of the paddle hits the left hand side of the ball AND that the top of the ball is lower than the top of the paddle AND the bottom of the ball is higher than the bottom of the paddle.

If these three AND statements are true, then the paddle has hit the ball so we return a -1 to flip the direction.

        return -1

We now do something very similar but for the computer paddle which is paddle 2. Notice we want the ball to be moving in a different direction and the right hand side of the ball will hit the left side of the paddle.

    elif ballDirX == 1 and paddle2.left == ball.right and paddle2.top < ball.top and paddle2.bottom > ball.bottom:
        return -1

Finally if nothing has been hit we should return a 1 so the direction of the ball doesn't change.

    else: return 1



Step 8 - Add a scoring system

import pygame, sys
from pygame.locals import *

# Number of frames per second
# Change this value to speed up or slow down your game
FPS = 200

#Global Variables to be used through our program
WINDOWWIDTH = 400
WINDOWHEIGHT = 300
LINETHICKNESS = 10
PADDLESIZE = 50
PADDLEOFFSET = 20

# Set up the colours
BLACK     = (0  ,0  ,0  )
WHITE     = (255,255,255)

#Draws the arena the game will be played in. 
def drawArena():
    DISPLAYSURF.fill((0,0,0))
    #Draw outline of arena
    pygame.draw.rect(DISPLAYSURF, WHITE, ((0,0),(WINDOWWIDTH,WINDOWHEIGHT)), LINETHICKNESS*2)
    #Draw centre line
    pygame.draw.line(DISPLAYSURF, WHITE, ((WINDOWWIDTH/2),0),((WINDOWWIDTH/2),WINDOWHEIGHT), (LINETHICKNESS/4))

#Draws the paddle
def drawPaddle(paddle):
    #Stops paddle moving too low
    if paddle.bottom > WINDOWHEIGHT - LINETHICKNESS:
        paddle.bottom = WINDOWHEIGHT - LINETHICKNESS
    #Stops paddle moving too high
    elif paddle.top < LINETHICKNESS:
        paddle.top = LINETHICKNESS
    #Draws paddle
    pygame.draw.rect(DISPLAYSURF, WHITE, paddle)

#draws the ball
def drawBall(ball):
    pygame.draw.rect(DISPLAYSURF, WHITE, ball)

#moves the ball returns new position
def moveBall(ball, ballDirX, ballDirY):
    ball.x += ballDirX
    ball.y += ballDirY
    return ball

#Checks for a collision with a wall, and 'bounces' ball off it.
#Returns new direction
def checkEdgeCollision(ball, ballDirX, ballDirY):
    if ball.top == (LINETHICKNESS) or ball.bottom == (WINDOWHEIGHT - LINETHICKNESS):
        ballDirY = ballDirY * -1
    if ball.left == (LINETHICKNESS) or ball.right == (WINDOWWIDTH - LINETHICKNESS):
        ballDirX = ballDirX * -1
    return ballDirX, ballDirY

#Checks is the ball has hit a paddle, and 'bounces' ball off it.     
def checkHitBall(ball, paddle1, paddle2, ballDirX):
    if ballDirX == -1 and paddle1.right == ball.left and paddle1.top < ball.top and paddle1.bottom > ball.bottom:
        return -1
    elif ballDirX == 1 and paddle2.left == ball.right and paddle2.top < ball.top and paddle2.bottom > ball.bottom:
        return -1
    else: return 1

#Checks to see if a point has been scored returns new score
def checkPointScored(paddle1, ball, score, ballDirX):
    #reset points if left wall is hit
    if ball.left == LINETHICKNESS: 
        return 0
    #1 point for hitting the ball
    elif ballDirX == -1 and paddle1.right == ball.left and paddle1.top < ball.top and paddle1.bottom > ball.bottom:
        score += 1
        return score
    #5 points for beating the other paddle
    elif ball.right == WINDOWWIDTH - LINETHICKNESS:
        score += 5
        return score
    #if no points scored, return score unchanged
    else: return score

#Artificial Intelligence of computer player 
def artificialIntelligence(ball, ballDirX, paddle2):
    #If ball is moving away from paddle, center bat
    if ballDirX == -1:
        if paddle2.centery < (WINDOWHEIGHT/2):
            paddle2.y += 1
        elif paddle2.centery > (WINDOWHEIGHT/2):
            paddle2.y -= 1
    #if ball moving towards bat, track its movement. 
    elif ballDirX == 1:
        if paddle2.centery < ball.centery:
            paddle2.y += 1
        else:
            paddle2.y -=1
    return paddle2

#Displays the current score on the screen
def displayScore(score):
    resultSurf = BASICFONT.render('Score = %s' %(score), True, WHITE)
    resultRect = resultSurf.get_rect()
    resultRect.topleft = (WINDOWWIDTH - 150, 25)
    DISPLAYSURF.blit(resultSurf, resultRect)

#Main function
def main():
    pygame.init()
    global DISPLAYSURF
    ##Font information
    global BASICFONT, BASICFONTSIZE
    BASICFONTSIZE = 20
    BASICFONT = pygame.font.Font('freesansbold.ttf', BASICFONTSIZE)

    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT)) 
    pygame.display.set_caption('Pong')

    #Initiate variable and set starting positions
    #any future changes made within rectangles
    ballX = WINDOWWIDTH/2 - LINETHICKNESS/2
    ballY = WINDOWHEIGHT/2 - LINETHICKNESS/2
    playerOnePosition = (WINDOWHEIGHT - PADDLESIZE) /2
    playerTwoPosition = (WINDOWHEIGHT - PADDLESIZE) /2
    score = 0

    #Keeps track of ball direction
    ballDirX = -1 ## -1 = left 1 = right
    ballDirY = -1 ## -1 = up 1 = down

    #Creates Rectangles for ball and paddles.
    paddle1 = pygame.Rect(PADDLEOFFSET,playerOnePosition, LINETHICKNESS,PADDLESIZE)
    paddle2 = pygame.Rect(WINDOWWIDTH - PADDLEOFFSET - LINETHICKNESS, playerTwoPosition, LINETHICKNESS,PADDLESIZE)
    ball = pygame.Rect(ballX, ballY, LINETHICKNESS, LINETHICKNESS)

    #Draws the starting position of the Arena
    drawArena()
    drawPaddle(paddle1)
    drawPaddle(paddle2)
    drawBall(ball)

    pygame.mouse.set_visible(0) # make cursor invisible

    while True: #main game loop
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            # mouse movement commands
            elif event.type == MOUSEMOTION:
                mousex, mousey = event.pos
                paddle1.y = mousey

        drawArena()
        drawPaddle(paddle1)
        drawPaddle(paddle2)
        drawBall(ball)

        ball = moveBall(ball, ballDirX, ballDirY)
        ballDirX, ballDirY = checkEdgeCollision(ball, ballDirX, ballDirY)
        score = checkPointScored(paddle1, ball, score, ballDirX)
        ballDirX = ballDirX * checkHitBall(ball, paddle1, paddle2, ballDirX)
        paddle2 = artificialIntelligence (ball, ballDirX, paddle2)

        displayScore(score)

        pygame.display.update()
        FPSCLOCK.tick(FPS)

if __name__=='__main__':
    main()

Right we should have the majority of our game written, so how about adding a display of the score?

We will do this in two parts. First we will update the score and then we will display the score.

Before we write our function to update the score lets call that function in our main program.

We only want the score to change if the ball has hit the paddle while moving in a certain direction. Therefore we need to call the function to check the score before we update the ball direction once it has been hit.

Add this line in the main function before you check the ball is hit, but after you have checked edge collision.

        score = checkPointScored(paddle1, ball, score, ballDirX)

This line updates the variable score with whatever is returned from the checkPointScored function. What have we forgotten? We need to initiate the variable 'score' as we have with the other variables.

So underneath the line

playerTwoPosition = (WINDOWHEIGHT - PADDLESIZE) /2

add

Score = 0

    playerTwoPosition = (WINDOWHEIGHT - PADDLESIZE) /2
    score = 0
     
Now we can write our function which will check if any points have been scored and update the score to reflect this.

Our checkPointScored function takes the position of paddle 1, the position of the ball, the current score and the direction of the ball to help determine if a point has been scored.

You can decide how and when points are scored if you want to. My function gives 5 points for beating the computers paddle and one point for hitting the ball. If your paddle is beaten, then your points are reset to 0.

The whole of the function looks as follows.

#Checks to see if a point has been scored returns new score
def checkPointScored(paddle1, ball, score, ballDirX):
    #reset points if left wall is hit
    if ball.left == LINETHICKNESS: 
        return 0
    #1 point for hitting the ball
    elif ballDirX == -1 and paddle1.right == ball.left and paddle1.top < ball.top and paddle1.bottom > ball.bottom:
        score += 1
        return score
    #5 points for beating the other paddle
    elif ball.right == WINDOWWIDTH - LINETHICKNESS:
        score += 5
        return score
    #if no points scored, return score unchanged
    else: return score

As always we define the name of the function and the parameters being passed into it.

def checkPointScored(paddle1, ball, score, ballDirX):

We then check to see if the players paddle has been beaten.

    #reset points if left wall is hit
    if ball.left == LINETHICKNESS: 
        return 0

If the ball makes it to the left hand side of the arena, we return 0 i.e. reset the score to 0.

Now we look to see if your paddle has been hit. We use the same check we carried out to see if we had hit the ball. We then increase the score by one and return it.

    #1 point for hitting the ball
    elif ballDirX == -1 and paddle1.right == ball.left and paddle1.top < ball.top and paddle1.bottom > ball.bottom:
        score += 1
        return score

Now we see if the computer paddle has been beaten. This is very similar to seeing if your own paddle has been passed by the ball. This time we check the right hand side of the ball and the right hand edge of the arena.

    #5 points for beating the other paddle
    elif ball.right == WINDOWWIDTH - LINETHICKNESS:
        score += 5
        return score

If this happens we add 5 onto the score and return that.

If nothing has happened to cause the score to change we return the score unaffected.

    #if no points scored, return score unchanged
    else: return score

That was all very straight forward wasn't it?

Now we have worked out what our score should be, we need to display the score.

In our main function we will call a function to display the score.

        displayScore(score)

Before we write the function we will have to create some information about the font we want to use to display the score.

Underneath global DISPLAYNAME at the top of our main function add the following.

    ##Font information
    global BASICFONT, BASICFONTSIZE
    BASICFONTSIZE = 20
    BASICFONT = pygame.font.Font('freesansbold.ttf', BASICFONTSIZE)

This creates a global variable called BASICFONT and another called BASICFONTSIZE.

We then set BASICFONTSIZE to 20

    BASICFONTSIZE = 20

and set the font to freesansbold

    BASICFONT = pygame.font.Font('freesansbold.ttf', BASICFONTSIZE)

Now we can write the function which displays the score.

#Displays the current score on the screen
def displayScore(score):
    resultSurf = BASICFONT.render('Score = %s' %(score), True, WHITE)
    resultRect = resultSurf.get_rect()
    resultRect.topleft = (WINDOWWIDTH - 150, 25)
    DISPLAYSURF.blit(resultSurf, resultRect)

The first line creates a new surface called resultSurf.

    resultSurf = BASICFONT.render('Score = %s' %(score), True, WHITE)

It then takes the information from BASICFONT and renders it with the following information defined within the brackets.

'Score = %s' %(score)

This explains the text to be displayed. It will say 'Score = %s' where %s is replaced by whatever follows the %. In our case the value stored in the variable score.

Therefore if the score was 4 we would see.

Score = 4

True refers to the fact we want anti-aliasing turned on. I will not go into the technical details of how this achieves its results, as it is beyond the scope of this tutorial. If you are curious then replace True with False and look at how blocky the font looks!

WHITE defines the colour and uses the information we stored in the global variable when we defined the WHITE and BLACK earlier in our program.

The next line

    resultRect = resultSurf.get_rect()

This creates a new rectangle called resultRect which is the same size as the surface we created on the previous line. It uses a built in function of pygame called get_rect().

Next we position the new rectangle

    resultRect.topleft = (WINDOWWIDTH - 150, 25)

This places the text 'WINDOWWIDTH - 150' in X which is 150 pixels from the right hand side of the screen, and 25 pixels from the top in Y.

Finally we display the surface with

    DISPLAYSURF.blit(resultSurf, resultRect)

which uses blit to update just the part of the screen specified by resultRect, which we have just created to match the size of our surface.

There we go, you should have a fully working game of pong.



Stage 9 - Speed Optimisation

import pygame, sys
from pygame.locals import *

# Number of frames per second
# Change this value to speed up or slow down your game
FPS = 40
INCREASESPEED = 5

#Global Variables to be used through our program

WINDOWWIDTH = 400
WINDOWHEIGHT = 300
LINETHICKNESS = 10
PADDLESIZE = 50
PADDLEOFFSET = 20

# Set up the colours
BLACK     = (0  ,0  ,0  )
WHITE     = (255,255,255)

#Draws the arena the game will be played in. 
def drawArena():
    DISPLAYSURF.fill((0,0,0))
    #Draw outline of arena
    pygame.draw.rect(DISPLAYSURF, WHITE, ((0,0),(WINDOWWIDTH,WINDOWHEIGHT)), LINETHICKNESS*2)
    #Draw centre line
    pygame.draw.line(DISPLAYSURF, WHITE, ((WINDOWWIDTH/2),0),((WINDOWWIDTH/2),WINDOWHEIGHT), (LINETHICKNESS/4))

#Draws the paddle
def drawPaddle(paddle):
    #Stops paddle moving too low
    if paddle.bottom > WINDOWHEIGHT - LINETHICKNESS:
        paddle.bottom = WINDOWHEIGHT - LINETHICKNESS
    #Stops paddle moving too high
    elif paddle.top < LINETHICKNESS:
        paddle.top = LINETHICKNESS
    #Draws paddle
    pygame.draw.rect(DISPLAYSURF, WHITE, paddle)

#draws the ball
def drawBall(ball):
    pygame.draw.rect(DISPLAYSURF, WHITE, ball)

#moves the ball returns new position
def moveBall(ball, ballDirX, ballDirY):
    ball.x += (ballDirX * INCREASESPEED)
    ball.y += (ballDirY * INCREASESPEED)
    return ball

#Checks for a collision with a wall, and 'bounces' ball off it.
#Returns new direction
def checkEdgeCollision(ball, ballDirX, ballDirY):
    if ball.top == (LINETHICKNESS) or ball.bottom == (WINDOWHEIGHT - LINETHICKNESS):
        ballDirY = ballDirY * -1
    if ball.left == (LINETHICKNESS) or ball.right == (WINDOWWIDTH - LINETHICKNESS):
        ballDirX = ballDirX * -1
    return ballDirX, ballDirY

#Checks is the ball has hit a paddle, and 'bounces' ball off it.     
def checkHitBall(ball, paddle1, paddle2, ballDirX):
    if ballDirX == -1 and paddle1.right == ball.left and paddle1.top < ball.top and paddle1.bottom > ball.bottom:
        return -1
    elif ballDirX == 1 and paddle2.left == ball.right and paddle2.top < ball.top and paddle2.bottom > ball.bottom:
        return -1
    else: return 1

#Checks to see if a point has been scored returns new score
def checkPointScored(paddle1, ball, score, ballDirX):
    #reset points if left wall is hit
    if ball.left == LINETHICKNESS: 
        return 0
    #1 point for hitting the ball
    elif ballDirX == -1 and paddle1.right == ball.left and paddle1.top < ball.top and paddle1.bottom > ball.bottom:
        score += 1
        return score
    #5 points for beating the other paddle
    elif ball.right == WINDOWWIDTH - LINETHICKNESS:
        score += 5
        return score
    #if no points scored, return score unchanged
    else: return score

#Artificial Intelligence of computer player       
def artificialIntelligence(ball, ballDirX, paddle2):
    #If ball is moving away from paddle, center bat
    if ballDirX == -1:
        if paddle2.centery < (WINDOWHEIGHT/2):
            paddle2.y += INCREASESPEED
        elif paddle2.centery > (WINDOWHEIGHT/2):
            paddle2.y -= INCREASESPEED
    #if ball moving towards bat, track its movement. 
    elif ballDirX == 1:
        if paddle2.centery < ball.centery:
            paddle2.y += INCREASESPEED
        else:
            paddle2.y -= INCREASESPEED
    return paddle2

#Displays the current score on the screen
def displayScore(score):
    resultSurf = BASICFONT.render('Score = %s' %(score), True, WHITE)
    resultRect = resultSurf.get_rect()
    resultRect.topleft = (WINDOWWIDTH - 150, 25)
    DISPLAYSURF.blit(resultSurf, resultRect)

#Main function
def main():
    pygame.init()
    global DISPLAYSURF
    ##Font information
    global BASICFONT, BASICFONTSIZE
    BASICFONTSIZE = 20
    BASICFONT = pygame.font.Font('freesansbold.ttf', BASICFONTSIZE)

    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT)) 
    pygame.display.set_caption('Pong')

    #Initiate variable and set starting positions
    #any future changes made within rectangles
    ballX = WINDOWWIDTH/2 - LINETHICKNESS/2
    ballY = WINDOWHEIGHT/2 - LINETHICKNESS/2
    playerOnePosition = (WINDOWHEIGHT - PADDLESIZE) /2
    playerTwoPosition = (WINDOWHEIGHT - PADDLESIZE) /2
    score = 0

    #Keeps track of ball direction
    ballDirX = -1 ## -1 = left 1 = right
    ballDirY = -1 ## -1 = up 1 = down

    #Creates Rectangles for ball and paddles.
    paddle1 = pygame.Rect(PADDLEOFFSET,playerOnePosition, LINETHICKNESS,PADDLESIZE)
    paddle2 = pygame.Rect(WINDOWWIDTH - PADDLEOFFSET - LINETHICKNESS, playerTwoPosition, LINETHICKNESS,PADDLESIZE)
    ball = pygame.Rect(ballX, ballY, LINETHICKNESS, LINETHICKNESS)

    #Draws the starting position of the Arena
    drawArena()
    drawPaddle(paddle1)
    drawPaddle(paddle2)
    drawBall(ball)

    pygame.mouse.set_visible(0) # make cursor invisible

    while True: #main game loop
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            # mouse movement commands
            elif event.type == MOUSEMOTION:
                mousex, mousey = event.pos
                paddle1.y = mousey

        drawArena()
        drawPaddle(paddle1)
        drawPaddle(paddle2)
        drawBall(ball)

        ball = moveBall(ball, ballDirX, ballDirY)
        ballDirX, ballDirY = checkEdgeCollision(ball, ballDirX, ballDirY)
        score = checkPointScored(paddle1, ball, score, ballDirX)
        ballDirX = ballDirX * checkHitBall(ball, paddle1, paddle2, ballDirX)
        paddle2 = artificialIntelligence (ball, ballDirX, paddle2)

        #displayScore(score)

        pygame.display.update()
        FPSCLOCK.tick(FPS)

if __name__=='__main__':
    main()

So we now have a fully working game of PONG. What do you feel about the speed of the game. For me it runs very well on my PC, a little slow on my Mac and very slow on my Raspberry Pi. Some of you will be able to tweak the value set in FPS to slow down or speed up the game. However if your system is running flat out what can you do to speed it up?

The main reason the game slows down is because it struggles to refresh the screen at the desired FPS. Quite a common problem in Pygame.

There are a few options to help improve the situation. You can do one or all of the following.

Increase movement per frame.

Well at the moment you are moving the ball only one pixel per frame. We can increase this.

Under the line where you defined the FPS add the following line.

INCREASESPEED = 5

Now when you move the ball in the moveBall function instead of increasing its location by one, increase it by 5 using the following.

#moves the ball returns new position
def moveBall(ball, ballDirX, ballDirY):
    ball.x += (ballDirX * INCREASESPEED)
    ball.y += (ballDirY * INCREASESPEED)
    return ball

To ensure the computers paddle is not massively slow in relation to this you should also make a similar change in the artificialIntelligence function.

#Artificial Intelligence of computer player       
def artificialIntelligence(ball, ballDirX, paddle2):
    #If ball is moving away from paddle, center bat
    if ballDirX == -1:
        if paddle2.centery < (WINDOWHEIGHT/2):
            paddle2.y += INCREASESPEED
        elif paddle2.centery > (WINDOWHEIGHT/2):
            paddle2.y -= INCREASESPEED
    #if ball moving towards bat, track its movement. 
    elif ballDirX == 1:
        if paddle2.centery < ball.centery:
            paddle2.y += INCREASESPEED
        else:
            paddle2.y -= INCREASESPEED
    return paddle2

Instead of increasing the paddle position by a value of 1 it increases it by the value stored in INCREASESPEED.

There is a warning with this method of increasing the speed. While the value 5 works in this case, increasing the value of INCREASESPEED may mean the paddles do not recognise when the ball is hit or the ball does not bounce off the side of the arena. The ball jumps by 5 pixels, which is ok. If it jumps by 10 or 20 it may not detect collision with the wall or paddle and will appear to pass through them.

Another option is simply don't display the score!

One thing which slows the game down more than anything is the displaying of the score. So to stop this slowing down the game, don't display the score!

Place a hash tag in front of the line displayScore(score) and this will stop the screen updating the score.

        #displayScore(score)

I hope you have enjoyed writing this game of pong as much as I did. Hopefully you have learned a few new Pygame tools to aid you with programming your own games!

Update

Since writing this blog post Nat over at Webucator has turned this blog post into an excellent video tutorial which refactors the code using OOP. Its definitely worth having a look at the video they have created, which I discuss in a later blog post.



Further Update

The wonderful MagPi magazine have used this blog post as the basis of one of their tutorials. Check it out in Issue 53 of the MagPi (Page 26/27). 

146 comments:

  1. every code i typed in on the program you offered came up with an error

    ReplyDelete
  2. Hi - thanks for the tutorial. I ended up with paddles that allow the ball to pass through on their edges - is this expected behavior?

    ReplyDelete
    Replies
    1. edit - on further inspection, it seems like the paddle's visual representation on the screen is not synched up with hit detection. Sometimes it will pass right through the middle if I rush the paddle over to the ball in a split second.

      Delete
  3. Thanks for sharing, nice post! Post really provice useful information!

    Giaonhan247 chuyên dịch vụ vận chuyển hàng đi mỹ cũng như dịch vụ ship hàng mỹ từ dịch vụ nhận mua hộ hàng mỹ từ website nổi tiếng Mỹ là mua hàng amazon về VN uy tín, giá rẻ.

    ReplyDelete
  4. Nice post, I also made a simple tutorial on building a pong game in python. Feel free to check it out. https://conditionalcoder.blogspot.com/2019/07/tutorial-programming-pong-in-python.html

    ReplyDelete
  5. Hiiii....Thanks for sharing Great information...Nice post...Keep move on....
    Python Training in Hyderabad

    ReplyDelete
  6. Hello do you know that the best treatment for opiod addiction,illegal or prescription is suboxone pills. Suboxone pills provides versatility in the way it helps patients.our medicated shop is the place to shop for all kinds of medication needs including;
    BuUY HYDROCODONE ONLINE
    BUY OXYCODONE ONLINE
    BUY OXYCONTIN ONLINE
    BUY VALIUM ONLINE
    BUY VYVANSE ONLINE
    BUY GABAPENTIN ONLINE
    BUY AMBIEN ONLINE
    Check out all our available pills on our online shop. https://greenlandspharmacy.com/

    ReplyDelete
  7. The drugs foster new perspectives on old problems. One of the things our mind does is tell stories about ourselves. If you're depressed, you're being told a story perhaps that you're worthless, that no one could possibly love you,
    best quality psychedelics,100% tested with discreet delivery
    buy-mescaline
    What the drugs appear to do is disable for a period of time the part of the brain where the self talks to itself.

    ReplyDelete
  8. Medical marijuana has already been successfully legalized in 23 US states.
    and California LA is one of the best state where top shelf medical marijuana are been sold at cali420supplies.com
    Why? Because there is substantial scientific proof that weed is actually good for you.
    In fact, some researchers claim marijuana to be a natural panacea to a large number of diseases.
    Email us at dovianlawson@gmail.com for your order process and talk to our experts for advice...
    we provide reliable and discreet overnight delivery within 50 states and Canada and our branch in Netherlands is in charge of orders from Europe and Asia....
    we provide you with the best buds,cbd oils,thc cartridges,dankwoods,backwoods,cbd massage ceams,cbd capsules......
    CALL/TEXT:+12563332189
    EMAIL US AT:dovianlawson@gmail.com
    blueberry-kush
    jungle-boys-weed
    packwoods-for-sale
    dankwoods-for-sale
    buy-space-monkeys-merd
    mario-carts-for-sale
    exotic-carts-for-sale
    brass-knuckles-for-sale/buy-brass-knuckles-online
    buy-dab-pen
    buy-smart-cart-cartridge
    gelato-for-sale
    buy-magic-mushrooms
    buy-girls-scout-cookies-online
    master-kush-for-sale
    kush-for-sale
    buy-fruity-pebbles-online
    buy-apple-jack-online
    buy-banana-og-online
    buy-mars-og-online
    we also give free offers for bulk orders

    ReplyDelete
  9. Cannabis, also known as marijuana among other names, is a psychoactive drug from the Cannabis plant used for medical or recreational purposes. The main psychoactive

    part of cannabis is tetrahydrocannabinol, one of the 483 known compounds in the plant, including at least 65 other cannabinoids

    buy real weed online
    buy edibles
    order cannabis edibles
    buy recreational weed online
    order cbd edibles online
    buy cbd hemp oil
    buy medical weed online
    buy dank vapes cartridges
    buy brass knuckles
    buy mario carts
    buy weed online without medical card
    buy cannabis seeds online
    buy OG kush
    buy sour diesel weed online
    buy moonrocks
    hybrid strains
    indica strains

    ReplyDelete
  10. Its full of information I am looking for and I love to post a comment that "The content of your post is awesome" Great
    will help you more:
    you want to make a game using Canvas and HTML5? Follow along with this tutorial and you'll be on your way in no time.
    HTML Game Tutorial

    ReplyDelete
  11. Quality assurance and great products have been a major concern among experts in the cannabis industry. To meet recommendations for producing medicinal marijuana products that are both effective and adequate to meet our clients’ needs, our company is strictly adherent to delivering just the gold standard. Based on a consensus, our quality assurance committee has resolved to a strict adherence to the protocol in order to maintain a standard across the production chain. The quality of our lab-tested products is unmatched in the entire industry. Cannabis Market is the leader!


    buy girls scourt cookies online

    buy strawberry cough online

    buy gorilla glue #4 online

    buy granddaddy purple feminized seeds

    buy blue dream weedstrain

    buy white widow weedstriain

    buy afghan kush online

    buy blueberry kush online

    buy lemon haze online

    buy pineapple express online

    buy purple haze feminized seeds

    buy alaskan thunderfuck weedstrain

    buy granddaddy purple feminized seeds online

    buy blue bream feminized Seedsonline

    buy lemon kush weed strain

    buy girls scourt cookies onlinr

    buy green crack online

    buy jack herb weedstrain

    buy skywalker og weedstrain

    buy sour disel weedstrain

    buy white widow online

    buy og kush onliine

    buy northern light weedstrain

    buy white widow online

    buy Obama kush weedstrain

    buy mario carts online

    buy bubble kush online

    buy blue dream weedstrain

    buy exortic carts online

    ReplyDelete
  12. Welcome to Ragdoll Kittens Cattery click here to adopt a ragdoll kitten online We are a small and loving cattery . We are pleased that you have chosen to visit our Ragdoll cats/Ragdoll kittens cattery, and hope you will notice right away from our website how beautiful and loved our Ragdoll cats and kittens are. These beauties are easily integrated into our loving family home, first as mothers carrying the litters, and then from the time the ragdoll kittens are born until they are adopted so we always have Ragdoll kittens for sale|Ragdoll kittens for adoption|Ragdoll kitten price|Ragdolls|Cost of Ragdoll kittens|. Our adult cats have tested negative for HCM and PKD1 through University of California Davis. Upon request, we have five generations of pedigree documentation on our adults available to anyone who is interested. Ragdoll kittens are registered with The International Cat Association (RAGDOLL KITTENS FOR SALE),and are never caged. The cats that are in our reputable breeding program can produce mink, sepia and traditional Ragdoll kittens. Ragdolls have a laid-back personality and enjoy being physically handled making them one of the best lap cats! We are all family here at Ragdoll kittens cattery since we are ragdoll kitten breeders and all the love we bestow by holding each cat and kitten daily further Teacup RAGDOLL Kittens for sale|Hypoallergenic kittens for sale nurtures their loving personalities and temperaments,TICA/CFA REGISTERED RAGDOLL KITTENS FOR SALE thanks for adopting a ragdoll ketten from us

    Hello, and welcome to our website for buy fennec foxes as pets. A family run farm, we have extensive experience in breeding fennec foxes.

    READ MORE We provide new homes for our baby fennec fox pets for sale. We equally provide shipping services to ensure your fox arrives your location with no hassles. Due to
    fennec fox pet for sele being illegal in certain states, we do not facilitate delivery nationwide. Our Fennec Foxes for Sale are limited to a select states which consider it legal. We equally facilitate the acquisition of Permits in states which require on before owning a
    buy baby fennec fox for sele

    ReplyDelete



  13. Despite the fact that Adderall is viewed as a physician recommended tranquilize and most patients
    buy adderall online,Buy Adderall Uk & USA on the web or purchase Adderall 30mg from an online drug store, a few patients will in general get dependent on the medication.can you buy adderal online? When it comes to buying fine research chemicals online, quality is the key. buychemstore research chemicals for sale ,LSD for sale,constantly strives at providing you with the continuing evaluation, innovation and improvement to quality. We assure you the highest degree of confidence in all of our products not limited to but including cocaine for sale, LSD for sale, heroin for sale, pain medications for sale etc. Here at buychemstore, we pride ourselves on safety.
    buy research chemicals online
    We are a one-stop-shop for the purchase of anabolic steroids.what is anabolic steroids, We are here to make sure you don’t bother about where to buy anabolic steroids from craigslist, eBay, or any other online forums. We are in contract with a large network of pharmacies in North America, Europe and Asia. With our years of experience in the distribution of anabolic-androgenic steroids, we provide top quality services to our clients that cannot be matched by our competitors.This is te best place to Buy legal steroids online, anabolic steroids for sale
    Apetamin vitamin syrup that’s marketed as a weight gain supplement. It was developed by TIL Healthcare PVT, a pharmaceutical company based in India.According to manufacturing labels, 1 teaspoon (5 ml) of Apetamin syrup contains.Apetamin in store,where to buy Apetamin,Is Apetamin syrup Effective in weight gain,buy Apetamin syrup online
    Welcom to our vyvanse online shop where you can buy vyvanse online,Buy vyvanse Uk & USA,learn about vyvanse side effects and have
    vyvanse coupon,vyvanse for sale.
    Buy atorvastatin, sold under the trade name Lipitor among others, is a statin medication used to prevent cardiovascular disease in those at high risk and treat abnormal lipid levels. For the prevention of cardiovascular disease, statins are a first-line treatment. It is taken by mouth.Learn How to use Lipitor, you are free to read about atorvastatin side effects,Buy lipitor UK ,atorvastatin side effects ,
    lipitor for sale. You can also buy juul

    ReplyDelete

  14. That’s a great article you got the. We are the best marijuana supplier online in the USA,Europe,and UK.We an organised and computerized selling system with modern payment methods,easy and fast delivery,with a modernized packaging system.we have some health products too,this is just the best marijuana site you can find.You can check it out at bestmarijuanaweedsupplier.com.Some of our featured products include;marijuana,vapes,catridges,shatter,hash and many orders

    https://www.bestmarijuanaweedsupplier.com/“product/buy-khalifa-kush-online/<dofollow https://www.bestmarijuanaweedsupplier.com/"product/order-mango-haze/<dofollow https://www.bestmarijuanaweedsupplier.com/"product/buy-amnesia-haze-online/<dofollow https://www.bestmarijuanaweedsupplier.com/"product/buy-super-silver-haze-online/<dofollow https://www.bestmarijuanaweedsupplier.com/"product/buy-jack-here/<dofollow https://www.bestmarijuanaweedsupplier.com/"product/gorrila-glue-4/<dofollow https://www.bestmarijuanaweedsupplier.com/"product/gelato/<dofollow https://www.bestmarijuanaweedsupplier.com/"product/ak-47/<dofollow https://www.bestmarijuanaweedsupplier.com/"product/buy-bubble-hash/<dofollow https://www.bestmarijuanaweedsupplier.com/"product/buy-malana-hash-online https://www.bestmarijuanaweedsupplier.com/"product/buy-kootenay-bubble-hash/<dofollow https://www.bestmarijuanaweedsupplier.com/"product/afghan-mazar-sharif-hash/<dofollow https://www.bestmarijuanaweedsupplier.com/"product/buy-charas-hash-online/ <dofollow

    ReplyDelete
  15. Marijuana—also called weed, herb, pot, grass, bud, ganja, Mary Jane, and a vast number of other slang terms—is a greenish-gray mixture of the dried flowers of Cannabis sativa.

    The main active chemical in marijuana is THC (delta-9-tetrahydrocannabinol), the psychoactive ingredient. The highest concentrations of THC are found in the dried flowers, or buds. When marijuana smoke is inhaled, THC rapidly passes from the lungs into the bloodstream and is carried to the brain and other organs throughout the body. THC from the marijuana acts on specific receptors in the brain, called cannabinoid receptors, starting off a chain of cellular reactions that finally lead to the euphoria, or "high" that users experience. Feeling of a relaxed state, euphoria, and an enhanced sensory perception may occur. With higher THC levels in those who are not used to the effects, some people may feel anxious, paranoid, or have a panic attack.
    Cannabis plant used for medical or recreational purposes. The main psychoactive part of cannabis is tetrahydrocannabinol, one of the 483 known compounds in the plant, including at least 65 other cannabinoids. 
    buy real weed online
    how to buy weed online
    buy legal weed online
    buy recreational weed online
    buy weed edibles online
    can i buy weed online
    buy medical weed online
    buy weed online canada
    buying weed online reviews
    buy weed online legit
    buy weed online without medical card
    buy weed seeds online canada
    order marijuana online
    order marijuana seeds online
    how to order marijuana online
    order marijuana online without a medical card
    can you order medical marijuana online
    order marijuana online

    ReplyDelete
  16. Marijuana—also called weed, herb, pot, grass, bud, ganja, Mary Jane, and a vast number of other slang terms—is a greenish-gray mixture of the dried flowers of Cannabis sativa.

    The main active chemical in marijuana is THC (delta-9-tetrahydrocannabinol), the psychoactive ingredient. The highest concentrations of THC are found in the dried flowers, or buds. When marijuana smoke is inhaled, THC rapidly passes from the lungs into the bloodstream and is carried to the brain and other organs throughout the body. THC from the marijuana acts on specific receptors in the brain, called cannabinoid receptors, starting off a chain of cellular reactions that finally lead to the euphoria, or "high" that users experience. Feeling of a relaxed state, euphoria, and an enhanced sensory perception may occur. With higher THC levels in those who are not used to the effects, some people may feel anxious, paranoid, or have a panic attack.
    Cannabis plant used for medical or recreational purposes. The main psychoactive part of cannabis is tetrahydrocannabinol, one of the 483 known compounds in the plant, including at least 65 other cannabinoids. 
    buy real weed online
    how to buy weed online
    buy legal weed online
    buy recreational weed online
    buy weed edibles online
    can i buy weed online
    buy medical weed online
    buy weed online canada
    buying weed online reviews
    buy weed online legit
    buy weed online without medical card
    buy weed seeds online canada
    order marijuana online
    order marijuana seeds online
    how to order marijuana online
    order marijuana online without a medical card
    can you order medical marijuana online
    order marijuana online

    ReplyDelete
  17. Marijuana—also called weed, herb, pot, grass, bud, ganja, Mary Jane, and a vast number of other slang terms—is a greenish-gray mixture of the dried flowers of Cannabis sativa.

    The main active chemical in marijuana is THC (delta-9-tetrahydrocannabinol), the psychoactive ingredient. The highest concentrations of THC are found in the dried flowers, or buds. When marijuana smoke is inhaled, THC rapidly passes from the lungs into the bloodstream and is carried to the brain and other organs throughout the body. THC from the marijuana acts on specific receptors in the brain, called cannabinoid receptors, starting off a chain of cellular reactions that finally lead to the euphoria, or "high" that users experience. Feeling of a relaxed state, euphoria, and an enhanced sensory perception may occur. With higher THC levels in those who are not used to the effects, some people may feel anxious, paranoid, or have a panic attack.
    Cannabis plant used for medical or recreational purposes. The main psychoactive part of cannabis is tetrahydrocannabinol, one of the 483 known compounds in the plant, including at least 65 other cannabinoids. 
    buy real weed online
    how to buy weed online
    buy legal weed online
    buy recreational weed online
    buy weed edibles online
    can i buy weed online
    buy medical weed online
    buy weed online canada
    buying weed online reviews
    buy weed online legit
    buy weed online without medical card
    buy weed seeds online canada
    order marijuana online
    order marijuana seeds online
    how to order marijuana online
    order marijuana online without a medical card
    can you order medical marijuana online
    order marijuana online

    ReplyDelete
  18. Marijuana—also called weed, herb, pot, grass, bud, ganja, Mary Jane, and a vast number of other slang terms—is a greenish-gray mixture of the dried flowers of Cannabis sativa.

    The main active chemical in marijuana is THC (delta-9-tetrahydrocannabinol), the psychoactive ingredient. The highest concentrations of THC are found in the dried flowers, or buds. When marijuana smoke is inhaled, THC rapidly passes from the lungs into the bloodstream and is carried to the brain and other organs throughout the body. THC from the marijuana acts on specific receptors in the brain, called cannabinoid receptors, starting off a chain of cellular reactions that finally lead to the euphoria, or "high" that users experience. Feeling of a relaxed state, euphoria, and an enhanced sensory perception may occur. With higher THC levels in those who are not used to the effects, some people may feel anxious, paranoid, or have a panic attack.
    Cannabis plant used for medical or recreational purposes. The main psychoactive part of cannabis is tetrahydrocannabinol, one of the 483 known compounds in the plant, including at least 65 other cannabinoids. 
    buy real weed online
    how to buy weed online
    buy legal weed online
    buy recreational weed online
    buy weed edibles online
    can i buy weed online
    buy medical weed online
    buy weed online canada
    buying weed online reviews
    buy weed online legit
    buy weed online without medical card
    buy weed seeds online canada
    order marijuana online
    order marijuana seeds online
    how to order marijuana online
    order marijuana online without a medical card
    can you order medical marijuana online
    order marijuana online

    ReplyDelete
  19. If your asked what is Marijuana what will be your responds. Buy Cannabis oil for sale which is one of the most unusual oil among the oil range. It cures Cancer, Epilepsy, and Insomnia etc.Visit our website for quality strain kushonlinemarket.com

    ReplyDelete
  20. Healthy blog, thanks for the post. We offer to you 

     Marijunana/Cannabis  plant used for medical or recreational purposes. The main psychoactive part of cannabis is tetrahydrocannabinol, one of the 483 known compounds in the plant, including at least 65 other cannabinoids. 

    buy weed online

    buy real weed online

    weed shop

    how to buy weed online

    shatter

    cbd oil

    weed bud

    edibles

    cannabis

    buy edibles online ship anywhere

    budmail

    moon rocks

     order marijuana online

    buds2go

    cheap weed

    buy marijuana online

    cannabis oil for sale

    how to smoke shatter

    marijuana for sale

    weed for sale

    cbd

    ReplyDelete
  21. Healthy blog, thanks for the post. We offer to you 

     Marijunana/Cannabis  plant used for medical or recreational purposes. The main psychoactive part of cannabis is tetrahydrocannabinol, one of the 483 known compounds in the plant, including at least 65 other cannabinoids. 

    buy weed online

    buy real weed online

    weed shop

    how to buy weed online

    shatter

    cbd oil

    weed bud

    edibles

    cannabis

    buy edibles online ship anywhere

    budmail

    moon rocks

     order marijuana online

    buds2go

    cheap weed

    buy marijuana online

    cannabis oil for sale

    how to smoke shatter

    marijuana for sale

    weed for sale

    cbd

    ReplyDelete
  22. The recent decision by the fda to ban juul pods and other juul accessories and juul devices such as juul wraps , juul skins .As matter of fact, not just juul pods where banned, juul flavors such as mango juul pods ,juul mango pods, mint juul pods ,mint juul creme brulee juul pods where part of the juul pods flavors banned by the fda before the complete ban on juul came up some few weeks back.But people still search for juul pods for sale,buy juul pods online,mango juul pods for sale,mint juul pods for sale.This has greatly affected the
    buy weed for online or marijuana for sale industry since people have seek for refuge in the
    buy weed online firms now.Girls eager to gain weight have shy away from juul pods and now concentrated on apetamin vitamin syrups.They consume apetamin and apetamin vitamin syrup since they no longer need to norm any part of the body because ketamine or ketamine hcl is in action.

    ReplyDelete
  23. Great post ! Thanks for your fabulous post! I enjoy while reading your post, you happen to be a great author. When I am free than reading your blog. I want to encourage yourself to continue to share fabulous posts, have a nice day! If you know more you can visit here Same day abortion pill procedure

    ReplyDelete
  24. Great job! Thanks for sharing.I regularly visit your site and find a lot of interesting information Ij Start Canon Canon Printer Setup Canon IJ Setup

    ReplyDelete
  25. CLICK BELOW TO ACCESS STORE>>> 

    if you are having problems getting your medicines without a prescription, we are here to give you a solution. At our secured pharmacy, you will be able to conveniently purchase your medicines and other healthy merchandise securely, We also refill prescriptions immediately yet accurately, buy medicines online with discreet shipping, Online Drugstore 

    Buy Vyvanse Online No Prescription | Order Percocet  Online | Buy quaaludes Online | Dsuvia 30mcg  for sale online |  Get Xanax 2mg Online | Order Ritalin Online |  Mundi pharma -Oxycontin 8Mg Oc For Sale | Get Adderall IR and Xr 30mg secure | How To Buy Pain Reliever Online | CRYPTOCURRENCY PAYMENTS ONLY  | Buy Oxycodone 3omg Online | SECURE AND DISCREET
     
    We offer: 
    * No Prescription Required (NO RX) 
    * Buy Direct and Save Time with Cryptocurrency 
    * Reshipment is offered if the package does not get to location or refunds 
    * High-Quality Pharmacy Grade Medicines. 
    * Best and Affordable prices. 
    * Fast and Stealth delivery - Tracking Available! 
    * Various Discreet Shipping options depending on location

    inquiries: immuneshades [at] protonmail [dot] com
    Call/Sms : (480) ... 420 .. 6859

    ReplyDelete
  26. If you are looking to Buy Marijuana Online or Buy Weed Online, there are a few things to look out for. Most dispensaries nowadays sell not provide ordr marijuana online orders, they equally provide hash oil, cannabis oil, Buy Marijuana Online or weed tins, marijuana concerntrate, marijuana marijuana edibles, Rick Simson's Oil and of course, Legal Marijuana For Sale Online, marijuana marijuana edibles, with a possibility to buy marijuana online with bitcoin
    >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    If you are looking to buy marijuana online or buy weed online, there are a few things to look out for. Most dispensaries nowadays sell not provide order marijuana online orders, they equally provide Hash Oil, Cannabis Oil, Weed Tins, Marijuana Concentrates, Marijuana Seeds, Gummy Bears, Rick Simpson Oil and of course, Legal Marijuana for Sale Online, Marijuana Edibles, with a possibility to buy marijuana online with bitcoin
    <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

    Looking for where to Buy Marijuana Online? Why not visit 420 Canna Shop for your top quality highly potent exotic weed strains. Mail order Marijuana from home at your convineince. Weed for sale online worldwide. Marijuana for sale online from the comfort of your home. High THC vape cartridges for sale online only at 420 Canna Shop

    ReplyDelete
  27. Our List of vaporizers such as the dry herb vaporizers,Pax

    3
    , firefly 2 and Davinci iq are being flaunted in the market alongside vape pens.Thc vape pens and

    cbd vape pens are one of the

    best vape pens
    you can think of if you want to buy a vape pen

    online
    or searching for vape pens for sale.With the new trend of

    vape pens in the market, people who buy vaporizers online from vape stores or smoke

    shops
    are now confused if the vape store or smoke shops are the ideal places for both portable vaporizers or deskstop vaporizers for sale.The concept of vaping and asking

    questions like what is a vape can now be answered here on

    our online vape store alongside other vape questions

    regarding the type and best vaporizers to buy

    online
    .Vaporizers like the pen vaporizers,the crafty and many more.Top selling vaporizers and vapes

    are the Pax,Herb-E Vaporizer,Firely 2 vs pax 3,Storz & Bickel,Smok vape pen 22,Pen Vape |

    Juul Vape Pen | Cartridge

    Vape Pen
    | Dry Herb Vape Pen |Juul pods near me.

    ReplyDelete
  28. Our List of vaporizers such as the dry herb vaporizers,Pax

    3
    , firefly 2 and Davinci iq are being flaunted in the market alongside vape pens.Thc vape pens and

    cbd vape pens are one of the

    best vape pens
    you can think of if you want to buy a vape pen

    online
    or searching for vape pens for sale.With the new trend of

    vape pens in the market, people who buy vaporizers online from vape stores or smoke

    shops
    are now confused if the vape store or smoke shops are the ideal places for both portable vaporizers or deskstop vaporizers for sale.The concept of vaping and asking

    questions like what is a vape can now be answered here on

    our online vape store alongside other vape questions

    regarding the type and best vaporizers to buy

    online
    .Vaporizers like the pen vaporizers,the crafty and many more.Top selling vaporizers and vapes

    are the Pax,Herb-E Vaporizer,Firely 2 vs pax 3,Storz & Bickel,Smok vape pen 22,Pen Vape |

    Juul Vape Pen | Cartridge

    Vape Pen
    | Dry Herb Vape Pen |Juul pods near me.

    ReplyDelete
  29. I would without reservation recommend working with "Rick Simpson Oil", My honest Gratitude goes to Dr. Rick Simpson for saving my dying life with his high quality RSO Hemp Oil, with the potency of the THC content in it very high. Some years back I was diagnosed with a deadly disease cancer of the lungs, we tried all kinds of medication all to no avail and also we even tried producing the DIY Rick Simpson oil at home ourselves but we were doing more harm than worse, Until I saw a post on facebook on how Cannabis Oil had cured a cancer patient then i decided to give it a try behold when i contacted Dr. Rick Simpson & i explained every detail of my problem to him and he told me that this Cannabis Oil will heal my cancer problem only if I can order for it as soon as possible. He then calculated the dosage for me to buy i think the dosage he calculated was 60grams of the oil which I ordered plus "30grams maintenance free" I was told that if I order for the Cannabis Oil right away by the next 48 hours the medication will get to my door step through the delivery services, Immediately I ordered for it behold it was delivered with the usage instruction manual at the exact time which i was told by Dr. Rick Simpson, Today i can boldly say I'm now free from my Cancer problem all Thanks to God Almighty, Dr. Rick Simpson and my lovely wife who stood by me during those years. I'm now living a healthy life and my utmost priority of sharing this short testimony is for those who are suffering from this Deadly Cancer Disease, Please don’t die in silence there is a cure for this Deadly Cancer Disease today. Without wasting more time kindly Contact Dr. Rick Simpson for ( "Rick Simpson Oil" RSO ) via Email: cbdoilfort@gmail.com or WhatsApp +1-781-656-7138.

    Today I acknowledge the greatness of Rick Simpson Cannabis Oil and to those that wish to purchase the medication kindly contact Dr. Rick Simpson via Email: cbdoilfort@gmail.com or WhatsApp +1-781-656-7138.
    Cannabis Oil is the medication for Cancerous Disease. Save your life and that of others.

    ReplyDelete
  30. WE ARE SPECIALIZE WITH SCOTTISH FOLD MUNCHKIN KITTENS / CATS FOR SALE , SCOTTISH FOLD KITTENS/ CATS
    All my Scottish Fold Kittens are

    raised in my home. We breed selectively for temperament and health and beauty. We offer adorable Scottish fold munchkin kittens and

    Scottish fold kittens colors including white, blue, cream, red, silver, cameo, brown, Colico, chinchilla, blue-cream, tortoiseshell,

    black etc. They also can have different patterns like a solid color, tortoiseshell, tricolor/calico, bicolor, tabby, ticking, smoke,

    shaded and spots. Most of my Scottish Fold munchkin kittens are Registered with CAF / TICA


    scottish fold munchkin kittens for sale



    scottish fold cat for sale



    scottish fold kittens



    munchkin cat for sale


    scottish fold cats



    munchkin kittens for sale


    scottish fold munchkin


    scottish fold kittens for sale



    scottish fold kitten



    cat breeders near me



    munchkin cat price



    scottish fold cat for sale



    scottish fold munchkin cat



    scottish fold price



    munchkin cats for sale



    midget cats


    scottish fold breeders


    scottish fold longhair


    cat health issues


    munchkin cat lifespan

    scottish fold rescue

    short leg cat

    how much does a scottish fold cat cost

    scottish fold personality

    ReplyDelete
  31. Browse Our Dank Vapes Flavors
    Welcome to the official dank vape carts store. Get dank vape cartridges (carts). We’ve got a plethora of dank vape flavors (vape dank flavors), dank vape pen(s) and much more. Throughout our years of existence and sales of dank vapes, we are proud to be the dank vape online store (shop) with the best dank vape cartridges review(s). Our Gallery of dank vape flavors is rich with flavors such as King Louie dank vape, Watermelon dank vape, Diamond OG dank vape, Blackberry kush dank vape, diablo dank vape, gelato dank vape, fruity pebbles dank vape, la confidential dank vape, cherry kush dank vape, pineapple express dank vape and much more.

    Dank Vape THC Juice
    All our products are professionally made. Our dank tank vapes are shipped in dank vape boxes. Hence you can affirm that we’ve got the best dank vape packaging. All products made professionally to give you the best vaping experience and improving on your puffing. Our dank vape battery (batteries) are solid and last a long time. Our customers don’t have to bother about how to refill dank vape cartridge(s). This is because our dank vape cartridges come pre-filled, in full-grams, thus ready-made for you to start puffing (vaping) when you open your package. These real dank vapes cartridges come with both THC vape juice(s) and CBD vape juice(s), thus feel free to choose the vape juice that best suits you.

    Best Dank Vape Flavors
    Depending on your health condition, or maybe you just want to have a relaxing time, we’ve got some top-knotch dank vape flavors such as ginger dank vape, durban poison dank vape, dank vape fruity pebbles, dank vape cart, chemdawg dank vape, ace of spades dank vape, jack herer dank vape, purple punch dank vape, grape ape dank vape, dank vape wedding cake, hardcore og dank vape, rose gold dank vape, white rhino dank vape, zkittlez dank vape, dank vape gelato, maui wowie dank vape.

    Vape Dank Price
    We are the market leaders in terms of dank vape quality and pricing. Our vape dank price(s) are the best. You can get up to 50% off when you order wholesale. Packaged with our dank vapes are tutorials on the following;

    How to open a dank vape cartridge,
    How to open dank vape cartridge,
    How to spot fake dank vape carts,
    How to tell if dank vape is fake
    How to empty dank vape cartridges,

    ReplyDelete
  32. Buy White Runtz Weed Online | Buy Marijuana Online

    Buy White Runtz Weed Online | Buy Marijuana Online at https://whiteruntz.co/

    hello, am mathews bud tender at white runtz dispensary, where We supply and retail good range of White runtz, White runtz,
    Buy Pink runtz strain online, Buy Moonrocks Online, buy Gushers weed strain online, Where to buy White runtz weed,
    Order Pink runtz strain online, White runtz weed for sale, Buy White runtz weed online, buy Biscotti strain online,
    vape pen cartridges for sale, buy vape pen cartridges online, cannabis oil for sale, Jungle Boys A vendre En Ligne,
    Buy Cannabis oil online, Buy cbd oil online, buy Smart bud cans online , buy Brass knuckles vape online,
    buy Sundae Driver Strain online, Sundae Driver Strain for sale, Where can I find real Jungle Boys Weed online,
    Candy Cake Exotic Weed for sale, Cookies Mint Chocolate Weed, Buy Jungle Boys, Buy Gushers weed online, gushers strain,
    order cbd oil online,buy Lemon slushie strain online, Buy Jungle Boys weed World wide Delivery,
    Cannabis Oil for sale, order weed online, Rick Simpson Oil for sale Australia, Legit Dispensary To Buy Jungle Boys Weed,
    buy jungle cake, Buy CBD Hemp Oil, Buy Cannabis Oil, Buy THC Cartridges, Pinkman Goo Exotic Weed for sale,
    Big Smokey Farms Cali Tins for sale, Biscotti, Biscotti Boyz Cali Weed for sale online, Jungle Boys Weed For Sale in UK,
    Blue Zkittlez Cali Weed for sale, Blue Fire OG By Space Monkey Meds, Buy Cookie Monster Cali Weed Tins Online
    Dank vapes durban poison, Do-Si-Do by Cali Cans for sale, Buy Marijuana Online, order weed for sale online,
    THC Oil Cartridges, Biscotti strain for sale, order Biscotti strain online, Rick Simpson Oil for sale Australia,
    buy Cherry punch strain online, New California Strains For Sale online, Jungle Boys A vendre En Ligne,
    medical marijuanafor joint pain- medical marijuana for sale near me, White Runtz Cannabis Strain Information,
    buy purple kush online, buy pink runtz in Uk, buy rove cartridges in UK, Where To Buy Jungle Boys Online,
    buy purple punch online, buy Gushers weed strain online, buy billy kimber OG online, Buy Rare Genetikz Online,
    Brass knuckles vape for sale,Jungle Boys for sale in London, Jungle Boys Weed For Sale FRANCE,
    CONTACT US:

    Website:

    https://whiteruntz.co/

    Whatssap: +1 (424) 2584-607

    Wickr ID: WEEDSSHOP

    ReplyDelete
  33. BUY UNDETECTED COUNTERFEIT MONEY-WOLD WIDE CURRENCY Whatsapp:+1 213-534-7437

    Email: legitbanknotescounterfeit@gmail.com
    Whatsapp:+1 213-534-7437
    WebPage :legitbanknotesforsale.online
    Purchase Real and Novelty Passports,ID cards, Visas, Drivers License, Counterfeits Email us at for details
    - Our bills/notes bypass everything, counterfeit pens, and machines.
    - Can be used in banks but can be used elsewhere same like normal money
    - We have the best HOLOGRAMS AND DUPLICATING MACHINES
    - UV: YES
    EUR - Euro
    USD - US Dollar
    DNR - DINAR
    GBP - British Pound
    INR - Indian Rupee
    AUD - Australian Dollar
    CAD - Canadian Dollar
    AED - Emirati Dirham
    ZAR - Rand
    CHF - Swiss Franc
    CNY - Chinese Yuan Renminbi
    MYR - Malaysian Ringgit
    THB - Thai Baht
    NZD - New Zealand Dollar
    SAR - Saudi Arabian Riyal
    QAR - Qatari Riyal


    We are the best and Unique producer of HIGH QUALITY Undetectable counterfeit Banknotes. With over a billion of our products circulating around the world. We offer only original high-quality counterfeit currency NOTES. We ship worldwide. We also print and sell Grade A banknotes of over 52 currencies in the world. Here is your chance to be a millionaire. Our money is perfectly reproduced, Indistinguishable to the eye and to the touch. We are sending in various sizes, packed and hidden. All our notes carries all the holograms and water marks and passes the light detector test. We will deliver the money directly to your home without the interference of customs. we have a Huge quantity ready in stock. EUROS, DOLLARS AND POUNDS, AND NOVELTY DOCUMENTS LIKE PASSPORTS, ID CARDS, GREEN CARDS, AND DRIVERS LICENSE.

    Email: legitbanknotescounterfeit@gmail.com
    Whatsapp:+1 213-534-7437

    ReplyDelete
  34. We have seen many blog but this the great one, Thanks for provide great informatic and looking beautiful blog, really nice required information & the things i never imagined and i would like to request, wright more blog and blog post like that for us. Thanks you once agian

    birth certificate in delhi
    birth certificate in noida
    birth certificate in ghaziabad
    birth certificate in gurgaon
    birth certificate agent in delhi
    marriage registration in delhi
    marriage certificate delhi
    correction in 10th mark sheet
    marriage registration in ghaziabad
    marriage registration in gurgaon

    ReplyDelete
  35. The Illuminati is an elite organization of world leaders, business authorities, innovators, artists, and other influential members of this planet. Our coalition unites influencers of all political, religious, and geographical backgrounds to further the prosperity of the human species as a whole.We appreciate your interest in our organization. Use our official website for details on our members, contact information, how to join the Illuminati, our beliefs, message archives, and more.


    How to join the illuminati

    ReplyDelete
  36. Learn the procedure of Online Case Proceeding at
    Sindh High Court. you can read more about other contents like, Daily cause list, Roster sittings, Electronic case Alert Messaging System.


    Roster of Sindh High Court. Check complete advocate and judge wise roster sittings. You can read more about roster of Sindh High Court.


    Welcome to Advance Case Search at Sindh High Court.site. You can easily learn how to check case status on Case Search portal of Sindh High Court.


    Your Blog is incredible with unique content. Please visit Sindh High Court to get latest Daily Cause List, Memorandum Civil, Criminal, Court Writ, Latest Jobs and Electronic Case Management System in Sindh High Court.

    ReplyDelete
  37. Get quality pack wood pre rolls at a reasonable price. You just need to visit our 420 pro medical online store and place your order for that. You will get your product ontime.

    Where Can I Buy Packwoods

    ReplyDelete
  38. Thanks for sharing an amazing content if you will love to visit this blog too buy marijuana online someone also recommended me to this blog buy 710kingpen catridges online and what i read was very interesting like this blog too buy boom carts online thanks for checking on the blogs









    Thanks for sharing an amazing content if you will love to visit this blog too buy marijuana online someone also recommended me to this blog buy blue dream online and what i read was very interesting like this blog too buy critical kush online thanks for checking on the blogs

    ReplyDelete
  39. Skip Hire Isle of Wight is the best skip hire services provider in Isle of Wight. Skip Hire Isle of Wight offers cheap prices and all kinds of skip sizes as we value the needs of our customers. We provide our services all across at a competitive price. Book your order now and Get 20% instant discount.

    Visit our site for more details....

    ReplyDelete

  40. Hi there,
    Thank you so much for the post you do and also I like your post, Are you looking for Buy Apple Celato , Cheetah Plss, Grandi Guava, Horchata, Blue Gotti,Ruby Red,Italian Ice, Insane Christmas Lights, Rainbow Chip, INSANE Hony Moon, NO MORE Partiez, NO MORE Partiez, Hony Bun, White Runtz, buy horchata online, Buy Rainbow Chip online, buy Blue Gotti online, buy Ruby Red online, buy Italian Ice online, buy Insane Christmas Lights online, Buy INSANE Hony Moon online, Buy NO MORE Parties online, Buy White Cherry Celato online, Buy Peanut Butter Celato online, Buy Lechoza online, Buy Grandi Candi online, Buy Chronic carts online, Buy Cannabis Chocolate Chip Cookies online, Buy Cannabis Trim Capsules online, Buy Blue Diamond Vape Cartridge online,


    with the well price and our services are very fast. Click here for
    Call us: +1406 9441867‬

    E-mail: info@backpackexotics.com




    ReplyDelete
  41. If you are feeling down in the dumps, you should try these Sativa pre-rolled blunts. By trying it you can feel better. Buy Blueberry Cheese Cake Pre Roll Online, it gives you an energetic boost, which will provide you need to accomplish tasks and be more creative.

    ReplyDelete
  42. Are you in need of Guarantee Cash quick money apply here contact us for more information (Whats App)

    number:+919394133968

    ReplyDelete
  43. Quality assurance and great products have been a major concern among experts in the cannabis industry. To meet recommendations for producing medicinal marijuana products that are both effective and adequate to meet our clients’ needs, our company is strictly adherent to delivering just the gold standard. Based on a consensus, our quality assurance committee has resolved to a strict adherence to the protocol in order to maintain a standard across the production chain. The quality of our lab-tested products is unmatched in the entire industry. Cannabis Market is the leader!

    BUY MARIJUANA ONLINE WITH WORLDWIDE DELIVERY

    CHEAP LEGIT ONLINE DISPENSARY SHIPPING WORLDWIDE

    Buy medical marijuana online

    Buy Dank Woods Online 

    Cheap Marijuana for Sale

    BUY MARIJUANA ONLINE WITH WORLDWIDE DELIVERY

    CHEAP LEGIT ONLINE DISPENSARY SHIPPING WORLDWIDE

    Buy medical marijuana online

    Buy Backwoods Online

    BUY MARIJUANA ONLINE WITH WORLDWIDE DELIVERY

    CHEAP LEGIT ONLINE DISPENSARY SHIPPING WORLDWIDE

    Buy bubba kush online

    Buy Dank Woods Online 

    Cheap Marijuana for Sale

    BUY MARIJUANA ONLINE WITH WORLDWIDE DELIVERY

    CHEAP LEGIT ONLINE DISPENSARY SHIPPING WORLDWIDE

    Buy medical marijuana online

    Buy Backwoods Online

    ReplyDelete
  44. buyweedonline.ltd, is the best online shop that ship to all 50 states within 24 hours
    an also ships 3-6 days internationally. buyweedonline.
    buy weed online, buy abusive ob, blue cheese weed for sale,
    buy cannatonic online, cotton candy kush strain, gorilla glue marijuana 4,
    gorillaglue marijuana 4,gelatoweed,can you buy weed online,lemon skunk strain,
    lemmon kush strain, buy lsd, mango kush weed strain, mazar kush strain,packwoods where to buy.

    ReplyDelete
  45. Dynamic Health Care products offer Sea moss which are spiny sea vegetable that’s primarily harvested for use in health supplements and as a thickening ingredient in commercial foods.
    If you’ve heard about sea moss supplements, you may wonder how you could use them and whether there are other ways to consume sea moss. For more information on our products you can visit our website by clicking the link Dyanmic Health Care Products

    sea moss near me
    How to make sea moss gel
    How to use sea moss gel
    How long does sea moss gel last
    Purple sea moss
    sea moss and bladderwrack
    Where to buy sea moss
    Sea moss capsules
    Where to buy sea moss

    ReplyDelete
  46. This comment has been removed by the author.

    ReplyDelete
  47. Great post!! Very informative and helpful for beginners needing to learn about SEO when creating their websites.jungle boy weed
    Who knew that content creation could be so easy. Thanks for sharing! please keep it up!

    ReplyDelete
  48. French bulldog puppies for sale - Roger breeder, your number one loving pet care, will provide you with the most amazing, cute, and adorable french bulldog for sale or adoption you can ever find on the internet. Its no doubts that french bulldogs are among the "Most Popular Dog Breeds in The World – with Frenchies Crowned #12 in this list, curated in 2021, according to an encyclo released on Memoir.
    french bulldog puppies for sale
    french bulldog for sale
    french bulldog for sale michigan

    ReplyDelete
  49. Really nice and interesting post. I was looking for this kind of information and enjoyed reading this one. Keep posting. Thanks for sharing. top 10 micronutrients company in india

    ReplyDelete

  50. Ammunitionszone of exceptional quality. All weaponry, including assault weapons, black powder pistols, pistols, bulk.44 mag ammo
    muzzleloaders, shotguns, rifles, and starter pistols, are listed or purchased on Amazon.

    ReplyDelete
  51. Köpa körkort. Köp Sverige körkort, körkort utan examen eller praktisk tentamen. köp körkort online. Köp körkort i Nederländerna,.
    körkort sverige
    köp körkort
    köp falsk pass
    BESTÄLLA KÖRKORT ONLINE

    ReplyDelete
  52. Echten Führerschein Kaufen
    Da der Führerschein auf Ihre Daten registriert ist, brauchen Sie keinerlei Prüfung ablegen. Bei einer Kontrolle der Polizei können Sie ganz unbesorgt sein, ein Wohnsitz in Deutschland, Österreich
    führerschein kaufen ohne prüfung

    osterreichischen-fuhrerschein kaufen 
    deutschen-fuhrerschein-kaufen 
    polen-fuhrerschein 
    mpu-gutachten 
    Führerschein online kaufen

    ReplyDelete
  53. A ranch cutter barrel saddle is designed for versatility and made for all-day riding. Use our site and grab the most affordable online buy NRS Lily Barrel Saddle. Ranch cutters are heavier saddles but typically have a narrow seat for comfort and longer square skirts. Custom ranch cutter saddle has a tall, strong horn for lashing and hanging.

    ReplyDelete
  54. I loved it as much as you will receive carried out right here. The sketch is attractive,
    your authored material stylish. nonetheless, you command get bought a nervousness over
    that you wish be delivering the following.
    unwellkiko & mis unquestionably
    come more formerly again since exactly the same nearly a lot often inside case you
    shield this increase.

    ReplyDelete
  55. Magic Mushrooms Dispensary is pleased to offer a new way to your magic mushrooms. Temple Mushroom tea bags come in three different flavors, Magical Remedy Shroom Tea Bags Order high-quality magic mushroom tea online from our website. We offer the best High Tea Company online, offering you a wide selection of magic mushroom teas for sale. You will find better website links below.

    ReplyDelete
  56. Baby Jeeters Pre rolls is now one of the leading pre-roll brands in California. Baby jeeter
    https://www.babyjeeter.org/

    he good news for United States consumers that with the evolution of the globe wide web, there are now a lot of extremely great viable options. Wocklean
    https://www.wocklean.org/

    ReplyDelete
  57. Down To Earth Organics is a fast-growing, consumer-facing brand with a mission to radically change the consumer landscape of beneficial hemp oil. We offer an all-natural, vegan supplement that supports you during the most vulnerable and demanding times of your life. Visit DTE and get Hemp Oil Online

    ReplyDelete
  58. Good theory, it is useful and effective afford, thanks.
    python training in hyderabad

    ReplyDelete