Tuesday 26 June 2012

The classy way

One of the issues with GUI programs is that it is event-driven. The code in the event handlers is not called directly by your program it is called in response to something happening. This really is a great way to manage GUI programs, but it takes a bit of thinking about. When your event handler is triggered it will want to interact with other parts of your program; read or change variables, call other functions and so on. The event handler is not called directly by your code, that is the point. It responds to an event whenever the event happens. This means that you cannot pass variables to the event handler as parameters. We used global variables to overcome this but this is not a great way to do it. A better way is to use a class.

I'm not going to describe the ins and outs of classes and object-oriented programming - Google it. I will say that a class is the definition of how an object works and an object is what a program creates from the class. Objects run in code, classes are the definition of objects. The class can define data which can be used throughout the object. The class can also define functions to work within the object, using the object's data.

Data variables defined in a class are known as fields and when they are used outside of the object they can be known as properties. Functions defined in a class that get used from  outside the object the class defines are known as methods.

If we define a class in which we include all of the data fields we need to make our game of Rye work and define the event handlers there too, the event handlers can access any of the data they need. The global variable disappear with a touch of class.

Python has good support for classes. If you want to know more, take a look at the documentation.

I have converted the previous program to a version that uses a class, without adding any extra functionality.

#!/usr/bin/env python

from Tkinter import *

import ryepics

class ryeapp(Tk): 1
   
    def __init__(self,parent): 2
        Tk.__init__(self,parent) 3
        self.parent=parent
      
        # create the shared variables 4
        self.imageList=ryepics.createImages()
        self.ryeId=0
        self.ryeX=0
        self.ryeY=0
      
        # GUI initialisation
        self.title('Raspberry Rye')
        self.grid()
      
        # create the canvas for the game
        self.cnvs = Canvas(self, height=300, width=300)
        self.cnvs.grid(column=0, row=0, columnspan=4)
      
        # draw Rye at position 2,2
        self.ryeX=2
        self.ryeY=2
        sx=self.ryeX*20+10
        sy=self.ryeY*20+10
        self.ryeId=self.cnvs.create_image(sx,sy,image=self.imageList['G'])
      
        self.cnvs.bind("<Key-Down>", self.ryeDown)
        self.cnvs.bind("<Key-Up>", self.ryeUp)
        self.cnvs.bind("<Key-Left>", self.ryeLeft)
        self.cnvs.bind("<Key-Right>", self.ryeRight)
   
        # set the canvas to receive the focus, so keypresses are captured
        self.cnvs.focus_set()
   
    def ryeDown(self,event): 5
        self.cnvs.move(self.ryeId, 0, 20)
   
    def ryeUp(self,event):
        self.cnvs.move(self.ryeId, 0, -20)

    def ryeLeft(self,event):
        self.cnvs.move(self.ryeId, -20, 0)

    def ryeRight(self,event):
        self.cnvs.move(self.ryeId, 20, 0)

if __name__ == "__main__":
    app = ryeapp(None) 6
    app.mainloop()


This version is very similar to the last one, except that almost all of the program is now wrapped up in a class call ryeapp 1. The class is based on the Tk class, so our class extends the Tk class. Extending a class is known as inheritance and a fundamental part of object-oriented programming. All of the functions of Tk are now part of our class and we can add as much extra stuff as we want. This is not the only way to use a class, when you know more you might do it another way.

Classes can define a special function that is always called __init__ (that's two underscores before and after the init). Whenever the class is used to create an object this function will be called, so this is a great way to initialise our GUI. We define it on line 2 which is slightly different from the def statements we used before. Firstly it is indented within the class level. This means it is part of the class. Secondly the first parameter is self. This is a special reference to the rest of the class. This is how all the parts of the class can see each other. We'll use self whenever we want to refer to other parts of the class.

We have based our class on Tk. Tk has its own __init__ function, so the first thing we must do is call it, as in line 3. All of the rest of the function is similar to what we did in the previous program and it is worth comparing them. All of the variables we created as global variables before we can now create as fields in the class, starting at line 4.Everywhere we want to refer to a field we must use self. as the prefix to use the one in the class and not some other variable.

The event handlers are now defined as part of the class, starting on line 5. They can refer to the self.cnvs and self.ryeId safely.

Lastly when we have defined the class we need to use it to create the object that actually does something. Line 6 does this, creating the object app from the class ryeapp, passing None as the parent name, since this is the top level window it has no parent. The creation of the object automatically called our __init__ function which here does most of the work. When that is complete the mainloop() can start which waits for the events which get processed as we have seen before.

You can download a copy of classRye.py here.

Sunday 24 June 2012

Make Rye move

The next stage is to draw something on the canvas and make it move. There are various ways to create an image on the canvas. One simple way would be to draw the objects you want to display in a drawing package and store each one as some kind of picture file like a .gif or .png file. There will be lots of images needed eventually, so there would be lots of separate pictures to manage. Another way would be to draw the objects in the program so there are no external pictures. This is the way I chose, partly as an example of how to draw these objects and use them. We need to use Python Imaging Library module to draw the pictures and a module to make these work with the Tkinter module. At the command prompt, with the R-Pi connected to the internet, type

sudo apt-get install python-imaging python-imaging-tk

This will install the two modules in one command.

Lets start with creating a simple object to draw. There will be lots of drawings later on, so lets start a new module of our own to keep this separate from the main code. I have chosen to call it ryepics.py. Create a new file in the rye folder called ryepics.py and open the empty file in Geany. The code is below, and you can download it from here.

#!/usr/bin/env python

from PIL import Image, ImageDraw, ImageTk 1

def createImages(): 2
    #create a dict for the images to return
    imageList = {} 3
   
    #start with rye
    iRye=Image.new('RGBA',(20,20)) 4
    draw = ImageDraw.Draw(iRye) 5
    draw.ellipse((1,1,19,19), fill=(0,255,0), outline=(0,40,0)) 6
    draw.point((7,7),fill=(255,255,200)) 7
    draw.point((7,8),fill=(255,255,200))
    draw.point((8,7),fill=(255,255,200))
    draw.point((8,8),fill=(255,255,200))
   
    drye=ImageTk.PhotoImage(iRye) 8
    imageList['G']=drye 9
   
    return imageList 10


The import line, 1, uses the new modules we have just installed on R-Pi. Modules can use functions, so we define ours on line 2, called createImages(). Next we introduce one of the superb built-in parts of Python, a dictionary 3. More about that below. We now use the Image object from the imported Image module. Line 4 creates an empty image that is 20x20 pixels. It is an RGBA type, that is it has the full range of colours and transparency. Because we didn't define a coloured background it will be transparent, which is really great for our needs. Line 5 creates a drawing object we use to add to the image. Line 6 draws an ellipse in a rectangle. All references to the location on the images and the canvas are based on the normal computer layout that top left is (0,0). The x axis increases to the right and the y axis increases down the screen. The rectangle is defined by the four numbers top left is (1,1) and bottom left is (19,19). This draws a circle, because the rectangle is square. It is filled with green  - the colours are defines as red-green-blue in the range 0-255. The outline is dark green. To make the blob look a bit smarter I added 4 bright pixels from line 7.

The image now needs to be converted to something that a canvas understands which is what line 8 does. At this point we don't want to use the image, we want to store it for later use. We add the image to the dictionary we created earlier in line 9. In later stages we will add other images to the dictionary, but at this stage we just return the imageList dictionary from the function in 10. Functions can always return something, but don't have to.

So what is this dictionary? It is one of the built-in types of collection that Python offers. There are four very commonly used collections: string, tuple, list and dictionary. Programming languages usually have some kind of string to hold pieces of text and they don't usually get thought of as a collection, but in Python it is worth thinking of it in that way, because the same way of breaking up a collection apply to strings too. A tuple is a group of objects and so is a list. Tuples (and strings) are immutable - once created they can't be changed, whereas lists can be changed, sorted appended to etc. Individual elements of lists, tuples and strings can be accessed by their numeric index. Dictionaries are like lists; they are mutable (can be changed) but their index can be any key you like, each key in a dictionary must be unique. There is a lot that you can do with these collections and you might like to read the Python online manual to find out more.

In our imageList dictionary in line 9 we used a key of 'G' to store the image. We can refer to the green blob we have drawn by the key 'G' when we want it later.

Now we have an function that will create an image list that we can use. We need to amend the original start.py program to add some code to use it. I copied the original start.py to moveRye.py which you can download from here

#!/usr/bin/env python

from Tkinter import *

import ryepics 1

def RyeSetup():
    global cnvs 2
    global ryeId 3
    # GUI initialisation
    ryewin.title('Raspberry Rye')
    ryewin.grid()
    # create the canvas for the game
    cnvs = Canvas(ryewin, height=300, width=300)
    cnvs.grid(column=0, row=0, columnspan=4)
   
    # draw Rye at position 2,2 4
    sx=2*20+10
    sy=2*20+10
    ryeId=cnvs.create_image(sx,sy,image=imageList['G']) 5
   
    cnvs.bind("<Key-Down>", ryeDown) 6
    cnvs.bind("<Key-Up>", ryeUp)
    cnvs.bind("<Key-Left>", ryeLeft)
    cnvs.bind("<Key-Right>", ryeRight)
   
    # set the canvas to receive the focus, so keypresses are captured
    cnvs.focus_set() 7

def ryeDown(event): 8
    cnvs.move(ryeId, 0, 20) 9

def ryeUp(event):
    cnvs.move(ryeId, 0, -20)

def ryeLeft(event):
    cnvs.move(ryeId, -20, 0)

def ryeRight(event):
    cnvs.move(ryeId, 20, 0)

if __name__ == "__main__":
    ryewin=Tk()
    imageList=ryepics.createImages() 10
    ryeId=0 11
    cnvs=0 12
    RyeSetup()
    ryewin.mainloop()


We need to import the ryepics module we have just created. Python will find the module if it is in the same folder as the importing program. We import it in line 1. Lines 2 and 3 are to overcome the issue that is we want to share data across functions we need to make the variables global. This is a horrible fudge which we will address later, but for now it keeps things simple.

To display the image on the canvas we created last time we need to decide where we want it. I made the blob an object 20x20 pixels, and everything will be based on this scaling, so to use 'Rye coordinates', based on 20x20 objects I will need to multiply any position by 20. Images are also placed by their centre by default. To define where we want to place the Rye blob 4 we need to multiply the x & y position by 20 and add 10 (half of 20) to get the centre. We then use the canvas create_image method to place the blob in place. We use the imageList with the key 'G' to display the blob 5.  When we create an object on the canvas it returns an id which we can use later to refer to that object. We call it ryeId and because we want to use it elsewhere it is one of the variables we declared as global (that fudge again).

Last time we said that GUI programming is event-driven. So let's use respond to some events. The four lines at 6 declare some events to respond to. We are binding a keyboard press on the canvas to a function. So when the down arrow key is pressed the function called ryeDown will be called and similarly for the other three arrow keys. To make sure that the canvas has the focus to receive any keypresses we force the canvas to have the focus on line 7.

To respond to the events we define four functions; these are sometimes called event handlers. These functions need a parameter (we call it event) which has extra information in it. We ignore it, but we must declare it, as in line 8. Line 9 shows we can move a canvas object when we know its id. We move it down 20 pixels and in the other event handlers up, left or right.

So now we come to how the imageList we created in the separate module is made available here. In line 10 we call the createImage() function in the external module and store the dictionary it returns as imageList.

Lines 11 & 12 are there just to create the fudge of global variables that can be shared by the functions.

If you run the program ./moveRye.py in a terminal, as before, you should see a window with a green blob that moves in response to the arrow keys.

Saturday 23 June 2012

Making a start (2)

The last post created a small piece of code, which you can download from here. It displays a simple window, but it needs explaining. Here's the code again, but with some more explanation:

#!/usr/bin/env python 1

from Tkinter import * 2

def RyeSetup(): 3

    # GUI initialisation 4
    ryewin.title('Raspberry Rye') 5
    ryewin.grid() 6

    # create the canvas for the game 7
    cnvs = Canvas(ryewin, height=300, width=300) 8
    cnvs.grid(column=0, row=0) 9


if __name__ == "__main__": 10
    ryewin=Tk() 11
    RyeSetup() 12
    ryewin.mainloop() 13

The first line 1 is something that often appears in Linux script. It's know as a shebang. Linux uses it to find the interpreter that runs the script, in this case Python. All of your Python scripts should begin with this.

Line 2 is how Python knows about extensions to the scripting language. The import statement looks for external modules of code and makes them available to the program. An external module (Tkinter in this case) can have many functions in it and you may choose to only import some of them by name or, as in this case, import them all with '*'. We'll use some this later.

Line 3 defines the beginning of a function called RyeSetup. The function has no parameters but we still need the parentheses () to show this. The function is marked with a colon to show where the function starts. Everything in the function must be indented by the same amount to group the statements together. Lines 4 to 8 are in the function called RyeSetup(). Nothing in the function will get run yet, it is just being defined.

Line 4 is a comment. Everything after a # on a line is ignored as a comment. Line 7 is a comment too.

Line 5 sets the title of the window and line 6 initialises a grid in the window, but what window? Well it is created elsewhere, remember this code is being defined, not run yet. The grid is a very important part of the way the things on the window look and behave, but more of that later.

Line 8 creates a Canvas. A canvas is something the program can draw on, which is where we will draw the elements of the game in later stages. It has a single parameter we must pass  and two optional parameters. The required parameter is the window that it belongs to - your program could display multiple windows. We pass ryewin - this mysterious window we haven't yet defines. We also pass height and width, both are in pixels and define the size of the canvas on the screen. We call the Canvas cnvs. Canvas is one of the things that we can use because with imported the Tkinter module.

Next, in line 9, we add the canvas to the grid for the window in row 0, column 0. Later when we define a some more things on the window we will position them using this grid.

That was the end of the ryewin() function. Now we get to the program that gets run when the script is started. Line 10 is a horrible looking line that is fairly common in Python programs. It is used to find out if a piece of code is being run or loaded as a module. Basically it is an if statement and if it is True (and it will be) the indented code that follows the colon will be run. I could have missed this line out - the program would generally work, but you will often see it so I left it in. The indents are just like the ones needed for the function - they

Line 11 creates the window we are going to use. It looks simple, and indeed it is simple to use, but it does a lot of work behind the scenes. It is another function that is included in the Tkinter module. We create an object called ryewin which is returned from the function Tk(). This we will use as the reference to our window. We have created it outside of any functions so it is a global variable and therefore accessible from anywhere in the program. Creating and using global variables is often frowned on, but a small number of key ones works well.

Now we have created our window we need to set up its looks and the things in it, so we call the function we defined earlier, Ryesetup(), in line 12 which we now know will add a title and a grid and add a canvas to the grid.

Lastly in line 13 we call a function mainloop(). Strictly this is a method of the ryewin object, but what does it do? Well it processes events for the window. The way GUI programs work is called event-driven. The mainloop() method displays the window and loops around waiting for any events to happen. We haven't defined any yet, so only a few built-in ones will work. We can move the window around the screen, minimise it, change its size and finally close it. All of these create events that have default responses. We can add our own events to respond to mouse events and keyboard events and other things too. We'll make use of some of these another time.

Getting going with GUI programming is a little slow, but the bulk of the work is done for you with Tkinter and now progress can be a bit more visible, as we'll see next time.

Thursday 21 June 2012

Making a start

We want to run some code using Tkinter, this is not installed on R-Pi by default, but it is easy to add if your R-Pi is connected to the internet. Login as normal and at the command prompt type

sudo apt-get install python-tk

This will install the extension to python.

To start creating a program that runs on R-Pi in a GUI way, we first have to enter that GUI environment. When you power up the R-Pi you get prompted to login and then get the Linux command prompt. If you type startx at that prompt the GUI environment of X-Windows will start.

We need to create the code of a program and working in the GUI environment means we can edit it and test it easily. There are various editors available and one that is available in Debian is Geany. What we are going to write is a Python program and Geany helps a little by understanding the way Python should look. To get started we need to create an folder to store our program in. If you click the File Manager on the bar near the bottom left. Hover the mouse over the icons for a hint about what they are. It will open up in the home folder for the user you are logged in as. Right-click in the window and create a new folder called python. Double click on that to open it and create another folder called rye. Notice that I'm using lower-case folder names. You can use mixed or upper-case names, but file and folder names in Linux are case sensitive, so folder Python is different from folder python. Open the folder rye, double-click and this time create a blank file called start.py.

If you right-click this blank file you can look at its properties from the pop-up menu. There is something we need to change. On the Permissions tab you need to check the setting 'Make the file executable'. This will allow us to run our typed code as a program.

If you right-click the file and select Geany from the menu, Geany should start with the empty file in it. type the following code and save it.

#!/usr/bin/env python

from Tkinter import *

def RyeSetup():

    # GUI initialisation
    ryewin.title('Raspberry Rye')
    ryewin.grid()

    # create the canvas for the game 
    cnvs = Canvas(ryewin, height=300, width=300)
    cnvs.grid(column=0, row=0, columnspan=4)


if __name__ == "__main__":
    ryewin=Tk()
    RyeSetup()
    ryewin.mainloop()

This is a simple GUI program in Python that doesn't do much, but does give us a start. The indenting is vital with python. Use the tab key to create the indents carefully as shown. The case and spelling of everything here is important too. Every part must be exactly as you see it above.

To run this program, go back to the File Manager window and from the Tools menu select 'Open current folder in terminal'. We are going to run the program from a terminal session, because any errors will display on the terminal. Later we can add debugging text to display on the terminal too. To run the program type ./start.py . You need to put the ./ in front of the filename because Linux only looks in its PATH to find programs and the folder we created will not be on the PATH. You can redo a previous command in the terminal by pressing up arrow. You can edit the line by inserting or deleting characters before pressing return. If you have typed everything as shown you should see a grey window appear with the title bar at the top displaying Raspberry Rye. You should be able to move the window, resize it and close it, which is not bad for a few lines of code.

That is enough for now. In the next post I'll explain what the code does and we will start to display something more useful. If you use this or have any problems please add a comment.

Full steam ahead

I've been doing some experiments with creating the Kye game look-alike Rye. I wondered how the performance of R-Pi would stand up to running a simple game like Rye in Python. My conclusion is that R-Pi hardly notices as lots of blocks zoom around the screen. Any worries about performance are behind me, now I just need to finish the workings of the game.

Thursday 7 June 2012

Rye for R-Pi

I've been thinking about the sort of programming that might be interesting on Raspberry Pi. Anyone who uses Windows just expects a GUI interface and so anyone coming to R-Pi from Windows will expect this too. The scripting language being pushed forward is Python, something I'm happy about, it really is a pleasant language to use. Python has more than one way to create a GUI program - I chose Tkinter.

GUI programming is a bit harder than just a terminal text-based program, but not too hard. I've been working on a program that will be a bit of fun to use. It is based on a program from about twenty years ago called Kye. It is a puzzle game to move Kye, a blob, around the level, staying away from the beasts and gather the prizes. You can see more about the original Kye here.

I'll hopefully create a series of posts describing the stages to get something displayed and more and more stuff working towards a fun game. We'll see how far and how fast I get on. I hope this will be interesting to anyone wanting to write Python GUI code. As a spin off I'll get a version of Kye that doesn't need Windows. I've decided to call my version Rye.