Python Locale Heisenbugs

“Heisenbug” refers to a bug that only occurs under very specific circumstances. As I’ve been discovering this week, localization creates quite a few of these.

Localization is a tough problem to crack, and no language really gets it right. With the nearly infinite combinations of character encodings and regional formatting quirks, it’s nigh impossible to get it completely right. And as a result, there are always a few corners of each language that fail to work correctly under various locales.

Consider the following code:

try:
    with open(filename) as f:
        return f.read()
except IOError, ioerr:
    logger.error("Failed to open file: %s" % str(ioerr))

This has a few issues on a non-ascii system:
– it will not cope properly with the encoding of the file opened.
– it will not cope with any encoded characters in the error message from the system.

Here’s a good attempt at fixing it:

try:
    with codecs.open(filename, encoding="utf-8") as f:
        return f.read()
except IOError, ioerr:
    logger.error(u"Failed to open file: %s" % unicode(ioerr))

In reality, if an IOError is ever raised with a message containing encoded characters, the logging call fails, the exception propagates up the stack, and your program spews a traceback on stderr. This traceback, assuming it’s visible (and not redirected to /dev/null or similar) mentions only UnicodeDecodeError, not the original error opening the file.

What’s breaking? IOError.__unicode__ is implemented in a way that simply calls IOError.__str__ and attempts to convert the resulting string to unicode, using the ascii codec. So if the error message returned from the system is non-ascii, unicode() fails to convert it. Furthermore, unicode(), when called on an object, does not allow specifying a particular encoding.

So how do we fix this?

The intermediate fix, given that Linux systems use utf-8 encoding for error messages:

try:
    with codecs.open(filename, encoding="utf-8") as f:
        return f.read()
except IOError, ioerr:
    logger.error(u"Failed to open file: %s" % unicode(str(ioerr), 'utf-8'))

This works! …at least, until you try to run this code on a Windows machine. The standard encoding on Windows machines is not utf-8, and therefore we yet again get a UnicodeDecodeError.

The final, fixed code:

try:
    with codecs.open(filename, encoding="utf-8") as f:
        return f.read()
except IOError, ioerr:
    logger.error(u"Failed to open file: %s" % unicode(str(ioerr), sys.stderr.encoding))

Our best guess for the encoding in error messages is the encoding of stderr. In theory this may not be guarenteed, but this works across Linux and Windows, so we’ll call that good enough.

But wait, there’s more!

We’ve seen that messages from the system are suspect to encoding issues, but there are other instances where locale plays a critical but hidden role.

For example, take the function strftime(), a C function for formatting dates (taking locale into account). Python implements this via a method on date, time, and datetime objects.

Our initial, un-localized code:

>>> dt = datetime.datetime.now()
>>> print dt.strftime("%c")
Fri Apr 26 20:25:28 2013

The “%c” format specification formats the datetime according to the active locale, and returns a utf-8 encoded string. The following code will break, then, under any non-ascii locale:

logger.info(u"Timestamp: %s", dt.strftime("%c"))

When the datetime string is formatted into the unicode string, it is coerced to unicode by Python, using the ascii codec. As we’ve seen, this is a recipe for frustrating bugs.

Again, the solution is to specify the proper encoding:

logger.info(u"Timestamp: %s", unicode(dt.strftime("%c"), "utf-8"))

Complicating the issue, Python’s interpreter does not run under the system locale, instead running with the default ‘C’ locale — the result being that code that works in the interpreter may not necessarily work in a program:

>>> import datetime
>>> dt = datetime.datetime.now()
>>> print unicode(dt.strftime("%c"))
Fri Apr 26 20:51:00 2013
>>>
>>> import locale
>>> locale.setlocale(locale.LC_ALL, locale.getdefaultlocale())
'ja_JP.UTF-8'
>>> print unicode(dt.strftime("%c"))
Traceback (most recent call last):
  File "", line 1, in
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 4: ordinal not in range(128)

The lesson to be learned: If you care about localization, learn where your code is impacted by locale settings, and test against *non-ascii* locales. (No, “en_US.utf-8” doesn’t count). Python’s locale module makes this relatively straightforward.

Mocking for UI Controller Tests in PyQt4

What is Mocking?

Mocking is essentially taking a ‘heavy’ component — a UI element, database/file accessor — and swapping it out with a lighter component that only *pretends* to be the heavier component.  This is useful in cases where you don’t care about how the heavy component works, but need to test an object coupled tightly to it.  A unit test that does not require filesystem access or setting up a stack of GUI elements will run faster and possibly even give more relevant results.

UI Mocking

The main use for mocking is testing code coupled to the UI. This sometimes requires some design changes. For example, consider the following Qt4 window class:

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__(self)

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.actionLoad.triggered.connect(self.fileLoad)
        self.ui.actionSave.triggered.connect(self.fileSave)
        self.ui.actionExit.triggered.connect(self.fileExit)
        # Other triggers...
    # Logic code...

This class cannot easily be tested — any QtGui object requires that the entire application base be created first. This incurs serious overhead for very little gain — we’re interested in testing the logic code, not the rendering.

The solution is to break our logic out into a separate object. Similarly to the UI itself, both the window and the controller need to hold references to the other so that signals can be routed properly.

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__(self)

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.controller = MainWindowController(self)
        self.ui.actionLoad.triggered.connect(self.controller.fileLoad)
        self.ui.actionSave.triggered.connect(self.controller.fileSave)
        self.ui.actionExit.triggered.connect(self.controller.fileExit)
    #...

class MainWindowController(object):
    def __init__(self, window):
        self.window = window

    def fileLoad(self):
        #....

The window can route signals from the UI through the controller reference, while the controller can manipulate the UI through the window.ui reference. Furthermore, we can now test the MainWindowController class by passing in a mock of the MainWindow class, eliminating the overhead of setting up the GUI.

So what do these mocks look like? Here are some examples from my current project:

class UiMock(object):

    def __init__(self):
        pass

class WindowMock(object):

    def __init__(self):
        super(WindowMock, self).__init__()
        self.ui = UiMock()

class ButtonGroupMock(object):

    def __init__(self, buttons):
        super(ButtonGroupMock, self).__init__()
        self.buttons = buttons

    def checkedButton(self):
        for button in self.buttons:
            if button.isChecked():
                return button
        else:
            return 0

class RadioButtonMock(object):

    def __init__(self):
        super(RadioButtonMock, self).__init__()
        self.checked = False

    def isChecked(self):
        return self.checked

These, obviously, don’t emulate the objects they’re mocking very deeply. This is intentional — in keeping with the spirit of TDD, we only need to mock the interfaces we actually use in our code. The ButtonGroupMock doesn’t worry about exclusivity, for example, as I’ve not needed it in my tests yet, and UiMock is just an empty namespace. The dynamic nature of Python means that I need only create enough interface to fool the calling code, and no more. (Mocking in static languages, such as Java, is much more involved.)

As a result, our test setup looks something like this:

class TestMainWindowController(unittest.TestCase):
    def setUp(self):
        win_mock = WindowMock()
        
        win_mock.ui.radio_button = RadioButtonMock()
        win_mock.ui.check_box = CheckboxMock()
        # etc...

Hopefully this makes testing UI just a little simpler

Building Tetris in Pygame – Part V

# 009 Spawning blocks

Last time we got our basic display code up and running, but before we can go any further we need a way to spawn pieces.

Let’s switch back to our game_state git branch:

$ git status
# On branch display
nothing to commit (working directory clean)
$ git checkout master
Switched to branch 'master'
$ git merge display
Updating d2c7908..e4bcf30
Fast-forward
 tetris.py       |   36 +++++++++++++++++++++++++++++++++++-
 tetris/const.py |   29 +++++++++++++++++++++++++++++
 2 files changed, 64 insertions(+), 1 deletion(-)
$ git checkout game_state
Switched to branch 'game_state'
$ git merge master
Updating d2c7908..e4bcf30
Fast-forward
 tetris.py       |   36 +++++++++++++++++++++++++++++++++++-
 tetris/const.py |   29 +++++++++++++++++++++++++++++
 2 files changed, 64 insertions(+), 1 deletion(-)
$ git status
# On branch game_state
nothing to commit (working directory clean)

When we left off last, GameState still had no way of tracking the coordinates of the current piece. As always, test first:

tests/test_state.py

    # ...
    def test_spawn_piece_coords(self):
        self.state.spawn_piece()

        self.assertEquals(self.state.piece_x, 4)
        self.assertEquals(self.state.piece_y, 0)

(This test case may not be valid when we move up to the full tetriminos, but for now it will do.)

Add the test to run_tests.py, and run the test suite:

$ ./run_tests.py
Running tests:
STATE:      .....E

------------

In test_spawn_piece_coords (tests.test_state.TestGameState)
Traceback (most recent call last):
  File ".../tetris/tests/test_state.py", line 58, in test_spawn_piece_coords
    self.assertEquals(self.state.piece_x, 4)
AttributeError: 'GameState' object has no attribute 'piece_x'

------------

6 tests run in 0.000673 seconds, 0 failures, 0 skipped, 1 unexpected errors.

This is a pretty simple test to pass:

    # ...
    def spawn_piece(self):
        if self.next_piece == None:
            self.next_piece = self.get_new_piece()

        self.curr_piece = self.next_piece
        self.next_piece = self.get_new_piece()

        self.piece_x = 4
        self.piece_y = 0

This is… getting ugly. I’ll need to keep this section in mind for later cleanup and refactoring. For now, though, we have more pressing problems.

Even if we modify our main function to call spawn_piece(), we won’t get any blocks on screen, because we don’t actually draw the active piece! Oops.

Let’s switch back over to the display branch and fix that:

$ git add .
$ git status
...
$ git commit -m "Spawn piece at coordinates"
$ git checkout master
Switched to branch 'master'
$ git merge game_state
Updating e4bcf30..44833c8
Fast-forward
 run_tests.py        |    1 +
 tests/test_state.py |    6 ++++++
 tetris/state.py     |    3 +++
 3 files changed, 10 insertions(+)
$ git checkout display
Switched to branch 'display'
$ git merge master
Updating e4bcf30..44833c8
Fast-forward
 run_tests.py        |    1 +
 tests/test_state.py |    6 ++++++
 tetris/state.py     |    3 +++
 3 files changed, 10 insertions(+)

First, a quick addition to main() so that we get a piece spawned:

def main():
    # ...
    while True:
        # ...
        # Game logic
        if game_state.curr_piece == None:
            game_state.spawn_piece()

        # Display
        draw_screen(displaysurf, game_state)

Now let’s add code for drawing the current piece:
tetris.py

    # ... in draw_screen()

    # Draw current piece:
    curr_left = const.MARGIN_LEFT + (game_state.piece_x * const.BLOCK_SIZE)
    curr_top = const.MARGIN_TOP + (game_state.piece_y * const.BLOCK_SIZE)
    curr_color = const.C_LIST[game_state.curr_piece[0][0]]

    pygame.draw.rect(surface, curr_color,
        (curr_left, curr_top, const.BLOCK_SIZE, const.BLOCK_SIZE))

    pygame.display.flip()

Run the game, and you should get a random colored block at the top of the well.

Finally, at long last, we’re ready to work on the actual game logic — moving pieces and afixing them to the board once they land. We will work on that next time, but for now we will get everything commited to git:

$ git add .
$ git status
...
$ git commit -m "Draw current piece."
 [display 3965dcd] Draw current piece.
 2 files changed, 15 insertions(+), 5 deletions(-)
$ git checkout master
Switched to branch 'master'
$ git merge display
Updating 44833c8..3965dcd
Fast-forward
 tetris.py       |   14 ++++++++++++--
 tetris/const.py |    6 +++---
 2 files changed, 15 insertions(+), 5 deletions(-)

Building Tetris in Pygame – Part IV

# 007 Tetris for Idiots

While it might seem that the next goal is to implement the various tetriminoes, I am going to leave that for later. Instead, I’m first going to work on “Tetris for Idiots”, that is, all pieces as a single 1×1 block. This is in keeping with the “smallest possible iteration” component of TDD — get a simplified version running before refactoring and filling in the full feature set. (This is sometimes called a “vertical slice”, in that we build the smallest possible part of each layer of the program from low-level event handling to high-level UI.)

Keeping that in mind, our state object needs a few more methods. As always, tests first:

tests/test_state.py

class TestGameState(unittest.TestCase):

    # ...

    def test_spawn_piece_valid_piece(self):
        # Freshly initiated, no pieces exist yet.
        self.assertEquals(self.state.next_piece, None)
        self.assertEquals(self.state.curr_piece, None)

        self.state.spawn_piece()

        # Check piece is valid (contains number 1-7)
        self.assertIn(self.state.next_piece[0][0], range(1,8))
        self.assertIn(self.state.curr_piece[0][0], range(1,8))

    def test_spawn_piece_get_correct_next(self):
        # Seed our pieces
        self.state.spawn_piece()
        expect_next = self.state.next_piece

        # Get our next piece -- did we get the right one?
        self.state.spawn_piece()
        self.assertEquals(self.state.curr_piece, expect_next)

The algorithm behind new pieces is simple, but more complex than what we’ve done so far:

1. Check that next_piece exists.
2. If it does:
– curr_piece = next_piece
– Create new next_piece
3. If it doesn’t:
– Create new curr_piece and next_piece

Pieces will be chosen randomly from a list.

tetris/state.py

import random

from tetris import const

class GameState(object):
    """Object to store the current state of a Tetris game."""
    def __init__(self):
        super(GameState, self).__init__()
        self.init_well()
        self.score = 0
        self.level = 1
        self.curr_piece = None
        self.next_piece = None

    # ...

    def get_new_piece(self):
        return [[random.choice(range(1,8))]]

    def spawn_piece(self):
        if self.next_piece == None:
            self.next_piece = self.get_new_piece()

        self.curr_piece = self.next_piece
        self.next_piece = self.get_new_piece()

Tests passing:

$ ./run_tests.py
Running tests:
STATE:      .....

------------

5 tests run in 0.000418 seconds, 0 failures, 0 skipped, 0 unexpected errors.
$ git add .
$ git status
...
$ git commit -m "Pieces implemented for 1x1 version"

Note that randomized functions like these can potentially pass their unit tests regardless of bugs (if I mistakenly enter range(1,9), it can take 10+ iterations to catch the bug). We could catch them more quickly by repeating the generate and check block, however as our test suite grows that would cause a bottleneck. Instead, we’ll leave it be — over the course of development the test suite should be run often enough to isolate any rare errors.

The other potential problem is distribution — assuring we get an even mix of pieces. At the moment, with our 1×1 pieces, this is not an issue, so we’ll put that aside for the moment.

With the basic GameState implementation now fleshed out, we can turn our focus elsewhere — the game screen.

# 008 Display

First, we’ll check in our game_state branch in git:

$ git status
# On branch game_state
nothing to commit (working directory clean)
$ git checkout master
Switched to branch 'master'
$ git merge game_state
Updating 55485a3..d2c7908
Fast-forward
 run_tests.py        |    9 +++++++-
 tests/test_state.py |   58 +++++++++++++++++++++++++++++++++++++++++++++++++++
 tetris/const.py     |    7 +++++++
 tetris/state.py     |   38 +++++++++++++++++++++++++++++++++
 4 files changed, 111 insertions(+), 1 deletion(-)
 create mode 100644 tests/test_state.py
 create mode 100644 tetris/const.py
 create mode 100644 tetris/state.py

Next, we need to check out a new branch for our display code:

$ git branch display
$ git checkout display
Switched to branch 'display'

To start, a few edits to our main function — capturing our display surface from .set_mode() and using our GameState class:

tetris.py

def main():
    pygame.init()
    displaysurf = pygame.display.set_mode((const.SCR_W, const.SCR_H))
    game_state = state.GameState()

    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                terminate()

        # Game logic

        # Display
        draw_screen(displaysurf, game_state)

(Again, I’m not writing tests for UI and display.)

Our display code is going to require a bunch of new constants, so let’s go define those now:

tetris/const.py

# ...
BLOCK_SIZE = 20
WELL_PX_W = WELL_W * BLOCK_SIZE
WELL_PX_H = WELL_H * BLOCK_SIZE

MARGIN_LEFT = 50
MARGIN_TOP  = 50

C_BLACK = (  0,   0,   0)
C_DGRAY = ( 20,  20,  20)
C_LGRAY = (125, 125, 125)
C_WHITE = (255, 255, 255)

C_CYAN   = (  0, 255, 255)
C_BLUE   = (  0,   0, 255)
C_ORANGE = (255, 128,   0)
C_YELLOW = (255, 255,   0)
C_GREEN  = (  0, 255,   0)
C_PURPLE = (200,   0, 200)
C_RED    = (255,   0,   0)

C_LIST = [C_BLACK,
          C_CYAN,
          C_BLUE,
          C_ORANGE,
          C_YELLOW,
          C_GREEN,
          C_PURPLE,
          C_RED,]

Most of this is self-explanatory.

The first run at draw_screen():

tetris.py

def draw_screen(surface, game_state):
    surface.fill(const.C_BLACK)

    # Draw well (with border)
    pygame.draw.rect(surface, const.C_LGRAY, (const.MARGIN_LEFT - 5, const.MARGIN_TOP - 5,
                                              const.WELL_PX_W + 10, const.WELL_PX_H + 10))
    pygame.draw.rect(surface, const.C_DGRAY, (const.MARGIN_LEFT, const.MARGIN_TOP,
                                              const.WELL_PX_W, const.WELL_PX_H))

    # Draw blocks:
    y = 0
    while y < const.WELL_H:
        x = 0
        while x < const.WELL_W:
            curr = game_state.well[y][x]
            if curr != 0:
                current_color = const.C_LIST[curr]
                pygame.draw.rect(surface, current_color,
                        (const.MARGIN_LEFT + x * const.BLOCK_SIZE,
                         const.MARGIN_TOP + y * const.BLOCK_SIZE,
                         const.BLOCK_SIZE, const.BLOCK_SIZE))
            x += 1
        y += 1

    pygame.display.flip()

This requires a bit of explanation.

Filling the window and drawing the well are fairly straightforward (though note the third argument — Pygame Rects are tuples of (left, top, width, height)).

What may seem odd is our code for drawing blocks on screen.

I am intentionally not using for loops. For loops in Python are inefficient. Were I to use range(const.WELL_H), the interpreter would allocate a list of all numbers in the range. xrange is more efficient for memory, but incurs function call overhead on each iteration. In many cases, this inefficiency is irrelevant (such as the initialization of the well structure), but we want display code to run as quickly possible.

The C_LIST list avoids a long if/elif tree of all possible block colors. Indexing a list is also slightly cheaper than evaluating 7 booleans, but keeping the code concise is the main motivation.

We now have a problem — we have display code, but no way yet to put a block on screen, and therefore no way to test the block display code. Next time, we’ll fix that.

Today’s git commit:

$ git add .
$ git status
...
$ git commit -m "Basic display code"

Building Tetris in Pygame – Part III

# 005 Actual Code

Having just talked about testing, we’re going to take a short break and get a window on the screen. I’m not writing unit tests for this — it’s theoretically possible, but in the end simply running the program is a simpler way to find out if it works.

tetris.py:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import pygame

from pygame.locals import *

def terminate(error=0):
    pygame.quit()
    sys.exit(error)

def main():
    pygame.init()
    pygame.display.set_mode((400, 500))

    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                terminate()

if __name__ == '__main__':
    main()

This puts a blank, black window on screen until the user closes it. Not terribly impressive, but we have to start somewhere. Note that I’ve broken the cleanup code down into a separate terminate() function. While the interpreter will clean up after you when you exit, it’s still good style to clean up as many resources as you can before closing. This way, we can quit out easily from anywhere, without worrying about cleaning up perfectly in each instance.

Run it, and we get a window. Yay. (If you don’t, go find out why.)

Now lets write something a bit more complicated:

# 006 The GameState Object

We talked about the GameState object last time, let’s work on implementing it. To start with, I’ll test something easy, the dimensions of the well.

First, we’ll commit our previous changes and set up a new branch:

$ git add tetris.py
$ git status
...
$ git commit -m "Basic event loop"
$ git branch game_state
$ git checkout game_state
Switched to branch 'game_state'

Now we can write our test:

tests/test_state.py

# -*- coding: utf-8 -*-

import unittest

from tetris import state, const

class TestGameState(unittest.TestCase):

    def setUp(self):
        super(TestGameState, self).setUp()
        self.state = state.GameState()

    def test_init_well(self):
        self.assertEquals(len(self.state.well), const.WELL_H)

        for row in self.state.well:
            self.assertEquals(len(row), const.WELL_W)

There are a few points to note:

1. I’m importing two separate modules — tetris.const is going to only hold what would otherwise be global constants. Putting them in a namespace makes things that much cleaner. (We’ll abstract out the screen size here as well.)
2. I’m not testing the well with a 10×20 grid and .assertEquals. That’s typo-prone, hard to adjust if the spec changes, and a lot more work.
3. GameState.init_well() will be called on the initialization of the GameState object. There’s no need for it at any other time, so we can assume it’s not meant to be called by external code.

Let’s plug it into the test framework and check that the test fails:

run_tests.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from lib.test_runner import ModuleTestRunner

from tests.test_state import TestGameState

suite = ModuleTestRunner()

suite.addTestList("State", [TestGameState('test_init_well'),
                            ])

if __name__ == '__main__':
    suite.run()

Create tetris/state.py and tetris/const.py, just to shortcut one round of errors, and run our test suite:

$ touch tetris/state.py tetris/const.py
$ ./run_tests.py
Running tests:
STATE:      E

------------

In test_init_well (tests.test_state.TestGameState)
Traceback (most recent call last):
  File ".../tetris/tests/test_state.py", line 12, in setUp
    self.state = state.GameState()
AttributeError: 'module' object has no attribute 'GameState'

------------

1 tests run in 0.000260 seconds, 0 failures, 0 skipped, 1 unexpected errors.

Imagine that, our empty files are not enough to pass.

This is not a treatise on TDD, and this post is going to be long already, so I’m not going to show all my iterations and will just skip ahead to the code that passes:

tetris/const.py

# -*- coding: utf-8 -*-

SCR_W = 400
SCR_H = 500

WELL_W = 10
WELL_H = 20

tetris/state.py

# -*- coding: utf-8 -*-

from tetris import const

class GameState(object):
    """Object to store the current state of a Tetris game."""
    def __init__(self):
        super(GameState, self).__init__()
        self.init_well()

    def init_well(self):
        self.well = []
        for y in xrange(const.WELL_H):
            row = []
            for x in xrange(const.WELL_W):
                row.append(0)
            self.well.append(row)

Passing test and git commit:

$ ./run_tests.py
Running tests:
STATE:      .

------------

1 tests run in 0.000146 seconds, 0 failures, 0 skipped, 0 unexpected errors.
$ git add .
$ git status
...
$ git commit -m "Begin implementation of GameState class, init_well"

(Yes, commit after *every* red/green cycle. Local commit histories are easily cleaned up before a push, and it will make rolling back mistakes that much easier.)

Next, let’s set up our scoring and levels. Modern Tetris games have a variety of different scoring algorithms, with bonuses for clearing multiple lines. For now, we’re going to keep it simple: one point per line cleared, and a level up every 10 points.

Again, starting with some basic tests:

tests/test_state.py

  # ... in TestGameState
    def test_init_score_level(self):
        self.assertEquals(self.state.score, 0)
        self.assertEquals(self.state.level, 1)

    def test_line_cleared(self):
        self.state.line_cleared()
        self.assertEquals(self.state.score, 1)
        self.assertEquals(self.state.level, 1)

        for i in xrange(10):
            self.state.line_cleared()

        self.assertEquals(self.state.score, 11)
        self.assertEquals(self.state.level, 2)

(Don’t forget to add the tests to run_tests.py)

Passing these tests is simple:

class GameState(object):
    """Object to store the current state of a Tetris game."""
    def __init__(self):
        super(GameState, self).__init__()
        self.init_well()
        self.score = 0
        self.level = 1
  # ...

    def line_cleared(self):
        self.score += 1
        if self.score % 10 == 0 and self.score > 0:
            self.level += 1

Another test run and git commit…

$ ./run_tests.py
Running tests:
STATE:      ...

------------

3 tests run in 0.000270 seconds, 0 failures, 0 skipped, 0 unexpected errors.
$ git add .
$ git status
# On branch game_state
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   run_tests.py
# modified:   tests/test_state.py
# modified:   tetris/state.py
#
$ git commit -m "GameState score/level updates"

There is more to be done with GameState, so we will be back to it next time.

Building Tetris in Pygame – Part II

# 003 Data Structures

Before we delve any deeper, it’s a good idea to stop and think about the data structures we’ll need to manage our game.

Ignoring UI cruft for now, the core of Tetris is a 10×20 well, and seven tetriminoes. For the sake of simplicity, it’s probably easiest to hold this in 2D arrays of numbers, like so:

well = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    # 19 more rows...
]

S_piece = [
  [
    [1, 0],
    [1, 1],
    [0, 1],
  ],
  [
    [0, 1, 1],
    [1, 1, 0],
  ],
]

Within this grid, a 0 is empty space, while a number 1-7 is a block. By giving each tetrimino a different number for its blocks, we will be able to easily track which type of tetrimino a given block came from, and will be able to color it appropriately.

In addition, there is a fair amount of other data we must keep track of:

  • Current score
  • Current level
  • The current piece, and its coordinates
  • The next piece

The simplest solution I see is a GameState object containing the well, score, level, and references to our current and next pieces. The tetriminoes themselves will have a module of their own.

And with that, we have an idea of where to begin.

# 004 On Testing…

If you’re following along and are not familiar with Test-Driven Development(TDD), take the time now to go read up on it, both its support and criticism.

TDD is a topic that inspires holy wars on the same scale as OS preferences and static/dynamic typing. I don’t have a particularly strong opinion either way. However, TDD does help you catch stupid mistakes earlier, and encourages tidy code, so I’m going to use it for this project.

We’ll be using Python’s builtin unittest module for writing our test cases. To actually run the test suite, there are plenty of options available — nosetests, py.test, etc. I use a custom framework (details, for those interested, in another post), and will be building my tests accordingly. Those of you following along are, of course, free to use whatever you want.

To start, we’ll write a sanity check test to get our framework up and running:

# tests/test_sanity.py

import unittest

class TestSanity(unittest.TestCase):

    def test_fail(self):
        self.assertTrue(False)

    def test_pass(self):
        self.assertTrue(True)

Now, to actually run these tests with our framework, we need to add them to run_tests.py:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from lib.test_runner import ModuleTestRunner

from tests.test_sanity import TestSanity

suite = ModuleTestRunner()

suite.addTestList("Sanity", [TestSanity("test_fail"),
                             TestSanity("test_pass"),
                             ])

if __name__ == '__main__':
    suite.run()

Finally, we run the tests in the terminal:

$ chmod +x run_tests.py
$ ./run_tests.py
Running tests:
SANITY:     F.

------------

In test_fail (tests.test_sanity.TestSanity)
Traceback (most recent call last):
  File ".../tetris/tests/test_sanity.py", line 8, in test_fail
    self.assertTrue(False)
AssertionError: False is not true

------------

2 tests run in 0.000280 seconds, 1 failures, 0 skipped, 0 unexpected errors.

(Note: chmod +x makes run_tests.py executable, so we don’t have to prefix ‘python’ every time we want to run the suite.)

One failure, one pass, and our test suite is properly set up. Remove/comment out the import and references to TestSanity in run_tests.py. (If you’re using nosetests or another test-discovery framework, you will probably want to delete or rename test_sanity.py.)

Git commit:

$ git add .
$ git status
...
$ git commit -m "Setup test framework"

This post is getting long, so we’ll stop here for today. Next time we’ll get a basic window on screen, and start implementing the data structures mentioned earlier.

Building Tetris in Pygame – Part I

# 000 Introduction

My interest in programming grew from playing video games as a kid. I don’t think I’m at all unique in that respect. So it’s natural that when I learned to program, one of the first things I wanted to do was write a game. Lately I’ve been using the Pygame framework to move up from the terminal to graphics.

There are a lot of Pygame tutorials out there. However, by and large, they all show signs of serious code smell — abuse of globals, magic numbers, etc. Since these are generally short one-file projects that’s not a problem, but it quickly becomes an issue if you want to scale up.

I want to attempt to build games using solid practices — clean namespaces, TDD-style unit testing, and so on. This will be a “live” series; when I inevitably screw something up, I’ll show how I work around it. While this isn’t necessarily a tutorial per se, feel free to follow along. (Some knowledge of basic programming and Python syntax is recommended. Zed Shaw’s Learn Python The Hard Way should get you up to speed should you need it.)

We’ll start with a Tetris clone. Tetris is fairly complex, with plenty of common game issues (controlling speed, collision detection) without being obscenely hard. It’s also a highly addicting game, and what better way to celebrate when we’re done than a six-hour gaming binge? Continue reading

Using Nested Functions for Cleaner Recursion

Python is not a particularly friendly language for recursion — a finite stack and significant overhead on function calls often make alternate implementations more desirable. However, there are some problems that are difficult to handle gracefully any other way — tree structures, for example — and other instances where clarity is preferable to performance. They do still have a place, and where we use them we may as well try to make them as clean and elegant as possible. Fortunately, Python makes this rather easy.
Continue reading