Gruhn's Python and Pygame Demos

pmg_util.py colorfloat.py

08-30-2012 I found that some of the programs required pmg_util.py. This is a library of useful routines that I wrote over the course of my class that used Python/Pygame. I removed the dependency from the programs here so you can use them straight up. I also include pmg_util.py here for you to use.

The best, most used function in there is valmap(x,a,b,m,n). It is very common to have a number from one range, say screen location, and want to map it to a different range, say color or angle. This function takes care of that. An example might be: red = valmap(mouse_x,0,width,0,255). I use this all the time.

The comments in pmg_util.py mention colorfloat.py. I figured I'd better either delete the comment or make the file available. So I made it available. Its primary purpose is to make it easier to change color slowly. If you increment a color by the smallest possible value, 1, you run through the full range in only 255 frames. That can happen pretty quickly. You can set up some kind of counter and test or ... yuck. Wouldn't it be nice to just be able to add 0.1 to a color? This class takes care of that. There's some demo code in the file, uncomment it and watch the show. (The easiest way to uncomment it is to do a replace all """ ==> #""".)

vsmall.py

Maybe not the smallest possible Pygame program but a very simple one. In fact, this program is too small to use as a template for general programs, but it makes a nice uncluttered quick test platform. Also, it is a good way to see what features don't exist without being written. For example, there is no elegant way to quit the program. That's something you need to write. Spending a little time fighting VSmall may help you better understand what some of the parts in the longer programs are doing.

colors.py

Sole purpose of this is to show how different colors are made using (red, green, blue) tuples. For the computer graphics beginner colors are not the same as you learned in kindergarten: "Yellow is WHAT?!" But you can also grab the text drawing code out of it.

loops.py

Examples of different ways to repeat some code. Very important basic programming tool.

loops-shapes.py

Same as loops.py but with more interesting drawing.

loops-nested.py

One loop gives you a row of things. If each thing is a loop of things you get a grid. This is very common in computer programming. Loop through all the customers and for each customer loop through all their purchases. Loop through all the rows of pixels and for each row loop through all the pixels. One loop inside another and we call them "nested."

The red here is just simple nested loops doing the same thing at each step. The green one changes the inner loop based on where the outer loop is. As it moves across, it moves down further. The blue one is four loops nested. The outer two are pretty much the same as the red set - a simple grid of dots. But instead of a dot, there are the two innermost nested loops that create their own simple 3x3 grid at that location on the larger grid. And finally the fall colors are pretty much the same as the red, but the color of the dot varies based on the loop control variables. You can start to see the logic and power behind nesting loops.

paint-01-circle.py

Simple pygame circle follows the mouse.

paint-02-brush.py brush.png

Instead of drawing a circle, an image is copied to the screen.

bitmap-drag.py cover.jpg

In the end, this is no different from the bitmap brush based paint program on the previous line. They were just written with different ends in mind. That one is "a paint program with a nice brush" and this one is "loading a bitmap and blitting it somewhere." I wonder how different the code is between them.

bitmap-tint.py star-small.png

Another bitmap painter, but this one stains the bitmap with color before blitting.

bitmap-xform.py star.png

Another bitmap painter, this one transforms the bitmap before blitting. Rotation is based on vertical location, scale based on horizontal location.

bouncy.py

Bounces a number of balls of different size and color around the screen. Deals with multiple persistent objects in a more interesting program than earlier structured data programs. Deals a little more interestingly with motion and boundaries.

bouncy-with-options.py

Same as previous but piles some more interesting stuff into the code to play around with.

bouncy-with-options-and-transparency.py

Same as above with transparency overlay per frame. See bouncyline-trail-overlay for a tidier program that deals with transparency the same way.

bouncy-line.py

Based on bouncy.py. This program bounces points around the screen in the exact same manner, but rather than draw a circle at each point, the points are drawn connected using a pygame polygon. It works much better with far fewer points. If you are looking for a challenge, experiment with color. Draw the polygon a different color from frame to frame. Random color is easy. Slowly changing color is a little harder (think of the color values r,g,b as points that bounce off the extreme valid color values (0 and 255)). Draw each segment in the polygon the same color from frame to frame but different from its neighbors (look back to bouncy.py for how you might store the color; hmm... how would you draw the polygon?)

Thumbnail and linked code not exactly the same - line "clear()" commented out in code used for thumbnail.

bouncyline-trail-overlay.py

Based on bouncy.py, this program draws the polygon filled. The fading effect is accomplished by drawing the polygon offscreen on a transparent black background then laying that on top of the existing drawing. Each frame lays a little more black over old pixels and fades them towards black.

CA1d.py

A one dimensional cellular automata. My favourite rule set is this one that creates Sierpinski gaskets, but there are a few other interesting rules. Program makes use of topics covered more directly in other programs. Of particular note, it draws on the screen by directly changing single pixels.

rectdrop.py

Creates a number of random rectangles and moves them down the screen. Disallows collisions by using Pygame's Rect.Collidelist().

rectdrop-fancy.py

Based on rectdrop.py. Adds drop shadows for a cool 3d effect. Adds rectangle size based on mouse location. Adds nicer colors. Adds random deletion of rectangles so the composition changes even after the screen fills up.

rotorect.py

Draws a rotating rectangle that follows the mouse. Uses some fancy but standard math and a handful of special case shortcuts to make the rotation.

Program does not actually draw trails. I just did that for the picture so you could see that "it rotates" better.

factorial.py

Classic introduction to recursion. A function that calls itself. The mathematical calculation of factorial. The factorial of a number is that number multiplied by all the numbers before it. The notation for factorial is an exlamation point.

5! = 5 * 4 * 3 * 2 * 1 = 120

You could do this with a loop:

x = 5
i = 1
for n in range(x,1,-1):
   i = i * n
print i
				
but notice since 4! = 4 * 3 * 2 * 1 and 5! = 5 * (4 * 3 * 2 * 1) that 5! = 5 * 4!. Factorial is defined in terms of itself. n! = n * (n-1)! You just have to stop when n = 1 so you don't accidentally multiply a good number by zero.

Recursion - a function calling itself - can give some problems elegant solutions. Since one of the characteristics of fractals is self-similarity at different scales, recursive algorithms form the backbone of many graphical fractal generators.

quartererer.py

Recursive quartering. Divides the screen into quarters. Takes each of those quarters and divides it into quarters. Takes each of those quarters and... Recursion is a cool and even sometimes useful programming concept.

quartererer-fancycolors.py

Same as quartererer.py with color overlay effect to build white color.

buttontest.py

The program isn't much to talk of. It's just anothe little paint program. What is important about it is that it is the testbed for control widgets, two kinds of button and a slider, written as Python classes descending from Pygame's Rect class.

Button_Momentary - a command button. "When this button is pressed, do something."

Button_Toggle - a button that can be on or off. "When this button is pressed it changes state."

Button_Slider - a control with a movable thumb to set a value between a given min and max.

Controls - an invisible container for all your controls. Add buttons and sliders to this and it manages them. Sort of. In order to make stuff happen you need to add some code to your Pygame event handler and the mechanism for retrieving slider values goes totally around this.

The important parts are globals for the sliders at the top "sld_r" etc. All the class definitions. Controls are actually created in init(). Control activity is managed in eventhandler() as part of mouse behaviour. Slider values are used in run() on the pygame.draw.rect() line. Note that in the code here, I don't consume mouse events if a control reacts to them, feel free to in your own code. Let me check to see if there is a way to know... no, I think control handling only returns an interesting value if an important action happened.

I need to package the buttons into their own self contained library. I need to write better documentation for how to use them. I need to change some details of how they are used.

You can do a lot with just sticking data in a list. And with tuples you can store an X and a Y coordinate for each entry in list. But what if you wnat to store a color? Or a color, name, shape, speed, acceleration, spin, favourite ice cream flavour and birthday all for one single thing? Then you need structured storage. Here are a number of different solutions to the problem.

The program creates a number of balls at the top of the screen. Each ball is colored a shade of gray depending on its horizontal position. Each ball is given a random speed down the screen. Then all the balls move until the pile up at the bottom. They group into rows of balls with the same speed. It looks doofy but looking cool isn't the idea ;-).

Use whichever approach makes the most sense to you depending on your personal needs. For my BAC workshop students, I recommend sd_classempty.py. It means learning a little bit more than "necessary" but offers the best compromise between learning curve, legibility and robustness.

sd_manylists.py

Hardly even worth calling structured data as the data isn't really structures. This is maybe the easiest for the beginner to understand as it just repeats stuff that is already known. Just keep a list for each attribute. Ball location in one list, color in another and speed in a third. Where is the fourth ball? ball[3]. What color is it? color[3]. See, easy. But cumbersome too and easy to let mistakes creep in.

sd_listperball.py

Since you can stuff anything you want to in a list - [[ 23,352], [255,0,0], 8] - including other lists, why not make the data for each ball into a list and then make a list of those little lists.

balls = 
   [
      [ [23,352], [255,0,0],     8]   # ball 0
      [ [75,94],  [255,255,0],   8]   # ball 1
      [ [2,86],   [255,0,255],   8]   # ball 2
      [ [329,12], [255,255,255], 8]   # ball 3
      [ [67,102], [0,128,0],     8]   # ball 4
   ]
				
What data represents the fourth ball? balls[3]; [[329,12], [255,255,255], 8]. What is its position? The first little sub list, balls[3][0]; [329,12]. It's vertical position? balls[3][0][1]. It's straight forward, all you have to do is keep a nice map around (something like [[x,y],[r,g,b],speed]) and be able to count. Starting at zero ;-). But it makes the code hard to read. Let me pull a couple lines out of the program...
if (ball[0][1] + ball[2]) < height:
   ball[0][1] = ball[0][1] + ball[2]
   number_moved = number_moved + 1
pygame.draw.circle(screen,ball[1],ball[0],size,0)
				
Lots of subscripts but what do they all mean?

sd_dictionary.py

This is a Python special sauce solution. Instead of hard to read subscripts you get to use a "dictionary" which is a "key:value" pair. They key gets to have a real name. Each ball becomes a little dictionary with values that correspond to real names. Then as before they all get put in a long list. The fourth ball? It is still balls[3]. But it looks like this...

{
   "loc" : [i,size],
   "color" : [c,c,c],
   "speed" : r.randint(1,maxspeed)
}
				
So those lines of code look like...
if (ball["loc"][1] + ball["speed"]) < height:
   ball["loc"][1] = ball["loc"][1] + ball["speed"]
   number_moved = number_moved + 1
pygame.draw.circle(screen,ball["color"],ball["loc"],size,0)
				
Oh, if the ball's location plus its speed is less than height... it's all so clear now.

sd_classempty.py

Now we're getting fancy. This is actually the first solution that I would consider to really be "structured data". But maybe that's just nit picking. Python supports object oriented programming techniques and in order to do that it supports classes. But it is also a run-time interpretted language without bound data types (hunh? whatever.) it lets you get away with creating an empty class and just sticking whatever you want to in it and then asking for it later. This would get you in trouble with a lot of other languages, but Python's cool with it and...

What I like about it for the beginner is that the data stays together in a nice bundle, that data is easy to get to, data access if very legible, it starts to introduce the important idea of classes BUT it doesn't burden the beginner with full class implementation issues. You can just look at the prototype here for how to set up the data and duplicate it without too much "wait, what's going on here" confusion.

Again, all the balls go in a list. The fourth ball is still balls[3]. The data that belongs to ball[3] is referenced with a dot and its name. Where is ball[3]? ball[3].loc. What is its color? ball[3].color.

if (ball.loc[1] + ball.speed) < height:
   ball.loc[1] = ball.loc[1] + ball.speed
   number_moved = number_moved + 1
pygame.draw.circle(screen,ball.color,ball.loc,size,0)
				
So, there's a few ways to go about creating your own "things" to store and manipulate. If you have the time look at them all. If you are in a hurry and writing something relatively simple just grab sd_classempty.py and work with that. If you are writing a larger more complex (more "real") program then maybe you should take the time to look at sd_classbetter.py and do the extra research for classes and object oriented programming. If all this programming stuff is making your head hurt and you aren't really sure what you have been doing up to this point, maybe you want to stick with sd_manylists.py.

sd_class.py

Based on sd_classempty.py, this adds just a little bit of rigor by defining a constructor for the class and setting the data fields properly. This takes five lines of code in the middle of the real part of the program and turns it into one tidy call to __init()__ like this... balls.append(Ball([i,size],[c,c,c],r.randint(1,maxspeed))) and lets __init()__ sort out the job of putting the numbers into the right data fields. Also by packaging the ball initialization code into one place it makes it a bit more robust. The fields are accessed the same way as before: ball[3].color.

sd_classbetter.py

This is based on sd_class.py, but it starts to use the class in a more proper object oriented manner. Classes don't just define data fields, they also define the behaviour (methods) that the data can do. So rather than the main program trying to move balls according to some rule set, it just asks the ball "hey, could you move yourself please?" The code looks like this...

for ball in balls:
   if ball.step():
      number_moved = number_moved + 1
   ball.draw()
				
Sure, this is getting fancy and depending on what you are doing may be more than you need to think about or it may be what you really need to learn. Thinking about programming problems in an object oriented way can be very helpful. See here how the code looks like the problem it is trying to solve? Go to each ball and ask it to move, if it moved increment a tally, now have the ball draw. How does the ball move? Don't know, don't care. That's left up to the ball in ball.step() which might look familiar...
if self.loc[1] + self.speed < height:
	self.loc[1] = self.loc[1] + self.speed
	took_it = True