Richard Jones' Log

Wed, 15 Sep 2004
PyGame sample: drawing a map, and moving around it

I'm re-acquainting myself with PyGame prior to the next 48-hour game programming comp (yes, I'm probably going to be hosting it). I decided to start with something simple that I'd never actually gotten around to writing: a scrollable map that you navigate by clicking around it. So, here's:

  1. — the source that does the work,
  2. tiles.png — map tile image source (with variants for most tiles),
  3. map.txt — map layout text file, and
  4. grid.png — 64x64 grid image, handy for using as a backdrop layer when drawing the map tiles.

The core of the code is the Map class:

# define where to get the tile images from in the tiles source image
tile_coords = {
    'a': (0,0),
    'b': [(64,0), (64, 128)],
    'c': (128,0),
    'd': [(0,64), (128, 64)],
    'e': (0,128),
    'f': (128,128),
    '.': None,

class Map:
    def __init__(self, map, tiles):
        self.tiles = pygame.image.load(tiles)

        l = [line.strip() for line in open(map).readlines()] = [[None]*len(l[0]) for j in range(len(l))]
        for i in range(len(l[0])):
            for j in range(len(l)):
                tile = l[j][i]
                tile = tile_coords[tile]
                if tile is None:
                elif isinstance(tile, type([])):
                    tile = random.choice(tile)
                cx, cy = tile
                if random.choice((0,1)):
                    cx += 192
                if random.choice((0,1)):
                    cy += 192
      [j][i] = (cx, cy)

    def draw(self, view, viewpos):
        '''Draw the map to the "view" with the top-left of "view" being at
           "viewpos" in the map.
        sx, sy = view.get_size()
        bx = viewpos[0]/64
        by = viewpos[1]/64
        for x in range(0, sx+64, 64):
            i = x/64 + bx
            for y in range(0, sy+64, 64):
                j = y/64 + by
                    tile =[j][i]
                except IndexError:
                    # too close to the edge
                if tile is None:
                cx, cy = tile
                view.blit(self.tiles, (x, y), (cx, cy, 64, 64))

    def limit(self, view, pos):
        '''Limit the "viewpos" variable such that it defines a valid top-left
           rectangle of "view"'s size over the map.
        x, y = pos
        # easy
        x = max(x, 0)
        y = max(y, 0)

        # figure number of tiles in a view, hence max x and y viewpos
        sx, sy = view.get_size()
        nx, ny = sx/64, sy/64
        mx = (len([0]) - nx) * 64
        my = (len( - ny) * 64
        print y, my

        return (min(x, mx), min(y, my))

To do:

  1. re-write the limit() method to use pygame's built-in Rect objects to do the clamping,
  2. make it smoother (currently moving around is done in 64 pixel jumps, and constant movement would be nice), and
  3. make it do less work (currently it re-draws the whole map each time - we should be able to blit() some of the existing map when we re-draw)
Mon, 13 Sep 2004
Fixing POSKeyErrors in Zope2.7

There's instructions at ZopeLab's cookbook on how to get in and fix POSKeyErrors. In my case, the objects are relatively anonymous BTrees, so finding them in order to delete them proves to be quite ... challenging. The following allows me to just pluck the offending object (as reported by from the pickle jar, and have a good look at it:

>>> from import configure;configure('zope-8000/zope.conf')
>>> from Zope import app; root = app()
>>> from ZODB.POSException import POSKeyError
>>> from ZODB.utils import p64
>>> o = root._p_jar[p64(0x277FEL)]
>>> o
OOBucket([('566', -1920968609), ...])

Note the configure line - that's necessary in Zope 2.7. Now, from the structure of the tree, I can hopefully determine where it might fit into our application (which makes heavy use of BTrees - something I'm starting to regret, with all the POSKeyErrors I'm pretty much constantly getting.

(yes, Zope's keeping me pretty busy these days)