Keeping it Small and Simple

2008.02.19

Pygame tutorial #7: more on lines

Filed under: Pygame, Pygame Tutorial — Tags: , , — Lorenzo E. Danielsson @ 23:52

This tutorial is a bit of a special. I’ve heard from several people who have had difficulty with Drawing lines – Exercise 6 in tutorial #2. So I am going to go through how to solve it here.

Understand the problem

To understand the problem we are trying to solve we need to draw a bit so get out your pen and paper. If you have square ruled paper, it might make things a bit easier for you. Draw a horizontal and a vertical line as in the picture below.

net001.png

You can of course use more squares than I have, but this should be enough to demonstrate the principle. Next, draw a line from (0, -5) to (1, 0) as I have.

net002.png

In each image, I have used blue to indicate the newest line. It is not something you need to do in your drawing. Next line goes from (0, -4) to (2, 0).

net003.png

Next line: (0, -3) to (3, 0):

net004.png

Then (0, -2) to (4, 0):

net005.png

Draw a final line from (0, -1) to (5, 0):

net006.png

Now you have the principle behind the pattern in the exercise. To get the horizontal and vertical lines you could do (0, -6) to (0, 0) and (0, 0) to (6, 0). To sum it all up:

(0, -6) to (0, 0)
(0, -5) to (1, 0)
(0, -4) to (2, 0)
(0, -3) to (3, 0)
(0, -2) to (4, 0)
(0, -1) to (5, 0)
(0, 0) to (6, 0)

Translating to python

We obviously have two variables here. One is the y-coordinate of the first point in each line that varies from -6 to 0. The other one is the x-coordinate of the second point in each pair that goes from 0 to 6. The following Python snippet can achieve what we want.


1 for x, y in zip(range(-6, 0+1), range(0, 6+1)):
2     print "(0, %d) to (%d, 0)" % (x, y)
3

(Note that in python, range(a, b) will include all numbers from a to b-1. That is why I wrote 0+1 and 6+1. You could have written 1 and 7 respectively, but I just wanted to add clarity)

If we think about it, the two variables vary together. So we could select one of them, say x, and express y in terms of x. Since x goes from 0 to 6 at the same time as y goes from -6 to 0, we could say that for each pair of points:

y = x - 6

So we could, if we so choose, write the python code as follows instead:

1 for x in range(0, 6+1):
2     print "(0, %d) to (%d, 0)" % (x-6, x)
3

Got it so far? Good. 🙂

Preparing to put this on the screen

First of all, we need to make a slight modification to what we have said so far. On the screen we need some space between each point. So instead of the x-coordinates being 0, 1, 2, 3, 4, 5 and 6, we would want them to be something like 0, 10, 20, 30, 40, 50 and 60. We will of course draw more lines as well.

But there is a more serious issue. I have been using Cartesian coordinates up to now. But screen coordinates are different. The top left hand corner of your screen is the point (0,0). As you move down the screen, y increases. So the lines we need to draw thus look as follows:

(0, 6) to (0, 0)
(0, 5) to (1, 0)
(0, 4) to (2, 0)
(0, 3) to (3, 0)
(0, 2) to (4, 0)
(0, 1) to (5, 0)
(0, 0) to (6, 0)

This means that the relationship between x and y is:

y = 6 - x

Make sure you understand this before you go on. Take as much time as you need to (hurry is a bad word in my vocabulary).

Now on to pygame

Time to use what we have just learned to put together a little program. The following program will solve the first part of the exercise.


 1 import pygame
 2
 3 screen = pygame.display.set_mode((500, 500))
 4 clock = pygame.time.Clock()
 5 running = True
 6 size = 250
 7 step = 10
 8
 9 for x in range(0, size+1, step):
10     pygame.draw.line(screen, (255, 255, 255), (0, 250-x), (x, 0))
11
12 pygame.display.flip()
13
14 while running:
15     for event in pygame.event.get():
16         if event.type == pygame.QUIT:
17             running = False
18
19     clock.tick()
20

Drawing in all corners

The second part of the exercise was to draw this pattern in all four corners of the screen. I suggest you go back to use the pen and paper and draw each figure so you get a good idea of how the coordinates vary in each case.

Next you need to remember to convert all the lines into screen coordinates. You will need to remember that “nets” on the right side need their x-coordinates to be in relation to the right-most screen coordinate, which is the width of the screen – 1 (if the width of the screen is 501 pixels, the coordinates go from 0 to 500).

The same applies to the “nets” at the bottom of the screen. The y-coordinates need to be expressed in relation to the height of the screen instead.

I am going to let you work out the math for yourself. It is not too difficult if you take your time and solve it one step at a time. Just follow the principle I have outlined already. I will give you the solution in code. If you are having difficulties, you can look at the code and “go backwards” to get to the math.


 1 import pygame
 2
 3 w = h = 500
 4 screen = pygame.display.set_mode((w+1, h+1))
 5 clock = pygame.time.Clock()
 6 running = True
 7 size = 250
 8 step = 10
 9 color = 255, 255, 255
10
11 for x in range(0, size+1, step):
12     pygame.draw.line(screen, color, (0, size-x), (x, 0))
13     pygame.draw.line(screen, color, (w – (size-x), 0), (w, x))
14     pygame.draw.line(screen, color, (w, h – (size-x)), (w-x, h))
15     pygame.draw.line(screen, color, (250-x, h), (0, h-x))
16
17 pygame.display.flip()
18
19 while running:
20     for event in pygame.event.get():
21         if event.type == pygame.QUIT:
22             running = False
23
24     clock.tick()
25

Note that the screen width is actually w+1 and the height is h+1. This is acceptable in my code because I make the rules. 😉 On a serious note, it makes the draw_line statements a little simpler.

You can (and should) experiment with size, step and color. Don’t worry if something ends up looking differently than you expected. If that happens, just take the time to understand why the program produces the results it does. This can be done with pen paper and a few debug statements (you could get the program to print out the coordinates of each line).

Animating it

Okay, let’s do something that is a little more interesting.


 1 #! /usr/bin/env python
 2
 3 import pygame
 4
 5 w = h = 500
 6 size = 250
 7 step = 10
 8 lines = []
 9 pos = 0
10 maxlines = 40
11
12 for x in range(0, size+1, step):
13     lines.append((0, size-x, x, 0))
14
15 for x in range(0, size+1, step):
16     lines.append((w – (size-x), 0, w, x))
17
18 for x in range(0, size+1, step):
19     lines.append((w, h – (size-x), w-x, h))
20
21 for x in range(0, size+1, step):
22     lines.append((size-x, h, 0, h-x))
23
24 screen = pygame.display.set_mode((w+1, h+1))
25 clock = pygame.time.Clock()
26 running = True
27
28 while running:
29     for event in pygame.event.get():
30         if event.type == pygame.QUIT:
31             running = False
32
33     screen.fill((0, 0, 0))
34     col = 0
35     cur = pos
36
37     for i in range(maxlines):
38         x1, y1, x2, y2 = lines[cur]
39         pygame.draw.line(screen, (col, col, col), (x1, y1), (x2, y2))
40
41         cur += 1
42         if cur >= len(lines): cur = 0
43         col += 240 / maxlines
44         
45     pos += 1
46     if pos >= len(lines): pos = 0
47
48     pygame.display.flip()
49     clock.tick(40)

I am not going to explain how it works here. Rather the following exercises are all related to this code. Solve the exercises as a guide to help you build up an understanding of how the program works.

Remember that as a programmer, one of the skills you will need to have is the ability to read code and make sense of it. That is the purpose of this. Just take it all one step at a time and you will be okay (I promise). If you are not sure of what a particular line does, comment it out or modify it in some way and see how it affects the result. That is an excellent and fun way to learn more.

Once you have been able to understand how the program works, see how you can improve upon it. There are several things you could do. You could look at optimizing it, making it more readable, cut out unnecessary variables, etc. Feel free to post your code.

Exercises

1. What date does the lines array hold?

2. What is the purpose of the pos variable?

3. What is the purpose of the maxlines variable? What happens when you change maxlines to 10? Or to 60? Try with different values for maxlines and see how it affects the program.

4. Why is it that I use four for loops to populate the lines array? What would happen if you used a single for loop containing all four appends? Is there any way you could re-write this part to use a single for loop without the lines ending up in the wrong order?

5. Why have I created the cur variable. It seems to always be initialized to the value of pos. Would it be possible to do away with cur and only use pos?

6. What on earth goes on in the loop that starts with for i in range(maxlines)?

7. What is it that causes the lines to “fade”?

8. Play around with the expression that changes the color value (col += 240 / maxlines)

9. Modify the program to draw red, green, blue or yellow lines. Better yet, make it use any color you prefer. Make sure that the lines still fade properly.

10. What is the purpose of this code?

pos += 1
if pos >= len(lines): pos = 0

11. Change the “tick” value. How does it affect the speed of the animation when you increase or decrease the value?

12. Can you reverse the direction of the animation?

Conclusion

Okay, that’s it for this time. Hope you enjoyed it. Don’t forget that curiosity is a good thing. Try things out. Modify the code. Any time you are uncertain about what something does, play around with it. You won’t damage your computer or your OS by messing around with the code.

Also, as the beginning of this tutorial was meant to show, there are times when pen and paper are a programmer’s best friend (sorry, Matz).

Most importantly, don’t give up! If I’m able to work things like this out, so can you.

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.

Blog at WordPress.com.