May 31st, 2008

Prototyping is the phase of a particular feature or project in which you don’t know what the client wants and/or the client doesn’t know what’s possible.

It’s important to realize that in prototyping, there is no “right” or “wrong”. A better set of terms would be “closer” and “farther” or “hot” and “cold”. This is mostly because there is no sense of correctness or completeness during the prototyping phase, simply adaption to the client’s or user’s intuition about the interface.

Since neither side truly understands the other, then the feature or project ends up becoming a means of communcation more than it is a finished product.

May 24th, 2008

In our last tutorial we created some tetrominoes for our Tetris game. Let’s write the code to draw them:

First, we need to know what the current tetromino is. That’s another variable that will go next to run_game:

# Remember the user's current tetro
current_tetro = tetro_s

run_game = True

Next we need another double for loop. As you should know by now, double for loops are used to draw grids, and all tetrominoes are just grids.

  for x in range(len(current_tetro[0])):
    for y in range(len(current_tetro)):
      if current_tetro[y][x] == 'x':
        screen.fill(red, ((x+current_location[0])*25, (y+current_location[1])*25, 25, 25))

There’s some new and new-ish stuff in here that I should probably go over again:

  for x in range(len(current_tetro[0])):
    for y in range(len(current_tetro)):
      ...

The len function tells us how long the list is. When drawing grids, the x coordinate needs to draw each column from 1 up to the number of columns in the grid. And the y coordinate needs to draw each row from 1 to the number of rows in the grid. This was pretty easy when we did the first grid, because that never changed and we just put a 5 in for both and everything worked:

# Draw the main grid
  for x in range(5):
    for y in range(15):
      ...

But now we have a problem. We have grids of different sizes and we’d like to draw them all with one block of code. We don’t want to have to write another block of code for each type of tetromino, do we? So instead we let the grid code adapt to the grid that we give it. That’s why we changed that.

The next line should seem familiar. We want to only draw the red square when we have an 'x' in our grid:

  for x in range(len(current_tetro[0])):
    for y in range(len(current_tetro)):
      if current_tetro[y][x] == 'x':
        ...

Now the next line is kind of complicated but I’ll explain it to you bit by bit:

screen.fill(red, ((x+current_location[0])*25, (y+current_location[1])*25, 25, 25))

The new part is adding x and current_location[0], and also, adding y and current_location[1]. What we’re saying here (mathematically) is this: “Draw the red square at this location in my grid, at this location on the screen”. After all that hard work, we finally get to test out our new functionality.

Success! We now have the very beginning of our tetris game. But there’s a problem. Watch what happens when you try to move:

The game isn’t updating properly. We need to fix this. We will do this by adding another line to the while loop, which will clear everything off.

# Add the color black
black = (0, 0, 0)

...

while run_game:
  # Clear off the screen                                                                                                                                                             
  screen.fill(black)

  # Draw the current tetro                                                                                                                                                           
  for x in range(len(current_tetro[0])):
    ...

The fill command clears off the screen for each loop of the game and lets us start new, so we don’t have that problem.

In the next tutorial we will do more player interaction and create the basic rules for our tetris game.

May 24th, 2008

In our last tutorial, we added some user input. Let’s make use of that input by adding more grids to our game.

Let’s start by making the screen longer:

window = pygame.display.set_mode((400, 600))

This makes the screen 2 times as tall as it was before.

Now let’s make the grid longer:

grid = [
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' '],
    [' ', ' ', ' ', ' ', ' ']
    ]

This makes our grid 5 columns wide and 15 rows long.

Now we’re going to add some more stuff to our game. Add these grids just below the main grid. Now you’ll finally see what kind of game you’re actually working on:


tetro_o = [
  ['x', 'x'],
  ['x', 'x'],
  ]

tetro_i = [
  ['x'],
  ['x'],
  ['x'],
  ['x']
  ]

tetro_s = [
  [' ', 'x', 'x'],
  ['x', 'x', ' ']
  ]

tetro_z = [
  ['x', 'x', ' '],
  [' ', 'x', 'x']
  ]

tetro_l = [
  ['x', ' ', ' '],
  ['x', 'x', 'x']
  ]

tetro_j = [
  [' ', ' ', 'x'],
  ['x', 'x', 'x']
  ]

tetro_t = [
  [' ', 'x', ' '],
  ['x', 'x', 'x']
  ]

These are the standard tetrominoes (rhymes with dominoes) for the game Tetris.

The next tutorial will cover drawing our new grids.

May 24th, 2008

As I promised, the next tutorial will support better user input. So let’s go.

We’re going to add three more keys to check for in our events. The 6, 4, and 2. Here’s the code:

# Add this line just above the while loop:
current_location = (0, 0)

# And this section is the new events section:
  if len(events) > 0:
    ...
    if events[0].unicode == u'6':
      current_location = (current_location[0]+1, current_location[1])
    if events[0].unicode == u'2':
      current_location = (current_location[0], current_location[1]+1)
    if events[0].unicode == u'4':
      current_location = (current_location[0]-1, current_location[1])

Let’s go through this line by line.

The first line is necessary because we’re going to be keeping track of the user’s current location. As you would expect, this is just the x and y coordinates, so it starts the user at the upper left corner:

current_location = (0, 0)

Next we’re adding some more if statements. One for each of the keys. Just as you would expect, we’re just checking for the unicode of the letter they press:

    if events[0].unicode == u'6':
      ...
    if events[0].unicode == u'4':
      ...
    if events[0].unicode == u'4':
      ...   

Then, for each of these statements, we want to perform a certain action. We want to change the current_location so that the rest of the program knows what the new location is, and can act accordingly:

    if events[0].unicode == u'6':
      current_location = (current_location[0]+1, current_location[1])
    if events[0].unicode == u'2':
      current_location = (current_location[0], current_location[1]+1)
    if events[0].unicode == u'4':
      current_location = (current_location[0]-1, current_location[1])

In the first case we’re adding 1 to the first coordinate. This is the mathematical way of saying “move right”.

current_location = (current_location[0]+1, current_location[1])

In the second case we’re adding 1 to the second coordinate. This is the mathematical way of saying “move down”.

current_location = (current_location[0], current_location[1]+1)

And finally, in the third case case we’re subtracting 1 from the first coordinate. This is the mathematical way of saying “move left”.

current_location = (current_location[0]-1, current_location[1])

To test it out, add a print statement at the end of all the if statements and you’ll see the location change as you press the keys:

    if events[0].unicode == u'6':
      current_location = (current_location[0]+1, current_location[1])
    if events[0].unicode == u'2':
      current_location = (current_location[0], current_location[1]+1)
    if events[0].unicode == u'4':
      current_location = (current_location[0]-1, current_location[1])
    print current_location

And this is what it prints:

[<Event(2-KeyDown {'key': 258, 'unicode': u'2', 'mod': 4096})>]
(0, 1)
[<Event(2-KeyDown {'key': 258, 'unicode': u'2', 'mod': 4096})>]
(0, 2)
[<Event(2-KeyDown {'key': 258, 'unicode': u'2', 'mod': 4096})>]
(0, 3)
[<Event(2-KeyDown {'key': 262, 'unicode': u'6', 'mod': 4096})>]
(1, 3)
[<Event(2-KeyDown {'key': 262, 'unicode': u'6', 'mod': 4096})>]
(2, 3)
[<Event(2-KeyDown {'key': 260, 'unicode': u'4', 'mod': 4096})>]
(1, 3)
[<Event(2-KeyDown {'key': 260, 'unicode': u'4', 'mod': 4096})>]
(0, 3)
[<Event(2-KeyDown {'key': 262, 'unicode': u'6', 'mod': 4096})>]

In the next tutorial we will draw the tetros onto our grid.

May 23rd, 2008

The core of every game has a game loop. Ours is no different. The loop will be a while loop:

run_game = True

while run_game:
  for x in range(5):
    for y in range(5):
      if grid[y][x] == 'x':
        screen.fill(red, (x*25, y*25, 25, 25))
      pygame.draw.rect(screen, white, (x*25, y*25, 25, 25), 1)
  pygame.display.flip()

Notice that we’ve taken the two for loops and put them inside the while loop. Also, we’ve put the flip command at the very end. As you probably already know, the two for loops and the flip command both combine to draw our entire screen. Now that we’ve put all the drawing code under the while loop, we are telling the computer that we want to draw the screen every time run_game == True, which, in this case, ends up being forever.

Not really forever, though. You can still force quit the program, which you will have to do for this particular bit of code. But let’s say we want to let the user quit more gracefully by hitting the 'q' button. Let’s start adding some code to do that.

First we need to add this to the very beginning of the file:

# Initialize the libraries
import pygame, time
from pygame.locals import *

You don’t need to know exactly what this does. Just know that it will give us all the different types of user input.

Next we want to start seeing what the user is actually doing. So adding this line at the end of the while loop.

while True:
  for x in range(5):
  ...
  ...
  events = pygame.event.get(KEYDOWN)
  print events

These two lines will give us the current user’s input for each run of the loop. Run this code and watch how fast your screen will fill up. Type some letters on the keyboard to get an idea how many times this loop is actually running each second.

[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[<Event(2-KeyDown {'key': 115, 'unicode': u's', 'mod': 4096})>]
[]
[]
[]
[]

So you’ll see that most of the time we didn’t get any input from the user, except for that one time I pressed the 's' key. You don’t need to worry about 'key' or 'mod' for the moment, but they might prove useful later.

Because we got all those empty lists, we should write some code to filter them out. That will help us make sure we have an actual event to look at at.

events = pygame.event.get(KEYDOWN)
# Only print out the events if we have more than zero
if len(events) > 0:
  print events

The len function is used to tell us the length of the event list. So this filter will give us lists that have more than zero items. Or, in other words, all lists that actually have contents.

Now try running the game again. You’ll see that the screen doesn’t get flooded nearly as fast:

[<Event(2-KeyDown {'key': 115, 'unicode': u's', 'mod': 4096})>]
[<Event(2-KeyDown {'key': 101, 'unicode': u'e', 'mod': 4096})>]
[<Event(2-KeyDown {'key': 103, 'unicode': u'g', 'mod': 4096})>]
[<Event(2-KeyDown {'key': 109, 'unicode': u'm', 'mod': 4096})>]
[<Event(2-KeyDown {'key': 118, 'unicode': u'v', 'mod': 4096})>]
[<Event(2-KeyDown {'key': 111, 'unicode': u'o', 'mod': 4096})>]
[<Event(2-KeyDown {'key': 119, 'unicode': u'w', 'mod': 4096})>]
[<Event(2-KeyDown {'key': 103, 'unicode': u'g', 'mod': 4096})>]
[<Event(2-KeyDown {'key': 104, 'unicode': u'h', 'mod': 4096})>]

So, as I promised, we will watch all these events and check for the letter 'q', and then quit. Here’s the code:

events = pygame.event.get(KEYDOWN)
if len(events) > 0:
  if events[0].unicode == u'q':
    run_game = False

Basically the if statement says that we want the unicode of first event. And if that unicode is a 'q', then we quit. Pygame gives us the unicode of any key we press. You can use unicodes of anything sequence of letters in Python by putting a 'u' in the front of it:

# One letter
u'a'
# Or a word
u'foobar'
# Or a phrase
u'This is unicode'

We’ll put the user input to better use in the next tutorial.

RSS Feed