Showing posts with label Raspberry Pi. Show all posts
Showing posts with label Raspberry Pi. Show all posts

Monday, 1 February 2016

Turn Your Raspberry Pi into a Twitterbot

Ok. So this was a post I had decided I was not going to write. The reason for this is I did not want to help people create something negative, such as a spam generator.

Then I had a quick look at the Wikipedia page about Twitterbots. I realised there are some really fantastic ideas out there for Twitterbots, which can have a positive impact.

So I decided to go ahead and write the blog post, and to trust people to use it the right way. :-)

Besides, one of the the Twitterbots I have written is testing the Infinite Monkey Theorem to simulate a monkey trying to type the complete works of Shakespeare. The monkey automatically tweets his progress (@TheMonkeyBard).

So who am I to judge? :-)



In this blog post I am going to show you how I wrote another Twitterbot, which sends out a tweet, from a list of pre-written tweets, at a certain time. For me I use it to automatically post my blogs to twitter at certain times of the day. To disguise the fact I am doing this, I have added a slight element of randomness in there, so it doesn't look too much like a Twitterbot. More on that later.

To start with here is the code in its entirety. Have a read through it and see if it makes sense to you, we will then run through this line by line.

from __future__ import print_function #Allows python3 use of print()
import random
import string
import tweepy
import os

FILEPATH = '/home/pi/Desktop/TwitterBot' 

# Consumer keys and access tokens, used for OAuth 
consumer_key =  'YourConsumerKey' 
consumer_secret = 'YourConsumerSecret'
access_token = 'YourAccessToken' 
access_token_secret =  'YourAccessTokenSecret'

# OAuth process, using the keys and tokens
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)

# Creation of the actual interface, using authentication
api = tweepy.API(auth)


## This function sends the tweets. It will check the length of them first of all.      
def sendTweet(tweetText):
    randomNumber = random.randrange(4) #number 0 - (randrange-1) use 4
    #print (randomNumber) #Use for debugging
    if randomNumber == 0:
        api.update_status(status=tweetText) 
        #print (tweetText) #Use for debugging
        return None
    else:
        return None 
        
## This function will print one of the random Tweets
def randomTweet():
    try:
        tweetFile = open(os.path.join(FILEPATH,'Tweets.txt'),'r')
        tweetList = tweetFile.readlines()
        tweetFile.close()
        randomChoice = random.randrange(len(tweetList))
        #print (tweetList[randomChoice]) #For debugging only
        sendTweet(tweetList[randomChoice])
        return None
    except IOError:
        return None        
 
randomTweet()

Now in order to get started with this you will need to do two things.

1. Install Tweepy.
2. Create an app on Twitter

I am not going to explain either of these processes, as I learned how to do this from another blog by the wonderful Raspi.tv. They have done a far better job than I could ever do on this, so please go and read this blog on setting up Tweepy, and then come back to me. :-)

The link you need to follow is here:

http://raspi.tv/2013/how-to-create-a-twitter-app-on-the-raspberry-pi-with-python-tweepy-part-1#app

Right, you are back? Good!

So the first lines of code call in some libraries including Tweepy, which should now be installed on your Raspberry Pi.

from __future__ import print_function #Allows python3 use of print()
import random
import string
import tweepy
import os

The first line allows us to use the Python 3 version of print, which is something I am trying to do a little more these days. The next 4 lines all call libraries we need to use.

The next line caused me the most problems. When you run your program using cron (more on this later), your program needs to know exactly where to go looking for any files you call. Normally I would be in the current directory when I run this program, so this line would not matter. When you run your program using cron it is starting from a different directory, so this line is necessary.

If you don't know what cron is don't worry, it's not difficult. I will explain all later.

FILEPATH = '/home/pi/Desktop/TwitterBot' 

The path to my directory is /home/pi/Desktop/TwitterBot, but you will have to modify this line to suit your requirements.

So how do you find out the location of your program? Well, you can navigate to it in the command line, and then type:

pwd

This will return the directory of where your files are. In my case I store them on the desktop in a folder called TwitterBot. We will use this information later in our program.

So you read the blog post by Raspi.tv? You should therefore have been able to set up an app in twitter, and be able to fill out the information required in these lines of code.

# Consumer keys and access tokens, used for OAuth 
consumer_key =  'YourConsumerKey' 
consumer_secret = 'YourConsumerSecret'
access_token = 'YourAccessToken' 
access_token_secret =  'YourAccessTokenSecret'

# OAuth process, using the keys and tokens
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)

# Creation of the actual interface, using authentication
api = tweepy.API(auth)

Ensure you replace the text in the first 4 lines with your consumer key, consumer secret, access token and access token secret. For your reference, if you just want a reminder of where to find all the information, you need to go to the twitter apps website.

https://apps.twitter.com/

That's all the difficult twitter stuff out of the way. There are just two functions we need to write. One chooses a random tweet, and one tweets it.

Let us look at the function which sends the tweets.

## This function sends the tweets. It will check the length of them first of all.      
def sendTweet(tweetText):
    randomNumber = random.randrange(4) #number 0 - (randrange-1) use 4
    #print (randomNumber) #Use for debugging
    if randomNumber == 0:
        api.update_status(status=tweetText) 
        #print (tweetText) #Use for debugging
        return None
    else:
        return None 

The first line

def sendTweet(tweetText):

creates the function, but also expects some text, stored in a variable called tweetText, to be passed into it. It is the text in tweetText that we will be tweeting.

So this function does two things. It sends the tweet, which is what you would expect, but not every time... I have added an element of randomness into this so it only tweets once in every four attempts. Why did I do this? Well I wanted to post tweets at four times during the day - Midnight, 6am, lunchtime and 6pm, using GMT.  But I don't want to send a tweet every time. I think one tweet a day, on average, is enough to advertise my blog post. So if I use cron to potentially post four times a day, but reduce the chance of posting to one time in four, on average I will post once a day, but at one of the four pre-determined times. Hope that makes sense!

I did this to make it look a little less like a Twitterbot, which would normally post the same time every day. It just mixes it up a little and means there is a chance for a reader from other time zones to see one of my posts.

So that's what the first line does, it picks a random number from 0 to 3, and stores it in the variable randomNumber. You can increase or decrease the frequency by playing around with the number in random.randrange().

    randomNumber = random.randrange(4)

I then use an if statement to check if that number is == 0, which in our program will be once in every four attempts on average.

    if randomNumber == 0:

If it is we then send a tweet made up of the text we passed to our function, which is stored in tweetText. This makes use of the Twitter API to send the tweet.

        api.update_status(status=tweetText) 

We then return from the function.

        return None

If randomNumber is not equal to zero, the program will just return without doing anything.

    else:
        return None

All very simple so far.

The second function we need to write is randomTweet(). This chooses one of the random tweets to send to the sendTweet() function. The tweets it will be choosing from are stored in a text file called Tweets.txt.

## This function will print one of the random Tweets
def randomTweet():
    try:
        tweetFile = open(os.path.join(FILEPATH,'Tweets.txt'),'r')
        tweetList = tweetFile.readlines()
        tweetFile.close()
        randomChoice = random.randrange(len(tweetList))
        #print (tweetList[randomChoice]) #For debugging only
        sendTweet(tweetList[randomChoice])
        return None
    except IOError:
        return None  


We start off by naming the function. As you will see from the empty brackets this function is not expecting anything to be passed into it.

def randomTweet():

We are going to use Try and Except in this function. I do this in case there is an error loading up the file as I don't want the program to crash if it cannot find the file it is trying to open.

    try:
        tweetFile = open(os.path.join(FILEPATH,'Tweets.txt'),'r')
        tweetList = tweetFile.readlines()
        tweetFile.close()

So we Try the following.

We open the file Tweets.txt and store the contents in the variable tweetFile. But as I mentioned earlier when we come to run cron, we need to give the full path to this file. So we use this command

(os.path.join(FILEPATH,'Tweets.txt')

to join Tweets.txt onto the filepath stored in FILEPATH. The reason I have stored the filepath in FILEPATH, is if I want to change it in the future, I only have to modify it in one place. That place is at the top of the program, so easy to find!

We then store the contents of tweetFile into tweetList using readlines. This separates the text into a list which stores each line as a different item in the list.

     tweetList = tweetFile.readlines()

Finally we close the open text file.

     tweetFile.close()

We then analyse the length of our list, and pick a random number from 0 to the length of the list. The result is stored in randomChoice.

        randomChoice = random.randrange(len(tweetList))

And then we send the item which is in that random location in the list to the function we wrote earlier called sendTweet.

        sendTweet(tweetList[randomChoice])

Then we return None

        return None

Finally if there was an error in our try part of the function we catch it and just return from the function doing nothing.

    except IOError:
        return None  

Ok so far so good? Anything missing?

Well if we don't call a function in our program then nothing will run. So we just call the function randomTweet() to kick things off the ground.

randomTweet()

And that is that, the programming is complete.  There really is nothing tricky in all of this.

But if you want this program to tweet we are not quite finished. There are two things remaining. You need to create a text file called Tweets.txt and store this in the same folder as your program. This text file should have each tweet you want to send on a different line in the text file, as shown below.

Tweet 1
Tweet 2
Tweet 3
Tweet 4
Tweet 5

Finally while we have our python program completed, and our Tweets.txt file created, we need some method of telling our Raspberry Pi to run it when we want it to be run.

This is where the wonderful tool called cron comes into its own.

In the past I have written a blog post explaining how cron works. Definitely worth a read. But if you just want to get on and get your Twitterbot underway then I will explain what you need to do.

I said earlier that I would like my Twitterbot to (potentially) tweet 4 times a day. I say potentially as there is randomness in the the code to ensure it only tweets one in four times. To tell the Raspberry Pi to try to tweet at four times in the day I will have to add four lines of code to my crontab file.

On your Raspberry Pi you need to open a terminal window and type

Crontab -e

This will open up the file you need to modify, probably using a text editor called nano.

You will need to enter the following text at the bottom of this:

00 00 * * * /usr/bin/python /home/pi/Desktop/TwitterBot/TwitterBot.py 
00 06 * * * /usr/bin/python /home/pi/Desktop/TwitterBot/TwitterBot.py 
00 12 * * * /usr/bin/python /home/pi/Desktop/TwitterBot/TwitterBot.py 
00 18 * * * /usr/bin/python /home/pi/Desktop/TwitterBot/TwitterBot.py

A brief explanation of each of these lines is as follows:
  • For each of the lines the first 00 refers to the minutes past the hour we want our program to run. 
  • 00,06,12,18 refer to the hour, (24 hour format) at which we want the program to run. 
  • * * * refer to day of Month, Month and Day of week respectively. We want to ignore all these, so use a * for each.  
  • The /usr/bin/python explains you want to launch your program with python. 
  • /home/pi/Desktop/TwitterBot/TwitterBot.py gives the full path to the program you want to run. 

Press ctrl-x and follow the instructions to save and exit.

And that is that. You have a fully functioning Twitterbot which will try to tweet 4 times a day, but on average will only manage it once :-)

I hope this has given you the basics of turning your Raspberry Pi into a Twitterbot. Let me know what Twitterbots you create!

Wednesday, 6 January 2016

Turn your Raspberry Pi into an Audiophile Music Player

So my younger brother came to stay recently and asked me to give him a hand to do something with his Raspberry Pi. Always eager to help out with all things Raspberry Pi related, I had a look into what he wanted to do.

He told me he wanted to set up Volumio. I have to admit I had never heard of Volumio, but by the time we were finished setting it up, I was sold! It is very cool! What's more it was so easy to set up! Almost too easy to warrant a blog post...

However - where is the fun in that?




So what is Volumio? Well Volumio calls itself a Linux Audiophile Music Player. Basically it turns your Raspberry Pi, or many other devices, into a Music Player. You can simply add a USB stick with your music on it, and Volumio will play it. The great thing is you control the music remotely, so don't need anything other than a speaker/headphones connected to your Raspberry Pi.

So first of all you will need to download Volumio. Go to their website, and click on Download.


Once the download is completed you need to flash it onto a suitable SD Card. I am assuming that you already know how to do this, but if not, there are instructions on the Volumio web site on how to do this using either Linux, MacOSX or Windows. It's the same method as you would load any Linux distribution onto a SD Card.

For the purposes of trying this out, I am using a 4GB card. However Volumio is fairly small so has no problem fitting on a card that size.

Once that is done put the newly created SD card into your Raspberry Pi and fire it up! At this point you will need to have your Raspberry Pi connected to your router via an ethernet cable. We will sort out going wireless later.

Open an internet browser on your computer, and go to the following link:


Your screen should look something like this. 


How easy was that? 

However there is no point having an Audiophile Music Player without any music to play.

The next thing you need to do is to put some music onto a USB stick, and insert this into your Raspberry Pi. You might notice the bottom left of the screen indicates it is Updating. This takes a few minutes depending on how many songs you have on the USB stick.



Once completed this will resort back to saying Browse.

Plug your Headphones / Speaker into the Headphone Port of your Raspberry Pi. On older Raspberry Pi's this is the blue port, or on a Raspberry Pi 2 the port next to the HDMI port.

Now click on Browse and then on USB, and then follow your filing system on your USB stick until you find the song you want to play and double click on it.



Here I chose the Cantina Band from the Star Wars album. A message will appear saying you have added it to the playlist. It should start playing immediately.

Selecting other songs will add them to the play list also. Click on Playlist at the bottom to view the Playlist you have created.



The Playback menu allows you to see what is playing and change the volume etc.



It really is so simple and intuitive to use!

Want to listen to internet radio? Instead of clicking on USB, you can click on Web Radio. There are a number of pre-defined radio stations.




Earlier I mentioned that you can set this up so it works over WiFi. I use an Edimax EW-7811UN Dongle, but I have also tested this with a Wi-Pi dongle and they both work equally well. You can pick these up cheaply on the internet.



To set this up click on Menu and then Network.

Type in your Network Name, for me this is Spongebob (Don't judge me - ok?), the type of security you have, along with your password.



Finally click on Save Changes. They may take a few minutes to save. However once finished you should see your Edimax Blue LED Light up.

You can now remove your network cable, and be connected wirelessly.

And there you have it, your Raspberry Pi is a fully functioning Jukebox!

Take Volumio a step further and download an app, so you can control your music selections using your phone or tablet. Enjoy!

Friday, 3 April 2015

Find Raspberry Pi IP Address using your Phone or Tablet

Previously I have posted a very useful blog explaining how to Remotely Find Raspberry Pi IP Address. This used a tool called Nmap which works on Linux, Macs and Windows.

Nmap is very powerful and is a great tool to use.

Recently however I was introduced to a very simple to use app called Fing.




Fing is an app which is available for most smartphones and tablets. I run it off both Android and an iPad.

Simply install it, and then load it up. It analyses your network and returns a list of all the IP Addresses and the Computers name associated with them. It really is that simple!

Here is the entry for my Raspberry Pi, which clearly shows the IP address is 192.168.1.110.


Fing is such a simple and easy tool to use I am sure you will find it indispensable in no time!

Tuesday, 10 February 2015

Check downloads using SHA-1 and MD5

When you download a large file, such as a distribution of Linux for the Raspberry Pi, you are provided with a checksum to check the download is all correct. Has anyone ever checked their download? Would you know how to? I have to admit I rarely check. However if things did go wrong it would be very easy to overlook the fact the download went wrong. As you will see in this blog it is also possible to use this tool to check that SD cards have been written to correctly.

There are two types of checksum which are commonly used SHA-1 and MD5. I will explain how to use both of these.

First lets look how to do this on an Apple Mac.

To determine the MD5 open the terminal and type either

openssl md5 filename

or for SHA-1

openssl sha1 filename

for the MD5 you don't need to type openssl.

On a Linux system type the following into a terminal.

md5sum filename

and for SHA-1 you need to type

sha1sum filename

depending which checksum you are wanting to use.

Don't forget, if your filename is in a different folder, you will have to include the location along with the filename as below

location/of/folder/filename

Did you know that you are able to check that the image has burned correctly onto say an SD Card by checking the card once you have burned it?

For example on an Apple Mac to check a disk type the following

sudo openssl sha1 /dev/rdisk2

where /dev/rdisk2 is the path to the disk you want to check. Note you will probably need to use the sudo command to ensure you have the correct permissions. This is handy if you want to check that all your disks have burned correctly.


Monday, 9 June 2014

Scheduling Python Programs using Cron

Cron has become one of my favourite utilities in Linux. It allows you to schedule tasks to automatically happen at certain times. It is one of those tools that is so simple and so powerful, but all too often overlooked, or not known about.



I first stumbled across Cron when I decided to set up and administer a wiki server for the company I work for. There is a lot of data on the wiki and it is important that it gets backed up regularly.

Initially I would take a dump of the MySQL database, and then copy the files over on a daily basis by hand.

I then wrote a Python script which carried out these tasks for me. I just had to remember to run the script on a daily basis.

Finally I automated this using Cron.
  • Now every morning (Monday - Friday) at 1:00 am the company wiki is automatically backed up to two locations.
  • Secondly on the 1st of every Month at 2:00 am the wiki is automatically backed up to two different locations.

All this is done without me ever having to worry if it has been done. Cron looks after it all for me.

What is more is it is very simple to use.

This tutorial will help you get to grips with Cron and have your Raspberry Pi or any other Linux computer working away for you in the background.

Firstly Cron does require the time on your Linux computer to be correct, otherwise tasks will not happen when you expect them to! Not that such a basic mistake would ever catch me out while writing this tutorial...

So if you are using a Raspberry Pi, it might be worth setting the time at this point.

type

sudo raspi-config

into the command line.

Then choose

Internationalisation Options > Change Timezone > Europe > London



for those of you in the UK. Obviously everyone else choose a location to suit your local timezone!

For the purposes of this blog post we will assume you are wanting to run a Python program called test.py saved on your Desktop. If you want an example Python program to run then save the following as a Python program called test.py. When run this will create a new folder called test on your Desktop.

import os

os.chdir("/home/pi/Desktop")
os.makedirs("test")

Now again in your command line we will edit a file called crontab, this is short for Cron Tables, a place where you store all your actions.

To edit crontab type the following into the command line.

sudo crontab -e

This will bring up a text file which looks as follows.


The majority of the text in this file briefly explains Cron and how to use it. The first thing you will notice is that all lines start with a #. This means these lines are ignored by Cron, they are for your reference only.

While there are some basic instructions included in this file the most useful line is the bottom line.

# m h dom mon dow command

These are all explained in the file to mean the following.
  • m - minute
  • h - hour
  • dom - day of month
  • mon - month
  • dow - day of week
  • command - This is the command you want to run.

Lets start with the command, as that's the easy bit.

If you want to automatically run a Python program the command is made up of two parts.

On the Raspberry Pi, if you are using Python 2.7, the first part of this will be as follows.

/usr/bin/python

or for Python 3

/usr/bin/python3

The second part is a link to the Python program. For a program called test.py saved on your desktop you would need to have

/home/pi/Desktop/test.py

These two parts are separated by a space to make up the command.

/usr/bin/python /home/pi/Desktop/test.py

These other parameters (m h dom mon dow) are what you will use to specify when your task will run. For each of them you can specify a number or if you want to use all possible values a * is used to signify this.
  • The minutes can be numbers from 0 - 59 and indicates the minute in the hour you want the task to run.
  • The hours can be from 0 - 23 and indicates the hour you want the task to run.
  • Day of the month, 1-31, indicates the date within the month in which you would like to run the task.
  • Month, 1-12, indicates in which month you would like to run the task.
  • Day of the Week, 0-6, indicates on which day you would like to run the task. 0 is Sunday, 1 is Monday etc. 
The secret to setting this correctly is to use a combination of these to define the schedule.

If you wanted to run your python program every hour, then you would only have to supply a value for the minutes, specifying at what point in the hour you wanted your program to run. For example to run your program every 35 minutes past the hour you would enter the following into your crontab.

35 * * * * /usr/bin/python /home/pi/Desktop/test.py

The hours and minutes are easy to work out. If you want to run something everyday at 4:35 you would simply create a line in your crontab which would say.

35 4 * * * /usr/bin/python /home/pi/Desktop/test.py

Making this run on the 12th of the month only you would need to put the figure 12 into the day of the month field also.

35 4 12 * * /usr/bin/python /home/pi/Desktop/test.py

Taking this further you could specify that you only want your program to run at 4:35 on the 12th March.

35 4 12 3 * /usr/bin/python /home/pi/Desktop/test.py

Alternatively you may want to to run at 4:35 every Monday, so would need to use the Day of the week field. The numbers from 0 - 6 represent the days of the week. 0 is a Sunday, 1 is Monday etc.

35 4 * * 1 /usr/bin/python /home/pi/Desktop/test.py

We have put the Day of the Month and the Month back to being a * for this to happen, as we don't want to limit when our program to run on the 12th March only. Leaving these values in would only run the program on the 12th March at 4:35 if that day actually happened to be a Monday! The * implies it can run any day or month.

Easy huh?

There are a few other special characters which help you define when your program should run.

A comma can be used to allow you to add more values into a field. For instance

35 4 * * 1,4 /usr/bin/python /home/pi/Desktop/test.py

The 1,4 indicates the program will run on Monday and Thursday.

Using a - allows you to choose a series of values.

35 4 * * 1-4 /usr/bin/python /home/pi/Desktop/test.py

The 1-4 indicates the program will run Monday - Thursday. ie. Monday, Tuesday, Wednesday and Thursday.

Finally a / denotes increments of ranges.

/15 * * * * /usr/bin/python /home/pi/Desktop/test.py

This will run the program every 15 minutes.

One final useful command is

@reboot /usr/bin/python /home/pi/Desktop/test.py

Will run the program when you reboot your Raspberry Pi or Computer.

From these few examples you have seen that Cron gives you a huge amount of flexibility on when you schedule tasks.

I hope you have found this blog post useful and that it will help you get started automating your programs.






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).