associação pythonbrasil[11] django zope/plone planet Início Logado como (Entrar)

AprendendoComPygame

Índice

Capítulo 0

Introdução

  • Por quê?
  • O que
  • Ferramentas
  • Lugar

Capítulo 1

Interaja

  • Interpretador
  • Nomes
  • Compare
  • Controle
  • Loops
  • Funções
  • Listas
  • Ajuda
  • Exercícios

Capítulo 2

Módulos

  • Import
  • Random
  • Namespaces
  • Funções
  • Classes
  • Exercícios

Capítulo 3

Demo

  • Bola
  • Demo
  • Exercícios

Capítulo 4

Pong

  • Paddle
  • Ball
  • Game
  • Tweak
  • Exercises

Capítulo 5

Frog

  • Hop Hop
  • Sploosh
  • Vroom
  • Rolling
  • Variation
  • Exercises

Licença

  • GNU Free Documentation License


Prefácio

O objetivo deste livro é introduzir a programação de computadores a qualquer pessoa que esteja realmente iniciando.

Dito isso, faz bastante tempo desde que eu próprio aprendi muitas destas idéias. Se algo não lhe parecer claro, ou se você achar que estou explicando o que é óbvio, por favor deixe-me saber para que eu possa corrigir o problema para os próximos leitores.

Outra coisa, ao ler este livro, tenha isso em mente:

  • Você não pode aprender a programar lendo um livro. Você precisa escrever código, um monte de código.

Então, conforme for lendo, teste os exemplos, mas se o livro lhe inspirar uma idéia, não hesite e pratique-a. Modifique os exemplos e veja no que dá.

Brinque, se divirta, veja o que você pode fazer, então volte e leia um pouco mais. Esta é a melhor maneira de aprender.


Capítulo 0

Introdução

Por quê?

O computador é uma ferramenta muito versátil e poderosa.

A coisa realmente mais interessante sobre computadores é que eles são programáveis. Isso significa que o computador pode fazer muitas coisas diferentes por carregar e seguir diferentes conjuntos de instruções, chamados programas.

  • abiword
  • gimp
  • gnumeric
  • sh
  • bzflag

Existem muitos programas disponíveis prontos para usar, o que não requer nenhuma programação de sua parte para instalá-los no seu computador ou usá-los, mas às vezes os programas disponíveis não são extamente o que você quer.

O ato de criar novos programas ou modificar os que já existem para que atendam melhoras suas necessidades é chamado programação.

O que é?

Instruções

Se um computador fosse um jogo de tabuleiro, um programa seria as instruções para o jogo. O programa diz ao computador como começar, o que fazer e certas situações particulares e quando parar.

Esta não é uma analogia perfeita, é claro, mas vai nos ajudar bem a começar.

Um maneira em que um programa é como um jogo de tabuleiro é que cada jogador precisa esperar a sua vez.

Às vezes, quando um complexo programa de computador está rodando, pode parecer que há todo um mundo ao redor dele, tudo se movendo simultaneamente, mas isso é apenas uma ilusão.

O computador pode processar apenas uma instrução por vez.

No entanto, ele pode processar as instruções tão rapidamente que para nós ele parece simultâneo e quase vivo.

Ferramentas

Antes de poder começar a programar, você precisa instalar certas ferramentas.

Computador

  • O seu computador não precisa ser o último nem o mais rápido, mas eu recomendo que você use um sistema operacional amigável para programar, como linux ou BSD. Todos os exemplos neste livro vão rodar perfeitamente no Windows, MacOS, Linux, ou BSD.

Compilador ou interpretador

  • Existem centenas de diferentes linguagens de programação disponíveis atualmente. Neste livro nós vamos iniciar com python. Esta linguagem é livre (pode ser usada livremente sem custos) e está disponível para uma ampla variedade de sistemas operacionais. (Download).

Editor

  • Um editor de textos é diferente de um processador de textos. Não tente editar os seus programas com o Word. Isso é possível, mas é difícil. O python já vem com um editor chamado IDLE, mas você pode querer testar outros editores também. Em sistemas parecidos com unix, como o Linux, eu recomendo o NEdit ou o Kate. No Windows você querer simplesmente usar o notepad por enquanto, mas você realente quiser programar você pode querer algo bem melhor. Algumas pessoas recomendam o SciTE para windows. No MacOS, o BBEdit serve bem.

As próximas duas coisas não são essenciais para a programação em geral, mas nós vamos usá-las para começar:

Pygame

  • Para fazer o aprendizado de programação mais divertido, nós vamos usar gráficos. Programas gráficos geralmente são considerados um tópico mais avançado, mas com as ferramentas apropriadas isso não é muito difícil, e vai nos ajudar a fazer as coisas ficarem mais claras. Pygame (http://pygame.org) é um conjunto de bibliotecas python que precisa ser instalado em seu sistema para ajudar com a parte gráfica.

pygsear

  • Como um suplemento para o Pygame, pegue os arquivos que eu fiz para ajudar as pessoas a começar a programar. Eu os chamo pygsear (http://www.nongnu.org/pygsear/). Pegue o arquivo com o maior número de versão da página, seja em .zip ou em .tar.gz, o que for mais fácil para você descompactar. O arquivo que você precisa é algo como pygsear-0.52.tar.gz ou pygsear-0.52.zip.

Lugar

  • Isso pode significar um abiente de trabalho quieto, onde ninguém vá interrompê-lo constantemente enquanto você está tentando pensar. Isso é importante, visto que programar requer pensar um tanto. No entanto, mais importante ainda é um lugar de trabalho no computador -- algum lugar seguro para colocar seus arquivos onde ninguém vá acidentalmente apagá-los -- algum lugar onde você possa organizar as coisas de uma forma que faça sentido para você. Crie uma pasta para trabalhar nela. dentro desta pasta, descompacte a biblioteca pygsear que você baixou. Isso deve criar uma nova pasta chamada como pygsear-0.47.

Teste o ambiente:

  • Para ver se tudo está funcionando bem, tente rodar o programa de teste. Isso deve ser fácil como clicar no arquivo test.py dentro da pasta pygsear. Se isso não funcionar, você deve abrir um console (janela de terminal modo texto), mudar para o diretório onde foi descompactado o pygsear e digitar:

 python test.py 

  • Próximo passo, tenha certeza de que você possa usar seu editor com os arquivos neste diretório, e também salvá-los ali. Se tudo está funcionando, nós estamos prontos para começar a programar!

Não está funcionando!

  • Se algo não está funcionando corretamente -- seja o programa de teste que não está iniciando, ou você não consegue entender como usar o editor de textos -- você pode precisar pedir a ajuda de alguém para deixar tudo funcionando nesta primeira vez. Quando você pedir ajuda à alguém, veja o que ele faz e pergunte se você não entender!


Capítulo 1

Interaja

Interpretador

  • Eventualmente, seu program vai parecer como outros programas. Ele vai ter um ícone onde as pessoas cliquem, o que fará rodar. Mas por enquanto, nós vamos trabalhar na linha de comando -- o modo interativo.

Nota: Se você está trabalhando em um sistema com um terminal de texto decente (BSD, Linux, etc) você pode querer usá-lo em vez do arquivo interact.py para estas sessões interativas. Simplesmente abra um terminal no diretório de exemplo, digite python e então digite from penguin import *. Depois disso, o efeito deve ser o mesmo de usar o arquivo interact.py, com a diferença de que você vai ter mais recursos de edição de texto. O interpretador interativo é uma maneira para digitar programas em python e obter os resultados imediatamente. Olhe no diretório de exemplos do pygsear e rode o programa chamado interact.py. Você deve ver uma nova janela com um pequeno pinguim no meio da tela. O >>> no canto inferior esquerdo é chamado de prompt. Ele é o interpretador Python esperando que você o diga o que fazer.

Vamos começar com algo chamado 'gráficos tartaruga'. Estranhamente, nossa tartaruga vai se parecer com um pinguim. O pingüim é chamado pete. Vamos fazer com que pete desenhe uma linha. Digite isso e pressione [ENTER]:

pete.forward(100)

Este comando é uma chamada de função ou de método. Ele diz para o pete andar 100 pixels para a frente. Você pode colocar qualquer número dentro dos parentêses (mesmo números negativos), mas tenha em mente que a tela onde pete está andando tem provavelmente apenas 600 pixels de largura e 500 pixels de altura.

Nós podemos também fazer pete se virar:

pete.right(30)

pete entende como se virar em graus, então quando nós dizemos para pete right(30), ele vira 30 graus para sua direita. Note que a quantidade é relativa à onde pete está olhando quando você o diz para se virar. Isso significa que se você dizer pete.right(30) de novo, pete vai se virar um pouco mais para a direita, e se você faz pete.right(30) mais uma vez ele vai ter virado um total de 90 graus e vai estar olhando para o lado direito da tela.

Agora zere o gráfico por digitar: reset()

Veja se vcê consegue fazer pete andar em volta de um quadrado. Como você pode fazer isso?

Algo como isso:

Primeiro, vá para a frente novamente. Note que usando as setas do teclado para cima e para baixo você pode recuperar as linhas que você digitou anteriormente e poupar um pouco de digitação. Então aperte a seta para cima até que a linha seja novamente pete.forward(100) e aperte [ENTER].

É claro que um quadrado são justamente quatro lados iguais, então faça com que pete se vire 90 graus para a direita: pete.right(90)

Agora a maneira mais simples de continuar é simplesmente continuar a seta para cima duas vezes e apertar [ENTER] até que pete esteja onde ele começou. Lembre-se, se você fizer uma besteira, ou as coisa ficarem confusas, não se preocupe. Apenas digite: reset() pete vai voltar para onde ele iniciou e limpar a tela no processo. Você também pode usar home() para levar pete de volta para onde ele começou sem mudar nada na tela, ou cls() para apagar tudo sem fazer com pete se mova.

Nomes

Enquanto programa você vai precisar achar nomes para muitas coisas diferentes que você criar. Esses nomes são às vezes chamados variáveis ou identificadores. Por exemplo, você pode dar um nome para o número 50:

comprimento = 50 A caixa acima representa um objeto do tipo inteiro ao qual foi dado o nome 'comprimento'. Essa idéia pode parecer ridícula, mas imagine um número que é usado várias vezes no seu código, e subitamente você percebe que precisa mudar o 50 por 75. Você pode fazer isso:

   1 forward(50)
   2 right(90)
   3 forward(50)
   4 right(90)
   5 forward(50)
   6 right(90)
   7 forward(50)
   8 right(90)

Você pode então usar a função de procurar/substituir no seu editor para mudar todos os 50s para 75s, mas existe uma grande chance de você deixar passar algum. Ainda pior, se o código for parte de uma seção maior, você pode acidentalmente mudar um 50 que não tem nada a ver com essa mudança.

This is much better:

   1 length = 50
   2 
   3 forward(length)
   4 right(90)
   5 forward(length)
   6 right(90)
   7 forward(length)
   8 right(90)
   9 forward(length)
  10 right(90)

So, if you needed to change the size of your squares from 50 to 75, instead of having to go through and change all of the 50s to 75s, you can just change length = 50 to length = 75 and instantly make all of the needed changes.

  • This picture shows the name length being taken from the 50 object and placed on the 75 object.

What Was That Again?

Once you have created variables, you will want to be able to see and to use the values they are holding on to. In the interactive session, you can just mention the name again, and the interpreter will tell you what it is.

Like this: >>> car = 'Ford Fairlane' >>> weight = 1700 >>> length = 23.4 >>> has_trunk = True >>> car 'Ford Fairlane' >>> weight 1700 >>> length 23.399999999999999 >>> has_trunk True

Notice here that there are a few different types of values that your variable names can be attached to.

'Ford Fairlane' is a string -- a series of characters enclosed in quotes. 1700 is an integer with nothing after the decimal point. 23.4 is called a floating point number -- it can have a fractional value, and notice that the value stored may not be exactly what you specified. True and False are available as boolean values.

Nicknames

When we used the calls pete.forward() and pete.right() to make a square, the words forward and right are names for different methods that pete the Penguin object understands.

Sometimes it is helpful to have more than one name for things. For instance, if you get tired of typing out pete.forward all the time, you could do:

forward = pete.forward

and then even:

fd = forward

(Note that there are no parenthesis at the end of any of the names.)

Now, instead of using pete.forward(20), you could just say fd(20)

Of course, forward(20) and pete.forward(20) still work too. An object can have as many names or aliases as you would like to give it.

  • Here the method object pete.forward has been given many different names: forward, fd and foo.

Good Names

Making short names -- like using fd for pete.forward -- can save you a lot of time and typing when working in the interactive interpreter.

However, when you start creating longer programs, you need to balance two things: You do not want to not type too much, but also you need to be able to remember what a particular name refers to.

Generally, unless a name will only be mentioned in one small area, you should not use names like:

a i q

since it is just too difficult to understand what they mean.

Instead, think about what the names refer to, and use names like:

number_of_apples appleindex quitAppleGame

Notice that there are a few different styles used to name things.

You should read through some other people's code and choose a style that you like, then try to use the same style consistently in all of your code.

A Little Math

Sometimes you do not know exactly what you want to store in a variable, but you could calculate it from other values that you do know. Just use mathematical symbols, and Python will do the calculations for you.

Like this: >>> price = 125 >>> tax_rate = 0.08 >>> tax = price * tax_rate >>> total = price + tax >>> total 135.0 >>> shares = 4 >>> total_per_share = total / shares >>> total_per_share 33.75

So, to multiply two numbers, use the *

Division uses the /

Compare

You know how to give objects names, but you will also need to know how to tell if one thing is the same as another, or if it is equal, greater or less than another.

To do this, we use comparison operators.

The most common comparison is equality:

this == 5

Note the difference between these two lines:

this = 4 this == 5

The first line is an assignment. It uses 1 equals sign (=) and it says "Set the variable this equal to 4." This is naming, like we talked about on the last page.

The second line is a comparison. It uses 2 equals signs (==) and it asks a question: "Is the variable this equal to 5?"

Try these lines in the Python interpreter:

this = 5 assignment that = 10 assignment this == that True or False? this = that assignment this == that True or False?

If this is equal to that then Python will return True (1) and if not, then Python returns False (0).

Some other common comparisons:

3 < 5 is less than True 9 > 8 is greater than True 1 != 0 is not equal to True 5 is not None is not True

The last example introduces two new ideas:

First, None is a special object you can use to mean "no value" or "unknown" or "not set" or "null". It took me a long time to really understand when to use None and the best way is just to read other people's code and see how they use it.

Second, the comparison is a bit different, as it does not compare the values of the two objects, but their actual identities.

In Python, every object has a unique id number which you can get by using the builtin id() function.

So, what the last line actually means is:

   1 id(5) != id(None)

We use is or is not because it is quicker and easier to read.

This comparison of identity also implies that two objects can be equal even if they are not identical.

For instance:

x1 = 3.2 3.2 is not an integer, it is a floating point number. x2 = 1.8 1.8 is also a float. x_total = x1 + x2 Adding floats always returns a float. x_total == 5 equals True x_total is 5 is False

Control

Now that you can make comparisons, you can control the flow of your programs.

Start up the interact.py program again, and try this:

   1 choice = 'blast'
   2 if choice == 'blast':
   3     pete.blast()

The drawing on the right is a flow chart and it represents the code on the left. You can follow the flow by starting at the top, following the lines, and acting on the instructions in the boxes.

In the code, notice how the line after the if statement is indented.

Indentation is how Python knows those statements are all run only if the condition is True. Like this:

   1 if choice == 'blast':
   2     pete.blast()
   3     pete.blast()
   4     pete.blast()
   5 
   6 pete.write(choice)      

So the three blast()s will only happen if the choice is 'blast', but the write will happen no matter what.

Sometimes, you will want to make a choice between two things:

It can help to make this more clear by reading the word else as "otherwise".

   1 choice = 'blast'
   2 if choice == 'blast':
   3     pete.blast()
   4 else:
   5     pete.reset()

There are only two possibilities here, and your program can only flow down one of these two branches.

Other times, you will want to have multiple branches in your flow.

Here, the word elif can be read as "else if" or "otherwise if"

   1 choice = 'tree'
   2 if choice == 'blast':
   3     pete.blast()
   4 elif choice == 'tree':
   5     pete.tree()
   6 else:
   7     pete.reset()

You can have as many elif sections as you need. Once any one of the if or elif conditions is True, that section will run and all other elif or else sections will be skipped.

else is not required, and will only run if none of the preceeding if or elif conditions were True.

Loops

We made a square by performing the same two steps repeatedly -- once for each side of the square:

   1 forward(100)
   2 right(90)

It made it a bit easier that we could use the up-arrow to get back things we typed previously and so save ourselves some typing, but it was still easy to make a mistake and turn our square in to something not exactly what we planned.

There must be a simpler way, and there is.

Computers are great at repeating things over and over again without ever making any mistakes. We just need to know how to tell the computer what to do.

for

One way to tell a computer to repeat a set of steps is called a loop.

Here is how we tell pete to make a square using a loop:

   1 for side in 1, 2, 3, 4:
   2     forward(100)
   3     right(90)

Go ahead and type in the first line for side in 1, 2, 3, 4: and hit [ENTER]

Notice that the interpreter does not come back with the >>> prompt this time. Instead it shows ...

This means it is waiting for more input before it can get started. You are saying "I want you to do something 4 times", and the computer comes back with "Ok. What should I do 4 times?"

In Python, the way we tell the computer which steps are part of the loop is by indenting.

It is sort of like an outline.

Use 4 spaces for each level of indentation. Go ahead now and hit 4 spaces, then type in the second line of the loop forward(100) and hit [ENTER]

The computer comes back with ... again.

It does not know if you are finished telling it what to do 4 times, or if there are more steps inside of the loop.

Turns out there is another step in the loop. Hit 4 more spaces, then type in the last line right(90) and hit [ENTER]

Again, the computer comes back with ... but this time we are finished with the steps in the loop.

To finish the loop, hit [ENTER] again.

pete should, very quickly, draw a square for you.

This loop is so simple that we can "unroll" the loop and take a look at exactly what is happening...

   1 for side in 1, 2, 3, 4:
   2     forward(100)
   3     right(90)
   4         side = 1
   5 forward(100)
   6 right(90)
   7 
   8 side = 2
   9 forward(100)
  10 right(90)
  11 
  12 side = 3
  13 forward(100)
  14 right(90)
  15 
  16 side = 4
  17 forward(100)
  18 right(90)

These two pieces of code are equivalent. The result will be exactly the same.

Two things to notice here.

First, the variable side is never used inside of the loop. You might have named it differently, and it would not make much difference. Other reasonable names might have been count or even i or x. You do have to be a bit careful though, as naming the variable forward would cause problems.

Second, although horizontal space is used to indicate which statements are part of the loop, vertical space is not meaningful. Use blank lines in your code to make it easier to read.

while

The for loop is best when you have a list of objects and you want to do something with each one of them.

Other times, you will want to continue looping until a particular condition is met. Like this:

   1 angle = 0
   2 per = 6
   3 
   4 while angle < 360:
   5     forward(10)
   6     right(per)
   7     angle += per        

Functions

Loops are useful for performing a set of steps repeatedly, but what if you want to repeat the entire loop over and over again -- maybe with different parameters.

For instance, maybe you want to draw a lot of squares all on different parts of the screen.

Many randomly placed squares

Using functions we can save a piece of code so that we can use it again later.

A function is sort of like a mini program inside of your program.

When we use the function, we say that we call it, almost like you might call the time service on your phone.

Functions can either return some value or object to your main program (like the current time), or they can perform some action (like setting the time) without returning a value. A function which does not return a value is sometimes called a procedure.

Let's make a function which, when called, will draw a square, starting wherever pete happens to be at the time:

   1 def square():
   2     for side in 1, 2, 3, 4:
   3         forward(100)
   4         right(90)

  • square is a function object.

Notice that the body of the function, just like the body of the loop before, is indented, and since there is a loop inside of our function, the body of the loop is indented again. The first indent should be 4 spaces, the second indent is 4 more for a total of 8 spaces.

To call our new function, use this:

square()

To see why we might want to make functions, try a combination of a loop which calls our function:

   1 for side in 1, 2, 3, 4:
   2     forward(125)
   3     square()
   4     left(90)            

Loop in progress...

If you had done that by hand, or tried to figure out how to put all those loops together, it would have taken a long time, and it would have been very easy to make a mistake. With functions, you can think in terms of larger pieces. This loop says: "Set side equal to 1, go forward, make a square, turn left, set side equal to 2, go forward, etc..."

Remember that "Set side equal to 1" has nothing to do with the length of the side. It is just counting.

Parameters

square() is fine as it is. It makes a square on the screen just as you asked it to, but what if all your squares are not the same size?

Let's make a function which is a bit more general purpose:

   1 def generalSquare(length):
   2     for side in 1, 2, 3, 4:
   3         forward(length)
   4         right(90)

  • generalSquare takes one parameter -- the length

This time, our function takes a parameter (also called an argument) the length of a side of the square, and when moving forward, instead of using the value 100, it uses the value of the length.

Now, to make a square with a side of length 20, we will call:

generalSquare(20)

When this function gets called, the variable length is given the value we put in the parentheses. Then any time python sees length in the body of the function, it substitutes that value.

This generalized square method is so useful, that it is built-in to all Penguin objects. In interact.py you can use it like this:

pete.square(20)

In fact, there is also another type of square built-in which is centered on the penguin's present location:

pete.cSquare(50)

will draw a 50-pixel square with pete right in the middle.

How might we use the squares? Let's try this:

   1 width = 5
   2 
   3 while width < 200:
   4     set_color('random')
   5     pete.square(width)
   6     width += 2
   7     left(65)

That's pretty neat. Let's make that in to a function too...

   1 def flow(angle, grow=2):
   2     width = 5
   3 
   4     while width < 200:
   5         set_color('random')
   6         pete.square(width)
   7         width += grow
   8         left(angle)
  • square, turn, square, turn, square, etc...

As you can see, functions can also take more than one parameter. flow takes two parameters: the angle to turn between squares, and the amount to grow between squares. Also, grow uses a default value of 2.

So, we can call flow with 2 parameters:

flow(4, 1)

or, if the default value is ok, then we can call it with just the one parameter:

flow(14)

and it will be the same as if we had called flow(14, 2)

More General

All of these squares might make you wonder if you can draw some other shape. Maybe you want to make an even more general function. Try this:

   1 def lineTurn(length, angle):
   2     for side in 1, 2, 3, 4:
   3         forward(length)
   4         right(angle)
  • Two parameters, no default values.

and call it like this:

   1 lineTurn(150, 90)
   2 lineTurn(200, 120)
   3 lineTurn(50, 72)

Notice that I did not give it a name that mentions square at all, since what lineTurn() makes will not always be a square.

Give those three function calls a try and see what you get.

The first call is fine. It is just as if we called generalSquare(150). The second call succeeds in making a triangle, but it is a bit wasteful as it draws over one of the sides twice.

What happens on the last call though? It gets most of the way through a pentagon and stops.

It is close to being a complete pentagon... You could finish it up by just typing forward(50) but it would be nice if the function could do that on its own.

In the next section, we will do just that.

Lists

In the loops in previous examples, we used code like this:

   1 for x in 1, 2, 3, 4:

The series of numbers 1, 2, 3, 4 is a called a sequence, and loops can iterate over the members of any sequence.

What this code means is: "set x = 1 and do the following things, then set x = 2, and do these things again", etc, until the end of the sequence.

A sequence like this will often be written either in square brackets [1, 2, 3, 4] (called a list) or in parentheses (1, 2, 3, 4) (a tuple).

The difference between these is a bit subtle.

A list can be changed after it is constructed (we say that it is mutable), whereas a tuple cannot be changed (immutable).

The differences will become more clear later, but I will use the word "list" for now to keep things simpler.

Create

It is very common to give the list a name instead of typing the list itself in to a loop statement. When creating a list like this, brackets are required.

You can make an empty list:

empty_list = []

or if you know exactly what needs to be in the list, just go ahead and fill it up:

   1 sides = ['side one', 'side two', 'side three', 'side four']

Sometimes, especially if the elements in the list are long, it looks better to break it up in to multiple lines. As long as you are inside the brackets, that is fine:

   1 verbose_sides = [
   2   'The side known as "side one"',
   3   'This one is side two',
   4   'Another side, called "side three"',
   5   'The final side, or side four'
   6 ]

Mutate

Lists are mutable, but what exactly does that mean?

Lists are objects with special methods built in to make it easy to add, remove, sort, and find things that are in the list.

For instance, if you did not know beforehand what words you wanted to print around the square, you could add them to the list as you found out:

   1 from pygsear.Widget import Dialog_LineInput
   2 sides = []
   3 for side in 'first', 'second', 'third', 'fourth':
   4     message = 'Enter the name of the %s side' % side
   5     namegetter = Dialog_LineInput(message=message)
   6     name = namegetter.modal()
   7     sides.append(name)

Iterate

In this case, the members in the list sides are not numbers, but text (called strings), so we could not use them in math formulas, but we could use them like this:

   1 for word in sides:
   2     write(word)
   3     forward(180)
   4     right(90)

Or, more generally:

   1 def messageSquare(messages=['one', 'two', 'three', 'four']):
   2     for word in messages:
   3         write(word)
   4         forward(180)
   5         right(90)

The function messageSquare() takes one parameter, and if called without a value it will use the default argument ['one', 'two', 'three', 'four'].

In other words, a call to:

messageSquare()

is the same as if you called it:

messageSquare(['one', 'two', 'three', 'four'])

You can also call it with a totally different list:

messageSquare(['penguins', 'like', 'to eat', 'herring'])

or with a list created before:

messageSquare(sides)

Range

The function messageSquare() has a problem similar to the problem with the function at the end of the last page. If you pass in a sequence which is longer or shorter than 4 elements, it is probably not going to do what you want.

Try it now. If the sequence is less than 4 elements, it will not make a complete square. If the sequence is more than 4 elements, it will write over the words on previous sides.

   1 reset()
   2 messageSquare(['one', 'side', 'short'])
   3 reset()
   4 messageSquare(['one', 'side', 'too', 'many', 'here'])

To get around this problem, we need to figure out what angle to turn from the number of sides we want the shape to have.

Using the built-in function range() we can make a very general function to draw polygons:

   1 def poly(sides, length):
   2     angle = 360.0 / sides
   3     for side in range(sides):
   4         forward(length)
   5         right(angle)

The function range() takes a number, and returns a list with that many elements.

That means: if sides=5 then range(sides) is the same as range(5) and it returns [0, 1, 2, 3, 4]

So the function call poly(5, 10) will draw a five-sided figure with sides of length 10.

Now we can make almost any sort of regular polygon by giving poly() the number of sides and the length of each side:

   1 poly(5, 20)
   2 poly(6, 30)
   3 poly(100, 3)

The last one will probably be indistinguishable from a circle. In fact, if pete did not have a built in circle() method, that would be a good way to simulate a circle.

Help

We will come back to functions and sequences in the next chapter, but first I want to bring up one more thing about the interactive interpreter:

Notice that by typing:

help

python will tell you how to get help.

Basically, it tells you to type help() with which you can enter the python help browser and get a lot of generally helpful information. Alternatively, you can put something inside the parentheses which you want to get help on. Try this: >>> help(range) Help on built-in function range:

range(...) range([start,] stop[, step]) -> list of integers

Return a list containing an arithmetic progression of integers. range(i, j) returns [i, i+1, i+2, ..., j-1]; start (!) defaults to 0. When step is given, it specifies the increment (or decrement). For example, range(4) returns [0, 1, 2, 3]. The end point is omitted! These are exactly the valid indices for a list of 4 elements.

You type in the first line, after the >>>

help(range)

and Python will tell you about the range function.

That is interesting. Let's try out the example they give:

range(4) returns: [0, 1, 2, 3]

Just like they said it would be. How about if we give range 2 numbers:

range(4, 10) returns: [4, 5, 6, 7, 8, 9]

Notice how the list returned does include the first number you give, but it does not include the last number you give.

Now try range with 3 arguments:

range(0, 100, 10) returns: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]

Very nice. I wonder if we can use this to make pete perform some even more amazing tricks?

Also notice that you can get help on pete himself!

Unfortunately, help(pete) only tells us that pete is an instance of the Penguin class. We can get help on pete and all of his kind with:

help(Penguin)

Exercises:

1. Play the practice.py game to get used to moving the penguin around. Read the comments at the start of the file to see how to play. (Hint)

2. You made pete move in a square. Now make him go in a rectangle that is twice as wide as it is high. (Hint)

3. Now see if you can make pete go in a triangle. (Hint)

4. Use a variable distance to make pete walk in a square or triangular spiral. (Hint)

5. Make two different loops -- one to draw a pentagon and one to draw a five-pointed star. (Hint)

6. Turn exercise 4 in to both a for loop and a while loop. (Hint)

7. Create functions for triangle and pentagon like we made for square. Make the functions take a parameter for the length of a side. (Hint)

8. Create a function which takes a sequence of strings and writes them around the edges of a polygon with one side for each string. (Hint)

9. Create a function which takes a sequence of strings and writes each one on a separate line, one above the next. (Hint)

Chapter 2 Modules

Import

At the top of most Python programs, you will see import statements like this:

   1 import random
   2 import os.path
   3 from math import sqrt
   4 from penguin import *

The import statement lets you quickly and easily load in python modules.

Modules are useful code that is already written -- either code which someone else wrote or your own code. Before starting any programming project, it is a good idea to look around and see if there is already a module which will do what you need.

Why would you want to import a module?

If you need to make your game behave randomly, you might want to choose from different attack strategies:

   1 import random
   2 strategies = ['aggressive', 'cautious', 'defensive']
   3 strategy = random.choice(strategies)

Or, if your game needs to know the size of a file:

   1 import os.path
   2 os.path.getsize('.')

Or, if you need to know the distance between two objects:

   1 from math import sqrt
   2 x0, y0 = (150, 75)
   3 x1, y1 = (275, 300)
   4 distance = sqrt((x0 - x1)**2 + (y0 - y1)**2)

This distance formula (the Pythagorean Theorem) is so useful that it is included in the math module:

   1 import math
   2 distance2 = math.hypot((x0 - x1), (y0 - y1))

You can see the values, and check if they are the same:

print distance, distance2, distance == distance2

Or, if you just want to use the penguin graphics module:

   1 from penguin import *
   2 pete.star()

Random

The random module is one which is used frequently in game code. The most common uses are random.randrange(), random.uniform(), random.choice() and random.shuffle().

I will summarize the use of these common functions here, but remember that you can also use the help() function to read more:

   1 import random
   2 from random import choice, shuffle
   3 help(random.randrange)
   4 help(random.uniform)
   5 help(choice)
   6 help(shuffle)

randrange

randrange() works somewhat like the range() function. It is used to return random integers.

To generate 5 random integers from 1 to 10, try this:

   1 for loop in range(5):
   2     print loop, random.randrange(1, 11)

Remember that range(5) is [0, 1, 2, 3, 4]. There are five elements in the list, but starting with 0. randrange() is the same way. It could return the first number, but never the last one. So, randrange(1, 11) will return one of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

shuffle

shuffle() will put the elements of a list in random order.

   1 bears = ['polar', 'grizzly', 'black', 'koala', 'panda']
   2 print bears
   3 random.shuffle(bears)
   4 print bears
   5 random.shuffle(bears)
   6 print bears
   7 random.shuffle(bears)
   8 print bears

choice

choice() will return a randomly selected element of any list. So random.randrange(1, 11) would be equivalent to random.choice(range(1, 11))

Also, choice() can work on lists of anything, like lists of strings:

   1 for choice_number in range(5):
   2     bear = random.choice(bears)
   3     print 'number', choice_number, 'is a', bear, 'bear'

Or it can even work on lists of penguins:

   1 from penguin import Penguin
   2 
   3 # First, make an empty list
   4 # and fill it with 10 penguins
   5 penguins = []
   6 for count in range(1, 11):
   7     print 'creating penguin number', count
   8     p = Penguin()
   9     p.moveTo('random')
  10     penguins.append(p)
  11 
  12 # Then make them explode randomly
  13 for explosion in range(50):
  14     penguin = random.choice(penguins)
  15     penguin.blast()

uniform

uniform() is for random numbers that are not integers:

   1 for loop in range(5):
   2     print random.uniform(0, 10)
   3     print random.uniform(-10, 0)

Similar to randrange() the result returned could be the first number, but it will never be the second number.

Namespaces

Notice that when you use

import random

You must access the functions in random by the full name:

random.randrange(1, 11)

The modules are said to have their own namespaces. This allows you to call on a particular piece of the module with no chance for any confusion. It is kind of like in a classroom where a teacher may need to call on "Joe Smith" or on "Joe Brown" in order to specify which Joe is being called.

from

If you only need one particular function from a module, you can import that function directly:

from random import randrange randrange(1, 11)

It is sort of like the kid in the class named "Ferdinand" who almost certainly will not have a classmate named Ferdinand, or just calling on "Joe" when Joe Smith is out sick for the day.

You probably noticed that we do something like this all the time to start up the penguin graphics mode:

from penguin import *

The * says to import everything from the penguin module.

To see exactly what is being imported, try this:

Restart Python, to get a fresh start, then:

   1 dir()
   2 from penguin import *
   3 dir()

Normally, it is considered bad style to import everything from a module. As you can see it may introduce a huge number of names in to the local namespace, but in this case the module is meant to be used on its own, and there is a definite advantage to being able to type "forward(50)" instead of "pete.forward(50)"

locals, globals, builtins

Python has 3 namespaces. The local namespace is inside of a function and is the first place checked to find a particular name. The global namespace is inside of a module, and is next in line for name resolution. Finally, the builtin namespace holds things which should be available everywhere, and is the last place searched for names.

Functions

The interactive python interpreter is very useful if you are testing and typing just a few lines, but anything more than that and you are going to want to save your code in a file.

Saving your code in a file that ends in .py makes it a Python module. Once you have a module, your code can be used from the interpreter, or by code in other files and programs.

The Python code that you save to a file will look much like the code you typed directly in to the interpreter. Try it now.

Create a new, blank file in your text editor. In that file, type these lines:

   1 from pygsear.Drawable import String
   2 
   3 def send(new_msg):
   4     m = String(message=new_msg, fontSize=80)
   5     m.center()
   6     m.udraw()

Save the file in the examples/ directory and call it message.py

import

When you try to import a module, Python will look first in the current directory. It looks for a file with the name of the module plus a .py ending.

Start Python in the examples/ directory, and import your new module:

import message

To use your new module, call the send() function with a text message:

message.send('Penguin Patrol!')

But there is a problem with this. What happens if you call the function again?

   1 message.send('Python & Pygame. Oh yea!')
   2 message.send('Take me to your leader')

global

One way to fix the problem of the new message writing over the previous one is to keep a handle on the old String object. That way when the function is called again, we can erase the old message and make a new one.

Add the highlighted lines to your message.py so it matches this:

   1 from pygsear.Drawable import String
   2 
   3 msg = None
   4 
   5 def send(new_msg):
   6     global msg
   7     if msg is not None:
   8         msg.uclear()
   9     m = String(message=new_msg, fontSize=80)
  10     m.center()
  11     m.udraw()
  12     msg = m

The variable msg is a module level or global variable. The first time through the function, msg will be None and we will skip the call to uclear()

On any other call, we first clear out the old message, then draw the new one and save it as msg

Using global variables, sort of like from foo import *, is usually considered bad form.

A much better solution is to use a class which we will work on next.

Classes

classes can also be loaded from modules.

A class defines a type of object.

Penguin, for example, is a class in the penguin module which we have already used extensively. To make a new instance of a Penguin, we first import the class, and then call it:

from penguin import Penguin pete = Penguin()

A class is sort of like a function which acts as a factory for creating objects. Each time you call the class, it returns an object of that class.

class Ball

Classes define data and methods which describe the different objects you want to use in your programs.

Let's start out with a very basic object, a ball.

   1 class Ball:
   2     def set_color(self, color):
   3         self.color = color
   4 
   5     def bounce(self):
   6         print 'boing!'

Put that code in to a file. Call it Ball.py

To create a ball, simply call your new class:

import Ball rb = Ball.Ball()

Keep in mind that this is all very abstract at this point, and this code is not going to show you a picture of a ball. This is just the idea of a ball. It is more beautiful, really, if you think about it.

If you really need to see a ball right now, here is a red one I made with the GIMP:

reload

Notice that we did not import your new class directly. It is import Ball and not from Ball import Ball. This makes it easier to change your module and see the changes.

If you want to make a change to your class -- like if you made a typing error -- you can just do this:

reload(Ball) rb = Ball.Ball()

color

The Ball class defines two methods: set_color() and bounce()

Methods are like functions, but they are bound to particular objects.

Once you create a ball, you can change its color:

rb.set_color('bright red')

Notice that with methods, unlike with functions, you do not pass in the first parameter (usually called self). The object itself is passed in automatically as the first parameter when the method is called from a class instance.

Also notice the difference between color and self.color in the set_color() method.

When you call rb.set_color('red') the variable self is bound to the same Ball instance that rb is bound to.

At the same time, the variable color is bound to the string 'red'.

Then with the code self.color = color the data attribute self.color is bound to that same string.

The set_color() method will create the attribute self.color if it does not exist, or if self.color already exists, it will just set it to the new value.

You can see what color the ball is by examining the attribute:

rb.color

get and set

It is a good idea to access data attributes through "getter" and "setter" methods, so we should create a get_color() method which just returns self.color

Using methods to access your object attributes allows you to keep the interfaces to the attributes the same, even if later the underlying implementation changes.

   1 class Ball:
   2     def set_color(self, color):
   3         self.color = color
   4 
   5     def get_color(self):
   6         return self.color
   7 
   8     def bounce(self):
   9         print 'boing!'

In other words, set_color() might have side effects other than just setting self.color -- it might check first to make sure that you do not try to set the color to orange, or you might decide later that the color would actually be better stored in a different way.

bounce

The other method in this class so far is bounce(). You can make the ball bounce by calling that method:

   1 rb.bounce()
   2 rb.bounce()
   3 rb.bounce()

All bounce() does is print a message to the console.

Using print statements like this can be a simple way to debug your class methods. You can print out various values at different times and see if they match up to what you think they should be.

Often you will get an error message or a weird value from the print, and you can start your investigation there.

init

One problem with the Ball class the way we have the code now is that if you try to check the color before you set it, you will get an error:

gb = Ball.Ball() gb.get_color()

To make sure that the Ball always has a color, we should include the special method init() to initialize the objects to a known state when they are created.

Adding init(), the class will now look like this:

   1 class Ball:
   2     def __init__(self, color='white'):
   3         self.set_color(color)
   4 
   5     def set_color(self, color):
   6         self.color = color
   7 
   8     def get_color(self):
   9         return self.color
  10 
  11     def bounce(self):
  12         print 'boing!'

The init() method is called automatically when you instantiate your class, and it can take parameters like any other function.

Remember that any time you make changes to your module you will need to either re-start the interpreter or else reload() the module.

reload(Ball) wb = Ball.Ball()

will create a ball with its color set to the default 'white', and

yb = Ball.Ball('yellow')

will make a 'yellow' ball.

Inheritance

The real power of classes emerges when you understand inheritance.

Inheritance means that a class can define a new object which is a type of another object.

For example, we might create a new object SuperBall which is a type of Ball:

   1 class SuperBall(Ball):
   2     def __init__(self):
   3         Ball.__init__(self, 'swirled')
   4         print 'SuperBall created!'
   5 
   6     def bounce(self):
   7         print 'boing, boing, boing'
   8         self.superbounce()
   9 
  10     def superbounce(self):
  11         print 'BOING!'

Add, Override, Extend

If SuperBall defined no methods at all, it would be just like Ball. What we want, though, is something that is like a Ball, but with some changes.

One thing we can do is add completely new methods.

The superbounce() method adds a capability which a normal Ball just does not have.

If your class defines a method which is also in its parent class, the new method will be called instead of the parent class method -- the new method overrides the parent method. In this case, The bounce() method is one which both Ball and SuperBall have.

Calling bounce() on a SuperBall overrides the normal Ball.bounce(). In this case, it prints out a message, and then calls the superbounce() method too.

Sometimes what you want is the parent class's behavior, plus something more.

The SuperBall.init() method starts out by calling Ball.init() to get the normal Ball initialization.

This is a common idiom in subclasses, and you should probably do it just like this until you have a better understanding of how classes and inheritance work.

Notice that since Ball.init() is called against the class Ball and not against an instance of class Ball you need to explicitly pass in the Ball instance (self) as the first parameter.

After that, SuperBall.init() also prints out an informative message. At this point, it could also set up any additional attributes which a SuperBall has, but which a Ball does not.

Exercises:

1. Make a function to have pete draw a line from one random point to another 100 times. (Hint)

2. Now, instead of drawing a line each time he moves, give the function a parameter for how many lines to skip between lines that he draws. (Hint)

3. Create a function that makes a list of the points on a polygon, then have pete draw lines among them randomly for a while. (Hint)

4. Put all of the functions you wrote in chapter 1 in to a file called firstFunctions.py so that you can easily load them and access them. (Hint)

Chapter 3 Demo

Ball

All of this abstract knowledge about classes is fine, but what we need is to see something real.

The pygsear modules provide classes which you can use as-is, or which you can subclass to make your own game objects.

Let's go back to the ball, but this time subclass pygsear.Drawable.Circle

   1 class WhoaBall
   2 
   3 from pygsear.Drawable import Circle
   4 
   5 class WhoaBall(Circle):
   6     def bounce(self):
   7         print 'boing!'

You can put WhoaBall in to Ball.py or start a new file. It is up to you. Just so long as you can find and import your new class:

   1 import Ball
   2 b = Ball.WhoaBall()

I kept the old bounce() method in there, just so you can see that this is still your ball, but you can also see that by inheriting from Circle we gain a lot of new functions.

For one thing, when you instantiated your WhoaBall another window should have opened up. If it did (and you did not get an error) you should now (finally) be able to see your ball:

   1 b.udraw()

Remember that if you want to see the methods available for an object, you can use:

   1 dir(b)

a better bounce

Now that we can actually see the ball, having bounce() just print out to the console is not so interesting. Let's see if we can make it more useful.

How about if we have the ball change direction if it tries to move off of the bottom of the screen? Let's do that:

   1 from pygsear.Drawable import Circle
   2 
   3 class WhoaBall(Circle):
   4     def bounce(self):
   5         if not self.onscreen(bottom=0, jail=1):
   6             self.path.vy = -self.path.vy
   7             print 'boing!'
   8 
   9     def move(self):
  10         self.bounce()
  11         Circle.move(self)

Circle already has a lot of built-in logic for how to move around on screen, which we want to leverage. So, when we want the ball to move, we need to call Circle.move() after we extend the method by calling bounce() first.

This onscreen() call is also inherited from Circle and it tells you if the ball has moved off of the bottom of the screen. The jail parameter tells it to move the ball back onscreen if it has moved off.

Ok, let's see:

   1 reload(Ball)
   2 b = Ball.WhoaBall()
   3 b.path.set_gravity(gy=400)
   4 b.runPath()

boing! boing! boing!

(Press ctrl-c to stop the bouncing.)

initialize

Now move those steps that set up the WhoaBall in to an init method:

   1 from pygsear.Drawable import Circle
   2 
   3 class WhoaBall(Circle):
   4     def __init__(self):
   5         Circle.__init__(self)
   6         self.path.set_gravity(gy=400)
   7 
   8     def bounce(self):
   9         if not self.onscreen(bottom=0, jail=1):
  10             self.path.vy = -self.path.vy
  11             print 'boing!'
  12 
  13     def move(self):
  14         self.bounce()
  15         Circle.move(self)

That makes it very easy to test your ball:

   1 reload(Ball)
   2 b = Ball.WhoaBall()
   3 b.runPath()

Now let's test for going off the sides of the screen so we can give the ball some sideways motion:

   1 from pygsear.Drawable import Circle
   2 
   3 class WhoaBall(Circle):
   4     def __init__(self):
   5         Circle.__init__(self)
   6         self.path.set_velocity(vx=150)
   7         self.path.set_gravity(gy=400)
   8 
   9     def walls(self):
  10         if not self.onscreen(left=0, right=0, jail=1):
  11             self.path.vx = -self.path.vx
  12 
  13     def bounce(self):
  14         if not self.onscreen(bottom=0, jail=1):
  15             self.path.vy = -self.path.vy
  16             print 'boing!'
  17 
  18     def move(self):
  19         self.bounce()
  20         self.walls()
  21         Circle.move(self)
  22 
  23 Here is a shorcut method to see your updated ball:
  24 
  25 reload(Ball)
  26 Ball.WhoaBall().runPath()

Demo

We got WhoaBall working pretty well, but it is still a hassle having to go in to the interpreter, import your module, make a ball, and run its path.

Let's use the pygsear.Game module to make this a bit easier:

   1 from pygsear.Game import Game
   2 
   3 from Ball import WhoaBall
   4 
   5 class BallGame(Game):
   6     def initialize(self):
   7         ball = WhoaBall()
   8         self.sprites.add(ball)
   9 
  10 if __name__ == '__main__':
  11     g = BallGame()
  12     g.mainloop()

Here we inherit from pygsear.Game.Game to get more functionality. Save this as BallGame.py and run the program:

python BallGame.py

Your code to set the game up goes in the initialize method.

The odd looking section at the bottom is an idiom which says "if this program is being run (and not just imported) do this:" It creates an instance of your game and starts the main loop.

Now let's make a few changes to WhoaBall so that we can improve BallGame too.

   1 class WhoaBall(Circle):
   2     def __init__(self, v=150):
   3         Circle.__init__(self, color='random')
   4         self.path.set_velocity(vx=v)
   5         self.path.set_gravity(gy=400)

I only show the method which needs to be changed.

This makes each WhoaBall a random color, and makes WhoaBall take a parameter to set the initial horizontal velocity.

Now your game can make a few balls each with different velocities:

   1 import random
   2 
   3 from pygsear.Game import Game
   4 
   5 from Ball import WhoaBall
   6 
   7 class BallGame(Game):
   8     def initialize(self):
   9         for b in range(5):
  10             vx = random.uniform(100, 200)
  11             ball = WhoaBall(vx)
  12             self.sprites.add(ball)
  13 
  14 if __name__ == '__main__':
  15     g = BallGame()
  16     g.mainloop()

One last change to WhoaBall and this will make a nice demo:

   1 class WhoaBall(Circle):
   2     def bounce(self):
   3         if not self.onscreen(bottom=0, jail=1):
   4             self.path.vy = -self.path.vy * 0.85
   5             if abs(self.path.vy) <= 20:
   6                 self.path.set_velocity(vy=-800)
   7                 print 'FLING!'

Again, I only show the method which needs to be changed.

This does two things:

First, on each bounce the velocity is cut to 85% of the previous bounce, to make the bounces decay more quickly.

Second, when the bounce goes below a certain height, the ball is re-energized and flinged back in to the air.

Here are the final versions of the two files. You can download the files by clicking on the link:

   1 # Ball.py
   2 from pygsear.Drawable import Circle
   3 
   4 class WhoaBall(Circle):
   5     def __init__(self, v=150):
   6         Circle.__init__(self, color='random')
   7         self.path.set_velocity(vx=v)
   8         self.path.set_gravity(gy=400)
   9 
  10     def walls(self):
  11         if not self.onscreen(left=0, right=0, jail=1):
  12             self.path.vx = -self.path.vx
  13 
  14     def bounce(self):
  15         if not self.onscreen(bottom=0, jail=1):
  16             self.path.vy = -self.path.vy * 0.85
  17             print 'boing!'
  18             if abs(self.path.vy) <= 20:
  19                 self.set_color('random')
  20                 self.path.set_velocity(vy=-800)
  21                 print 'FLING!'
  22 
  23     def move(self):
  24         self.bounce()
  25         self.walls()
  26         Circle.move(self) 
  27 
  28 # BallGame.py
  29 import random
  30 
  31 from pygsear.Game import Game
  32 
  33 from Ball import WhoaBall
  34 
  35 class BallGame(Game):
  36     def initialize(self):
  37         for b in range(5):
  38             vx = random.uniform(100, 200)
  39             ball = WhoaBall(vx)
  40             self.sprites.add(ball)
  41             
  42 if __name__ == '__main__':
  43         g = BallGame()
  44         g.mainloop()

Exercises:

1. Change the Ball class such that some balls are more bouncy than others. (Hint)

2. Remove the 'boing!' and 'FLING!' messages, and instead print a message when any 2 balls collide. (Hint)

Chapter 4 Pong

Paddle

We got a nice demo from WhoaBall and BallGame, but that is not really a game. Let's keep going and see if we can make this in to a real game.

Pong was the first real video game. Let's do that.

First, a paddle.

   1 from pygsear.Drawable import Rectangle
   2 
   3 class Paddle(Rectangle):
   4     def __init__(self):
   5         Rectangle.__init__(self, width=15, height=50)
   6         self.center(x=10)

You can put this and all of the pong code in to one file Pong.py

Your Paddle will need some control from the player.

To convert input from the keyboard in to motion of the paddle, we will use events. Events let you create functions which will be called whenever something external happens. We will use the up and down arrow keys to control to movement of the paddle.

In the Paddle we will set up the functions that need to be called, and a couple of flags which will remember the state of the controls.

   1 from pygsear.Drawable import Rectangle
   2 
   3 class Paddle(Rectangle):
   4     def __init__(self):
   5         Rectangle.__init__(self, width=15, height=50)
   6         self.center(x=10)
   7 
   8         self.up_pressed = 0
   9         self.down_pressed = 0
  10 
  11     def up(self):
  12         self.up_pressed = 1
  13 
  14     def noup(self):
  15         self.up_pressed = 0
  16 
  17     def down(self):
  18         self.down_pressed = 1
  19 
  20     def nodown(self):
  21         self.down_pressed = 0

We are going through a bit of a roundabout here because we want to do the right thing when the player pushes or holds down both the up and down controls at the same time. Instead of actually setting the velocity of the paddle each time a control is pressed, we change the state of a flag which tells which controls are being pressed.

We will change the velocity in the move() method.

   1 class Paddle(Rectangle):
   2     def set_vel(self):
   3         if self.up_pressed and not self.down_pressed:
   4             self.path.set_velocity(vy=-100)
   5         elif self.down_pressed and not self.up_pressed:
   6             self.path.set_velocity(vy=100)
   7         else:
   8             self.path.set_velocity(vy=0)
   9 
  10     def move(self):
  11         self.set_vel()
  12         Rectangle.move(self)

Also, we want the Paddle to be always onscreen:

   1 class Paddle(Rectangle):
   2     def move(self):
   3         self.set_vel()
   4         Rectangle.move(self)
   5         self.onscreen(top=-5, bottom=-5, jail=1)

This change restricts the motion of the paddle between 5 pixels from the top edge of the screen and 5 pixels from the bottom edge of the screen.

Now let's put the Paddle in to a Game and see what happens. Put this right in the same file:

   1 from pygame.locals import K_UP, K_DOWN
   2 
   3 from pygsear.Game import Game
   4 from pygsear.Event import KEYDOWN_Event, KEYUP_Event
   5 
   6 class Pong(Game):
   7     def initialize(self):
   8         paddle = Paddle()
   9         self.sprites.add(paddle)
  10 
  11         self.events.add(KEYDOWN_Event(key=K_UP, callback=paddle.up))
  12         self.events.add(KEYUP_Event(key=K_UP, callback=paddle.noup))
  13 
  14         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=paddle.down))
  15         self.events.add(KEYUP_Event(key=K_DOWN, callback=paddle.nodown))
  16 
  17 if __name__ == '__main__':
  18     game = Pong()
  19     game.mainloop()

So, here is what we have so far. You can download the code by clicking on the link, and run it with python Pong-0.1.py

   1 # Pong-0.1.py
   2 from pygsear.Drawable import Rectangle
   3 from pygsear.Game import Game
   4 from pygsear.Event import KEYDOWN_Event, KEYUP_Event
   5 
   6 from pygame.locals import K_UP, K_q, K_DOWN, K_a
   7 
   8 class Paddle(Rectangle):
   9     def __init__(self):
  10         Rectangle.__init__(self, width=15, height=50)
  11         self.center(x=10)
  12 
  13         self.up_pressed = 0
  14         self.down_pressed = 0
  15 
  16     def up(self, ev):
  17         self.up_pressed = 1
  18 
  19     def noup(self, ev):
  20         self.up_pressed = 0
  21 
  22     def down(self, ev):
  23         self.down_pressed = 1
  24 
  25     def nodown(self, ev):
  26         self.down_pressed = 0 
  27 
  28     def set_vel(self):
  29         if self.up_pressed and not self.down_pressed:
  30             self.path.set_velocity(vy=-100)
  31         elif self.down_pressed and not self.up_pressed:
  32             self.path.set_velocity(vy=100)
  33         else:
  34             self.path.set_velocity(vy=0)
  35 
  36     def move(self):
  37         self.set_vel()
  38         Rectangle.move(self) 
  39         self.onscreen(top=-5, bottom=-5, jail=1)
  40 
  41 
  42 class Pong(Game):
  43     def initialize(self):
  44         paddle = Paddle()
  45         self.sprites.add(paddle)
  46 
  47         self.events.add(KEYDOWN_Event(key=(K_UP, K_q), callback=paddle.up))
  48         self.events.add(KEYUP_Event(key=(K_UP, K_q), callback=paddle.noup))
  49 
  50         self.events.add(KEYDOWN_Event(key=(K_DOWN, K_a), callback=paddle.down))
  51         self.events.add(KEYUP_Event(key=(K_DOWN, K_a), callback=paddle.nodown))
  52 
  53 if __name__ == '__main__':
  54     game = Pong()
  55     game.mainloop()

Notice that in this version, you can also control the paddle with the 'Q' and 'A' keys, in addition to the up and down arrows.

Ball

We have experience with a ball from the WhoaBall class.

We can draw on that experience, but I am not going to inherit from WhoaBall for the Pong ball. It is just too different.

   1 from pygsear.Drawable import Square
   2 
   3 class Ball(Square):
   4     def __init__(self):
   5         Square.__init__(self, size=15)
   6         self.center()
   7         self.path.set_velocity(vx=150, vy=100)
   8 
   9     def walls(self):
  10         vx, vy = self.path.get_velocity()
  11         if not self.onscreen(top=-5, bottom=-5, jail=1)
  12             self.path.set_velocity(vy=-vy)
  13         if not self.onscreen(right=-5, jail=1)
  14             self.path.set_velocity(vx=-vx)
  15 
  16     def move(self):
  17         self.walls()
  18         Square.move(self)

Now we can add the new Ball in to our Pong game and see what happens.

   1 class Pong(Game):
   2     def initialize(self):
   3         paddle = Paddle()
   4         self.sprites.add(paddle)
   5         ball = Ball()
   6         self.sprites.add(ball)

Here is our progress so far. We have the paddle and the ball:

   1 # Pong-0.2.py
   2 from pygsear.Drawable import Rectangle, Square
   3 from pygsear.Game import Game
   4 from pygsear.Event import KEYDOWN_Event, KEYUP_Event
   5 
   6 from pygame.locals import K_UP, K_DOWN
   7 
   8 
   9 class Paddle(Rectangle):
  10     def __init__(self):
  11         Rectangle.__init__(self, width=15, height=50)
  12         self.center(x=10)
  13 
  14         self.up_pressed = 0
  15         self.down_pressed = 0
  16 
  17     def up(self, ev):
  18         self.up_pressed = 1
  19 
  20     def noup(self, ev):
  21         self.up_pressed = 0
  22 
  23     def down(self, ev):
  24         self.down_pressed = 1
  25 
  26     def nodown(self, ev):
  27         self.down_pressed = 0 
  28 
  29     def setVel(self):
  30         if self.up_pressed and not self.down_pressed:
  31             self.path.set_velocity(vy=-100)
  32         elif self.down_pressed and not self.up_pressed:
  33             self.path.set_velocity(vy=100)
  34         else:
  35             self.path.set_velocity(vy=0)
  36 
  37     def move(self):
  38         self.setVel()
  39         Rectangle.move(self) 
  40         self.onscreen(top=-5, bottom=-5, jail=1)
  41 
  42 
  43 class Ball(Square):
  44     def __init__(self):
  45         Square.__init__(self, side=15)
  46         self.center()
  47         self.path.set_velocity(vx=150, vy=100)
  48 
  49     def walls(self):
  50         vx, vy = self.path.get_velocity()
  51         if not self.onscreen(top=-5, bottom=-5, jail=1):
  52             self.path.set_velocity(vy=-vy)
  53         if not self.onscreen(right=-5, jail=1):
  54             self.path.set_velocity(vx=-vx)
  55 
  56     def move(self):
  57         self.walls()
  58         Square.move(self) 
  59 
  60 
  61 class Pong(Game):
  62     def initialize(self):
  63         paddle = Paddle()
  64         self.sprites.add(paddle)
  65         ball = Ball()
  66         self.sprites.add(ball)    
  67 
  68         self.events.add(KEYDOWN_Event(key=K_UP, callback=paddle.up))
  69         self.events.add(KEYUP_Event(key=K_UP, callback=paddle.noup))
  70 
  71         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=paddle.down))
  72         self.events.add(KEYUP_Event(key=K_DOWN, callback=paddle.nodown))
  73 
  74 
  75 if __name__ == '__main__':
  76     game = Pong()
  77     game.mainloop()

You can run the new game with:

python Pong-0.2.py

The only problem is that the ball passes right through the paddle!

To fix this we need to introduce collisions.

Game

In the simplest case, every Drawable object is a rectangle.

Even when the image that you see is not just a rectangle, the sprite is considered to be the rectangle which would cover the entire image. pygsear defines two different colors you can use for transparency, or it can use the transparency created by an image program like GIMP.

A collision is when two of these rectangles overlap.

Notice here that even though the images themselves do not touch, these two sprites are colliding. In most cases this will not be a problem, since sprites tend to be small and move relatively fast.

In Pong the only collision we are worried about is between the Paddle and the Ball.

   1 class Ball(Square):
   2     def hit(self):
   3         vx, vy = self.path.get_velocity()
   4         vx = abs(vx)
   5         self.path.set_velocity(vx=vx)
   6 
   7 class Pong(Game):
   8     def initialize(self):
   9         paddle = Paddle()
  10         self.sprites.add(paddle)
  11         ball = Ball()
  12         self.sprites.add(ball)
  13 
  14         self.events.add(KEYDOWN_Event(key=K_UP, callback=paddle.up))
  15         self.events.add(KEYUP_Event(key=K_UP, callback=paddle.noup))
  16 
  17         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=paddle.down))
  18         self.events.add(KEYUP_Event(key=K_DOWN, callback=paddle.nodown))
  19 
  20         self.paddle = paddle
  21         self.ball = ball
  22 
  23     def checkCollisions(self):
  24         if self.ball.collide(self.paddle):
  25             self.ball.hit()

Each time through the mainloop() loop, the Game will call checkCollisions()

Our checkCollisions() will check to see if the Ball has hit the Paddle and if so, it will call the hit() for the Ball.

Important: Notice how we need to modify the initialize() method to keep a handle on the Ball and the Paddle. If the Game did not hold on to those, it would have no way of checking the collisions.

Once we know there has been a collision, we know the Ball should end up moving to the right, and so that is all hit() does. It makes sure vx is positive, using the absolute value function.

The only thing left is to check for the Ball exiting the screen to the left, and to show the score.

   1 from pygsear.Drawable import Score
   2 
   3 class Pong(Game):
   4     def initialize(self):
   5         self.window.border(left=0, top=5, right=5, bottom=5)
   6         self.window.setTitle('Pong!')
   7 
   8         paddle = Paddle()
   9         self.sprites.add(paddle)
  10         ball = Ball()
  11         self.sprites.add(ball)
  12 
  13         self.score = Score()
  14         self.sprites.add(self.score)
  15 
  16         self.events.add(KEYDOWN_Event(key=K_UP, callback=paddle.up))
  17         self.events.add(KEYUP_Event(key=K_UP, callback=paddle.noup))
  18 
  19         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=paddle.down))
  20         self.events.add(KEYUP_Event(key=K_DOWN, callback=paddle.nodown))
  21 
  22         self.paddle = paddle
  23         self.ball = ball
  24 
  25     def checkCollisions(self):
  26         if self.ball.collide(self.paddle):
  27             self.ball.hit()
  28             self.score.addPoints(1)
  29             self.score.updateScore()
  30 
  31         if not self.ball.onscreen(left=10):
  32             self.ball.center()
  33             self.ball.path.set_velocity(vx=150, vy=100)

Actually, while I was in there, I also set a title for the window and added a border around the screen.

That border might make it a bit more clear why all of the onscreen() calls were checking for the ball to be 5 pixels from the edge.

Here is the full source for the game. You can click on the link to download the file:

   1 # Pong-1.0.py
   2 from pygsear.Drawable import Rectangle, Square
   3 from pygsear.Widget import Score
   4 from pygsear.Game import Game
   5 from pygsear.Event import KEYDOWN_Event, KEYUP_Event
   6 
   7 from pygame.locals import K_UP, K_DOWN
   8 
   9 class Paddle(Rectangle):
  10     def __init__(self):
  11         Rectangle.__init__(self, width=15, height=50)
  12         self.center(x=10)
  13 
  14         self.up_pressed = 0
  15         self.down_pressed = 0
  16 
  17     def up(self, ev):
  18          self.up_pressed = 1
  19   
  20     def noup(self, ev):
  21          self.up_pressed = 0
  22 
  23     def down(self, ev):
  24          self.down_pressed = 1
  25 
  26     def nodown(self, ev):
  27          self.down_pressed = 0
  28 
  29     def setVel(self):
  30         if self.up_pressed and not self.down_pressed:
  31             self.path.set_velocity(vy=-100)
  32         elif self.down_pressed and not self.up_pressed:
  33             self.path.set_velocity(vy=100)
  34         else:
  35             self.path.set_velocity(vy=0)
  36 
  37     def move(self):
  38         self.setVel()
  39         Rectangle.move(self)
  40         self.onscreen(top=-5, bottom=-5, jail=1)
  41 
  42 
  43 class Ball(Square):
  44     def __init__(self):
  45         Square.__init__(self, side=15)
  46         self.center()
  47         self.path.set_velocity(vx=150, vy=100)
  48 
  49     def walls(self):
  50         vx, vy = self.path.get_velocity()
  51         if not self.onscreen(top=-5, bottom=-5, jail=1):
  52             self.path.set_velocity(vy=-vy)
  53         if not self.onscreen(right=-5, jail=1):
  54             self.path.set_velocity(vx=-vx)
  55 
  56     def hit(self):
  57         vx, vy = self.path.get_velocity()
  58         vx = abs(vx)
  59         self.path.set_velocity(vx=vx)
  60 
  61     def move(self):
  62         self.walls()
  63         Square.move(self) 
  64 
  65 
  66 class Pong(Game):
  67     def initialize(self):
  68         self.window.border(left=0, top=5, right=5, bottom=5)
  69         self.window.set_title('Pong!')
  70 
  71         paddle = Paddle()
  72         self.sprites.add(paddle)
  73         ball = Ball()
  74         self.sprites.add(ball)
  75 
  76         self.score = Score()
  77         self.sprites.add(self.score)
  78 
  79         self.events.add(KEYDOWN_Event(key=K_UP, callback=paddle.up))
  80         self.events.add(KEYUP_Event(key=K_UP, callback=paddle.noup))
  81  
  82         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=paddle.down))
  83         self.events.add(KEYUP_Event(key=K_DOWN, callback=paddle.nodown))
  84  
  85         self.paddle = paddle
  86         self.ball = ball
  87 
  88     def checkCollisions(self):
  89         if self.ball.collide(self.paddle):
  90             self.ball.hit()
  91             self.score.addPoints(1)
  92             self.score.updateScore()
  93 
  94         if not self.ball.onscreen(left=10):
  95             self.ball.center()
  96             self.ball.path.set_velocity(vx=150, vy=100) 
  97 
  98 
  99 if __name__ == '__main__':
 100     game = Pong()
 101     game.mainloop()

Tweak

Ok, that is a pretty good start. We have a ball and a paddle and everything basically works.

The game is not much fun though. What we need is to shake things up a little bit. Let's see how we can make the game better.

How about each time you hit the ball, it speeds up and goes in a random direction?

   1 import random
   2 
   3 class Ball(Square):
   4     def hit(self):
   5         vx, vy = self.path.get_velocity()
   6         vx = abs(vx) + 20
   7         vy = random.uniform(-100, 100)
   8         self.path.set_velocity(vx, vy)

That should make things a bit more exciting. Especially after you have hit the ball a few times.

In fact, if you try it out, it soon becomes impossible to hit the ball. We need to speed the paddle up as the ball goes faster.

   1 class Paddle(Rectangle):
   2     def __init__(self):
   3         Rectangle.__init__(self, width=15, height=50)
   4         self.center(x=10)
   5 
   6         self.speed = 100
   7 
   8         self.up_pressed = 0
   9         self.down_pressed = 0
  10 
  11     def hit(self):
  12         self.speed += 4
  13 
  14     def setVel(self):
  15         if self.up_pressed and not self.down_pressed:
  16             self.path.set_velocity(vy=-self.speed)
  17         elif self.down_pressed and not self.up_pressed:
  18             self.path.set_velocity(vy=self.speed)
  19         else:
  20             self.path.set_velocity(vy=0)
  21 
  22 class Pong(Game):
  23     def checkCollisions(self):
  24         if self.ball.collide(self.paddle):
  25             self.ball.hit()
  26             self.paddle.hit()
  27             self.score.addPoints(1)
  28             self.score.updateScore()
  29 
  30         if not self.ball.onscreen(left=10):
  31             self.ball.center()
  32             self.ball.path.set_velocity(vx=150, vy=100)

Finally, to make the game ever so slightly addictive, I want to keep track of the high score so far.

   1 class Pong(Game):
   2     def initialize(self):
   3         self.window.border(left=0, top=5, right=5, bottom=5)
   4         self.window.setTitle('Pong!')
   5 
   6         paddle = Paddle()
   7         self.sprites.add(paddle)
   8         ball = Ball()
   9         self.sprites.add(ball)
  10 
  11         self.score = Score()
  12         self.sprites.add(self.score)
  13 
  14         self.events.add(KEYDOWN_Event(key=K_UP, callback=paddle.up))
  15         self.events.add(KEYUP_Event(key=K_UP, callback=paddle.noup))
  16 
  17         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=paddle.down))
  18         self.events.add(KEYUP_Event(key=K_DOWN, callback=paddle.nodown))
  19 
  20         self.paddle = paddle
  21         self.ball = ball
  22 
  23         self.hscore = Score(text='High:', position=(100,70))
  24         self.sprites.add(self.hscore)
  25 
  26     def checkCollisions(self):
  27         if self.ball.collide(self.paddle):
  28             self.ball.hit()
  29             self.paddle.hit()
  30             self.score.addPoints(1)
  31             self.score.updateScore()
  32 
  33         if not self.ball.onscreen(left=10):
  34             self.ball.center()
  35             self.ball.path.set_velocity(vx=150, vy=100)
  36 
  37             high = max(self.score.points, self.hscore.points)
  38             self.hscore.setPoints(high)
  39             self.maxscore.updateScore()
  40             self.score.setPoints(0)
  41             self.score.updateScore()

So, here is the code for our tweaked pong game:

   1 # Pong-1.1.py
   2 import random
   3 
   4 from pygsear.Drawable import Rectangle, Square
   5 from pygsear.Widget import Score
   6 from pygsear.Game import Game
   7 from pygsear.Event import KEYDOWN_Event, KEYUP_Event
   8 
   9 from pygame.locals import K_UP, K_DOWN
  10 
  11 class Paddle(Rectangle):
  12     def __init__(self):
  13         Rectangle.__init__(self, width=15, height=50)
  14         self.center(x=10)
  15 
  16         self.up_pressed = 0
  17         self.down_pressed = 0
  18 
  19         self.speed = 100
  20 
  21     def up(self, ev):
  22          self.up_pressed = 1
  23   
  24     def noup(self, ev):
  25          self.up_pressed = 0
  26 
  27     def down(self, ev):
  28          self.down_pressed = 1
  29 
  30     def nodown(self, ev):
  31          self.down_pressed = 0
  32 
  33     def hit(self):
  34         self.speed += 4
  35 
  36     def setVel(self):
  37         if self.up_pressed and not self.down_pressed:
  38             self.path.set_velocity(vy=-self.speed)
  39         elif self.down_pressed and not self.up_pressed:
  40             self.path.set_velocity(vy=self.speed)
  41         else:
  42             self.path.set_velocity(vy=0)
  43 
  44     def move(self):
  45         self.setVel()
  46         Rectangle.move(self)
  47         self.onscreen(top=-5, bottom=-5, jail=1)
  48 
  49 
  50 class Ball(Square):
  51     def __init__(self):
  52         Square.__init__(self, side=15)
  53         self.center()
  54         self.path.set_velocity(vx=150, vy=100)
  55 
  56     def walls(self):
  57         vx, vy = self.path.get_velocity()
  58         if not self.onscreen(top=-5, bottom=-5, jail=1):
  59             self.path.set_velocity(vy=-vy)
  60         if not self.onscreen(right=-5, jail=1):
  61             self.path.set_velocity(vx=-vx)
  62 
  63     def hit(self):
  64         vx, vy = self.path.get_velocity()
  65         vx = abs(vx) + 20
  66         vy = random.uniform(-100, 100)
  67         self.path.set_velocity(vx=vx, vy=vy)
  68 
  69     def move(self):
  70         self.walls()
  71         Square.move(self) 
  72 
  73 
  74 class Pong(Game):
  75     def initialize(self):
  76         self.window.border(left=0, top=5, right=5, bottom=5)
  77         self.window.set_title('Pong!')
  78 
  79         paddle = Paddle()
  80         self.sprites.add(paddle)
  81         ball = Ball()
  82         self.sprites.add(ball)
  83 
  84         self.paddle = paddle
  85         self.ball = ball
  86 
  87         self.events.add(KEYDOWN_Event(key=K_UP, callback=paddle.up))
  88         self.events.add(KEYUP_Event(key=K_UP, callback=paddle.noup))
  89 
  90         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=paddle.down))
  91         self.events.add(KEYUP_Event(key=K_DOWN, callback=paddle.nodown))
  92 
  93         self.score = Score(text='Score:')
  94         self.sprites.add(self.score)
  95 
  96         self.hscore = Score(text='Max:', position=(100, 70))
  97         self.sprites.add(self.hscore)
  98 
  99     def checkCollisions(self):
 100         if self.ball.collide(self.paddle):
 101             self.ball.hit()
 102             self.paddle.hit()
 103             self.score.addPoints(1)
 104             self.score.updateScore()
 105 
 106         if not self.ball.onscreen(left=10):
 107             self.ball.center()
 108             self.ball.path.set_velocity(vx=150, vy=100)
 109 
 110             high = max(self.score.points, self.hscore.points)
 111             self.hscore.set_points(high)
 112             self.hscore.updateScore()
 113             self.score.set_points(0)
 114             self.score.updateScore()
 115 
 116 
 117 if __name__ == '__main__':
 118     game = Pong()
 119     game.mainloop()

There is actually at least one more bug that I know of in this version. See if you can find it. The pong.py in the pygsear examples/ directory has the bug fixed.

Exercises:

1. Allow the paddle to move left and right, also. (Hint)

2. Add a second paddle on the right side of the screen. (Hint)

3. Extend the two paddle program from exercise 2 to make a two player pong game. (Hint)

4. Extend the 2 player pong game by devising some way for players to affect the direction of the ball. (Hint)

Chapter 5 Frog

Hop Hop

I want to play a game like "frogger". Remember that one? I looked around at the games that are ported to FreeBSD, but nothing to be found... so, I guess I will just make one myself.

I figure while I am at it, I may as well let you follow along.

My first step is to make a frog.

Later on I will clean it up and make it look much better. For now I just want to get the basic game working. I can work on beautification afterwards.

   1 from pygsear.Drawable import Image
   2 from pygsear.Game import Game
   3 
   4 
   5 class Frog(Image):
   6     def __init__(self):
   7         Image.__init__(self, filename='frog.png')
   8         self.center(y=-5)
   9 
  10 
  11 class FrogGame(Game):
  12     def initialize(self):
  13         frog = Frog()
  14         self.sprites.add(self.frog)
  15 
  16 
  17 if __name__ == '__main__':
  18     g =FrogGame()
  19     g.mainloop()

Now, add some controls and a background image, and things start to shape up quickly.

   1 from pygsear.Drawable import Image
   2 from pygsear.Game import Game
   3 from pygsear.Event import KEYDOWN_Event
   4 
   5 from pygame.locals import K_UP, K_DOWN, K_LEFT, K_RIGHT
   6 
   7 
   8 class Frog(Image):
   9     def __init__(self):
  10         Image.__init__(self, filename='frog.png')
  11         self.JUMP = 60
  12         self.WALK = 30
  13 
  14         self.center(y=-5)
  15 
  16     def up(self, ev=None):
  17         self.nudge(dy=-self.JUMP)
  18     def down(self, ev=None):
  19         self.nudge(dy=self.JUMP)
  20     def left(self, ev=None):
  21         self.nudge(dx=-self.WALK)
  22     def right(self, ev=None):
  23         self.nudge(dx=self.WALK)
  24 
  25 
  26 class FrogGame(Game):
  27     def initialize(self):
  28         self.set_background(filename='bg.png')
  29 
  30         frog = Frog()
  31         self.sprites.add(frog)
  32 
  33         self.events.add(KEYDOWN_Event(key=K_UP, callback=frog.up))
  34         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=frog.down))
  35         self.events.add(KEYDOWN_Event(key=K_LEFT, callback=frog.left))
  36         self.events.add(KEYDOWN_Event(key=K_RIGHT, callback=frog.right))
  37 
  38 
  39 if __name__ == '__main__':
  40     g =FrogGame()
  41     g.mainloop()

Sploosh

Ok, right now the frog can walk on the water. Plus there is also no way to win. Let's see if we can solve both of those problems.

First, we will add a way to win.

   1 from pygsear.Drawable import Image
   2 from pygsear.Game import Game
   3 from pygsear.Event import KEYDOWN_Event
   4 from pygsear.Widget import Score
   5 
   6 from pygame.locals import K_UP, K_DOWN, K_LEFT, K_RIGHT
   7 
   8 
   9 class Frog(Image):
  10     def __init__(self):
  11         Image.__init__(self, filename='frog.png')
  12         self.JUMP = 60
  13         self.WALK = 30
  14         self.restart()
  15 
  16     def restart(self):
  17         self.center(y=-5)
  18         self.lane = 0
  19 
  20     def up(self, ev=None):
  21         self.nudge(dy=-self.JUMP)
  22         self.lane += 1
  23     def down(self, ev=None):
  24         self.nudge(dy=self.JUMP)
  25         self.lane -= 1
  26     def left(self, ev=None):
  27         self.nudge(dx=-self.WALK)
  28     def right(self, ev=None):
  29         self.nudge(dx=self.WALK)
  30 
  31 
  32 class FrogGame(Game):
  33     def initialize(self):
  34         self.set_background(filename='bg.png')
  35 
  36         frog = Frog()
  37         self.sprites.add(frog)
  38         self.frog = frog
  39 
  40         self.score = Score(text='', position=(610, 550))
  41         self.sprites.add(self.score)
  42 
  43         self.events.add(KEYDOWN_Event(key=K_UP, callback=frog.up))
  44         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=frog.down))
  45         self.events.add(KEYDOWN_Event(key=K_LEFT, callback=frog.left))
  46         self.events.add(KEYDOWN_Event(key=K_RIGHT, callback=frog.right))
  47 
  48     def checkCollisions(self):
  49         if self.frog.lane == 9:
  50             self.score.addPoints(1)
  51             self.score.updateScore()
  52             self.frog.restart()
  53 
  54 if __name__ == '__main__':
  55     g =FrogGame()
  56     g.mainloop()

I drew the lilly pads directly on the background, but now I think it may actually be easier to make them in to separate sprites. Maybe in advanced levels they might move around or change color or disappear. Yep. This should also make it easier to find if the frog has landed on the pad correctly.

   1 class Pad(Image):
   2     def __init__(self):
   3         Image.__init__(self, filename='pad.png')
   4 
   5 
   6 class FrogGame(Game):
   7     def initialize(self):
   8         self.set_background(filename='bg.png')
   9 
  10         self.pads = self.addGroup()
  11         for p in range(5):
  12             pad = Pad()
  13             pad.set_position(50 + 155*p, 5)
  14             self.sprites.add(pad)
  15             self.pads.add(pad)
  16 
  17         frog = Frog()
  18         self.sprites.add(frog)
  19         self.frog = frog
  20 
  21         self.score = Score(text='', position=(610, 550))
  22         self.sprites.add(self.score)
  23 
  24         self.events.add(KEYDOWN_Event(key=K_UP, callback=frog.up))
  25         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=frog.down))
  26         self.events.add(KEYDOWN_Event(key=K_LEFT, callback=frog.left))
  27         self.events.add(KEYDOWN_Event(key=K_RIGHT, callback=frog.right))

Ok. Looks good. Now let's make those lilly pads safe.

   1 from pygsear.Drawable import Image, MultiImage
   2 
   3 class Pad(MultiImage):
   4     def __init__(self):
   5         MultiImage.__init__(self, filenames=('pad.png', 'padfull.png'))
   6         self.flip('pad.png')
   7 
   8 
   9 class FrogGame(Game):
  10     def checkCollisions(self):
  11         if self.frog.lane == 9:
  12             pads = self.pads.sprites()
  13             pad = self.frog.collidelist(pads)
  14             if pad:
  15                 pad.flip('padfull.png')
  16                 self.score.addPoints(1)
  17                 self.score.updateScore()
  18                 self.frog.restart()

Note that I am only including the methods that need changing, so don't erase the rest of the methods when you work on it.

I put this part in first, because otherwise the frog would not be able to cross the water and test out the pads. Now, fix the problem of multiple frogs on one pad, and make the water more dangerous.

   1 class Frog(Image):
   2     def __init__(self):
   3         Image.__init__(self, filename='frog.png')
   4         self.JUMP = 60
   5         self.WALK = 30
   6         self.floating = 0 # set if standing on something that floats
   7         self.lives = 3
   8         self.restart()
   9 
  10     def croak(self):
  11         print 'croak' # fill this in later ...
  12         self.lives -= 1
  13 
  14     def sploosh(self):
  15         print 'sploosh' # fill this in later ...
  16         self.croak()
  17 
  18 
  19 class Pad(MultiImage):
  20     def __init__(self):
  21         MultiImage.__init__(self, filenames=('pad.png', 'padfull.png'))
  22         self.flip('pad.png')
  23         self.occupied = 0
  24 
  25     def occupy(self):
  26         self.occupied = 1
  27         self.flip('padfull.png')
  28 
  29 
  30 class FrogGame(Game):
  31     def checkCollisions(self):
  32         if self.frog.lane == 9:
  33             pads = self.pads.sprites()
  34             pad = self.frog.collidelist(pads)
  35             if pad and not pad.occupied:
  36                 pad.occupy()
  37                 self.score.addPoints(1)
  38                 self.score.updateScore()
  39                 self.frog.restart()
  40         if 6 <= self.frog.lane <= 9:
  41             if not self.frog.floating:
  42                 self.frog.sploosh()
  43                 self.frog.restart()
  44         if not self.frog.onscreen():
  45             self.frog.croak()
  46             self.frog.restart()
  47         if self.frog.lives <= 0:
  48             self.stop = 1

Remember that if you want to test out the pads, you can still comment out the lines that go sploosh() like this:

   1 class FrogGame(Game):
   2     def checkCollisions(self):
   3         if self.frog.lane == 9:
   4             pads = self.pads.sprites()
   5             pad = self.frog.collidelist(pads)
   6             if pad and not pad.occupied:
   7                 pad.occupy()
   8                 self.score.addPoints(1)
   9                 self.score.updateScore()
  10                 self.frog.restart()
  11         #if 6 <= self.frog.lane <= 9:
  12         #    if not self.frog.floating:
  13         #        self.frog.sploosh()
  14         #        self.frog.restart()
  15         if not self.frog.onscreen():
  16             self.frog.croak()
  17             self.frog.restart()
  18         if self.frog.lives <= 0:
  19             self.stop = 1

Now to add a little traffic.

Vroom

First up, a few cars.

Now add them in to the game.

from pygsear.Drawable import Image, MultiImage, RotatedImage
from pygsear.locals import PI
from pygsear import conf

import random

class Car(RotatedImage):
    def __init__(self, lane, vx):
        color = random.choice(['01', '02', '03', '04'])
        fn = 'car%s.png' % color
        RotatedImage.__init__(self, filename=fn, steps=2)
        if vx > 0:
            x = -100
        else:
            x = conf.WINWIDTH + 148
            self.set_rotation(PI)

        y = conf.WINHEIGHT - 25 - (60 * lane)

        self.set_position(x, y)
        self.path.set_velocity(vx=vx)
        self.set_closest()


class FrogGame(Game):
    def initialize(self):
        self.set_background(filename='bg.png')

        self.pads = self.addGroup()
        for p in range(5):
            pad = Pad()
            pad.set_position(50 + 155*p, 5)
            self.sprites.add(pad)
            self.pads.add(pad)

        frog = Frog()
        self.sprites.add(frog)
        self.frog = frog

        self.score = Score(text='', position=(610, 550))
        self.sprites.add(self.score)v 
        self.events.add(KEYDOWN_Event(key=K_UP, callback=frog.up))
        self.events.add(KEYDOWN_Event(key=K_DOWN, callback=frog.down))
        self.events.add(KEYDOWN_Event(key=K_LEFT, callback=frog.left))
        self.events.add(KEYDOWN_Event(key=K_RIGHT, callback=frog.right))

        self.cars = self.addGroup()
        for lane, vx in [(1, 120), (2, 150), (3, -100), (4, -120)]:
            car = Car(lane, vx)
            self.sprites.add(car)
            self.cars.add(car)

    def checkCollisions(self):
        if self.frog.lane == 9:
            pads = self.pads.sprites()
            pad = self.frog.collidelist(pads)
            if pad and not pad.occupied:
                pad.occupy()
                self.score.addPoints(1)
                self.score.updateScore()
                self.frog.restart()
        if 6 <= self.frog.lane <= 9:
            if not self.frog.floating:
                self.frog.sploosh()
                self.frog.restart()
        if 1 <= self.frog.lane <=4:
            cars = self.cars.sprites()
            car = self.frog.collidelist(cars)
            if car:
                self.frog.croak()
                self.frog.restart()
        if not self.frog.onscreen():
            self.frog.croak()
            self.frog.restart()
        if self.frog.lives <= 0:
            self.stop = 1

That works. Sort of. I thought I might just reset the car when it goes off the other edge of the screen, but really what we need is more control over the cars. I think I will add a Lane object which handles all of that.

   1 from pygsear.Event import KEYDOWN_Event, TIMEOUT_Event
   2 
   3 
   4 class Lane:
   5     def __init__(self, lane_number, freq, speed):
   6         self.number = lane_number
   7         self.freq = freq
   8 
   9         speed = abs(speed)
  10         if lane_number > 2:
  11             self.vx = -speed
  12         else:
  13             self.vx = speed
  14 
  15         wavelength = speed / freq
  16 
  17         self.cars = conf.game.addGroup()
  18 
  19         x = random.randrange(int(wavelength))
  20         while x < conf.WINWIDTH:
  21             car = Car(lane_number, self.vx)
  22             self.cars.add(car)
  23             car_x, car_y = car.get_position()
  24             car.set_position(x, car_y)
  25             x += wavelength
  26 
  27         self.spawn()
  28 
  29     def spawn(self, ev=None):
  30         car = Car(self.number, self.vx)
  31         self.cars.add(car)
  32         conf.game.sprites.add(car)
  33         conf.game.cars.add(car)
  34         conf.game.events.add(TIMEOUT_Event(delay=(1000/self.freq), callback=self.spawn))
  35 
  36 
  37 class FrogGame(Game):
  38     def initialize(self):
  39         self.set_background(filename='bg.png')
  40 
  41         self.pads = self.addGroup()
  42         for p in range(5):
  43             pad = Pad()
  44             pad.set_position(50 + 155*p, 5)
  45             self.sprites.add(pad)
  46             self.pads.add(pad)
  47 
  48         frog = Frog()
  49         self.sprites.add(frog)
  50         self.frog = frog
  51 
  52         self.score = Score(text='', position=(610, 550))
  53         self.sprites.add(self.score)
  54 
  55         self.events.add(KEYDOWN_Event(key=K_UP, callback=frog.up))
  56         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=frog.down))
  57         self.events.add(KEYDOWN_Event(key=K_LEFT, callback=frog.left))
  58         self.events.add(KEYDOWN_Event(key=K_RIGHT, callback=frog.right))
  59 
  60         self.cars = self.addGroup()
  61         for n in [1, 2, 3, 4]:
  62             lane = Lane(n, 0.5, 120)
  63             self.sprites.add(lane.cars)
  64             self.cars.add(lane.cars)

It can still use some tweaking, plus it is going to need some work when we want to be able to add different levels, but it works for now.

Once we make some way to get across the water, the game will at least be playable.

Rolling

A log.

Some log code.

   1 class Log(Image):
   2     def __init__(self, lane, vx):
   3         Image.__init__(self, filename='log.png')
   4         if vx > 0:
   5             x = -200
   6         else:
   7             x = conf.WINWIDTH + 50
   8 
   9         y = conf.WINHEIGHT - 50 - (60 * lane)
  10 
  11         self.set_position(x, y)
  12         self.path.set_velocity(vx=vx)
  13 
  14 
  15 class LogLane:
  16     def __init__(self, lane_number, freq, speed):
  17         self.number = lane_number
  18         self.freq = freq
  19         speed = abs(speed)
  20 
  21         assert 6 <= lane_number <= 8, 'logs only allowed in the water'
  22 
  23         direction = random.choice((1, -1))
  24         self.vx = direction * speed
  25 
  26         self.logs = conf.game.addGroup()
  27 
  28         self.spawn()
  29 
  30     def spawn(self, ev=None):
  31         log = Log(self.number, self.vx)
  32         self.logs.add(log)
  33         conf.game.sprites.add(log)
  34         conf.game.logs.add(log)
  35         conf.game.events.add(TIMEOUT_Event(delay=(1000/self.freq), callback=self.spawn))
  36 
  37 
  38 class FrogGame(Game):
  39     def initialize(self):
  40         self.set_background(filename='bg.png')
  41 
  42         self.pads = self.addGroup()
  43         for p in range(5):
  44             pad = Pad()
  45             pad.set_position(50 + 155*p, 5)
  46             self.sprites.add(pad)
  47             self.pads.add(pad)
  48 
  49         frog = Frog()
  50         self.sprites.add(frog)
  51         self.frog = frog
  52 
  53         self.score = Score(text='', position=(610, 550))
  54         self.sprites.add(self.score)
  55 
  56         self.events.add(KEYDOWN_Event(key=K_UP, callback=frog.up))
  57         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=frog.down))
  58         self.events.add(KEYDOWN_Event(key=K_LEFT, callback=frog.left))
  59         self.events.add(KEYDOWN_Event(key=K_RIGHT, callback=frog.right))
  60 
  61         self.cars = self.addGroup()
  62         for n in [1, 2, 3, 4]:
  63             lane = Lane(n, 0.5, 120)
  64             self.sprites.add(lane.cars)
  65             self.cars.add(lane.cars)
  66 
  67         self.logs = self.addGroup()
  68         for n in [6, 7, 8]:
  69             lane = LogLane(n, 0.3, 120)
  70             self.sprites.add(lane.logs)
  71             self.logs.add(lane.logs)
  72 
  73     def checkCollisions(self):
  74         if self.frog.lane == 9:
  75             pads = self.pads.sprites()
  76             pad = self.frog.collidelist(pads)
  77             if pad and not pad.occupied:
  78                 pad.occupy()
  79                 self.score.addPoints(1)
  80                 self.score.updateScore()
  81                 self.frog.restart()
  82 
  83         if self.frog.lane == 5:
  84             self.frog.floating = 0
  85             self.frog.path.set_velocity(vx=0)
  86 
  87         if 6 <= self.frog.lane <= 9:
  88             logs = self.logs.sprites()
  89             log = self.frog.collidelist(logs)
  90             if log:
  91                 self.frog.floating = 1
  92                 vx, vy = log.path.get_velocity()
  93                 self.frog.path.set_velocity(vx=vx)
  94             else:
  95                 self.frog.floating = 0
  96                 self.frog.path.set_velocity(vx=0)
  97 
  98             if not self.frog.floating:
  99                 self.frog.sploosh()
 100                 self.frog.restart()
 101 
 102         if 1 <= self.frog.lane <=4:
 103             cars = self.cars.sprites()
 104             car = self.frog.collidelist(cars)
 105             if car:
 106                 self.frog.croak()
 107                 self.frog.restart()
 108 
 109         if not self.frog.onscreen():
 110             self.frog.croak()
 111             self.frog.restart()
 112 
 113         if self.frog.lives <= 0:
 114             self.stop = 1

Notice how similar the LogLane code is to the Lane code before? I will probably try to re-factor the code to make it more simple later. Right now though, there is a problem with the logs floating on top of the frogs.

That's easy enough to fix. Just add the frog at a higher level and it will be drawn on top.

   1 class FrogGame(Game):
   2     def initialize(self):
   3         self.set_background(filename='bg.png')
   4 
   5         self.pads = self.addGroup()
   6         for p in range(5):
   7             pad = Pad()
   8             pad.set_position(50 + 155*p, 5)
   9             self.sprites.add(pad)
  10             self.pads.add(pad)
  11 
  12         frog = Frog()
  13         self.sprites.add(frog, level=1)
  14         self.frog = frog
  15 
  16         self.score =  Score(text='', position=(610, 550))
  17         self.sprites.add(self.score)
  18 
  19         self.events.add(KEYDOWN_Event(key=K_UP, callback=frog.up))
  20         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=frog.down))
  21         self.events.add(KEYDOWN_Event(key=K_LEFT, callback=frog.left))
  22         self.events.add(KEYDOWN_Event(key=K_RIGHT, callback=frog.right))
  23 
  24         self.cars = self.addGroup()
  25         for n in [1, 2, 3, 4]:
  26             lane = Lane(n, 0.5, 120)
  27             self.sprites.add(lane.cars)
  28             self.cars.add(lane.cars)
  29 
  30         self.logs = self.addGroup()
  31         for n in [6, 7, 8]:
  32             lane = LogLane(n, 0.3, 120)
  33             self.sprites.add(lane.logs)
  34             self.logs.add(lane.logs)

Variation

The way it is now, the logs and cars are produced at completely regular intervals, and they always have the same speeds.

By using a random method, we can get a nice distribution of cars and logs and make the game much more interesting.

   1 class LogLane:
   2     def __init__(self, lane_number, freq, speed, sigma, delaysigma):
   3         self.number = lane_number
   4         self.freq = freq
   5         speed = abs(speed)
   6         speed = random.normalvariate(speed, sigma)
   7         self.delaysigma = delaysigma
   8 
   9         assert 6 <= lane_number <= 8, 'logs only allowed in the water'
  10 
  11         if lane_number % 2:
  12             direction = -1
  13         else:
  14             direction = 1
  15         self.vx = direction * speed
  16 
  17         self.logs = conf.game.addGroup()
  18 
  19         self.spawn()
  20 
  21     def spawn(self, ev=None):
  22         log = Log(self.number, self.vx)
  23         self.logs.add(log)
  24         conf.game.sprites.add(log)
  25         conf.game.logs.add(log)
  26 
  27         delay = random.normalvariate(1000/self.freq, self.delaysigma)
  28         conf.game.events.add(TIMEOUT_Event(delay=delay, callback=self.spawn))
  29 
  30 
  31 class Lane:
  32     def __init__(self, lane_number, freq, speed, sigma, delaysigma):
  33         self.number = lane_number
  34         self.freq = freq
  35         speed = abs(speed)
  36         speed = random.normalvariate(speed, sigma)
  37         self.delaysigma = delaysigma
  38 
  39         assert 1 <= lane_number <= 4, 'cars only allowed on the road'
  40 
  41         if lane_number > 2:
  42             self.vx = -speed
  43         else:
  44             self.vx = speed
  45 
  46         wavelength = speed / freq
  47 
  48         self.cars = conf.game.addGroup()
  49 
  50         x = random.randrange(int(wavelength))
  51         while x < conf.WINWIDTH:
  52             car = Car(lane_number, self.vx)
  53             self.cars.add(car)
  54             car_x, car_y = car.get_position()
  55             car.set_position(x, car_y)
  56             x += wavelength
  57 
  58         self.spawn()
  59 
  60     def spawn(self, ev=None):
  61         car = Car(self.number, self.vx)
  62         self.cars.add(car)
  63         conf.game.sprites.add(car)
  64         conf.game.cars.add(car)
  65 
  66         delay = random.normalvariate(1000/self.freq, self.delaysigma)
  67         conf.game.events.add(TIMEOUT_Event(delay=delay, callback=self.spawn))
  68 
  69 
  70 class FrogGame(Game):
  71     def initialize(self):
  72         self.set_background(filename='bg.png')
  73 
  74         self.pads = self.addGroup()
  75         for p in range(5):
  76             pad = Pad()
  77             pad.set_position(50 + 155*p, 5)
  78             self.sprites.add(pad)
  79             self.pads.add(pad)
  80 
  81         frog = Frog()
  82         self.sprites.add(frog, level=1)
  83         self.frog = frog
  84 
  85         self.score = Score(text='', position=(610, 550))
  86         self.sprites.add(self.score)
  87 
  88         self.events.add(KEYDOWN_Event(key=K_UP, callback=frog.up))
  89         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=frog.down))
  90         self.events.add(KEYDOWN_Event(key=K_LEFT, callback=frog.left))
  91         self.events.add(KEYDOWN_Event(key=K_RIGHT, callback=frog.right))
  92 
  93         self.cars = self.addGroup()
  94         for n in [1, 2, 3, 4]:
  95             lane = Lane(n, 0.3, 120, 30, 500)
  96             self.sprites.add(lane.cars)
  97             self.cars.add(lane.cars)
  98 
  99         self.logs = self.addGroup()
 100         for n in [6, 7, 8]:
 101             lane = LogLane(n, 0.2, 100, 40, 1000)
 102             self.sprites.add(lane.logs)
 103             self.logs.add(lane.logs)

At this point, the game is playable. It still needs some tweaking -- to add levels of difficulty and to clean up the graphics.

   1 # Frog-1.0.py
   2 from pygsear.Drawable import Image, MultiImage, RotatedImage
   3 from pygsear.Game import Game
   4 from pygsear.Event import KEYDOWN_Event, TIMEOUT_Event
   5 from pygsear.Widget import Score
   6 from pygsear.locals import PI
   7 from pygsear import conf
   8 
   9 from pygame.locals import K_UP, K_DOWN, K_LEFT, K_RIGHT
  10 
  11 import random
  12 
  13 
  14 class Frog(Image):
  15     def __init__(self):
  16         Image.__init__(self, filename='frog.png')
  17         self.JUMP = 60
  18         self.WALK = 30
  19         self.floating = False # set if standing on something that floats
  20         self.lives = 3
  21 
  22         self.restart()
  23  
  24     def croak(self):
  25          print 'croak' # fill this in later ...
  26          self.lives -= 1
  27 
  28     def sploosh(self):
  29          print 'sploosh' # fill this in later ...
  30          self.croak()
  31 
  32     def restart(self):
  33         self.path.set_velocity(vx=0)
  34         self.center(y=-5)
  35         self.lane = 0
  36 
  37     def up(self, ev=None):
  38         self.nudge(dy=-self.JUMP)
  39         self.lane += 1
  40     def down(self, ev=None):
  41         self.nudge(dy=self.JUMP)
  42         self.lane -= 1
  43     def left(self, ev=None):
  44         self.nudge(dx=-self.WALK)
  45     def right(self, ev=None):
  46         self.nudge(dx=self.WALK)
  47 
  48 
  49 class Pad(MultiImage):
  50     def __init__(self):
  51         MultiImage.__init__(self, filenames=('pad.png', 'padfull.png'))
  52         self.flip('pad.png')
  53         self.occupied = False
  54  
  55     def occupy(self):
  56          self.occupied = True
  57          self.flip('padfull.png')
  58 
  59 
  60 class Car(RotatedImage):
  61      def __init__(self, lane, vx):
  62          color = random.choice(['01', '02', '03', '04'])
  63          fn = 'car%s.png' % color
  64          RotatedImage.__init__(self, filename=fn, steps=2)
  65          if vx > 0:
  66              x = -100
  67          else:
  68              x = conf.WINWIDTH + 148
  69              self.set_rotation(PI)
  70  
  71          y = conf.WINHEIGHT - 25 - (60 * lane)
  72  
  73          self.set_position(x, y)
  74          self.path.set_velocity(vx=vx)
  75          self.set_closest()
  76 
  77 
  78 class Lane:
  79     def __init__(self, lane_number, freq, speed, sigma, delaysigma):
  80         self.number = lane_number
  81         self.freq = freq
  82 
  83         speed = abs(speed)
  84         speed = random.normalvariate(speed, sigma)
  85         self.delaysigma = delaysigma
  86 
  87         assert 1 <= lane_number <= 4, 'cars only allowed on the road'
  88 
  89         if lane_number > 2:
  90             self.vx = -speed
  91         else:
  92             self.vx = speed
  93 
  94         wavelength = speed / freq
  95 
  96         self.cars = conf.game.addGroup()
  97 
  98         x = random.randrange(int(wavelength))
  99         while x < conf.WINWIDTH:
 100             car = Car(lane_number, self.vx)
 101             self.cars.add(car)
 102             car_x, car_y = car.get_position()
 103             car.set_position(x, car_y)
 104             x += wavelength
 105 
 106         self.spawn()
 107 
 108     def spawn(self, ev=None):
 109         car = Car(self.number, self.vx)
 110         self.cars.add(car)
 111         conf.game.sprites.add(car)
 112         conf.game.cars.add(car)
 113         #conf.game.events.add(TIMEOUT_Event(delay=(1000/self.freq), callback=self.spawn))
 114 
 115         delay = random.normalvariate(1000/self.freq, self.delaysigma)
 116         conf.game.events.add(TIMEOUT_Event(delay=delay, callback=self.spawn))
 117 
 118  
 119 
 120 class Log(Image):
 121     def __init__(self, lane, vx):
 122         Image.__init__(self, filename='log.png')
 123         if vx > 0:
 124             x = -200
 125         else:
 126             x = conf.WINWIDTH + 50
 127 
 128         y = conf.WINHEIGHT - 50 - (60 * lane)
 129 
 130         self.set_position(x, y)
 131         self.path.set_velocity(vx=vx)
 132 
 133 
 134 class LogLane:
 135     def __init__(self, lane_number, freq, speed, sigma, delaysigma):
 136         self.number = lane_number
 137         self.freq = freq
 138         speed = abs(speed)
 139 
 140         speed = random.normalvariate(speed, sigma)
 141         self.delaysigma = delaysigma
 142 
 143         assert 6 <= lane_number <= 8, 'logs only allowed in the water'
 144 
 145         if lane_number % 2:
 146             direction = -1
 147         else:
 148             direction = 1
 149         self.vx = direction * speed
 150 
 151         self.logs = conf.game.addGroup()
 152 
 153         self.spawn()
 154 
 155     def spawn(self, ev=None):
 156         log = Log(self.number, self.vx)
 157         self.logs.add(log)
 158         conf.game.sprites.add(log)
 159         conf.game.logs.add(log)
 160         #conf.game.events.add(TIMEOUT_Event(delay=(1000/self.freq), callback=self.spawn))
 161 
 162         delay = random.normalvariate(1000/self.freq, self.delaysigma)
 163         conf.game.events.add(TIMEOUT_Event(delay=delay, callback=self.spawn))
 164 
 165 
 166 
 167 
 168 
 169 class FrogGame(Game):
 170     def initialize(self):
 171         self.set_background(filename='bg.png')
 172 
 173         self.pads = self.addGroup()
 174         for p in range(5):
 175             pad = Pad()
 176             pad.set_position(50 + 155*p, 5)
 177             self.sprites.add(pad)
 178             self.pads.add(pad)
 179 
 180         frog = Frog()
 181         self.sprites.add(frog, level=1)
 182         self.frog = frog
 183  
 184         self.score = Score(text='', position=(610, 550))
 185         self.sprites.add(self.score)
 186 
 187 
 188         self.events.add(KEYDOWN_Event(key=K_UP, callback=frog.up))
 189         self.events.add(KEYDOWN_Event(key=K_DOWN, callback=frog.down))
 190         self.events.add(KEYDOWN_Event(key=K_LEFT, callback=frog.left))
 191         self.events.add(KEYDOWN_Event(key=K_RIGHT, callback=frog.right))
 192 
 193         self.cars = self.addGroup()
 194         for n in [1, 2, 3, 4]:
 195             lane = Lane(n, 0.3, 120, 30, 500)
 196             self.sprites.add(lane.cars)
 197             self.cars.add(lane.cars)
 198 
 199         self.logs = self.addGroup()
 200         for n in [6, 7, 8]:
 201             lane = LogLane(n, 0.2, 100, 40, 1000)
 202             self.sprites.add(lane.logs)
 203             self.logs.add(lane.logs)
 204 
 205     def checkCollisions(self):
 206         if self.frog.lane == 9:
 207             pads = self.pads.sprites()
 208             pad = self.frog.collidelist(pads)
 209             if pad and not pad.occupied:
 210                  pad.occupy()
 211                  self.score.addPoints(1)
 212                  self.score.updateScore()
 213                  self.frog.restart()
 214 
 215         if self.frog.lane == 5:
 216              self.frog.floating = 0
 217              self.frog.path.set_velocity(vx=0)
 218 
 219         if 6 <= self.frog.lane <= 9:
 220             logs = self.logs.sprites()
 221             log = self.frog.collidelist(logs)
 222             if log:
 223                 self.frog.floating = 1
 224                 vx, vy = log.path.get_velocity()
 225                 self.frog.path.set_velocity(vx=vx)
 226             else:
 227                 self.frog.floating = 0
 228                 self.frog.path.set_velocity(vx=0)
 229 
 230             if not self.frog.floating:
 231                 self.frog.sploosh()
 232                 self.frog.restart()
 233 
 234         if 1 <= self.frog.lane <=4:
 235              cars = self.cars.sprites()
 236              car = self.frog.collidelist(cars)
 237              if car:
 238                  self.frog.croak()
 239                  self.frog.restart()
 240 
 241         if not self.frog.onscreen():
 242             self.frog.croak()
 243             self.frog.restart()
 244 
 245         if self.frog.lives <= 0:
 246             self.stop = 1
 247 
 248 
 249 
 250 
 251 
 252 if __name__ == '__main__':
 253     g = FrogGame()
 254     g.mainloop()

Este artigo foi traduzido do original encontrado em http://staff.easthighschool.net/lee/computers/book/Start_Programming.html, escrito por Lee Harr. Ele possui uma licença livre, o que significa que você pode se sentir à vontade para modificá-lo e usá-lo conforme desejar. Para mais detalhes sobre a licença veja: http://www.gnu.org/copyleft/fdl.html