Keeping it Small and Simple

2008.04.05

Good beginner book for learning Python

Filed under: Python Programming — Tags: , , , , , , , — Lorenzo E. Danielsson @ 21:32

If you are completely new to programming take a look at How to Think Like a Computer Scientist. It seems quite good, and starts at the very beginning. It is also available on-line, so you don’t have to run to the bookstore and fork out $$$.

It teaches Python, which I think is a good beginner language. It also teaches programming in general, and you will find that what you learn will help you later if you want to learn other languages. Many people will tell you a lot about which programming language is the best first programming language. I don’t think it matters much really. What is more important is to learn one language and to learn it well.

Of course, there are a lot of other good resources as well. It’s good to have access to as many as possible. Each one will have its strengths and drawbacks.

Once you have got a good understanding of the basics, I recommend Dive Into Python which is also available in the Debian repositories (aptitude install diveintopython).

2008.03.20

Python doc strings: of course you can, but don’t

Filed under: Python Programming — Tags: , , — Lorenzo E. Danielsson @ 20:46

Just because somebody asked, I had to show that it can be done. But no, doc strings are not the place to store static data.


 1 #! /usr/bin/env python
 2
 3 # Foo! Don’t do this.
 4 # Author: Lorenzo E. Danielsson <danielsson +dot+ lorenzo [at] gmail *dot* com>
 5
 6 def bleech():
 7     "0"
 8     bleech.__doc__ = str(int(bleech.__doc__) + 1)
 9     print "I have been called %s time%s." % (
10             bleech.__doc__,
11             bleech.__doc__ != ‘1‘ and ‘s‘ or ”)
12
13 for i in range(10):
14     bleech()

2007.12.18

Pygame tutorial #6: from pixel to worm

Filed under: Pygame Tutorial, Python Programming — Tags: , , , — Lorenzo E. Danielsson @ 18:03

In the previous tutorial we learned how to plot individual pixels. We wrote a simple program where you move a pixel around the screen. Today we are going to turn our single pixel into a worm.

Two classic games that could easily be built from what we will look at today are “the worm game” and Tron. In this tutorial we will learn how to create the worm itself, and in the next tutorial we will turn it all into a little game that we can actually play.

A first try

Our worm will move across the screen. We can control it with the cursor keys. There are two things are worm must avoid at all costs: the screen borders and itself.

The following code implements just that.


 1 #! /usr/bin/env python
 2
 3 # Move a worm across the screen. Beware of borders and self!
 4
 5 import pygame
 6
 7 class Worm:
 8     """ A worm. """
 9
10     def __init__(self, surface, x, y, length):
11         self.surface = surface
12         self.x = x
13         self.y = y
14         self.length = length
15         self.dir_x = 0
16         self.dir_y = -1
17         self.body = []
18         self.crashed = False
19
20     def key_event(self, event):
21         """ Handle key events that affect the worm. """
22         if event.key == pygame.K_UP:
23             self.dir_x = 0
24             self.dir_y = -1
25         elif event.key == pygame.K_DOWN:
26             self.dir_x = 0
27             self.dir_y = 1
28         elif event.key == pygame.K_LEFT:
29             self.dir_x = -1
30             self.dir_y = 0
31         elif event.key == pygame.K_RIGHT:
32             self.dir_x = 1
33             self.dir_y = 0
34
35     def move(self):
36         """ Move the worm. """
37         self.x += self.dir_x
38         self.y += self.dir_y
39
40         if (self.x, self.y) in self.body:
41             self.crashed = True
42
43         self.body.insert(0, (self.x, self.y))
44
45         if len(self.body) > self.length:
46             self.body.pop()
47         
48     def draw(self):
49         for x, y in self.body:
50             self.surface.set_at((x, y), (255, 255, 255))
51
52 # Dimensions.
53 width = 640
54 height = 400
55
56 screen = pygame.display.set_mode((width, height))
57 clock = pygame.time.Clock()
58 running = True
59
60 # Our worm.
61 w = Worm(screen, width/2, height/2, 200)
62
63 while running:
64     screen.fill((0, 0, 0))
65     w.move()
66     w.draw()
67
68     if w.crashed or w.x <= 0 or w.x >= width-1 or w.y <= 0 or w.y >= height-1:
69         print "Crash!"
70         running = False
71
72     for event in pygame.event.get():
73         if event.type == pygame.QUIT:
74             running = False
75         elif event.type == pygame.KEYDOWN:
76             w.key_event(event)
77
78     pygame.display.flip()
79     clock.tick(240)

The first thing we do a define what a worm is. Notice how the functionality of the worm is contained within the class. The worm knows how to move itself, draw itself, and it handles the events that relate to it. (Note: if you have done Java programming before, you may wonder where your accessors and mutators are. Don’t worry about them. You don’t need them.)

The body of the worm is stored in a list. Each time we move one step, we insert the new position of the body into the beginning of the list and pop off the last list item. That keeps our worm constant in length. Actually, that is not entirely true. Our worm starts off being only a single pixel long. We have a bit of extra code that allows the worm to grow to its full length. A little bit of code analysis should make that clear.

The move() method also contains a check to see if the worm has crashed onto itself. What we do is see if the position of the worm’s head (self.x, self.y) happens to be at the same place as any part of the worm’s body, by comparing (self.x, self.y) with each position in the body list.

The draw() method simply plots all the points held in the body list. The main program should be fairly straight-forward by now. Notice that in addition to checking if the worm has crashed onto any of the borders, the worm object is also asked if it has crashed with itself.

Exercises

  1. Let the worm grow over time. You could for instance have a counter that decreases every frame, or every time the worm turns or whatever. When the counter reaches zero, you increase the length of the worm.
  2. Create a program that displays two worms. What changes do you need to make to the Worm class in order for this to work?
  3. Modify the worm to have a gradient effect. The head of the mouse should be white. Each body segment should gradually become darker and darker shades of gray.

Collision detection

In our first example, we checked if the worm had collided with itself by comparing the position of the worm’s head with all the positions of the body. That worked fine, but there are instances where it’s not practical to keep a lot of positions in a list. An example would be the Tron game that I referred to earlier. The more you move, the longer the list would be, and the longer it would take to determine whether or the object has collided with its own body.

We will try a slightly different approach, by using Surface.get_at, which tells us the color of the pixel at the given position. It actually returns (red, green, blue, alpha), but we won’t worry about the alpha part just yet.

The new code is below:


 1 #! /usr/bin/env python
 2
 3 # Move a worm across the screen. Beware of borders and self!
 4
 5 import pygame
 6
 7 class Worm:
 8     def __init__(self, surface, x, y, length):
 9         self.surface = surface
10         self.x = x
11         self.y = y
12         self.length = length
13         self.dir_x = 0
14         self.dir_y = -1
15         self.body = []
16         self.crashed = False
17
18     def key_event(self, event):
19         """ Handle keyboard events that affect the worm. """
20         if event.key == pygame.K_UP:
21             self.dir_x = 0
22             self.dir_y = -1
23         elif event.key == pygame.K_DOWN:
24             self.dir_x = 0
25             self.dir_y = 1
26         elif event.key == pygame.K_LEFT:
27             self.dir_x = -1
28             self.dir_y = 0
29         elif event.key == pygame.K_RIGHT:
30             self.dir_x = 1
31             self.dir_y = 0
32
33     def move(self):
34         """ Move the mouse. """
35         self.x += self.dir_x
36         self.y += self.dir_y
37
38         r, g, b, a = self.surface.get_at((self.x, self.y))
39         if (r, g, b) != (0, 0, 0):
40             self.crashed = True
41
42         self.body.insert(0, (self.x, self.y))
43
44         if len(self.body) > self.length:
45             self.body.pop()
46
47     def draw(self):
48         """ Draw the worm. """
49         for x, y in self.body:
50             self.surface.set_at((x, y), (255, 255, 255))
51
52 # Window dimensions.
53 width = 640
54 height = 400
55
56 screen = pygame.display.set_mode((width, height))
57 clock = pygame.time.Clock()
58 running = True
59
60 # Our worm.
61 w = Worm(screen, width/2, height/2, 200)
62
63 while running:
64     screen.fill((0, 0, 0))
65     w.draw()
66     w.move()
67
68     if w.crashed or w.x <= 0 or w.x >= width-1 or w.y <= 0 or w.y >= height-1:
69         print "Crash!!!"
70         running = False
71
72     for event in pygame.event.get():
73         if event.type == pygame.QUIT:
74             running = False
75         elif event.type == pygame.KEYDOWN:
76             w.key_event(event)
77
78     pygame.display.flip()
79     clock.tick(240)
80

As you can see, the new version is similar to the previous one for most part. Where it does differ is in the part where we detect whether or not the worm has collided with itself. We no longer need to compare the position of the head with each position of the body. Instead we simply compare the color of the pixel where the head is with the color of the background. If they don’t match then we have a collision, since the worm is the only object on the screen.

There is one other difference between the programs. In the event loop, look at the order in which the w.draw() and w.move() are in each program. Notice the difference? In order for the second program to be able to work at all, it is vital that we draw that worm before we move it. Think about it for a while and you will understand why.

Exercises

  1. Change the program to compare the pixel color at the worm’s head with the worm’s body color instead of the background color. If the color of the pixel is the same as that of the body it is a crash.
  2. Add obstacles around the screen. These should be randomly positioned pixels. Use a different color than the worm’s color. Add collision detection to determine if the worm has crashed onto one of the obstacles. If so, the program should exit.
  3. Give the worm food to eat. Food should be a pixel randomly positioned on the screen. Make it a different color from both obstacles and the worm itself. Any time the worm gets the food, the message “yummy” should be printed to the console and a new food should be generated. Keep a counter that increases every time the worm grabs a food item.

Optimizing

As a game developer, one thing you will have to get used to is spending hours with your code looking for places to optimize. What I’m going to do here is very trivial, but then again, we are working with a very trivial example.

One thing that we do that is really wasteful is “clear” the surface each frame. This wouldn’t be necessary if we were to instead plot a single pixel, the color of the background, at the very tail of the worm. The following program does that:


 1 #! /usr/bin/env python
 2
 3 # Move a worm around the screen. Beware of borders and self!
 4
 5 import pygame
 6
 7 class Worm:
 8     """ A worm. """
 9
10     def __init__(self, surface, x, y, length):
11         self.surface = surface
12         self.x = x
13         self.y = y
14         self.dx = 0
15         self.dy = -1
16         self.length = length
17         self.body = []
18         self.last = (0, 0)
19         self.crashed = False
20
21     def key_event(self, event):
22         """ Handle keyboard events that affect the worm. """
23         if event.key == pygame.K_UP:
24             self.dx = 0
25             self.dy = -1
26         elif event.key == pygame.K_DOWN:
27             self.dx = 0
28             self.dy = 1
29         elif event.key == pygame.K_LEFT:
30             self.dx = -1
31             self.dy = 0
32         elif event.key == pygame.K_RIGHT:
33             self.dx = 1
34             self.dy = 0
35
36     def move(self):
37         """ Move the mouse. """
38         self.body.insert(0, (self.x, self.y))
39         self.x += self.dx
40         self.y += self.dy
41
42         r, g, b, a = self.surface.get_at((self.x, self.y))
43
44         if (r, g, b) != (0, 0, 0):
45             self.crashed = True
46
47         if len(self.body) >= self.length:
48             self.last = self.body.pop()
49         else:
50             self.last = self.body[-1]
51
52     def draw(self):
53         """ Draw the worm """
54         self.surface.set_at((self.x, self.y), (255, 255, 255))
55         self.surface.set_at(self.last, (0, 0, 0))
56
57 # Window dimensions.
58 width = 640
59 height = 400
60
61 screen = pygame.display.set_mode((width, height))
62 screen.fill((0, 0, 0))
63 pygame.display.flip()
64
65 clock = pygame.time.Clock()
66 running = True
67
68 # Our worm.
69 w = Worm(screen, width/2, height/2, 200)
70
71 while running:
72     w.draw()
73     w.move()
74
75     if w.crashed or w.x <= 0 or w.x >= width-1 or w.y <= 0 or w.y >= height-1:
76         print "Crashed!"
77         running = False
78
79     for event in pygame.event.get():
80         if event.type == pygame.QUIT:
81             running = False
82         elif event.type == pygame.KEYDOWN:
83             w.key_event(event)
84
85     pygame.display.flip()
86     clock.tick(240)
87

Study the move() and draw() methods and compare them to the ones in the previous version. Also notice that there is no longer any screen.fill() call inside the event loop.

I have rearranged some of the code in the move() method. Is this necessary? Figure it out. You can always try rearranging lines to see what happens. You will learn much by experimenting with the code.

Exercises

  1. Analyze the last version of the worm program. How does it differ from the previous one? How does it work? In what ways can this said to be “optimized” as compared to the previous versions?
  2. Extend the worm program that draws food for the worm to eat in such a way that each time the worm eats food it grows a little longer.
  3. Extend the program in the previous exercise so that there is “poison mushroom” as well as food on the screen. If the worm eats one of these, it reduces in length. If the length goes below a certain minimum, the program should end.

Conclusion

That brings to end another pygame tutorial. I hope you enjoyed it. I know that I am moving way too slow for some people. There are loads of other good tutorials out there if you are in a hurry to learn. Many (all?) of them are far superior to my own.

Please do try and solve at least some of the exercises. If you want to send me your solutions or questions, you can find my email if you look around a bit. 😉

2007.05.30

Pygame tutorial #3: mouse events

Filed under: Graphics Programming, Pygame Tutorial, Python Programming — Lorenzo E. Danielsson @ 10:17

In the last tutorial, you learned how to draw lines. This time we will deal with mouse events. As usual we will keep it simple.

Already in the first tutorial you learned about the QUIT event. Now let’s add a little bit of code to track the position of the mouse on our window. This is done by the MOUSEMOTION event. The code looks as follows:

 1 #! /usr/bin/env python
 2 
 3 import pygame
 4 
 5 x = y = 0
 6 running = 1
 7 screen = pygame.display.set_mode((640, 400))
 8 
 9 while running:
10     event = pygame.event.poll()
11     if event.type == pygame.QUIT:
12         running = 0
13     elif event.type == pygame.MOUSEMOTION:
14         print "mouse at (%d, %d)" % event.pos
15 
16     screen.fill((0, 0, 0))
17     pygame.display.flip()

Most of the code should look familiar by now. What is new is the check to see if we have an event of the type MOUSEMOTION (line 13). Also notice that if the event is a mouse motion event we can get a little bit more information out of the Event object. In the next line (14), we use call event.pos to get the current coordinates of the mouse pointer. This method returns a pair of values representing the x-position and y-position of the mouse pointer.

Note that the values returned by event.pos are relative to the top left-hand corner of the window, not the entire screen (unless, of course, the window covers the entire screen). You probably already know that the top-left hand corner is (0, 0) in screen coordinates.

Exercises

  1. Write a program that prints the position of the mouse pointer using Cartesian coordinates. To begin with assume that the x-axis is at half the height of the window and the the y-axis is located at half the width of the window. You already know how to draw lines, so you might as well draw the axes. Take care with the difference in direction along the y-axis between screen coordinates and Cartesian coordinates.
  2. Replace MOUSEMOTION in the example above with MOUSEBUTTONDOWN. Run the program. Move the mouse over the window and press any mouse button. What happens?
  3. Imagine that your window consists of tiles, each one 32×32 pixels. Write a program that detects the current mouse position, translates the screen coordinates into tile coordinates and prints this.
  4. Write a program that calculates the distance of the mouse pointer from the center of the window. Remember that there is an imaginary straight line from the center point to the point where the mouse pointer is located. Just calculate the length of the straight line.

Seeing screen coordinates dumped onto you terminal window is exciting for just about 0.02 seconds. Let’s do something else. Since what we have learned so far is drawing lines we will stick to that. Here is a program that draws lines that cut the mouse pointer’s coordinates.

 1 #! /usr/bin/env python
 2 
 3 import pygame
 4 
 5 bgcolor = 0, 0, 0
 6 linecolor = 255, 255, 255
 7 x = y = 0
 8 running = 1
 9 screen = pygame.display.set_mode((640, 400))
10 
11 while running:
12     event = pygame.event.poll()
13     if event.type == pygame.QUIT:
14         running = 0
15     elif event.type == pygame.MOUSEMOTION:
16         x, y = event.pos
17 
18     screen.fill(bgcolor)
19     pygame.draw.line(screen, linecolor, (x, 0), (x, 399))
20     pygame.draw.line(screen, linecolor, (0, y), (639, y))
21     pygame.display.flip()

There is really nothing new in this code so I don’t think you will have any difficult understanding it. We have already learned how to set the color of the line we are drawing, so let’s extend the program slightly:

 1 #! /usr/bin/env python
 2 
 3 import pygame
 4 
 5 bgcolor = 0, 0, 0
 6 blueval = 0
 7 bluedir = 1
 8 x = y = 0
 9 running = 1
10 screen = pygame.display.set_mode((640, 400))
11 
12 while running:
13     event = pygame.event.poll()
14     if event.type == pygame.QUIT:
15         running = 0
16     elif event.type == pygame.MOUSEMOTION:
17         x, y = event.pos
18 
19     screen.fill(bgcolor)
20     pygame.draw.line(screen, (0, 0, blueval), (x, 0), (x, 399))
21     pygame.draw.line(screen, (0, 0, blueval), (0, y), (639, y))
22     blueval += bluedir
23     if blueval == 255 or blueval == 0: bluedir *= -1
24     pygame.display.flip()

Exercises

  1. In the examples above, the program draws lines that extend the full width and the height of the window. Create a program that draws a much smaller ‘+’, say from 10 pixels to the left/top of the mouse pointer to 10 pixels to the right/below the pointer.
  2. Write a program that draws a horizontal line at the y-coordinate of the mouse pointer. The color of the line should vary according to the following: divide the window into four quadrants. Check which of the quadrants the pointer is in and set the line color to red if it is in the first quadrant, green if it is in the second quadrant, blue if in the third quadrant or white if the mouse pointer is in the last quadrant.
  3. Write a program that tracks the positon of the mouse pointer and draws one line from the bottom right right-hand corner of the window to the current mouse position as well as one line from the bottom left-hand corner of the window to the position of the mouse pointer.

Mouse buttons

If you have been doing the exercises so far then you already know how to deal with a mouse button being pressed. Let’s look at an example.

 1 #! /usr/bin/env python
 2 
 3 import pygame
 4 
 5 LEFT = 1
 6 
 7 running = 1
 8 screen = pygame.display.set_mode((320, 200))
 9 
10 while running:
11     event = pygame.event.poll()
12     if event.type == pygame.QUIT:
13         running = 0
14     elif event.type == pygame.MOUSEBUTTONDOWN and event.button == LEFT:
15         print "You pressed the left mouse button at (%d, %d)" % event.pos
16     elif event.type == pygame.MOUSEBUTTONUP and event.button == LEFT:
17         print "You released the left mouse button at (%d, %d)" % event.pos
18 
19     screen.fill((0, 0, 0))
20     pygame.display.flip()

In this example we handle the two events MOUSEBUTTONDOWN and MOUSEBUTTONUP. Run the program and try this. Press the left mouse button down. While holding the button down, move it to a different position, the release the mouse button. Good, now you know how that works.

Exercises

  1. Write a program that draws a horizontal line and a vertical line that both intersect the position of the mouse pointer. The color of the lines should change every time the left button is clicked. The vertical line should be set to a new random color. After generating a random color value, make sure that it is different from the current color. The horizontal line should be set to the previous color of the vertical line.
  2. Write a program that draws a ‘+’ surrounding the mouse pointer when it is pressed down. The size of the cross should be 20 pixels in each direction. When the mouse button is released the cross should disappear.

Conclusion

In this tutorial you have learned how to deal with mouse motion and mouse button state. I think you are now comfortable enough with what events that we can look into them into some detail. Up to now we have been using poll() to get an event off the event queue, for the sake of simplicity. In the next tutorial you will see why that is not really a good idea and what you should do instead. Don’t worry, it won’t really become more complicated.

We will also learn a few more drawing methods.

2007.05.27

Pygame tutorial #2: drawing lines

Filed under: Graphics Programming, Pygame, Pygame Tutorial, Python Programming — Lorenzo E. Danielsson @ 02:46

Welcome to part 2 in my pygame tutorial. This time we will build upon what you learned last time as we start drawing onto surfaces. We will continue to move very slowly so that everybody gets a chance to learn.

Drawing a line

You draw a line by using pygame.draw.line. You can also use pygame.draw.aaline which draws an anti-aliased line. Using anti-aliasing can make the line appear less jagged in some cases at the expense of the function call being much slower.

To draw a blue line from (0, 0) to (200, 100) (note: we are measuring in pixels) onto the surface screen you do:


pygame.draw.line(screen, (0, 0, 255), (0, 0), (200, 100))

You could also do this:


blue = 0, 0, 255
point1 = 0, 0
point2 = 200, 100
pygame.draw.line(screen, blue, point1, point2)

This is will do exactly the same thing as the previous example, but is (possibly) more readable. Let’s try to put this together to a little program. We will draw two diagonal lines: one going from the top left-hand corner to the bottom right-hand corner and one from the top right-hand corner to the bottom left-hand corner. We will use pygame.draw.line for one of the lines and pygame.draw.aaline for the other.

 1 #! /usr/bin/env python
 2 
 3 import pygame
 4 
 5 screen = pygame.display.set_mode((640, 480))
 6 running = 1
 7 
 8 while running:
 9     event = pygame.event.poll()
10     if event.type == pygame.QUIT:
11         running = 0
12 
13     screen.fill((0, 0, 0))
14     pygame.draw.line(screen, (0, 0, 255), (0, 0), (639, 479))
15     pygame.draw.aaline(screen, (0, 0, 255), (639, 0), (0, 479))
16     pygame.display.flip()

I hope you recognize most of the code from the first tutorial. If you run the program you should see a big blue ‘x’ across the window.

Exercises

  1. Improve the program by getting rid of “magic numbers”. Declare two variables width and height and initialize them to the width and the height of the window. Then modify the rest of the program to use these variables instead of actual numbers. To verify that you have made all the required changes, try setting width and height to different values and confirm that you still get two diagonal lines and that each line’s end points are at one of the corners of the window.
  2. Improve the program further by declaring variables called linecolor, topleft, bottomright and so on. Modify the program to make use of the variables.
  3. Modify the program to draw the two lines in different colors.
  4. Write a program that instead draws one horizontal and one vertical line. The lines should both be centered.
  5. Write a program that draws four lines: the diagonal lines as in the example as well has the horizontal and vertical lines as in the last exercise. Each line should be in a different color. The program should take the height and width of the window as command-line arguments.
  6. Write a program that draws the following pattern:
    fishnet.png
    Once you have solved it for the upper left hand corner, repeat it so that the same pattern is drawn in all four corners of the screen. You should be able to draw all in all corners with a single loop and four calls to pygame.draw.line.

Moving things around

Now that we know how to draw lines on the screen with pygame, let’s start moving them around. Moving lines around is simple. we store the line coordinates in variables. Inside the event loop we modify the values of those variables. Let’s draw a line that jumps up and down.

 1 #! /usr/bin/env python
 2 
 3 import pygame
 4 
 5 y = 0
 6 dir = 1
 7 running = 1
 8 width = 800
 9 height = 600
10 screen = pygame.display.set_mode((width, height))
11 linecolor = 255, 0, 0
12 bgcolor = 0, 0, 0
13 
14 while running:
15     event = pygame.event.poll()
16     if event.type == pygame.QUIT:
17         running = 0
18 
19     screen.fill(bgcolor)
20     pygame.draw.line(screen, linecolor, (0, y), (width-1, y))
21 
22     y += dir
23     if y == 0 or y == height-1: dir *= -1
24 
25     pygame.display.flip()

There we go. The y-position of the line is determined by the variable y which increased by dir in each iteration of the event loop. The value of dir is 1 when the line is moving downwards, or -1 when the line is moving upwards. Simple, isn’t it?

Exercises

  1. Comment out the line screen.fill(bgcolor) and run the program. What happens?
  2. Extend the program to also include a vertical line that moves across the screen.

Drawing a color bar

Our final example for today will draw a single color bar (“copper bar” if you’ve ever owned an Amiga). Just like our last example, this one will also jump.

 1 #! /usr/bin/env python
 2 
 3 import pygame
 4 
 5 y = 0
 6 dir = 1
 7 running = 1
 8 barheight = 124
 9 screen = pygame.display.set_mode((800, 600));
10 
11 barcolor = []
12 for i in range(1, 63):
13     barcolor.append((0, 0, i*4))
14 for i in range(1, 63):
15     barcolor.append((0, 0, 255 - i*4))
16 
17 while running:
18     event = pygame.event.poll()
19     if event.type == pygame.QUIT:
20         running = 0
21 
22     screen.fill((0, 0, 0))
23     for i in range(0, barheight):
24         pygame.draw.line(screen, barcolor[i], (0, y+i), (799, y+i))
25 
26     y += dir
27     if y + barheight > 599 or y < 0:
28         dir *= -1
29 
30     pygame.display.flip()

In this example I create an array called colorbar (for the lack of a better name). This will hold values for the colorbar as it shifts from black to bright blue and back to black. Keep in mind that a color is composed of red, green and blue. Each one of these can be a value between 0 and 255.

If I change the blue value by one for each new line, I would get a really smooth gradient bar. But, the height would be 256 pixels from black to blue and another 256 pixels from blue back to black = 512 pixels, which is too high. There would hardly be enough space in our window to see the bar moving up and down.

So I have to decide on a bar height that I find acceptable. I settled on 124, which means I have 62 pixels from the black to blue gradient and another 62 for the blue to black gradient. This also means that I have to move from a blue value of 1 to a blue value of 255 in 62 pixels. The change in blue per line must be 4 per line.

I use two for loops to populate the barcolor array. The first loop pushes increasing values of blue to the array and the second one decreasing values of blue. It is important to notice that this array is nothing other than a lookup. It doesn’t contain the bar as you see it on the screen. It just contains color values.

Run it and see what it looks like. Once you’ve seen it run, analyze the code and make sure you understand exactly how it works. Then start experimenting with it.

Exercises

  1. The color bar example code is horrible. Full of “magic numbers” everywhere! Fix it.
  2. Try using pygame.draw.aaline for drawing the lines. Run the program. Do you notice any difference in speed?
  3. Change the color of the color bar.
  4. Write a program that draws three color bars in different colors. Take note of the height per bar, so that all three can fit and have some space for movement.

Conclusion

In this tutorial you have learned to draw lines. We will be staying with methods of drawing to the screen a little while longer, before we move on to other interesting things like loading and displaying images. In the next tutorial you will also learn a bit about mouse events.


2007.12.11:Added explanation of what the colorbar array does in the last example. Thanks to reader Sergy for pointing out that it needed some explanation.

2007.05.25

Pygame tutorial #1: getting started

Filed under: Game Programming, Graphics Programming, Pygame, Pygame Tutorial, Python Programming — Lorenzo E. Danielsson @ 02:22

This is the first part of my pygame tutorial, aimed at beginners. I will keep it small and simple, meaning I will proceed very slowly. I am not going to go into too much detail. I will explain just about as much as is necessary to get you started. You will need to have the pygame documentation available. Always keep it open while you are going through this tutorial.

One little thing before we begin. I am chronically lazy. It is amazing that I even manage to get out of bed every day. So please don’t expect this to be a regular thing. I will write tutorials as and when my eyes are open.

What you will need

  • Python: I am using Python 2.4 which is the default Python version on Debian Etch. Other versions will work as well.
  • Pygame: There really isn’t much point in programming in pygame unless you have it installed.
  • A text editor: I recommend Vim a powerful editor with an unfortunately bad reputation for being complex. Don’t believe the rumors. They are not true.

If you are on Debian or one of its derivates, try this (as root):

# aptitude install python-pygame

That will install pygame and all dependencies for you. Other distros and operating systems: both Python and pygame are most likely available in your repositories. Figure out how to install them and you are ready to go.

What you should know already

It is a good idea if you at least have some basic knowledge of Python. I guess you could be learning it as you go along, but I will assume that you know how to program in Python already.

Getting started

First of all, let us look at creating a pygame application that does absolutely nothing. Well, nearly absolutely nothing. It actually does display a window. It also does some very rudimentary event handling. You can see this as a template for the next few programs that we will write.

Here is the code:

1 #! /usr/bin/env python
2 
3 import pygame
4 
5 screen = pygame.display.set_mode((640, 400))
6 
7 while 1:
8     pass

First of all, to use pygame we have to import it. Then we create a Surface that is 640 pixels wide and 400 pixels high. There are a lot more things you can do with set_mode, but we will keep it simple at this point.

Next we enter into an infinite loop. We need this because otherwise the window will
If you run this program it should display a 640×400 window. That wasn’t too difficult was it. If you try to close this application by clicking on the window’s close button, you will notice that it does nothing. You will have to go activate the terminal that you started the program from and
hit CTRL+C to stop the program. We will fix that soon.

Exercises

  1. Create a window that is 320 pixels wide and 200 pixels high.
  2. Create a program where the user can specify the width and the height as command line arguments.
  3. Create a program that asks the users for the width and the height and then displays the window.
  4. Write a program that calls pygame.display.set_mode twice with different sizes. What do you expect should happen? Run the program. What actually happens? Can you explain why?

Adding an event loop

Our first example was maybe a little too simple. Staring at a completely blank window soon gets boring. Also, having to go to the terminal and hit CTRL+C to close the window seems a little awkward. Let’s add a bit of code!

Here is an updated version of the first program:

 1 #! /usr/bin/env python
 2 
 3 import pygame
 4 
 5 screen = pygame.display.set_mode((640, 400))
 6 running = 1
 7 
 8 while running:
 9     event = pygame.event.poll()
10     if event.type == pygame.QUIT:
11         running = 0
12 

What is new is that I have added a simple event loop. The loop is controlled by a flag called running. As long as the flag is set the loop keeps running. Inside the loop we use the poll method to grab the next event from the event queue. There are other ways of doing this, but polling works just fine for now.

There are several different event types that pygame knows about and can respond to. One of these is QUIT, which gets triggered when the user clicks on the window’s close button. All we do if we get an event of this type is clear the running flag, which will exit the loop and cause the program to terminate. Still simple, isn’t it.

Exercises

  1. Adapt each of the programs you wrote for the exercises in the previous section to use an event loop.
  2. Rewrite the program to do away with the running flag. Make sure that the program still jumps out of the event loop on the QUIT event.

Finishing touches to the template

As a final step before we start doing real things, let’s add just a little bit more so that we have a complete template for what follows. We will paint the our Surface and we will learn how to do a bit of screen flipping. First the code:

 1 #! /usr/bin/env python
 2 
 3 import pygame
 4 
 5 screen = pygame.display.set_mode((640, 400))
 6 running = 1
 7 
 8 while running:
 9     event = pygame.event.poll()
10     if event.type == pygame.QUIT:
11         running = 0
12     screen.fill((0, 0, 0))
13     pygame.display.flip()
14 

We have added just two lines of code. The first one sets a background color for the Surface. We have passed in a sequence of three values: red, green and blue. Each one can be a value between 0 and 255. Since we set all to zero, we get a black screen. You should experiment with different values for red, green and blue.

The next thing that is new is that we call pygame.display.flip. Drawing directly to the screen is usually a very bad idea. Instead, we have a invisible buffer that we draw onto. When we have finished drawing we make the buffer visible. That way we get flicker-free animations (when we get to that).

Exercises

  1. Create a window with a white background color.
  2. Create a window with a red background color.
  3. Experiment with setting different background colors. If you are not familiar with RGB values then spend a little extra time to figure out how to get colors like yellow, brown, cyan etc.
  4. Create a program that asks the user to specify the values for red, green and blue. Check that the values are in the valid range (0-255) and then use these for the background color.
  5. Create a program that upon start-up checks the time of the day and sets the brightness of the background color accordingly. Use a blue scale. If it is midnight, the screen should be black. If it is midday, the screen should be bright blue. Any other time the background should be something in between corresponding to the brightness outside.

Conclusion

So what have you learned so far? Not an awful lot, by the looks of it. But in a way you have. You have acquired some basics that you will need to understand the next tutorial, which will come soon. In the meantime, try your hands on a few of the exercises. If you complete all you can make up a few of your own.

2007.05.14

Pygame: game programming in Python

Filed under: Computer Games, Python Programming, Web Sites — Lorenzo E. Danielsson @ 09:57

Those of you who want to relax with a game or two in between coding sessions should definitely check out http://pygame.org. Pygame is a library for developing games and multimedia applications in Python.

The pygame site contains documentation and tutorials on pygame programming, but also lists games and demos that are developed using pygame. Some of the games listed there are really good. I’ll probably write up on some of the better ones when I have a little bit of spare time.

Also, don’t forget that the games listed on the site are an excellent source of knowledge for those who want to learn how to program games as well. There are a few relatively basic games that you might want to start out with.

Games not your cup of tea, you say? Not a problem, the pygame site also links to a few other applications that use pygame. Currently, most (but not all) relate to games in some way, but with time you may find more applications coming out that use pygame.

Pygame is written on top of Simple DirectMedia Layer, a library for developing multimedia applications. Therefore, it’s uses extend beyond the arena of games. You could, for instance, create music players or presentation programs using pygame (and both of these have already been done. If you’re studying physics, you could use pygame to write a program to visualize projectile motion, just to give a single example.

Pygame is obviously not the only library available for writing multimedia applications. And it might not even be the best (I won’t go into that discussion). But if you look at the long list of games, demos and other applications that are using it, you will see that it definitely has a large following. It’s worth checking out. And while you’re at it, you could take a look at SDL as well.

So do yourself a favor and head off to the pygame site. I’m sure you won’t be disappointed.


Update 2007.05.14: fixed a broken link.

Blog at WordPress.com.