changeset 8:ca34c14ea3a8

Files, resources, objects
author Radomir Dopieralski <sheep-devel@sheep.art.pl>
date Fri, 29 Jun 2012 23:06:48 +0200
parents 2a0f43c87b80
children e9c19e1e630e
files 004/000/__init__.py 004/000/__main__.py 004/000/control.png 004/000/effects.png 004/000/game.py 004/000/level.py 004/000/level.tmx 004/000/pytmx/__init__.py 004/000/pytmx/constants.py 004/000/pytmx/pytmx.py 004/000/pytmx/tmxloader.py 004/000/pytmx/tmxloader3.py 004/000/pytmx/utils.py 004/000/sprites.py 004/000/spritesheet.png 004/000/tilesheet.png 004/001/__init__.py 004/001/__main__.py 004/001/game.py 004/001/level.py 004/001/pytmx/__init__.py 004/001/pytmx/constants.py 004/001/pytmx/pytmx.py 004/001/pytmx/tmxloader.py 004/001/pytmx/tmxloader3.py 004/001/pytmx/utils.py 004/001/resources.py 004/001/resources/control.png 004/001/resources/effects.png 004/001/resources/level.tmx 004/001/resources/spritesheet.png 004/001/resources/tilesheet.png 004/001/sprites.py 004/002/__init__.py 004/002/__main__.py 004/002/game.py 004/002/level.py 004/002/pytmx/__init__.py 004/002/pytmx/constants.py 004/002/pytmx/pytmx.py 004/002/pytmx/tmxloader.py 004/002/pytmx/tmxloader3.py 004/002/pytmx/utils.py 004/002/resources.py 004/002/resources/control.png 004/002/resources/effects.png 004/002/resources/objects.png 004/002/resources/spritesheet.png 004/002/resources/tilesheet.png 004/002/sprites.py 004/003/__init__.py 004/003/__main__.py 004/003/game.py 004/003/level.py 004/003/pytmx/__init__.py 004/003/pytmx/constants.py 004/003/pytmx/pytmx.py 004/003/pytmx/tmxloader.py 004/003/pytmx/tmxloader3.py 004/003/pytmx/utils.py 004/003/resources.py 004/003/resources/control.png 004/003/resources/effects.png 004/003/resources/level.tmx 004/003/resources/objects.png 004/003/resources/spritesheet.png 004/003/resources/tilesheet.png 004/003/sprites.py
diffstat 64 files changed, 8054 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/000/__main__.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from game import Game
+
+
+game = Game()
+game.load_level('level.tmx')
+game.run()
Binary file 004/000/control.png has changed
Binary file 004/000/effects.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/000/game.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import pygame
+import pygame.locals as pg
+
+import level
+import sprites
+
+
+class Game(object):
+    def __init__(self):
+        pygame.display.init()
+        pygame.font.init()
+        self.screen = pygame.display.set_mode((800, 600))
+        self.screen.fill((0x66, 0x66, 0x66))
+        self.keys = {
+            pg.K_UP: (self.move_hero, (0, -1)),
+            pg.K_DOWN: (self.move_hero, (0, 1)),
+            pg.K_LEFT: (self.move_hero, (-1, 0)),
+            pg.K_RIGHT: (self.move_hero, (1, 0)),
+            pg.K_ESCAPE: (self.quit,),
+        }
+        self.shadow_sprites = pygame.sprite.RenderUpdates()
+        self.sprites = pygame.sprite.RenderUpdates()
+        self.key = None
+
+    def load_level(self, filename):
+        self.level = level.Level(filename)
+        self.hero_sprite = sprites.HeroSprite(self.level.hero_position)
+        self.sprites.add(self.hero_sprite)
+        self.shadow_sprites.add(sprites.ShadowSprite(self.hero_sprite))
+        self.background = self.screen.copy()
+        self.level.render(self.background)
+        self.screen.blit(self.background, (0, 0))
+        self.sprites.draw(self.screen)
+        pygame.display.update()
+
+    def quit(self):
+        self.game_over = True
+
+    def move_hero(self, direction):
+        if self.level.move_hero(direction):
+            self.hero_sprite.move(direction)
+
+    def control(self):
+        for event in pygame.event.get():
+            if event.type == pg.QUIT:
+                self.quit()
+            elif event.type == pg.KEYDOWN:
+                self.key = event.key
+        if not self.hero_sprite.animations:
+            keys = pygame.key.get_pressed()
+            for key, action in self.keys.iteritems():
+                if keys[key] or self.key == key:
+                    action[0](*action[1:])
+                    break
+            self.key = None
+
+    def display(self):
+        self.sprites.clear(self.screen, self.background)
+        self.shadow_sprites.clear(self.screen, self.background)
+        self.sprites.update()
+        self.shadow_sprites.update()
+        dirty = []
+        dirty.extend(self.shadow_sprites.draw(self.screen))
+        dirty.extend(self.sprites.draw(self.screen))
+        pygame.display.update(dirty)
+
+    def run(self):
+        self.clock = pygame.time.Clock()
+        self.game_over = False
+        while not self.game_over:
+            self.display()
+            self.control()
+            self.clock.tick(30)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/000/level.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+from pytmx import tmxloader
+
+
+class Level(object):
+    def __init__(self, filename):
+        self.tiled_map = tmxloader.load_pygame(filename)
+        self.hero_position = 1, 1
+
+    def render(self, screen):
+        w = self.tiled_map.tilewidth
+        h = self.tiled_map.tileheight
+        get_image = self.tiled_map.getTileImage
+        for l, layer in enumerate(self.tiled_map.tilelayers):
+            for y in xrange(self.tiled_map.height):
+                for x in xrange(self.tiled_map.width):
+                    image = get_image(x, y, l)
+                    if image:
+                        screen.blit(image, (x * w, y * h))
+        x, y = self.hero_position
+
+    def get_properties(self, (x, y)):
+        properties = {}
+        for l, layer in enumerate(self.tiled_map.tilelayers):
+            properties.update(self.tiled_map.getTileProperties((x, y, l)) or {})
+        return properties
+
+    def is_blocked(self, (x, y)):
+        return self.get_properties((x, y)).get('block')
+
+    def move_hero(self, (dx, dy)):
+        x, y = self.hero_position
+        if not self.is_blocked((x + dx, y + dy)):
+            self.hero_position = x + dx, y + dy
+            return True
+        return False
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/000/level.tmx	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" width="25" height="18" tilewidth="32" tileheight="32">
+ <tileset firstgid="1" name="control" tilewidth="32" tileheight="32">
+  <properties>
+   <property name="control" value="1"/>
+  </properties>
+  <image source="control.png" trans="ff00ff" width="64" height="32"/>
+  <tile id="0">
+   <properties>
+    <property name="block" value="1"/>
+   </properties>
+  </tile>
+  <tile id="1">
+   <properties>
+    <property name="block" value="1"/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="3" name="tilesheet" tilewidth="32" tileheight="32">
+  <image source="tilesheet.png" trans="ff00ff" width="288" height="192"/>
+ </tileset>
+ <layer name="Control" width="25" height="18">
+  <data encoding="base64" compression="zlib">
+   eJxjZGBgYKQDpiUY7nYwUYBH7Ri1Y9SOoWEHNcFws4PWGABJRwF/
+  </data>
+ </layer>
+ <layer name="Background" width="25" height="18">
+  <data encoding="base64" compression="zlib">
+   eJyTZ2BgkKcDVqIhHu52UDPcR6Id+MJ61A762zFc0hW17aBHWTJU7aA1BgDmfzlJ
+  </data>
+ </layer>
+ <layer name="Corners" width="25" height="18">
+  <data encoding="base64" compression="zlib">
+   eJy1VdtqwzAMFWQbFHaBwNq1DyFhD92lD7Zr//+v1WY65ERTRyFMIOzKro6OJCtJRJL8SNA1qi3KLNG5x8L/xd1E+7uqH/+g94Q7OnF1tM/mrNBanDt91XPVQeZcjHT/tep2he4Ia9A1EI9i7EJ7rk3642yk/UB+rN1KvNEGX4+Or2YveoYcIta2f6u6d/RAPsDnVh5BMXA+mVizxvRu4mFfmeJFPc4OP+RjonPwFcXge168ydiBLbJ8g5P63hAOYzw4voABns3ey9xXQZb8OVdFOWXFsPMAGB3xQJ3whry++qr67ehJzzE/PB4Wm+0v9H87n7jO3BuoLXLqYXTyu+acMwj7tdjwhXeAvDOPo6ybg5+y7FGuB3qlSSa+ntgZCWk+evrN7+NJ1s3Ba/qsWODC3xbuRc47vjtB5tlh5yMk0noBeX8qEg==
+  </data>
+ </layer>
+</map>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/000/pytmx/__init__.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,5 @@
+from tmxloader import load_tmx, load_pygame
+from pytmx import TiledElement, TiledMap, TiledTileset, TiledLayer, \
+                  TiledObjectGroup, TiledObject
+from utils import buildDistributionRects
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/000/pytmx/constants.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,11 @@
+# internal flags
+TRANS_FLIPX = 1
+TRANS_FLIPY = 2
+TRANS_ROT = 4
+
+
+# Tiled gid flags
+GID_TRANS_FLIPX = 1<<31
+GID_TRANS_FLIPY = 1<<30
+GID_TRANS_ROT   = 1<<29
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/000/pytmx/pytmx.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,572 @@
+from itertools import chain, product
+from xml.etree import ElementTree
+from collections import defaultdict 
+from utils import decode_gid, types, parse_properties, read_points
+from constants import *
+
+
+
+"""
+Class declarations and other important stuff
+"""
+
+
+
+class TiledElement(object):
+    def set_properties(self, node):
+        """
+        read the xml attributes and tiled "properties" from a xml node and fill
+        in the values into the object's dictionary.  Names will be checked to
+        make sure that they do not conflict with reserved names.
+        """
+
+        # set the attributes reserved for tiled
+        [ setattr(self, k, types[str(k)](v)) for (k,v) in node.items() ] 
+
+        # set the attributes that are derived from tiled 'properties'
+        for k,v in parse_properties(node).items():
+            if k in self.reserved:
+                msg = "{} \"{}\" has a property called \"{}\""
+                print msg.format(self.__class__.__name__, self.name, k, self.__class__.__name__)
+                msg = "This name is reserved for {} objects and can cannot be used."
+                print msg.format(self.__class__.__name__)
+                print "Please change the name in Tiled and try again."
+                print v
+                raise ValueError
+            setattr(self, k, types[str(k)](v))
+
+
+class TiledMap(TiledElement):
+    """
+    Contains the tile layers, tile images, object groups, and objects from a
+    Tiled TMX map.
+    """
+
+    reserved = "version orientation width height tilewidth tileheight properties tileset layer objectgroup".split()
+
+
+    def __init__(self, filename):
+        from collections import defaultdict
+
+        TiledElement.__init__(self)
+        self.tilesets = []          # list of TiledTileset objects
+        self.tilelayers   = []      # list of TiledLayer objects
+        self.objectgroups = []      # list of TiledObjectGroup objects
+        self.tile_properties = {}   # dict of tiles that have metadata
+        self.filename = filename
+
+        # only used tiles are actually loaded, so there will be a difference
+        # between the GID's in the Tile map data (tmx) and the data in this
+        # class and the layers.  This dictionary keeps track of that difference.
+        self.gidmap = defaultdict(list)
+
+        # should be filled in by a loader function
+        self.images = []
+
+        # defaults from the TMX specification
+        self.version = 0.0
+        self.orientation = None
+        self.width = 0
+        self.height = 0
+        self.tilewidth = 0
+        self.tileheight = 0 
+
+        self.imagemap = {}  # mapping of gid and trans flags to real gids
+        self.maxgid = 1
+
+        self.load()
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.filename)
+
+
+    def getTileImage(self, x, y, layer):
+        """
+        return the tile image for this location
+        x and y must be integers and are in tile coordinates, not pixel
+
+        return value will be 0 if there is no tile with that location.
+        """
+
+        try:
+            gid = self.tilelayers[int(layer)].data[int(y)][int(x)]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is not valid."
+            raise Exception, msg.format(x, y, layer)
+        except TypeError:
+            msg = "Tiles must be specified in integers."
+            raise TypeError, msg
+
+        else:
+            try:
+                return self.images[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invalid GID: {3}"
+                raise Exception, msg.format(x, y, layer, gid)
+
+
+    def getTileGID(self, x, y, layer):
+        """
+        return GID of a tile in this location
+        x and y must be integers and are in tile coordinates, not pixel
+        """
+
+        try:
+            return self.tilelayers[int(layer)].data[int(y)][int(x)]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid"
+            raise Exception, msg.format(x, y, layer)
+
+
+    def getDrawOrder(self):
+        """
+        return a list of objects in the order that they should be drawn
+        this will also exclude any layers that are not set to visible
+
+        may be useful if you have objects and want to control rendering
+        from tiled
+        """
+
+        raise NotImplementedError
+
+
+    def getTileImages(self, r, layer):
+        """
+        return a group of tiles in an area
+        expects a pygame rect or rect-like list/tuple
+
+        useful if you don't want to repeatedly call getTileImage
+        """
+
+        raise NotImplementedError
+
+
+    def getObjects(self):
+        """
+        Return iterator of all the objects associated with this map
+        """
+
+        return chain(*(i for i in self.objectgroups))
+
+
+    def getTileProperties(self, (x, y, layer)):
+        """
+        return the properties for the tile, if any
+        x and y must be integers and are in tile coordinates, not pixel
+
+        returns a dict of there are properties, otherwise will be None
+        """
+
+        try:
+            gid = self.tilelayers[int(layer)].data[int(y)][int(x)]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid."
+            raise Exception, msg.format(x, y, layer)
+
+        else:
+            try:
+                return self.tile_properties[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invaid GID: {3}"
+                raise Exception, msg.format(x, y, layer, gid)
+            except KeyError:
+                return None
+
+
+    def getLayerData(self, layer):
+        """
+        Return the data for a layer.
+
+        Data is an array of arrays.
+
+        >>> pos = data[y][x]
+        """
+
+        try:
+            return self.tilelayers[layer].data
+        except IndexError:
+            msg = "Layer {} does not exist."
+            raise ValueError, msg.format(layer)
+
+
+    def getTileLocation(self, gid):
+        # experimental way to find locations of a tile by the GID
+
+        p = product(xrange(self.width),
+                    xrange(self.height),
+                    xrange(len(self.tilelayers)))
+
+        return [ (x,y,l) for (x,y,l) in p 
+               if self.tilelayers[l].data[y][x] == gid ]
+
+
+    def getTilePropertiesByGID(self, gid):
+        try:
+            return self.tile_properties[gid]
+        except KeyError:
+            return None
+
+
+    def setTileProperties(self, gid, d):
+        """
+        set the properties of a tile by GID.
+        must use a standard python dict as d
+        """
+
+        try:
+            self.tile_properties[gid] = d
+        except KeyError:
+            msg = "GID #{} does not exist."
+            raise ValueError, msg.format(gid)
+
+
+    def getTilePropertiesByLayer(self, layer):
+        """
+        Return a list of tile properties (dict) in use in this tile layer.
+        """
+
+        try:
+            layer = int(layer)
+        except:
+            msg = "Layer must be an integer.  Got {} instead."
+            raise ValueError, msg.format(type(layer))
+
+        p = product(range(self.width),range(self.height))
+        layergids = set( self.tilelayers[layer].data[y][x] for x, y in p )
+
+        props = []
+        for gid in layergids:
+            try:
+                props.append((gid, self.tile_properties[gid]))
+            except:
+                continue
+            
+        return props
+
+
+    def registerGID(self, real_gid, flags=0):
+        """
+        used to manage the mapping of GID between the tmx data and the internal
+        data.
+        
+        number returned is gid used internally
+        """
+
+        if real_gid:
+            try:
+                return self.imagemap[(real_gid, flags)][0]
+            except KeyError:
+                # this tile has not been encountered before, or it has been
+                # transformed in some way.  make a new GID for it.
+                gid = self.maxgid
+                self.maxgid += 1
+                self.imagemap[(real_gid, flags)] = (gid, flags)
+                self.gidmap[real_gid].append((gid, flags))
+                return gid
+
+        else:
+            return 0
+
+
+
+    def mapGID(self, real_gid):
+        """
+        used to lookup a GID read from a TMX file's data
+        """
+
+        try:
+            return self.gidmap[int(real_gid)]
+        except KeyError:
+            return None
+        except TypeError:
+            msg = "GID's must be an integer"
+            raise TypeError, msg
+
+
+    def loadTileImages(self, filename):
+        raise NotImplementedError
+
+
+    def load(self):
+        """
+        parse a map node from a tiled tmx file
+        """
+        etree = ElementTree.parse(self.filename).getroot()
+        self.set_properties(etree)
+
+        # initialize the gid mapping
+        self.imagemap[(0,0)] = 0
+
+        for node in etree.findall('layer'):
+            self.tilelayers.append(TiledLayer(self, node))
+
+        for node in etree.findall('objectgroup'):
+            self.objectgroups.append(TiledObjectGroup(self, node))
+
+        for node in etree.findall('tileset'):
+            self.tilesets.append(TiledTileset(self, node))
+
+        # "tile objects", objects with a GID, have need to have their
+        # attributes set after the tileset is loaded
+        for o in self.getObjects():
+            p = self.getTilePropertiesByGID(o.gid)
+            if p:
+                o.name = "TileObject"
+                o.__dict__.update(p)
+
+
+    @property
+    def visibleTileLayers(self):
+        return [layer for layer in self.tilelayers if layer.visible]
+
+
+class TiledTileset(TiledElement):
+    reserved = "firstgid source name tilewidth tileheight spacing margin image tile properties".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+
+        # defaults from the specification
+        self.firstgid = 0
+        self.source = None
+        self.name = None
+        self.tilewidth = 0
+        self.tileheight = 0
+        self.spacing = 0
+        self.margin = 0
+        self.tiles = {}
+
+        self.parse(node)
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        """
+        parse a tileset element and return a tileset object and properties for
+        tiles as a dict
+
+        a bit of mangling is doew here so that tilesets that have external
+        TSX files appear the same as those that don't
+        """
+        import os
+
+
+        # if true, then node references an external tileset
+        source = node.get('source', False)
+        if source:
+            if source[-4:].lower() == ".tsx":
+
+                # external tilesets don't save this, store it for later
+                self.firstgid = int(node.get('firstgid'))
+
+                # we need to mangle the path - tiled stores relative paths
+                dirname = os.path.dirname(self.parent.filename)
+                path = os.path.abspath(os.path.join(dirname, source))
+                try:
+                    node = ElementTree.parse(path).getroot()
+                except IOError:
+                    msg = "Cannot load external tileset: {}"
+                    raise Exception, msg.format(path)
+
+            else:
+                msg = "Found external tileset, but cannot handle type: {}"
+                raise Exception, msg.format(self.source)
+           
+        self.set_properties(node)
+
+        # since tile objects [probably] don't have a lot of metadata,
+        # we store it seperately in the parent (a TiledMap instance)
+        for child in node.iter('tile'):
+            real_gid = int(child.get("id"))
+            p = parse_properties(child)
+            p['width'] = self.tilewidth
+            p['height'] = self.tileheight
+            for gid, flags in self.parent.mapGID(real_gid + self.firstgid):
+                self.parent.setTileProperties(gid, p)
+
+
+        image_node = node.find('image')
+        self.source = image_node.get('source')
+        self.trans = image_node.get("trans", None)
+
+
+class TiledLayer(TiledElement):
+    reserved = "name x y width height opacity properties data".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+        self.data = []
+
+        # defaults from the specification
+        self.name = None
+        self.opacity = 1.0
+        self.visible = True
+       
+        self.parse(node)
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        """
+        parse a layer element
+        """
+        from utils import group
+        from itertools import product, imap
+        from struct import unpack
+        import array
+
+        self.set_properties(node)
+
+        data = None
+        next_gid = None
+
+        data_node = node.find('data')
+
+        encoding = data_node.get("encoding", None)
+        if encoding == "base64":
+            from base64 import decodestring
+            data = decodestring(data_node.text.strip())
+
+        elif encoding == "csv":
+            next_gid = imap(int, "".join(
+                line.strip() for line in data_node.text.strip()
+                ).split(","))
+
+        elif encoding:
+            msg = "TMX encoding type: {} is not supported."
+            raise Exception, msg.format(encoding)
+
+        compression = data_node.get("compression", None)
+        if compression == "gzip":
+            from StringIO import StringIO
+            import gzip
+            with gzip.GzipFile(fileobj=StringIO(data)) as fh:
+                data = fh.read()
+
+        elif compression == "zlib":
+            import zlib
+            data = zlib.decompress(data)
+
+        elif compression:
+            msg = "TMX compression type: {} is not supported."
+            raise Exception, msg.format(str(attr["compression"]))
+     
+        # if data is None, then it was not decoded or decompressed, so
+        # we assume here that it is going to be a bunch of tile elements
+        # TODO: this will probably raise an exception if there are no tiles
+        if encoding == next_gid == None:
+            def get_children(parent):
+                for child in parent.findall('tile'):
+                    yield int(child.get('gid'))
+
+            next_gid = get_children(data_node)
+
+        elif data:
+            # data is a list of gid's. cast as 32-bit ints to format properly
+            # create iterator to effeciently parse data
+            next_gid=imap(lambda i:unpack("<L", "".join(i))[0], group(data, 4))
+
+        # using bytes here limits the layer to 256 unique tiles
+        # may be a limitation for very detailed maps, but most maps are not
+        # so detailed. 
+        [ self.data.append(array.array("B")) for i in xrange(self.height) ]
+
+        for (y, x) in product(xrange(self.height), xrange(self.width)):
+            self.data[y].append(self.parent.registerGID(*decode_gid(next(next_gid))))
+
+
+class TiledObjectGroup(TiledElement, list):
+    """
+    Stores TiledObjects.  Supports any operation of a normal list.
+    """
+    reserved = "name color x y width height opacity object properties".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+
+        # defaults from the specification
+        self.name = None
+
+        self.parse(node)
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        """
+        parse a objectgroup element and return a object group
+        """
+
+        self.set_properties(node)
+
+        for child in node.findall('object'):
+            o = TiledObject(self.parent, child)
+            self.append(o)
+
+
+class TiledObject(TiledElement):
+    reserved = "name type x y width height gid properties polygon polyline image".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+
+        # defaults from the specification
+        self.name = None
+        self.type = None
+        self.x = 0
+        self.y = 0
+        self.width = 0
+        self.height = 0
+        self.gid = 0
+
+        self.parse(node)
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        self.set_properties(node)
+
+        # correctly handle "tile objects" (object with gid set)
+        if self.gid:
+            self.gid = self.parent.registerGID(self.gid)
+
+        polygon = node.find('polygon')
+        if polygon is not None:
+            x1 = x2 = y1 = y2 = 0
+            self.points = read_points(polygon.get('points'))
+            self.closed = 1
+            for x, y in self.points:
+                if x < x1: x1 = x
+                if x > x2: x2 = x
+                if y < y1: y1 = y
+                if y > y2: y2 = y
+            self.width = abs(x1) + abs(x2) 
+            self.height = abs(y1) + abs(y2)
+
+        polyline = node.find('polyline')
+        if polyline is not None:
+            x1 = x2 = y1 = y2 = 0
+            self.points = read_points(polyline.get('points'))
+            self.closed = 0
+            for x, y in self.points:
+                if x < x1: x1 = x
+                if x > x2: x2 = x
+                if y < y1: y1 = y
+                if y > y2: y2 = y
+            self.width = abs(x1) + abs(x2) 
+            self.height = abs(y1) + abs(y2)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/000/pytmx/tmxloader.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,304 @@
+"""
+Map loader for TMX Files
+bitcraft (leif dot theden at gmail.com)
+v.15 - for python 2.7
+
+If you have any problems or suggestions, please contact me via email.
+Tested with Tiled 0.8.1 for Mac.
+
+released under the LGPL v3
+
+===============================================================================
+
+This map loader can be used to load maps created in the Tiled map editor.  It
+provides a simple way to get tiles and associated metadata so that you can draw
+a map onto the screen.  It does not force you to draw your map in any
+particular way.
+
+This is *not* a rendering engine.  It will load the data that is necessary to
+render a map onto the screen.  All tiles will be loaded into in memory and
+available to blit onto the screen.
+
+
+Design Goals:
+    Simple api
+    Memory efficient and fast
+
+Features:
+    Loads data and "properties" metadata from Tile's TMX format
+    "Properties" for: maps, tilesets, layers, objectgroups, objects, and tiles
+    Point data for polygon and polyline objects
+    Automatic flipping and rotation of tiles
+    Supports base64, csv, gzip, zlib and uncompressed XML
+    Image loading with pygame
+
+New in .15:
+    loader: fixed issue where objects with tile gid did not load properties
+    loader: polygon and polyline objects
+    loader: new lookup methods use iterators
+    loader: loading function moved into classes
+    loader: data/images can be reloaded on the fly
+    loader: uses etree for faster xml parsing
+
+New in .14:
+    loader: Fixed gid lookup for "buildDistributionRects"
+    loader: Added useful output to a few classes "__repr__"
+    loader: Fixed a gid mapping issue that broke rotated tiles
+    pygame: fixed colorkey handling
+    pygame: correctly handles margins and spacing between tiles in tilesets
+    pygame: b/c of changes, now correctly renders tiled's example maps
+    added scrolling demo
+
+New in .13:
+    loader: Renamed "get_tile_image" to "getTileImage"
+    loader: Removed duplicates returned from getTilePropertiesByLayer
+    loader: Modified confusing messages for GID errors
+    loader: Fixed bug where transformed tile properties are not available
+    loader: No longer loads metadata for tiles that are not used
+    loader: Reduced tile cache to 256 unique tiles
+    loader: Removed 'visible' from list of reserved words
+    loader: Added 'buildDistributionRects' and maputils module
+    loader: Added some misc. functions for retrieving properties
+    pygame: Smarter tile management made tile loading cache useless; removed it
+    pygame: pygame.RLEACCEL flag added when appropriate
+
+New in .12:
+    loader: Fixed bug where tile properties could contain reserved words
+    loader: Reduced size of image index by only allocating space for used tiles
+
+New in .11:
+    loader: Added support for tileset properties
+    loader: Now checks for property names that are reserved for internal use
+    loader: Added support for rotated tiles
+    pygame: Only the tiles that are used in the map will be loaded into memory
+    pygame: Added support for rotated tiles
+    pygame: Added option to force a bitsize (depth) for surfaces
+    pygame: Added option to convert alpha transparency to colorkey transparency
+    pygame: Tilesets no longer load with per-pixel alphas by default
+    pygame: Colorkey transparency should be correctly handled now
+
+
+NOTES:
+
+* The Tiled "properties" have reserved names.
+
+If you use "properties" for any of the following object types, you cannot use
+any of theese words as a name for your property.  A ValueError will be raised
+if there are any conflicts.
+
+As of 0.8.1, these values are:
+
+map:        version, orientation, width, height, tilewidth, tileheight
+            properties, tileset, layer, objectgroup
+
+tileset:    firstgid, source, name, tilewidth, tileheight, spacing, margin,
+            image, tile, properties
+
+tile:       id, image, properties
+
+layer:      name, x, y, width, height, opacity, properties, data
+
+objectgroup: name, color, x, y, width, height, opacity, object, properties
+
+object:     name, type, x, y, width, height, gid, properties, polygon,
+            polyline, image
+
+
+
+I have been intentionally not including a module rendering utility since
+rendering a map will have different requirements for every situation.  However,
+I can appreciate that some poeple won't understand how it works unless they see
+it, so I am including a sample map and viewer.  It includes a scrolling/zooming
+renderer.  They are for demonstation purposes, and may not be suitable for all
+projects.  Use at your own risk.
+
+I've also included a copy of this loader that may work with python 3.x.  I
+personally do not think that python 3.x should be used with pygame, yet (and I
+am not the only person).  You can try it if you insist on using pygame with
+python 3.x, but I don't update that often.
+
+===============================================================================
+
+Basic usage sample:
+
+    >>> from pytmx import tmxloader
+    >>> tmxdata = tmxloader.load_pygame("map.tmx")
+
+
+When you want to draw tiles, you simply call "getTileImage":
+
+    >>> image = tmxdata.getTileImage(x, y, layer)    
+    >>> screen.blit(position, image)
+
+
+Maps, tilesets, layers, objectgroups, and objects all have a simple way to
+access metadata that was set inside tiled: they all become object attributes.
+
+    >>> layer = tmxdata.layers[0]
+    >>> print layer.tilewidth
+    32
+    >>> print layer.weather
+    'sunny'
+
+
+Tiles properties are the exception here, and must be accessed through
+"getTileProperties".  The data is a regular Python dictionary:
+
+    >>> tile = tmxdata.getTileProperties(x, y, layer)
+    >>> tile["name"]
+    'CobbleStone'
+
+
+
+Please see the TiledMap class for some api information.
+"""
+from utils import types
+from constants import *
+
+
+
+def load_tmx(filename, *args, **kwargs):
+    # for .14 compatibility
+    from pytmx import TiledMap
+
+    tiledmap = TiledMap(filename)
+    return tiledmap
+
+
+def load_images_pygame(tmxdata, mapping, *args, **kwargs):
+    """
+    due to the way the tiles are loaded, they will be in the same pixel format
+    as the display when it is loaded.  take this into consideration if you
+    intend to support different screen pixel formats.
+
+    by default, the images will not have per-pixel alphas.  this can be
+    changed by including "pixelalpha=True" in the keywords.  this will result
+    in much slower blitting speeds.
+
+    if the tileset's image has colorkey transparency set in Tiled, the loader
+    will return images that have their transparency already set.  using a
+    tileset with colorkey transparency will greatly increase the speed of
+    rendering the map.
+
+    optionally, you can force the loader to strip the alpha channel of the
+    tileset image and to fill in the missing areas with a color, then use that
+    new color as a colorkey.  the resulting tiles will render much faster, but
+    will not preserve the transparency of the tile if it uses partial
+    transparency (which you shouldn't be doing anyway, this is SDL).
+    """
+    from itertools import product
+    from pygame import Surface, mask
+    import pygame, os
+
+
+    def handle_transformation(tile, flags):
+        if flags:
+            fx = flags & TRANS_FLIPX == TRANS_FLIPX
+            fy = flags & TRANS_FLIPY == TRANS_FLIPY
+            r  = flags & TRANS_ROT == TRANS_ROT
+
+            if r:
+                # not sure why the flip is required...but it is.
+                newtile = pygame.transform.rotate(tile, 270)
+                newtile = pygame.transform.flip(newtile, 1, 0)
+
+                if fx or fy:
+                    newtile = pygame.transform.flip(newtile, fx, fy)
+
+            elif fx or fy:
+                newtile = pygame.transform.flip(tile, fx, fy)
+
+            # preserve any flags that may have been lost after the transformation
+            return newtile.convert(tile)
+
+        else:
+            return tile
+
+
+    pixelalpha     = kwargs.get("pixelalpha", False)
+    force_colorkey = kwargs.get("force_colorkey", False)
+    force_bitdepth = kwargs.get("depth", False)
+
+    if force_colorkey:
+        try:
+            force_colorkey = pygame.Color(*force_colorkey)
+        except:
+            msg = "Cannot understand color: {}"
+            raise Exception, msg.format(force_colorkey)
+
+    tmxdata.images = [0] * tmxdata.maxgid
+
+    for firstgid, t in sorted((t.firstgid, t) for t in tmxdata.tilesets):
+        path = os.path.join(os.path.dirname(tmxdata.filename), t.source)
+
+        image = pygame.image.load(path)
+
+        w, h = image.get_size()
+        tile_size = (t.tilewidth, t.tileheight)
+        real_gid = t.firstgid - 1
+
+        if t.trans:
+            tileset_colorkey = pygame.Color("#{}".format(t.trans)) 
+
+        # i dont agree with margins and spacing, but i'll support it anyway
+        # such is life.  okay.jpg
+        tilewidth = t.tilewidth + t.spacing
+        tileheight = t.tileheight + t.spacing
+
+        # some tileset images may be slightly larger than the tile area
+        # ie: may include a banner, copyright, ect.  this compensates for that
+        width = ((int((w-t.margin*2) + t.spacing) / tilewidth) * tilewidth) - t.spacing
+        height = ((int((h-t.margin*2) + t.spacing) / tileheight) * tileheight) - t.spacing
+
+        # using product avoids the overhead of nested loops
+        p = product(xrange(t.margin, height+t.margin, tileheight),
+                    xrange(t.margin, width+t.margin, tilewidth))
+
+        for (y, x) in p:
+            real_gid += 1
+            gids = tmxdata.mapGID(real_gid)
+            if gids == []: continue
+
+            # we do some tests to correctly handle the tile and set the right
+            # blitting flags.  just grab a section of it.
+            temp = image.subsurface(((x,y), tile_size))
+
+            # count the number of pixels in the tile that are not transparent
+            px = mask.from_surface(temp).count()
+
+            # there are no transparent pixels in the image
+            if px == tile_size[0] * tile_size[1]:
+                tile = temp.convert()
+
+            # there are transparent pixels, and set to force a colorkey
+            elif force_colorkey:
+                tile = Surface(tile_size)
+                tile.fill(force_colorkey)
+                tile.blit(temp, (0,0))
+                tile.set_colorkey(force_colorkey, pygame.RLEACCEL)
+
+            # there are transparent pixels, and tiled set a colorkey
+            elif t.trans:
+                tile = temp.convert()
+                tile.set_colorkey(tileset_colorkey, pygame.RLEACCEL)
+
+            # there are transparent pixels, and set for perpixel alpha
+            elif pixelalpha:
+                tile = temp.convert_alpha()
+
+            # there are transparent pixels, and we won't handle them
+            else:
+                tile = temp.convert()
+
+            for gid, flags in gids:
+                tmxdata.images[gid] = handle_transformation(tile, flags)
+
+
+def load_pygame(filename, *args, **kwargs):
+    tmxdata = load_tmx(filename, *args, **kwargs)
+    load_images_pygame(tmxdata, None, *args, **kwargs)
+
+    return tmxdata
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/000/pytmx/tmxloader3.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,612 @@
+"""
+Map loader for TMX Files
+bitcraft (leif.theden at gmail.com)
+v.7  -- for python 3.x
+
+If you have any problems, please contact me via email.
+Tested with Tiled 0.7.1 for Mac.
+
+released under the LGPL v3
+
+======================================================================
+Design Goals:
+    Simple api
+    Memory efficient and fast
+    Quick access to tiles, attributes, and properties
+
+Non-Goals:
+    Rendering
+
+Works:
+    Pygame image loading
+    Map loading with all required types
+    Properties for all types: maps, layers, objects, tiles
+    Automatic flipping of tiles
+
+Todo:
+    Pygame: test colorkey transparency
+
+    Optimized for maps that do not make heavy use of tile
+    properties.  If i find that it is used a lot then i can rework
+    it for better performance.
+
+======================================================================
+
+Basic usage sample:
+
+    >>> import tmxloader
+    >>> tiledmap = tmxloader.load_pygame("map.tmx")
+
+
+When you want to draw tiles, you simply call "get_tile_image":
+
+    >>> image = tiledmap.get_tile_image(x, y, layer)    
+    >>> screen.blit(position, image)
+
+
+Layers, objectgroups, tilesets, and maps all have a simple way to access
+metadata that was set inside tiled: they all become class attributes.
+
+    >>> print(layer.tilewidth)
+    32
+    >>> print(layer.weather)
+    'sunny'
+
+
+Tiles are the exception here, and must be accessed through "getTileProperties"
+and are regular Python dictionaries:
+
+    >>> tile = tiledmap.getTileProperties(x, y, layer)
+    >>> tile["name"]
+    'CobbleStone'
+
+"""
+
+from itertools import chain
+
+
+# internal flags
+FLIP_X = 1
+FLIP_Y = 2
+
+
+# Tiled gid flags
+GID_FLIP_X = 1<<31
+GID_FLIP_Y = 1<<30
+
+
+class TiledElement(object):
+    pass
+
+class TiledMap(TiledElement):
+    """
+    not really useful unless "loaded"  ie: don't instance directly.
+    see the pygame loader for inspiration
+    """
+
+    def __init__(self):
+        TiledElement.__init__(self)
+        self.layers = []            # list of all layer types (tile layers + object layers)
+        self.tilesets = []          # list of TiledTileset objects
+        self.tilelayers   = []      # list of TiledLayer objects
+        self.objectgroups = []      # list of TiledObjectGroup objects
+        self.tile_properties = {}   # dict of tiles that have additional metadata (properties)
+        self.filename = None
+
+        # this is a work around to tiled's strange way of storing gid's
+        self.images = [0]
+
+        # defaults from the TMX specification
+        self.version = 0.0
+        self.orientation = None
+        self.width = 0
+        self.height = 0
+        self.tilewidth = 0
+        self.tileheight = 0 
+
+    def get_tile_image(self, x, y, layer):
+        """
+        return the tile image for this location
+        x and y must be integers and are in tile coordinates, not pixel
+
+        return value will be 0 if there is no tile with that location.
+        """
+
+        try:
+            gid = self.tilelayers[layer].data[y][x]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer)
+            raise Exception(msg)
+
+        else:
+            try:
+                return self.images[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invalid GID: {3}/{4}.".format(x, y, layer, gid, len(self.images))
+                raise Exception(msg)
+
+    def getTileGID(self, x, y, layer):
+        """
+        return GID of a tile in this location
+        x and y must be integers and are in tile coordinates, not pixel
+        """
+
+        try:
+            return self.tilelayers[layer].data[y][x]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer)
+            raise Exception(msg)
+
+    def getDrawOrder(self):
+        """
+        return a list of objects in the order that they should be drawn
+        this will also exclude any layers that are not set to visible
+
+        may be useful if you have objects and want to control rendering
+        from tiled
+        """
+
+        raise NotImplementedError
+
+    def getTileImages(self, r, layer):
+        """
+        return a group of tiles in an area
+        expects a pygame rect or rect-like list/tuple
+
+        usefull if you don't want to repeatedly call get_tile_image
+        probably not the most effecient way of doing this, but oh well.
+        """
+
+        raise NotImplementedError
+
+    def getObjects(self):
+        """
+        Return iterator all of the objects associated with this map
+        """
+
+        return chain(*[ i.objects for i in self.objectgroups ])
+
+    def getTileProperties(self, x, y, layer):
+        """
+        return the properties for the tile, if any
+        x and y must be integers and are in tile coordinates, not pixel
+
+        returns a dict of there are properties, otherwise will be None
+        """
+
+        try:
+            gid = self.tilelayers[layer].data[y][x]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer)
+            raise Exception(msg)
+
+        else:
+            try:
+                return self.tile_properties[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invaid GID: {3}/{4}.".format(x, y, layer, gid, len(self.images))
+                raise Exception(msg)
+
+    def getTilePropertiesByGID(self, gid):
+        try:
+            return self.tile_properties[gid]
+        except KeyError:
+            return None
+
+# the following classes get their attributes filled in with the loader
+
+class TiledTileset(TiledElement):
+    def __init__(self):
+        TiledElement.__init__(self)
+
+        # defaults from the specification
+        self.firstgid = 0
+        self.lastgid = 0
+        self.name = None
+        self.tilewidth = 0
+        self.tileheight = 0
+        self.spacing = 0
+        self.margin = 0
+
+class TiledLayer(TiledElement):
+    def __init__(self):
+        TiledElement.__init__(self)
+        self.data = None
+
+        # defaults from the specification
+        self.name = None
+        self.opacity = 1.0
+        self.visible = 1
+        
+class TiledObjectGroup(TiledElement):
+    def __init__(self):
+        TiledElement.__init__(self)
+        self.objects = []
+
+        # defaults from the specification
+        self.name = None
+
+class TiledObject(TiledElement):
+    __slots__ = ['name', 'type', 'x', 'y', 'width', 'height', 'gid']
+
+    def __init__(self):
+        TiledElement.__init__(self)
+
+        # defaults from the specification
+        self.name = None
+        self.type = None
+        self.x = 0
+        self.y = 0
+        self.width = 0
+        self.height = 0
+        self.gid = 0
+
+
+def load_tmx(filename):
+    """
+    Utility function to parse a Tiled TMX and return a usable object.
+    Images will not be loaded, so probably not useful to call this directly
+
+    See the load_pygame func for an idea of what to do
+    """
+
+    from xml.dom.minidom import parse
+    from itertools import tee, islice, chain
+    from collections import defaultdict 
+    from struct import unpack
+    import array, os
+
+    # used to change the unicode string returned from minidom to
+    # proper python variable types.
+    types = {
+        "version": float,
+        "orientation": str,
+        "width": int,
+        "height": int,
+        "tilewidth": int,
+        "tileheight": int,
+        "firstgid": int,
+        "source": str,
+        "name": str,
+        "spacing": int,
+        "margin": int,
+        "source": str,
+        "trans": str,
+        "id": int,
+        "opacity": float,
+        "visible": bool,
+        "encoding": str,
+        "compression": str,
+        "gid": int,
+        "type": str,
+        "x": int,
+        "y": int,
+        "value": str,
+    }
+ 
+    def pairwise(iterable):
+        # return a list as a sequence of pairs
+        a, b = tee(iterable)
+        next(b, None)
+        return zip(a, b)
+
+    def group(l, n):
+        # return a list as a sequence of n tuples
+        return zip(*[islice(l, i, None, n) for i in range(n)])
+
+    def parse_properties(node):
+        """
+        parse a node and return a dict that represents a tiled "property"
+        """
+
+        d = {}
+
+        for child in node.childNodes:
+            if child.nodeName == "properties":
+                for subnode in child.getElementsByTagName("property"):
+                    # the "properties" from tiled's tmx have an annoying
+                    # quality that "name" and "value" is included as part of it.
+                    # so we mangle it to get that stuff out.
+                    d.update(dict(pairwise([ str(i.value) for i in list(subnode.attributes.values()) ])))
+
+        return d
+
+    def get_properties(node):
+        """
+        parses a node and returns a dict that contains the data from the node's
+        attributes and any data from "property" elements as well.
+        """
+
+        d = {}
+
+        # get tag attributes
+        d.update(get_attributes(node))
+
+        # get vlues of the properties element, if any
+        d.update(parse_properties(node))
+     
+        return d
+
+    def set_properties(obj, node):
+        """
+        read the xml attributes and tiled "properties" from a xml node and fill in
+        the values into an object's dictionary
+        """
+
+        [ setattr(obj, k, v) for k,v in list(get_properties(node).items()) ]
+
+    def get_attributes(node):
+        """
+        get the attributes from a node and fix them to the correct type
+        """
+
+        d = defaultdict(lambda:None)
+
+        for k, v in list(node.attributes.items()):
+            k = str(k)
+            d[k] = types[k](v)
+
+        return d
+
+    def decode_gid(raw_gid):
+        # gid's are encoded with extra information
+        # as of 0.7.0 it determines if the tile should be flipped when rendered
+
+        flags = 0
+        if raw_gid & GID_FLIP_X == GID_FLIP_X: flags += FLIP_X
+        if raw_gid & GID_FLIP_Y == GID_FLIP_Y: flags += FLIP_Y
+        gid = raw_gid & ~(GID_FLIP_X | GID_FLIP_Y)
+
+        return gid, flags
+
+
+    def parse_map(node):
+        """
+        parse a map node from a tiled tmx file
+        return a tiledmap
+        """
+
+        tiledmap = TiledMap()
+        tiledmap.filename = filename
+        set_properties(tiledmap, map_node)
+
+        for node in map_node.getElementsByTagName("tileset"):
+            t, tiles = parse_tileset(node)
+            tiledmap.tilesets.append(t)
+            tiledmap.tile_properties.update(tiles)
+
+        for node in dom.getElementsByTagName("layer"):
+            l = parse_layer(tiledmap.tilesets, node)
+            tiledmap.tilelayers.append(l)
+            tiledmap.layers.append(l)
+
+        for node in dom.getElementsByTagName("objectgroup"):
+            o = parse_objectgroup(node)
+            tiledmap.objectgroups.append(o)
+            tiledmap.layers.append(o)
+
+        return tiledmap
+
+
+    def parse_tileset(node, firstgid=None):
+        """
+        parse a tileset element and return a tileset object and properties for tiles as a dict
+        """
+
+        tileset = TiledTileset()
+        set_properties(tileset, node)
+        tiles = {}
+
+        if firstgid != None:
+            tileset.firstgid = firstgid
+
+        # since tile objects probably don't have a lot of metadata,
+        # we store it seperately from the class itself
+        for child in node.childNodes:
+            if child.nodeName == "tile":
+                p = get_properties(child)
+                gid = p["id"] + tileset.firstgid
+                del p["id"]
+                tiles[gid] = p
+
+        # check for tiled "external tilesets"
+        if hasattr(tileset, "source"):
+            if tileset.source[-4:].lower() == ".tsx":
+                try:
+                    # we need to mangle the path some because tiled stores relative paths
+                    path = os.path.join(os.path.dirname(filename), tileset.source)
+                    tsx = parse(path)
+                except IOError:
+                    raise IOError("Cannot load external tileset: " + path)
+
+                tileset_node = tsx.getElementsByTagName("tileset")[0]
+                tileset, tiles = parse_tileset(tileset_node, tileset.firstgid)
+            else:
+                raise Exception("Found external tileset, but cannot handle type: " + tileset.source)
+
+        # if we have an "image" tag, process it here
+        try:
+            image_node = node.getElementsByTagName("image")[0]
+        except IndexError:
+            pass
+        else:
+            attr = get_attributes(image_node)
+            tileset.source = attr["source"]
+            tileset.trans = attr["trans"]
+
+            # calculate the number of tiles in this tileset
+            x, r = divmod(attr["width"], tileset.tilewidth)
+            y, r = divmod(attr["height"], tileset.tileheight)
+
+            tileset.lastgid = tileset.firstgid + x + y
+          
+        return tileset, tiles
+
+
+    def parse_layer(tilesets, node):
+        """
+        parse a layer element and return a layer object
+
+        tilesets is required since we need to mangle gid's here
+        """
+
+        layer = TiledLayer()
+        layer.data = []
+        layer.flipped_tiles = []
+        set_properties(layer, node)
+
+        data = None
+        next_gid = None
+
+        data_node = node.getElementsByTagName("data")[0]
+        attr = get_attributes(data_node)
+
+        if attr["encoding"] == "base64":
+            from base64 import b64decode
+            data = b64decode(bytes(data_node.lastChild.nodeValue, 'ascii'))
+
+        elif attr["encoding"] == "csv":
+            next_gid = map(int, "".join([line.strip() for line in data_node.lastChild.nodeValue]).split(","))
+
+        elif not attr["encoding"] == None:
+            raise Exception("TMX encoding type: " + str(attr["encoding"]) + " is not supported.")
+      
+        if attr["compression"] == "gzip":
+            from io import BytesIO
+            import gzip
+            with gzip.GzipFile(fileobj=BytesIO(data)) as fh:
+                data = fh.read()
+
+        elif not attr["compression"] == None:
+            raise Exception("TMX compression type: " + str(attr["compression"]) + " is not supported.")
+     
+        # if data is None, then it was not decoded or decompressed, so
+        # we assume here that it is going to be a bunch of tile elements
+        if attr["encoding"] == next_gid == None:
+            def get_children(parent):
+                for child in parent.getElementsByTagName("tile"):
+                    yield int(child.getAttribute("gid"))
+
+            next_gid = get_children(data_node)
+
+        elif not data == None:
+            # cast the data as a list of 32-bit integers
+            def u(i): return unpack("<L", bytes(i))[0]
+            next_gid = map(u, group(data, 4))
+
+        gids = [ i.firstgid for i in tilesets ]
+
+        # fill up our 2D array of gid's.
+        for y in range(layer.height):
+
+            # store as 16-bit ints, since we will never use enough tiles to fill a 32-bit int
+            layer.data.append(array.array("H"))
+
+            for x in range(layer.width):
+                gid, flags = decode_gid(next(next_gid))
+                if not flags == 0: layer.flipped_tiles.append((x, y, gid, flags))
+                layer.data[y].append(gid)
+
+        return layer
+
+
+    def parse_objectgroup(node):
+        """
+        parse a objectgroup element and return a object group
+        """
+
+        objgroup = TiledObjectGroup()
+        set_properties(objgroup, node)
+
+        for subnode in node.getElementsByTagName("object"):
+            obj = TiledObject()
+            set_properties(obj, subnode)
+            objgroup.objects.append(obj)
+
+        return objgroup
+
+
+    # open and read our TMX (which is really just xml)
+    dom = parse(filename)
+    map_node =  dom.getElementsByTagName("map")[0]
+    return parse_map(map_node)
+
+
+def load_pygame(filename):
+    """
+    load a tiled TMX map for use with pygame
+    """
+
+    from pygame import Surface
+    import pygame, os
+
+    tiledmap = load_tmx(filename)
+
+    # cache will find duplicate tiles to reduce memory usage
+    # mostly this is a problem in the blank areas of a tilemap
+    cache = {}
+
+    # just a precaution to make sure tileset images are added in the correct order
+    for firstgid, t in sorted([ (t.firstgid, t) for t in tiledmap.tilesets ]):
+        path = os.path.join(os.path.dirname(tiledmap.filename), t.source)
+
+        image = pygame.image.load(path)
+
+        w, h = image.get_rect().size
+
+        tile_size = (t.tilewidth, t.tileheight)
+
+        # some tileset images may be slightly larger than the tiles area
+        # ie: may include a banner, copyright, ect.  this compensates for that
+        for y in range(0, int(h / t.tileheight) * t.tileheight, t.tileheight):
+            for x in range(0, int(w / t.tilewidth) * t.tilewidth, t.tilewidth):
+
+                # somewhat handle transparency, though colorkey handling is not tested
+                if t.trans == None:
+                    tile = Surface(tile_size, pygame.SRCALPHA)
+                else:
+                    tile = Surface(tile_size)
+
+                # blit the smaller portion onto a new image from the larger one
+                tile.blit(image, (0,0), ((x, y), tile_size))
+
+                # make a unique id for this image, not sure if this is the best way, but it works
+                key = pygame.image.tostring(tile, "RGBA")
+        
+                # make sure we don't have a duplicate tile
+                try:
+                    tile = cache[key]
+                except KeyError:
+                    if t.trans == None:
+                        tile = tile.convert_alpha()
+                    else:
+                        tile = tile.convert()
+                        tile.set_colorkey(t.trans)
+
+                    # update the cache
+                    cache[key] = tile
+
+                tiledmap.images.append(tile)
+
+    # correctly handle transformed tiles.  currently flipped tiles
+    # work by creating a new gid for the flipped tile and changing the gid
+    # in the layer to the new gid.
+    for layer in tiledmap.tilelayers:
+        for x, y, gid, trans in layer.flipped_tiles:
+            fx = trans & FLIP_X == FLIP_X
+            fy = trans & FLIP_Y == FLIP_Y
+
+            tile = pygame.transform.flip(tiledmap.images[gid], fx, fy)
+            tiledmap.images.append(tile)
+
+            # change the original gid in the layer data to the new gid
+            layer.data[y][x] = len(tiledmap.images) - 1
+
+        del layer.flipped_tiles
+    del cache
+
+    return tiledmap
+
+if __name__ == '__main__':
+    print('[tmxloader] starting built-in test')
+    load_tmx("../resources/maps/village.tmx")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/000/pytmx/utils.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,251 @@
+from pygame import Rect
+from itertools import tee, islice, izip, product
+from collections import defaultdict
+from constants import *
+
+
+
+"""
+Various code that doesn't really fit anywhere else
+"""
+
+
+def read_points(text):
+    return [ tuple(map(lambda x: int(x), i.split(',')))
+         for i in text.split() ]
+
+
+
+def parse_properties(node):
+    """
+    parse a node and return a dict that represents a tiled "property"
+    """
+
+    # the "properties" from tiled's tmx have an annoying quality that "name"
+    # and "value" is included. here we mangle it to get that junk out.
+
+    d = {}
+
+    for child in node.findall('properties'):
+        for subnode in child.findall('property'):
+            d[subnode.get('name')] = subnode.get('value')
+
+    return d
+
+
+def decode_gid(raw_gid):
+    # gid's are encoded with extra information
+    # as of 0.7.0 it determines if the tile should be flipped when rendered
+    # as of 0.8.0 bit 30 determines if GID is rotated
+
+    flags = 0
+    if raw_gid & GID_TRANS_FLIPX == GID_TRANS_FLIPX: flags += TRANS_FLIPX
+    if raw_gid & GID_TRANS_FLIPY == GID_TRANS_FLIPY: flags += TRANS_FLIPY
+    if raw_gid & GID_TRANS_ROT == GID_TRANS_ROT: flags += TRANS_ROT
+    gid = raw_gid & ~(GID_TRANS_FLIPX | GID_TRANS_FLIPY | GID_TRANS_ROT)
+
+    return gid, flags
+
+
+def handle_bool(text):
+    # properly convert strings to a bool
+    try:
+        return bool(int(text))
+    except:
+        pass
+
+    try:
+        text = str(text).lower()
+        if text == "true":   return True
+        if text == "yes":    return True
+        if text == "false":  return False
+        if text == "no":     return False
+    except:
+        pass
+        
+    raise ValueError
+
+
+# used to change the unicode string returned from xml to proper python 
+# variable types.
+types = defaultdict(lambda: str)
+types.update({
+    "version": float,
+    "orientation": str,
+    "width": int,
+    "height": int,
+    "tilewidth": int,
+    "tileheight": int,
+    "firstgid": int,
+    "source": str,
+    "name": str,
+    "spacing": int,
+    "margin": int,
+    "source": str,
+    "trans": str,
+    "id": int,
+    "opacity": float,
+    "visible": handle_bool,
+    "encoding": str,
+    "compression": str,
+    "gid": int,
+    "type": str,
+    "x": int,
+    "y": int,
+    "value": str,
+})
+
+
+def pairwise(iterable):
+    # return a list as a sequence of pairs
+    a, b = tee(iterable)
+    next(b, None)
+    return izip(a, b)
+
+
+def group(l, n):
+    # return a list as a sequence of n tuples
+    return izip(*(islice(l, i, None, n) for i in xrange(n)))
+
+
+def buildDistributionRects(tmxmap, layer, tileset=None, real_gid=None):
+    """
+    generate a set of non-overlapping rects that represents the distribution
+    of the specfied gid.
+
+    useful for generating rects for use in collision detection
+    """
+    
+    if isinstance(tileset, int):
+        try:
+            tileset = tmxmap.tilesets[tileset]
+        except IndexError:
+            msg = "Tileset #{} not found in map {}."
+            raise IndexError, msg.format(tileset, tmxmap)
+
+    elif isinstance(tileset, str):
+        try:
+            tileset = [ t for t in tmxmap.tilesets if t.name == tileset ].pop()
+        except IndexError:
+            msg = "Tileset \"{}\" not found in map {}."
+            raise ValueError, msg.format(tileset, tmxmap)
+
+    elif tileset:
+        msg = "Tileset must be either a int or string. got: {}"
+        raise ValueError, msg.format(type(tileset))
+
+    gid = None
+    if real_gid:
+        try:
+            gid, flags = tmxmap.mapGID(real_gid)[0]
+        except IndexError:
+            msg = "GID #{} not found"
+            raise ValueError, msg.format(real_gid)
+
+
+    if isinstance(layer, int):
+        layer_data = tmxmap.getLayerData(layer).data
+    elif isinstance(layer, str):
+        try:
+            layer = [ l for l in tmxmap.tilelayers if l.name == layer ].pop()
+            layer_data = layer.data
+        except IndexError:
+            msg = "Layer \"{}\" not found in map {}."
+            raise ValueError, msg.format(layer, tmxmap)
+
+    p = product(xrange(tmxmap.width), xrange(tmxmap.height))
+    if gid:
+        points = [ (x,y) for (x,y) in p if layer_data[y][x] == gid ]
+    else:
+        points = [ (x,y) for (x,y) in p if layer_data[y][x] ]
+
+    rects = simplify(points, tmxmap.tilewidth, tmxmap.tileheight)
+    return rects
+
+
+def simplify(all_points, tilewidth, tileheight):
+    """
+    klugde:
+
+    "A kludge (or kluge) is a workaround, a quick-and-dirty solution,
+    a clumsy or inelegant, yet effective, solution to a problem, typically
+    using parts that are cobbled together."
+
+    -- wikipedia
+   
+    turn a list of points into a rects 
+    adjacent rects will be combined.
+
+    plain english:
+        the input list must be a a list of tuples that represent
+        the areas to be combined into rects
+        the rects will be blended together over solid groups
+
+        so if data is something like:
+
+        0 1 1 1 0 0 0 
+        0 1 1 0 0 0 0 
+        0 0 0 0 0 4 0 
+        0 0 0 0 0 4 0
+        0 0 0 0 0 0 0 
+        0 0 1 1 1 1 1
+
+        you'll have the 4 rects that mask the area like this:
+
+        ..######......
+        ..####........
+        ..........##..
+        ..........##..
+        ..............
+        ....##########
+
+        pretty cool, right?
+
+    there may be cases where the number of rectangles is not as low as possible,
+    but i haven't found that it is excessively bad.  certainly much better than
+    making a list of rects, one for each tile on the map!
+
+    """
+
+    def pick_rect(points, rects):
+        ox, oy = sorted([ (sum(p), p) for p in points ])[0][1]
+        x = ox
+        y = oy
+        ex = None            
+
+        while 1:
+            x += 1
+            if not (x, y) in points:
+                if ex == None:
+                    ex = x - 1
+
+                if ((ox, y+1) in points):
+                    if x == ex + 1 :
+                        y += 1
+                        x = ox
+
+                    else:
+                        y -= 1
+                        break
+                else:
+                    if x <= ex: y-= 1
+                    break
+
+        c_rect = Rect(ox*tilewidth,oy*tileheight,\
+                     (ex-ox+1)*tilewidth,(y-oy+1)*tileheight)
+
+        rects.append(c_rect)
+
+        rect = Rect(ox,oy,ex-ox+1,y-oy+1)
+        kill = [ p for p in points if rect.collidepoint(p) ]
+        [ points.remove(i) for i in kill ]
+
+        if points:
+            pick_rect(points, rects)
+
+    rect_list = []
+    while all_points:
+        pick_rect(all_points, rect_list)
+
+    return rect_list
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/000/sprites.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import pygame
+
+
+FACING = {
+    (0, -1): 0,
+    (-1, 0): 1,
+    (0, 1): 2,
+    (1, 0): 3,
+}
+
+
+def load_sheet(filename, width=32, height=None):
+    if height is None:
+        height = width
+    image = pygame.image.load(filename)
+    image = image.convert()
+    image.set_colorkey((0xff, 0, 0xff))
+    w, h = image.get_size()
+    images = []
+    for x in xrange(w // width):
+        for y in xrange(h // height):
+            rect = pygame.rect.Rect(x * width, y * height, width, height)
+            images.append(image.subsurface(rect))
+    return images
+
+
+class ShadowSprite(pygame.sprite.Sprite):
+    def __init__(self, sprite):
+        super(ShadowSprite, self).__init__()
+        self.spritesheet = load_sheet('effects.png', 32)
+        self.image = self.spritesheet[0]
+        self.rect = self.image.get_rect()
+        self.image.set_alpha(0.6 * 255)
+        self.sprite = sprite
+        self.rect.midbottom = self.sprite.rect.midbottom
+
+    def update(self):
+        self.rect.midbottom = self.sprite.rect.midbottom
+
+
+class HeroSprite(pygame.sprite.Sprite):
+    def __init__(self, (x, y)):
+        super(HeroSprite, self).__init__()
+        self.spritesheet = load_sheet('spritesheet.png', 64)
+        self.facing = 2
+        self.image = self.spritesheet[self.facing]
+        self.rect = self.image.get_rect()
+        self.rect.midbottom = (x * 32 + 16, y * 32 + 32)
+        self.animations = []
+
+    def move(self, (dx, dy)):
+        self.facing = FACING[dx, dy]
+        self.animations.append(self.animation_walk((dx, dy)))
+
+    def animation_walk(self, (dx, dy)):
+        for step in xrange(8):
+            self.image = self.spritesheet[self.facing + step * 4 + 4]
+            self.rect.move_ip(dx * 4, dy * 4)
+            yield
+
+    def animation_stand(self):
+        self.image = self.spritesheet[self.facing]
+        return
+        yield
+
+    def update(self):
+        if not self.animations:
+            self.animations.append(self.animation_stand())
+        while self.animations:
+            try:
+                self.animations[0].next()
+                return
+            except StopIteration:
+                self.animations.pop(0)
Binary file 004/000/spritesheet.png has changed
Binary file 004/000/tilesheet.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/001/__main__.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from game import Game
+
+
+game = Game()
+game.load_level('level.tmx')
+game.run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/001/game.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import pygame
+import pygame.locals as pg
+
+from level import Level
+from sprites import HeroSprite, ShadowSprite
+
+
+class Game(object):
+    def __init__(self):
+        pygame.display.init()
+        pygame.font.init()
+        self.screen = pygame.display.set_mode((800, 600))
+        self.screen.fill((0x66, 0x66, 0x66))
+        self.keys = {
+            pg.K_UP: (self.move_hero, (0, -1)),
+            pg.K_DOWN: (self.move_hero, (0, 1)),
+            pg.K_LEFT: (self.move_hero, (-1, 0)),
+            pg.K_RIGHT: (self.move_hero, (1, 0)),
+            pg.K_ESCAPE: (self.quit,),
+        }
+        self.shadow_sprites = pygame.sprite.RenderUpdates()
+        self.sprites = pygame.sprite.RenderUpdates()
+        self.key = None
+
+    def load_level(self, filename):
+        self.level = Level(filename)
+        self.hero_sprite = HeroSprite(self.level.hero_position)
+        self.sprites.add(self.hero_sprite)
+        self.shadow_sprites.add(ShadowSprite(self.hero_sprite))
+        self.background = self.screen.copy()
+        self.level.render(self.background)
+        self.screen.blit(self.background, (0, 0))
+        self.sprites.draw(self.screen)
+        pygame.display.update()
+
+    def quit(self):
+        self.game_over = True
+
+    def move_hero(self, direction):
+        if self.level.move_hero(direction):
+            self.hero_sprite.move(direction)
+
+    def control(self):
+        for event in pygame.event.get():
+            if event.type == pg.QUIT:
+                self.quit()
+            elif event.type == pg.KEYDOWN:
+                self.key = event.key
+        if not self.hero_sprite.animations:
+            keys = pygame.key.get_pressed()
+            for key, action in self.keys.iteritems():
+                if keys[key] or self.key == key:
+                    action[0](*action[1:])
+                    break
+            self.key = None
+
+    def display(self):
+        self.sprites.clear(self.screen, self.background)
+        self.shadow_sprites.clear(self.screen, self.background)
+        self.sprites.update()
+        self.shadow_sprites.update()
+        dirty = []
+        dirty.extend(self.shadow_sprites.draw(self.screen))
+        dirty.extend(self.sprites.draw(self.screen))
+        pygame.display.update(dirty)
+
+    def run(self):
+        self.clock = pygame.time.Clock()
+        self.game_over = False
+        while not self.game_over:
+            self.display()
+            self.control()
+            self.clock.tick(30)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/001/level.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+from pytmx import tmxloader
+
+import resources
+
+
+class Level(object):
+    def __init__(self, filename):
+        path = resources.file_path(filename)
+        self.tiled_map = tmxloader.load_pygame(path)
+        self.hero_position = 1, 1
+
+    def render(self, screen):
+        w = self.tiled_map.tilewidth
+        h = self.tiled_map.tileheight
+        get_image = self.tiled_map.getTileImage
+        for l, layer in enumerate(self.tiled_map.tilelayers):
+            for y in xrange(self.tiled_map.height):
+                for x in xrange(self.tiled_map.width):
+                    image = get_image(x, y, l)
+                    if image:
+                        screen.blit(image, (x * w, y * h))
+        x, y = self.hero_position
+
+    def get_properties(self, (x, y)):
+        properties = {}
+        for l, layer in enumerate(self.tiled_map.tilelayers):
+            properties.update(self.tiled_map.getTileProperties((x, y, l)) or {})
+        return properties
+
+    def is_blocked(self, (x, y)):
+        return self.get_properties((x, y)).get('block')
+
+    def move_hero(self, (dx, dy)):
+        x, y = self.hero_position
+        if not self.is_blocked((x + dx, y + dy)):
+            self.hero_position = x + dx, y + dy
+            return True
+        return False
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/001/pytmx/__init__.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,5 @@
+from tmxloader import load_tmx, load_pygame
+from pytmx import TiledElement, TiledMap, TiledTileset, TiledLayer, \
+                  TiledObjectGroup, TiledObject
+from utils import buildDistributionRects
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/001/pytmx/constants.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,11 @@
+# internal flags
+TRANS_FLIPX = 1
+TRANS_FLIPY = 2
+TRANS_ROT = 4
+
+
+# Tiled gid flags
+GID_TRANS_FLIPX = 1<<31
+GID_TRANS_FLIPY = 1<<30
+GID_TRANS_ROT   = 1<<29
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/001/pytmx/pytmx.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,572 @@
+from itertools import chain, product
+from xml.etree import ElementTree
+from collections import defaultdict 
+from utils import decode_gid, types, parse_properties, read_points
+from constants import *
+
+
+
+"""
+Class declarations and other important stuff
+"""
+
+
+
+class TiledElement(object):
+    def set_properties(self, node):
+        """
+        read the xml attributes and tiled "properties" from a xml node and fill
+        in the values into the object's dictionary.  Names will be checked to
+        make sure that they do not conflict with reserved names.
+        """
+
+        # set the attributes reserved for tiled
+        [ setattr(self, k, types[str(k)](v)) for (k,v) in node.items() ] 
+
+        # set the attributes that are derived from tiled 'properties'
+        for k,v in parse_properties(node).items():
+            if k in self.reserved:
+                msg = "{} \"{}\" has a property called \"{}\""
+                print msg.format(self.__class__.__name__, self.name, k, self.__class__.__name__)
+                msg = "This name is reserved for {} objects and can cannot be used."
+                print msg.format(self.__class__.__name__)
+                print "Please change the name in Tiled and try again."
+                print v
+                raise ValueError
+            setattr(self, k, types[str(k)](v))
+
+
+class TiledMap(TiledElement):
+    """
+    Contains the tile layers, tile images, object groups, and objects from a
+    Tiled TMX map.
+    """
+
+    reserved = "version orientation width height tilewidth tileheight properties tileset layer objectgroup".split()
+
+
+    def __init__(self, filename):
+        from collections import defaultdict
+
+        TiledElement.__init__(self)
+        self.tilesets = []          # list of TiledTileset objects
+        self.tilelayers   = []      # list of TiledLayer objects
+        self.objectgroups = []      # list of TiledObjectGroup objects
+        self.tile_properties = {}   # dict of tiles that have metadata
+        self.filename = filename
+
+        # only used tiles are actually loaded, so there will be a difference
+        # between the GID's in the Tile map data (tmx) and the data in this
+        # class and the layers.  This dictionary keeps track of that difference.
+        self.gidmap = defaultdict(list)
+
+        # should be filled in by a loader function
+        self.images = []
+
+        # defaults from the TMX specification
+        self.version = 0.0
+        self.orientation = None
+        self.width = 0
+        self.height = 0
+        self.tilewidth = 0
+        self.tileheight = 0 
+
+        self.imagemap = {}  # mapping of gid and trans flags to real gids
+        self.maxgid = 1
+
+        self.load()
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.filename)
+
+
+    def getTileImage(self, x, y, layer):
+        """
+        return the tile image for this location
+        x and y must be integers and are in tile coordinates, not pixel
+
+        return value will be 0 if there is no tile with that location.
+        """
+
+        try:
+            gid = self.tilelayers[int(layer)].data[int(y)][int(x)]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is not valid."
+            raise Exception, msg.format(x, y, layer)
+        except TypeError:
+            msg = "Tiles must be specified in integers."
+            raise TypeError, msg
+
+        else:
+            try:
+                return self.images[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invalid GID: {3}"
+                raise Exception, msg.format(x, y, layer, gid)
+
+
+    def getTileGID(self, x, y, layer):
+        """
+        return GID of a tile in this location
+        x and y must be integers and are in tile coordinates, not pixel
+        """
+
+        try:
+            return self.tilelayers[int(layer)].data[int(y)][int(x)]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid"
+            raise Exception, msg.format(x, y, layer)
+
+
+    def getDrawOrder(self):
+        """
+        return a list of objects in the order that they should be drawn
+        this will also exclude any layers that are not set to visible
+
+        may be useful if you have objects and want to control rendering
+        from tiled
+        """
+
+        raise NotImplementedError
+
+
+    def getTileImages(self, r, layer):
+        """
+        return a group of tiles in an area
+        expects a pygame rect or rect-like list/tuple
+
+        useful if you don't want to repeatedly call getTileImage
+        """
+
+        raise NotImplementedError
+
+
+    def getObjects(self):
+        """
+        Return iterator of all the objects associated with this map
+        """
+
+        return chain(*(i for i in self.objectgroups))
+
+
+    def getTileProperties(self, (x, y, layer)):
+        """
+        return the properties for the tile, if any
+        x and y must be integers and are in tile coordinates, not pixel
+
+        returns a dict of there are properties, otherwise will be None
+        """
+
+        try:
+            gid = self.tilelayers[int(layer)].data[int(y)][int(x)]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid."
+            raise Exception, msg.format(x, y, layer)
+
+        else:
+            try:
+                return self.tile_properties[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invaid GID: {3}"
+                raise Exception, msg.format(x, y, layer, gid)
+            except KeyError:
+                return None
+
+
+    def getLayerData(self, layer):
+        """
+        Return the data for a layer.
+
+        Data is an array of arrays.
+
+        >>> pos = data[y][x]
+        """
+
+        try:
+            return self.tilelayers[layer].data
+        except IndexError:
+            msg = "Layer {} does not exist."
+            raise ValueError, msg.format(layer)
+
+
+    def getTileLocation(self, gid):
+        # experimental way to find locations of a tile by the GID
+
+        p = product(xrange(self.width),
+                    xrange(self.height),
+                    xrange(len(self.tilelayers)))
+
+        return [ (x,y,l) for (x,y,l) in p 
+               if self.tilelayers[l].data[y][x] == gid ]
+
+
+    def getTilePropertiesByGID(self, gid):
+        try:
+            return self.tile_properties[gid]
+        except KeyError:
+            return None
+
+
+    def setTileProperties(self, gid, d):
+        """
+        set the properties of a tile by GID.
+        must use a standard python dict as d
+        """
+
+        try:
+            self.tile_properties[gid] = d
+        except KeyError:
+            msg = "GID #{} does not exist."
+            raise ValueError, msg.format(gid)
+
+
+    def getTilePropertiesByLayer(self, layer):
+        """
+        Return a list of tile properties (dict) in use in this tile layer.
+        """
+
+        try:
+            layer = int(layer)
+        except:
+            msg = "Layer must be an integer.  Got {} instead."
+            raise ValueError, msg.format(type(layer))
+
+        p = product(range(self.width),range(self.height))
+        layergids = set( self.tilelayers[layer].data[y][x] for x, y in p )
+
+        props = []
+        for gid in layergids:
+            try:
+                props.append((gid, self.tile_properties[gid]))
+            except:
+                continue
+            
+        return props
+
+
+    def registerGID(self, real_gid, flags=0):
+        """
+        used to manage the mapping of GID between the tmx data and the internal
+        data.
+        
+        number returned is gid used internally
+        """
+
+        if real_gid:
+            try:
+                return self.imagemap[(real_gid, flags)][0]
+            except KeyError:
+                # this tile has not been encountered before, or it has been
+                # transformed in some way.  make a new GID for it.
+                gid = self.maxgid
+                self.maxgid += 1
+                self.imagemap[(real_gid, flags)] = (gid, flags)
+                self.gidmap[real_gid].append((gid, flags))
+                return gid
+
+        else:
+            return 0
+
+
+
+    def mapGID(self, real_gid):
+        """
+        used to lookup a GID read from a TMX file's data
+        """
+
+        try:
+            return self.gidmap[int(real_gid)]
+        except KeyError:
+            return None
+        except TypeError:
+            msg = "GID's must be an integer"
+            raise TypeError, msg
+
+
+    def loadTileImages(self, filename):
+        raise NotImplementedError
+
+
+    def load(self):
+        """
+        parse a map node from a tiled tmx file
+        """
+        etree = ElementTree.parse(self.filename).getroot()
+        self.set_properties(etree)
+
+        # initialize the gid mapping
+        self.imagemap[(0,0)] = 0
+
+        for node in etree.findall('layer'):
+            self.tilelayers.append(TiledLayer(self, node))
+
+        for node in etree.findall('objectgroup'):
+            self.objectgroups.append(TiledObjectGroup(self, node))
+
+        for node in etree.findall('tileset'):
+            self.tilesets.append(TiledTileset(self, node))
+
+        # "tile objects", objects with a GID, have need to have their
+        # attributes set after the tileset is loaded
+        for o in self.getObjects():
+            p = self.getTilePropertiesByGID(o.gid)
+            if p:
+                o.name = "TileObject"
+                o.__dict__.update(p)
+
+
+    @property
+    def visibleTileLayers(self):
+        return [layer for layer in self.tilelayers if layer.visible]
+
+
+class TiledTileset(TiledElement):
+    reserved = "firstgid source name tilewidth tileheight spacing margin image tile properties".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+
+        # defaults from the specification
+        self.firstgid = 0
+        self.source = None
+        self.name = None
+        self.tilewidth = 0
+        self.tileheight = 0
+        self.spacing = 0
+        self.margin = 0
+        self.tiles = {}
+
+        self.parse(node)
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        """
+        parse a tileset element and return a tileset object and properties for
+        tiles as a dict
+
+        a bit of mangling is doew here so that tilesets that have external
+        TSX files appear the same as those that don't
+        """
+        import os
+
+
+        # if true, then node references an external tileset
+        source = node.get('source', False)
+        if source:
+            if source[-4:].lower() == ".tsx":
+
+                # external tilesets don't save this, store it for later
+                self.firstgid = int(node.get('firstgid'))
+
+                # we need to mangle the path - tiled stores relative paths
+                dirname = os.path.dirname(self.parent.filename)
+                path = os.path.abspath(os.path.join(dirname, source))
+                try:
+                    node = ElementTree.parse(path).getroot()
+                except IOError:
+                    msg = "Cannot load external tileset: {}"
+                    raise Exception, msg.format(path)
+
+            else:
+                msg = "Found external tileset, but cannot handle type: {}"
+                raise Exception, msg.format(self.source)
+           
+        self.set_properties(node)
+
+        # since tile objects [probably] don't have a lot of metadata,
+        # we store it seperately in the parent (a TiledMap instance)
+        for child in node.iter('tile'):
+            real_gid = int(child.get("id"))
+            p = parse_properties(child)
+            p['width'] = self.tilewidth
+            p['height'] = self.tileheight
+            for gid, flags in self.parent.mapGID(real_gid + self.firstgid):
+                self.parent.setTileProperties(gid, p)
+
+
+        image_node = node.find('image')
+        self.source = image_node.get('source')
+        self.trans = image_node.get("trans", None)
+
+
+class TiledLayer(TiledElement):
+    reserved = "name x y width height opacity properties data".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+        self.data = []
+
+        # defaults from the specification
+        self.name = None
+        self.opacity = 1.0
+        self.visible = True
+       
+        self.parse(node)
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        """
+        parse a layer element
+        """
+        from utils import group
+        from itertools import product, imap
+        from struct import unpack
+        import array
+
+        self.set_properties(node)
+
+        data = None
+        next_gid = None
+
+        data_node = node.find('data')
+
+        encoding = data_node.get("encoding", None)
+        if encoding == "base64":
+            from base64 import decodestring
+            data = decodestring(data_node.text.strip())
+
+        elif encoding == "csv":
+            next_gid = imap(int, "".join(
+                line.strip() for line in data_node.text.strip()
+                ).split(","))
+
+        elif encoding:
+            msg = "TMX encoding type: {} is not supported."
+            raise Exception, msg.format(encoding)
+
+        compression = data_node.get("compression", None)
+        if compression == "gzip":
+            from StringIO import StringIO
+            import gzip
+            with gzip.GzipFile(fileobj=StringIO(data)) as fh:
+                data = fh.read()
+
+        elif compression == "zlib":
+            import zlib
+            data = zlib.decompress(data)
+
+        elif compression:
+            msg = "TMX compression type: {} is not supported."
+            raise Exception, msg.format(str(attr["compression"]))
+     
+        # if data is None, then it was not decoded or decompressed, so
+        # we assume here that it is going to be a bunch of tile elements
+        # TODO: this will probably raise an exception if there are no tiles
+        if encoding == next_gid == None:
+            def get_children(parent):
+                for child in parent.findall('tile'):
+                    yield int(child.get('gid'))
+
+            next_gid = get_children(data_node)
+
+        elif data:
+            # data is a list of gid's. cast as 32-bit ints to format properly
+            # create iterator to effeciently parse data
+            next_gid=imap(lambda i:unpack("<L", "".join(i))[0], group(data, 4))
+
+        # using bytes here limits the layer to 256 unique tiles
+        # may be a limitation for very detailed maps, but most maps are not
+        # so detailed. 
+        [ self.data.append(array.array("B")) for i in xrange(self.height) ]
+
+        for (y, x) in product(xrange(self.height), xrange(self.width)):
+            self.data[y].append(self.parent.registerGID(*decode_gid(next(next_gid))))
+
+
+class TiledObjectGroup(TiledElement, list):
+    """
+    Stores TiledObjects.  Supports any operation of a normal list.
+    """
+    reserved = "name color x y width height opacity object properties".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+
+        # defaults from the specification
+        self.name = None
+
+        self.parse(node)
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        """
+        parse a objectgroup element and return a object group
+        """
+
+        self.set_properties(node)
+
+        for child in node.findall('object'):
+            o = TiledObject(self.parent, child)
+            self.append(o)
+
+
+class TiledObject(TiledElement):
+    reserved = "name type x y width height gid properties polygon polyline image".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+
+        # defaults from the specification
+        self.name = None
+        self.type = None
+        self.x = 0
+        self.y = 0
+        self.width = 0
+        self.height = 0
+        self.gid = 0
+
+        self.parse(node)
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        self.set_properties(node)
+
+        # correctly handle "tile objects" (object with gid set)
+        if self.gid:
+            self.gid = self.parent.registerGID(self.gid)
+
+        polygon = node.find('polygon')
+        if polygon is not None:
+            x1 = x2 = y1 = y2 = 0
+            self.points = read_points(polygon.get('points'))
+            self.closed = 1
+            for x, y in self.points:
+                if x < x1: x1 = x
+                if x > x2: x2 = x
+                if y < y1: y1 = y
+                if y > y2: y2 = y
+            self.width = abs(x1) + abs(x2) 
+            self.height = abs(y1) + abs(y2)
+
+        polyline = node.find('polyline')
+        if polyline is not None:
+            x1 = x2 = y1 = y2 = 0
+            self.points = read_points(polyline.get('points'))
+            self.closed = 0
+            for x, y in self.points:
+                if x < x1: x1 = x
+                if x > x2: x2 = x
+                if y < y1: y1 = y
+                if y > y2: y2 = y
+            self.width = abs(x1) + abs(x2) 
+            self.height = abs(y1) + abs(y2)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/001/pytmx/tmxloader.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,304 @@
+"""
+Map loader for TMX Files
+bitcraft (leif dot theden at gmail.com)
+v.15 - for python 2.7
+
+If you have any problems or suggestions, please contact me via email.
+Tested with Tiled 0.8.1 for Mac.
+
+released under the LGPL v3
+
+===============================================================================
+
+This map loader can be used to load maps created in the Tiled map editor.  It
+provides a simple way to get tiles and associated metadata so that you can draw
+a map onto the screen.  It does not force you to draw your map in any
+particular way.
+
+This is *not* a rendering engine.  It will load the data that is necessary to
+render a map onto the screen.  All tiles will be loaded into in memory and
+available to blit onto the screen.
+
+
+Design Goals:
+    Simple api
+    Memory efficient and fast
+
+Features:
+    Loads data and "properties" metadata from Tile's TMX format
+    "Properties" for: maps, tilesets, layers, objectgroups, objects, and tiles
+    Point data for polygon and polyline objects
+    Automatic flipping and rotation of tiles
+    Supports base64, csv, gzip, zlib and uncompressed XML
+    Image loading with pygame
+
+New in .15:
+    loader: fixed issue where objects with tile gid did not load properties
+    loader: polygon and polyline objects
+    loader: new lookup methods use iterators
+    loader: loading function moved into classes
+    loader: data/images can be reloaded on the fly
+    loader: uses etree for faster xml parsing
+
+New in .14:
+    loader: Fixed gid lookup for "buildDistributionRects"
+    loader: Added useful output to a few classes "__repr__"
+    loader: Fixed a gid mapping issue that broke rotated tiles
+    pygame: fixed colorkey handling
+    pygame: correctly handles margins and spacing between tiles in tilesets
+    pygame: b/c of changes, now correctly renders tiled's example maps
+    added scrolling demo
+
+New in .13:
+    loader: Renamed "get_tile_image" to "getTileImage"
+    loader: Removed duplicates returned from getTilePropertiesByLayer
+    loader: Modified confusing messages for GID errors
+    loader: Fixed bug where transformed tile properties are not available
+    loader: No longer loads metadata for tiles that are not used
+    loader: Reduced tile cache to 256 unique tiles
+    loader: Removed 'visible' from list of reserved words
+    loader: Added 'buildDistributionRects' and maputils module
+    loader: Added some misc. functions for retrieving properties
+    pygame: Smarter tile management made tile loading cache useless; removed it
+    pygame: pygame.RLEACCEL flag added when appropriate
+
+New in .12:
+    loader: Fixed bug where tile properties could contain reserved words
+    loader: Reduced size of image index by only allocating space for used tiles
+
+New in .11:
+    loader: Added support for tileset properties
+    loader: Now checks for property names that are reserved for internal use
+    loader: Added support for rotated tiles
+    pygame: Only the tiles that are used in the map will be loaded into memory
+    pygame: Added support for rotated tiles
+    pygame: Added option to force a bitsize (depth) for surfaces
+    pygame: Added option to convert alpha transparency to colorkey transparency
+    pygame: Tilesets no longer load with per-pixel alphas by default
+    pygame: Colorkey transparency should be correctly handled now
+
+
+NOTES:
+
+* The Tiled "properties" have reserved names.
+
+If you use "properties" for any of the following object types, you cannot use
+any of theese words as a name for your property.  A ValueError will be raised
+if there are any conflicts.
+
+As of 0.8.1, these values are:
+
+map:        version, orientation, width, height, tilewidth, tileheight
+            properties, tileset, layer, objectgroup
+
+tileset:    firstgid, source, name, tilewidth, tileheight, spacing, margin,
+            image, tile, properties
+
+tile:       id, image, properties
+
+layer:      name, x, y, width, height, opacity, properties, data
+
+objectgroup: name, color, x, y, width, height, opacity, object, properties
+
+object:     name, type, x, y, width, height, gid, properties, polygon,
+            polyline, image
+
+
+
+I have been intentionally not including a module rendering utility since
+rendering a map will have different requirements for every situation.  However,
+I can appreciate that some poeple won't understand how it works unless they see
+it, so I am including a sample map and viewer.  It includes a scrolling/zooming
+renderer.  They are for demonstation purposes, and may not be suitable for all
+projects.  Use at your own risk.
+
+I've also included a copy of this loader that may work with python 3.x.  I
+personally do not think that python 3.x should be used with pygame, yet (and I
+am not the only person).  You can try it if you insist on using pygame with
+python 3.x, but I don't update that often.
+
+===============================================================================
+
+Basic usage sample:
+
+    >>> from pytmx import tmxloader
+    >>> tmxdata = tmxloader.load_pygame("map.tmx")
+
+
+When you want to draw tiles, you simply call "getTileImage":
+
+    >>> image = tmxdata.getTileImage(x, y, layer)    
+    >>> screen.blit(position, image)
+
+
+Maps, tilesets, layers, objectgroups, and objects all have a simple way to
+access metadata that was set inside tiled: they all become object attributes.
+
+    >>> layer = tmxdata.layers[0]
+    >>> print layer.tilewidth
+    32
+    >>> print layer.weather
+    'sunny'
+
+
+Tiles properties are the exception here, and must be accessed through
+"getTileProperties".  The data is a regular Python dictionary:
+
+    >>> tile = tmxdata.getTileProperties(x, y, layer)
+    >>> tile["name"]
+    'CobbleStone'
+
+
+
+Please see the TiledMap class for some api information.
+"""
+from utils import types
+from constants import *
+
+
+
+def load_tmx(filename, *args, **kwargs):
+    # for .14 compatibility
+    from pytmx import TiledMap
+
+    tiledmap = TiledMap(filename)
+    return tiledmap
+
+
+def load_images_pygame(tmxdata, mapping, *args, **kwargs):
+    """
+    due to the way the tiles are loaded, they will be in the same pixel format
+    as the display when it is loaded.  take this into consideration if you
+    intend to support different screen pixel formats.
+
+    by default, the images will not have per-pixel alphas.  this can be
+    changed by including "pixelalpha=True" in the keywords.  this will result
+    in much slower blitting speeds.
+
+    if the tileset's image has colorkey transparency set in Tiled, the loader
+    will return images that have their transparency already set.  using a
+    tileset with colorkey transparency will greatly increase the speed of
+    rendering the map.
+
+    optionally, you can force the loader to strip the alpha channel of the
+    tileset image and to fill in the missing areas with a color, then use that
+    new color as a colorkey.  the resulting tiles will render much faster, but
+    will not preserve the transparency of the tile if it uses partial
+    transparency (which you shouldn't be doing anyway, this is SDL).
+    """
+    from itertools import product
+    from pygame import Surface, mask
+    import pygame, os
+
+
+    def handle_transformation(tile, flags):
+        if flags:
+            fx = flags & TRANS_FLIPX == TRANS_FLIPX
+            fy = flags & TRANS_FLIPY == TRANS_FLIPY
+            r  = flags & TRANS_ROT == TRANS_ROT
+
+            if r:
+                # not sure why the flip is required...but it is.
+                newtile = pygame.transform.rotate(tile, 270)
+                newtile = pygame.transform.flip(newtile, 1, 0)
+
+                if fx or fy:
+                    newtile = pygame.transform.flip(newtile, fx, fy)
+
+            elif fx or fy:
+                newtile = pygame.transform.flip(tile, fx, fy)
+
+            # preserve any flags that may have been lost after the transformation
+            return newtile.convert(tile)
+
+        else:
+            return tile
+
+
+    pixelalpha     = kwargs.get("pixelalpha", False)
+    force_colorkey = kwargs.get("force_colorkey", False)
+    force_bitdepth = kwargs.get("depth", False)
+
+    if force_colorkey:
+        try:
+            force_colorkey = pygame.Color(*force_colorkey)
+        except:
+            msg = "Cannot understand color: {}"
+            raise Exception, msg.format(force_colorkey)
+
+    tmxdata.images = [0] * tmxdata.maxgid
+
+    for firstgid, t in sorted((t.firstgid, t) for t in tmxdata.tilesets):
+        path = os.path.join(os.path.dirname(tmxdata.filename), t.source)
+
+        image = pygame.image.load(path)
+
+        w, h = image.get_size()
+        tile_size = (t.tilewidth, t.tileheight)
+        real_gid = t.firstgid - 1
+
+        if t.trans:
+            tileset_colorkey = pygame.Color("#{}".format(t.trans)) 
+
+        # i dont agree with margins and spacing, but i'll support it anyway
+        # such is life.  okay.jpg
+        tilewidth = t.tilewidth + t.spacing
+        tileheight = t.tileheight + t.spacing
+
+        # some tileset images may be slightly larger than the tile area
+        # ie: may include a banner, copyright, ect.  this compensates for that
+        width = ((int((w-t.margin*2) + t.spacing) / tilewidth) * tilewidth) - t.spacing
+        height = ((int((h-t.margin*2) + t.spacing) / tileheight) * tileheight) - t.spacing
+
+        # using product avoids the overhead of nested loops
+        p = product(xrange(t.margin, height+t.margin, tileheight),
+                    xrange(t.margin, width+t.margin, tilewidth))
+
+        for (y, x) in p:
+            real_gid += 1
+            gids = tmxdata.mapGID(real_gid)
+            if gids == []: continue
+
+            # we do some tests to correctly handle the tile and set the right
+            # blitting flags.  just grab a section of it.
+            temp = image.subsurface(((x,y), tile_size))
+
+            # count the number of pixels in the tile that are not transparent
+            px = mask.from_surface(temp).count()
+
+            # there are no transparent pixels in the image
+            if px == tile_size[0] * tile_size[1]:
+                tile = temp.convert()
+
+            # there are transparent pixels, and set to force a colorkey
+            elif force_colorkey:
+                tile = Surface(tile_size)
+                tile.fill(force_colorkey)
+                tile.blit(temp, (0,0))
+                tile.set_colorkey(force_colorkey, pygame.RLEACCEL)
+
+            # there are transparent pixels, and tiled set a colorkey
+            elif t.trans:
+                tile = temp.convert()
+                tile.set_colorkey(tileset_colorkey, pygame.RLEACCEL)
+
+            # there are transparent pixels, and set for perpixel alpha
+            elif pixelalpha:
+                tile = temp.convert_alpha()
+
+            # there are transparent pixels, and we won't handle them
+            else:
+                tile = temp.convert()
+
+            for gid, flags in gids:
+                tmxdata.images[gid] = handle_transformation(tile, flags)
+
+
+def load_pygame(filename, *args, **kwargs):
+    tmxdata = load_tmx(filename, *args, **kwargs)
+    load_images_pygame(tmxdata, None, *args, **kwargs)
+
+    return tmxdata
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/001/pytmx/tmxloader3.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,612 @@
+"""
+Map loader for TMX Files
+bitcraft (leif.theden at gmail.com)
+v.7  -- for python 3.x
+
+If you have any problems, please contact me via email.
+Tested with Tiled 0.7.1 for Mac.
+
+released under the LGPL v3
+
+======================================================================
+Design Goals:
+    Simple api
+    Memory efficient and fast
+    Quick access to tiles, attributes, and properties
+
+Non-Goals:
+    Rendering
+
+Works:
+    Pygame image loading
+    Map loading with all required types
+    Properties for all types: maps, layers, objects, tiles
+    Automatic flipping of tiles
+
+Todo:
+    Pygame: test colorkey transparency
+
+    Optimized for maps that do not make heavy use of tile
+    properties.  If i find that it is used a lot then i can rework
+    it for better performance.
+
+======================================================================
+
+Basic usage sample:
+
+    >>> import tmxloader
+    >>> tiledmap = tmxloader.load_pygame("map.tmx")
+
+
+When you want to draw tiles, you simply call "get_tile_image":
+
+    >>> image = tiledmap.get_tile_image(x, y, layer)    
+    >>> screen.blit(position, image)
+
+
+Layers, objectgroups, tilesets, and maps all have a simple way to access
+metadata that was set inside tiled: they all become class attributes.
+
+    >>> print(layer.tilewidth)
+    32
+    >>> print(layer.weather)
+    'sunny'
+
+
+Tiles are the exception here, and must be accessed through "getTileProperties"
+and are regular Python dictionaries:
+
+    >>> tile = tiledmap.getTileProperties(x, y, layer)
+    >>> tile["name"]
+    'CobbleStone'
+
+"""
+
+from itertools import chain
+
+
+# internal flags
+FLIP_X = 1
+FLIP_Y = 2
+
+
+# Tiled gid flags
+GID_FLIP_X = 1<<31
+GID_FLIP_Y = 1<<30
+
+
+class TiledElement(object):
+    pass
+
+class TiledMap(TiledElement):
+    """
+    not really useful unless "loaded"  ie: don't instance directly.
+    see the pygame loader for inspiration
+    """
+
+    def __init__(self):
+        TiledElement.__init__(self)
+        self.layers = []            # list of all layer types (tile layers + object layers)
+        self.tilesets = []          # list of TiledTileset objects
+        self.tilelayers   = []      # list of TiledLayer objects
+        self.objectgroups = []      # list of TiledObjectGroup objects
+        self.tile_properties = {}   # dict of tiles that have additional metadata (properties)
+        self.filename = None
+
+        # this is a work around to tiled's strange way of storing gid's
+        self.images = [0]
+
+        # defaults from the TMX specification
+        self.version = 0.0
+        self.orientation = None
+        self.width = 0
+        self.height = 0
+        self.tilewidth = 0
+        self.tileheight = 0 
+
+    def get_tile_image(self, x, y, layer):
+        """
+        return the tile image for this location
+        x and y must be integers and are in tile coordinates, not pixel
+
+        return value will be 0 if there is no tile with that location.
+        """
+
+        try:
+            gid = self.tilelayers[layer].data[y][x]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer)
+            raise Exception(msg)
+
+        else:
+            try:
+                return self.images[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invalid GID: {3}/{4}.".format(x, y, layer, gid, len(self.images))
+                raise Exception(msg)
+
+    def getTileGID(self, x, y, layer):
+        """
+        return GID of a tile in this location
+        x and y must be integers and are in tile coordinates, not pixel
+        """
+
+        try:
+            return self.tilelayers[layer].data[y][x]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer)
+            raise Exception(msg)
+
+    def getDrawOrder(self):
+        """
+        return a list of objects in the order that they should be drawn
+        this will also exclude any layers that are not set to visible
+
+        may be useful if you have objects and want to control rendering
+        from tiled
+        """
+
+        raise NotImplementedError
+
+    def getTileImages(self, r, layer):
+        """
+        return a group of tiles in an area
+        expects a pygame rect or rect-like list/tuple
+
+        usefull if you don't want to repeatedly call get_tile_image
+        probably not the most effecient way of doing this, but oh well.
+        """
+
+        raise NotImplementedError
+
+    def getObjects(self):
+        """
+        Return iterator all of the objects associated with this map
+        """
+
+        return chain(*[ i.objects for i in self.objectgroups ])
+
+    def getTileProperties(self, x, y, layer):
+        """
+        return the properties for the tile, if any
+        x and y must be integers and are in tile coordinates, not pixel
+
+        returns a dict of there are properties, otherwise will be None
+        """
+
+        try:
+            gid = self.tilelayers[layer].data[y][x]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer)
+            raise Exception(msg)
+
+        else:
+            try:
+                return self.tile_properties[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invaid GID: {3}/{4}.".format(x, y, layer, gid, len(self.images))
+                raise Exception(msg)
+
+    def getTilePropertiesByGID(self, gid):
+        try:
+            return self.tile_properties[gid]
+        except KeyError:
+            return None
+
+# the following classes get their attributes filled in with the loader
+
+class TiledTileset(TiledElement):
+    def __init__(self):
+        TiledElement.__init__(self)
+
+        # defaults from the specification
+        self.firstgid = 0
+        self.lastgid = 0
+        self.name = None
+        self.tilewidth = 0
+        self.tileheight = 0
+        self.spacing = 0
+        self.margin = 0
+
+class TiledLayer(TiledElement):
+    def __init__(self):
+        TiledElement.__init__(self)
+        self.data = None
+
+        # defaults from the specification
+        self.name = None
+        self.opacity = 1.0
+        self.visible = 1
+        
+class TiledObjectGroup(TiledElement):
+    def __init__(self):
+        TiledElement.__init__(self)
+        self.objects = []
+
+        # defaults from the specification
+        self.name = None
+
+class TiledObject(TiledElement):
+    __slots__ = ['name', 'type', 'x', 'y', 'width', 'height', 'gid']
+
+    def __init__(self):
+        TiledElement.__init__(self)
+
+        # defaults from the specification
+        self.name = None
+        self.type = None
+        self.x = 0
+        self.y = 0
+        self.width = 0
+        self.height = 0
+        self.gid = 0
+
+
+def load_tmx(filename):
+    """
+    Utility function to parse a Tiled TMX and return a usable object.
+    Images will not be loaded, so probably not useful to call this directly
+
+    See the load_pygame func for an idea of what to do
+    """
+
+    from xml.dom.minidom import parse
+    from itertools import tee, islice, chain
+    from collections import defaultdict 
+    from struct import unpack
+    import array, os
+
+    # used to change the unicode string returned from minidom to
+    # proper python variable types.
+    types = {
+        "version": float,
+        "orientation": str,
+        "width": int,
+        "height": int,
+        "tilewidth": int,
+        "tileheight": int,
+        "firstgid": int,
+        "source": str,
+        "name": str,
+        "spacing": int,
+        "margin": int,
+        "source": str,
+        "trans": str,
+        "id": int,
+        "opacity": float,
+        "visible": bool,
+        "encoding": str,
+        "compression": str,
+        "gid": int,
+        "type": str,
+        "x": int,
+        "y": int,
+        "value": str,
+    }
+ 
+    def pairwise(iterable):
+        # return a list as a sequence of pairs
+        a, b = tee(iterable)
+        next(b, None)
+        return zip(a, b)
+
+    def group(l, n):
+        # return a list as a sequence of n tuples
+        return zip(*[islice(l, i, None, n) for i in range(n)])
+
+    def parse_properties(node):
+        """
+        parse a node and return a dict that represents a tiled "property"
+        """
+
+        d = {}
+
+        for child in node.childNodes:
+            if child.nodeName == "properties":
+                for subnode in child.getElementsByTagName("property"):
+                    # the "properties" from tiled's tmx have an annoying
+                    # quality that "name" and "value" is included as part of it.
+                    # so we mangle it to get that stuff out.
+                    d.update(dict(pairwise([ str(i.value) for i in list(subnode.attributes.values()) ])))
+
+        return d
+
+    def get_properties(node):
+        """
+        parses a node and returns a dict that contains the data from the node's
+        attributes and any data from "property" elements as well.
+        """
+
+        d = {}
+
+        # get tag attributes
+        d.update(get_attributes(node))
+
+        # get vlues of the properties element, if any
+        d.update(parse_properties(node))
+     
+        return d
+
+    def set_properties(obj, node):
+        """
+        read the xml attributes and tiled "properties" from a xml node and fill in
+        the values into an object's dictionary
+        """
+
+        [ setattr(obj, k, v) for k,v in list(get_properties(node).items()) ]
+
+    def get_attributes(node):
+        """
+        get the attributes from a node and fix them to the correct type
+        """
+
+        d = defaultdict(lambda:None)
+
+        for k, v in list(node.attributes.items()):
+            k = str(k)
+            d[k] = types[k](v)
+
+        return d
+
+    def decode_gid(raw_gid):
+        # gid's are encoded with extra information
+        # as of 0.7.0 it determines if the tile should be flipped when rendered
+
+        flags = 0
+        if raw_gid & GID_FLIP_X == GID_FLIP_X: flags += FLIP_X
+        if raw_gid & GID_FLIP_Y == GID_FLIP_Y: flags += FLIP_Y
+        gid = raw_gid & ~(GID_FLIP_X | GID_FLIP_Y)
+
+        return gid, flags
+
+
+    def parse_map(node):
+        """
+        parse a map node from a tiled tmx file
+        return a tiledmap
+        """
+
+        tiledmap = TiledMap()
+        tiledmap.filename = filename
+        set_properties(tiledmap, map_node)
+
+        for node in map_node.getElementsByTagName("tileset"):
+            t, tiles = parse_tileset(node)
+            tiledmap.tilesets.append(t)
+            tiledmap.tile_properties.update(tiles)
+
+        for node in dom.getElementsByTagName("layer"):
+            l = parse_layer(tiledmap.tilesets, node)
+            tiledmap.tilelayers.append(l)
+            tiledmap.layers.append(l)
+
+        for node in dom.getElementsByTagName("objectgroup"):
+            o = parse_objectgroup(node)
+            tiledmap.objectgroups.append(o)
+            tiledmap.layers.append(o)
+
+        return tiledmap
+
+
+    def parse_tileset(node, firstgid=None):
+        """
+        parse a tileset element and return a tileset object and properties for tiles as a dict
+        """
+
+        tileset = TiledTileset()
+        set_properties(tileset, node)
+        tiles = {}
+
+        if firstgid != None:
+            tileset.firstgid = firstgid
+
+        # since tile objects probably don't have a lot of metadata,
+        # we store it seperately from the class itself
+        for child in node.childNodes:
+            if child.nodeName == "tile":
+                p = get_properties(child)
+                gid = p["id"] + tileset.firstgid
+                del p["id"]
+                tiles[gid] = p
+
+        # check for tiled "external tilesets"
+        if hasattr(tileset, "source"):
+            if tileset.source[-4:].lower() == ".tsx":
+                try:
+                    # we need to mangle the path some because tiled stores relative paths
+                    path = os.path.join(os.path.dirname(filename), tileset.source)
+                    tsx = parse(path)
+                except IOError:
+                    raise IOError("Cannot load external tileset: " + path)
+
+                tileset_node = tsx.getElementsByTagName("tileset")[0]
+                tileset, tiles = parse_tileset(tileset_node, tileset.firstgid)
+            else:
+                raise Exception("Found external tileset, but cannot handle type: " + tileset.source)
+
+        # if we have an "image" tag, process it here
+        try:
+            image_node = node.getElementsByTagName("image")[0]
+        except IndexError:
+            pass
+        else:
+            attr = get_attributes(image_node)
+            tileset.source = attr["source"]
+            tileset.trans = attr["trans"]
+
+            # calculate the number of tiles in this tileset
+            x, r = divmod(attr["width"], tileset.tilewidth)
+            y, r = divmod(attr["height"], tileset.tileheight)
+
+            tileset.lastgid = tileset.firstgid + x + y
+          
+        return tileset, tiles
+
+
+    def parse_layer(tilesets, node):
+        """
+        parse a layer element and return a layer object
+
+        tilesets is required since we need to mangle gid's here
+        """
+
+        layer = TiledLayer()
+        layer.data = []
+        layer.flipped_tiles = []
+        set_properties(layer, node)
+
+        data = None
+        next_gid = None
+
+        data_node = node.getElementsByTagName("data")[0]
+        attr = get_attributes(data_node)
+
+        if attr["encoding"] == "base64":
+            from base64 import b64decode
+            data = b64decode(bytes(data_node.lastChild.nodeValue, 'ascii'))
+
+        elif attr["encoding"] == "csv":
+            next_gid = map(int, "".join([line.strip() for line in data_node.lastChild.nodeValue]).split(","))
+
+        elif not attr["encoding"] == None:
+            raise Exception("TMX encoding type: " + str(attr["encoding"]) + " is not supported.")
+      
+        if attr["compression"] == "gzip":
+            from io import BytesIO
+            import gzip
+            with gzip.GzipFile(fileobj=BytesIO(data)) as fh:
+                data = fh.read()
+
+        elif not attr["compression"] == None:
+            raise Exception("TMX compression type: " + str(attr["compression"]) + " is not supported.")
+     
+        # if data is None, then it was not decoded or decompressed, so
+        # we assume here that it is going to be a bunch of tile elements
+        if attr["encoding"] == next_gid == None:
+            def get_children(parent):
+                for child in parent.getElementsByTagName("tile"):
+                    yield int(child.getAttribute("gid"))
+
+            next_gid = get_children(data_node)
+
+        elif not data == None:
+            # cast the data as a list of 32-bit integers
+            def u(i): return unpack("<L", bytes(i))[0]
+            next_gid = map(u, group(data, 4))
+
+        gids = [ i.firstgid for i in tilesets ]
+
+        # fill up our 2D array of gid's.
+        for y in range(layer.height):
+
+            # store as 16-bit ints, since we will never use enough tiles to fill a 32-bit int
+            layer.data.append(array.array("H"))
+
+            for x in range(layer.width):
+                gid, flags = decode_gid(next(next_gid))
+                if not flags == 0: layer.flipped_tiles.append((x, y, gid, flags))
+                layer.data[y].append(gid)
+
+        return layer
+
+
+    def parse_objectgroup(node):
+        """
+        parse a objectgroup element and return a object group
+        """
+
+        objgroup = TiledObjectGroup()
+        set_properties(objgroup, node)
+
+        for subnode in node.getElementsByTagName("object"):
+            obj = TiledObject()
+            set_properties(obj, subnode)
+            objgroup.objects.append(obj)
+
+        return objgroup
+
+
+    # open and read our TMX (which is really just xml)
+    dom = parse(filename)
+    map_node =  dom.getElementsByTagName("map")[0]
+    return parse_map(map_node)
+
+
+def load_pygame(filename):
+    """
+    load a tiled TMX map for use with pygame
+    """
+
+    from pygame import Surface
+    import pygame, os
+
+    tiledmap = load_tmx(filename)
+
+    # cache will find duplicate tiles to reduce memory usage
+    # mostly this is a problem in the blank areas of a tilemap
+    cache = {}
+
+    # just a precaution to make sure tileset images are added in the correct order
+    for firstgid, t in sorted([ (t.firstgid, t) for t in tiledmap.tilesets ]):
+        path = os.path.join(os.path.dirname(tiledmap.filename), t.source)
+
+        image = pygame.image.load(path)
+
+        w, h = image.get_rect().size
+
+        tile_size = (t.tilewidth, t.tileheight)
+
+        # some tileset images may be slightly larger than the tiles area
+        # ie: may include a banner, copyright, ect.  this compensates for that
+        for y in range(0, int(h / t.tileheight) * t.tileheight, t.tileheight):
+            for x in range(0, int(w / t.tilewidth) * t.tilewidth, t.tilewidth):
+
+                # somewhat handle transparency, though colorkey handling is not tested
+                if t.trans == None:
+                    tile = Surface(tile_size, pygame.SRCALPHA)
+                else:
+                    tile = Surface(tile_size)
+
+                # blit the smaller portion onto a new image from the larger one
+                tile.blit(image, (0,0), ((x, y), tile_size))
+
+                # make a unique id for this image, not sure if this is the best way, but it works
+                key = pygame.image.tostring(tile, "RGBA")
+        
+                # make sure we don't have a duplicate tile
+                try:
+                    tile = cache[key]
+                except KeyError:
+                    if t.trans == None:
+                        tile = tile.convert_alpha()
+                    else:
+                        tile = tile.convert()
+                        tile.set_colorkey(t.trans)
+
+                    # update the cache
+                    cache[key] = tile
+
+                tiledmap.images.append(tile)
+
+    # correctly handle transformed tiles.  currently flipped tiles
+    # work by creating a new gid for the flipped tile and changing the gid
+    # in the layer to the new gid.
+    for layer in tiledmap.tilelayers:
+        for x, y, gid, trans in layer.flipped_tiles:
+            fx = trans & FLIP_X == FLIP_X
+            fy = trans & FLIP_Y == FLIP_Y
+
+            tile = pygame.transform.flip(tiledmap.images[gid], fx, fy)
+            tiledmap.images.append(tile)
+
+            # change the original gid in the layer data to the new gid
+            layer.data[y][x] = len(tiledmap.images) - 1
+
+        del layer.flipped_tiles
+    del cache
+
+    return tiledmap
+
+if __name__ == '__main__':
+    print('[tmxloader] starting built-in test')
+    load_tmx("../resources/maps/village.tmx")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/001/pytmx/utils.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,251 @@
+from pygame import Rect
+from itertools import tee, islice, izip, product
+from collections import defaultdict
+from constants import *
+
+
+
+"""
+Various code that doesn't really fit anywhere else
+"""
+
+
+def read_points(text):
+    return [ tuple(map(lambda x: int(x), i.split(',')))
+         for i in text.split() ]
+
+
+
+def parse_properties(node):
+    """
+    parse a node and return a dict that represents a tiled "property"
+    """
+
+    # the "properties" from tiled's tmx have an annoying quality that "name"
+    # and "value" is included. here we mangle it to get that junk out.
+
+    d = {}
+
+    for child in node.findall('properties'):
+        for subnode in child.findall('property'):
+            d[subnode.get('name')] = subnode.get('value')
+
+    return d
+
+
+def decode_gid(raw_gid):
+    # gid's are encoded with extra information
+    # as of 0.7.0 it determines if the tile should be flipped when rendered
+    # as of 0.8.0 bit 30 determines if GID is rotated
+
+    flags = 0
+    if raw_gid & GID_TRANS_FLIPX == GID_TRANS_FLIPX: flags += TRANS_FLIPX
+    if raw_gid & GID_TRANS_FLIPY == GID_TRANS_FLIPY: flags += TRANS_FLIPY
+    if raw_gid & GID_TRANS_ROT == GID_TRANS_ROT: flags += TRANS_ROT
+    gid = raw_gid & ~(GID_TRANS_FLIPX | GID_TRANS_FLIPY | GID_TRANS_ROT)
+
+    return gid, flags
+
+
+def handle_bool(text):
+    # properly convert strings to a bool
+    try:
+        return bool(int(text))
+    except:
+        pass
+
+    try:
+        text = str(text).lower()
+        if text == "true":   return True
+        if text == "yes":    return True
+        if text == "false":  return False
+        if text == "no":     return False
+    except:
+        pass
+        
+    raise ValueError
+
+
+# used to change the unicode string returned from xml to proper python 
+# variable types.
+types = defaultdict(lambda: str)
+types.update({
+    "version": float,
+    "orientation": str,
+    "width": int,
+    "height": int,
+    "tilewidth": int,
+    "tileheight": int,
+    "firstgid": int,
+    "source": str,
+    "name": str,
+    "spacing": int,
+    "margin": int,
+    "source": str,
+    "trans": str,
+    "id": int,
+    "opacity": float,
+    "visible": handle_bool,
+    "encoding": str,
+    "compression": str,
+    "gid": int,
+    "type": str,
+    "x": int,
+    "y": int,
+    "value": str,
+})
+
+
+def pairwise(iterable):
+    # return a list as a sequence of pairs
+    a, b = tee(iterable)
+    next(b, None)
+    return izip(a, b)
+
+
+def group(l, n):
+    # return a list as a sequence of n tuples
+    return izip(*(islice(l, i, None, n) for i in xrange(n)))
+
+
+def buildDistributionRects(tmxmap, layer, tileset=None, real_gid=None):
+    """
+    generate a set of non-overlapping rects that represents the distribution
+    of the specfied gid.
+
+    useful for generating rects for use in collision detection
+    """
+    
+    if isinstance(tileset, int):
+        try:
+            tileset = tmxmap.tilesets[tileset]
+        except IndexError:
+            msg = "Tileset #{} not found in map {}."
+            raise IndexError, msg.format(tileset, tmxmap)
+
+    elif isinstance(tileset, str):
+        try:
+            tileset = [ t for t in tmxmap.tilesets if t.name == tileset ].pop()
+        except IndexError:
+            msg = "Tileset \"{}\" not found in map {}."
+            raise ValueError, msg.format(tileset, tmxmap)
+
+    elif tileset:
+        msg = "Tileset must be either a int or string. got: {}"
+        raise ValueError, msg.format(type(tileset))
+
+    gid = None
+    if real_gid:
+        try:
+            gid, flags = tmxmap.mapGID(real_gid)[0]
+        except IndexError:
+            msg = "GID #{} not found"
+            raise ValueError, msg.format(real_gid)
+
+
+    if isinstance(layer, int):
+        layer_data = tmxmap.getLayerData(layer).data
+    elif isinstance(layer, str):
+        try:
+            layer = [ l for l in tmxmap.tilelayers if l.name == layer ].pop()
+            layer_data = layer.data
+        except IndexError:
+            msg = "Layer \"{}\" not found in map {}."
+            raise ValueError, msg.format(layer, tmxmap)
+
+    p = product(xrange(tmxmap.width), xrange(tmxmap.height))
+    if gid:
+        points = [ (x,y) for (x,y) in p if layer_data[y][x] == gid ]
+    else:
+        points = [ (x,y) for (x,y) in p if layer_data[y][x] ]
+
+    rects = simplify(points, tmxmap.tilewidth, tmxmap.tileheight)
+    return rects
+
+
+def simplify(all_points, tilewidth, tileheight):
+    """
+    klugde:
+
+    "A kludge (or kluge) is a workaround, a quick-and-dirty solution,
+    a clumsy or inelegant, yet effective, solution to a problem, typically
+    using parts that are cobbled together."
+
+    -- wikipedia
+   
+    turn a list of points into a rects 
+    adjacent rects will be combined.
+
+    plain english:
+        the input list must be a a list of tuples that represent
+        the areas to be combined into rects
+        the rects will be blended together over solid groups
+
+        so if data is something like:
+
+        0 1 1 1 0 0 0 
+        0 1 1 0 0 0 0 
+        0 0 0 0 0 4 0 
+        0 0 0 0 0 4 0
+        0 0 0 0 0 0 0 
+        0 0 1 1 1 1 1
+
+        you'll have the 4 rects that mask the area like this:
+
+        ..######......
+        ..####........
+        ..........##..
+        ..........##..
+        ..............
+        ....##########
+
+        pretty cool, right?
+
+    there may be cases where the number of rectangles is not as low as possible,
+    but i haven't found that it is excessively bad.  certainly much better than
+    making a list of rects, one for each tile on the map!
+
+    """
+
+    def pick_rect(points, rects):
+        ox, oy = sorted([ (sum(p), p) for p in points ])[0][1]
+        x = ox
+        y = oy
+        ex = None            
+
+        while 1:
+            x += 1
+            if not (x, y) in points:
+                if ex == None:
+                    ex = x - 1
+
+                if ((ox, y+1) in points):
+                    if x == ex + 1 :
+                        y += 1
+                        x = ox
+
+                    else:
+                        y -= 1
+                        break
+                else:
+                    if x <= ex: y-= 1
+                    break
+
+        c_rect = Rect(ox*tilewidth,oy*tileheight,\
+                     (ex-ox+1)*tilewidth,(y-oy+1)*tileheight)
+
+        rects.append(c_rect)
+
+        rect = Rect(ox,oy,ex-ox+1,y-oy+1)
+        kill = [ p for p in points if rect.collidepoint(p) ]
+        [ points.remove(i) for i in kill ]
+
+        if points:
+            pick_rect(points, rects)
+
+    rect_list = []
+    while all_points:
+        pick_rect(all_points, rect_list)
+
+    return rect_list
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/001/resources.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+import pygame
+import os
+
+
+def file_path(filename):
+    return os.path.join(
+            os.path.dirname(os.path.abspath(__file__)),
+            'resources',
+            filename
+        )
+
+
+def load_sheet(filename, width=32, height=None):
+    if height is None:
+        height = width
+    path = file_path(filename)
+    image = pygame.image.load(path)
+    image = image.convert()
+    image.set_colorkey((0xff, 0, 0xff))
+    w, h = image.get_size()
+    images = []
+    for x in xrange(w // width):
+        for y in xrange(h // height):
+            rect = pygame.rect.Rect(x * width, y * height, width, height)
+            images.append(image.subsurface(rect))
+    return images
+
+
+
Binary file 004/001/resources/control.png has changed
Binary file 004/001/resources/effects.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/001/resources/level.tmx	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" width="25" height="18" tilewidth="32" tileheight="32">
+ <tileset firstgid="1" name="control" tilewidth="32" tileheight="32">
+  <properties>
+   <property name="control" value="1"/>
+  </properties>
+  <image source="control.png" trans="ff00ff" width="64" height="32"/>
+  <tile id="0">
+   <properties>
+    <property name="block" value="1"/>
+   </properties>
+  </tile>
+  <tile id="1">
+   <properties>
+    <property name="block" value="1"/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="3" name="tilesheet" tilewidth="32" tileheight="32">
+  <image source="tilesheet.png" trans="ff00ff" width="288" height="192"/>
+ </tileset>
+ <layer name="Control" width="25" height="18">
+  <data encoding="base64" compression="zlib">
+   eJxjZGBgYKQDpiUY7nYwUYBH7Ri1Y9SOoWEHNcFws4PWGABJRwF/
+  </data>
+ </layer>
+ <layer name="Background" width="25" height="18">
+  <data encoding="base64" compression="zlib">
+   eJyTZ2BgkKcDVqIhHu52UDPcR6Id+MJ61A762zFc0hW17aBHWTJU7aA1BgDmfzlJ
+  </data>
+ </layer>
+ <layer name="Corners" width="25" height="18">
+  <data encoding="base64" compression="zlib">
+   eJy1VdtqwzAMFWQbFHaBwNq1DyFhD92lD7Zr//+v1WY65ERTRyFMIOzKro6OJCtJRJL8SNA1qi3KLNG5x8L/xd1E+7uqH/+g94Q7OnF1tM/mrNBanDt91XPVQeZcjHT/tep2he4Ia9A1EI9i7EJ7rk3642yk/UB+rN1KvNEGX4+Or2YveoYcIta2f6u6d/RAPsDnVh5BMXA+mVizxvRu4mFfmeJFPc4OP+RjonPwFcXge168ydiBLbJ8g5P63hAOYzw4voABns3ey9xXQZb8OVdFOWXFsPMAGB3xQJ3whry++qr67ehJzzE/PB4Wm+0v9H87n7jO3BuoLXLqYXTyu+acMwj7tdjwhXeAvDOPo6ybg5+y7FGuB3qlSSa+ntgZCWk+evrN7+NJ1s3Ba/qsWODC3xbuRc47vjtB5tlh5yMk0noBeX8qEg==
+  </data>
+ </layer>
+</map>
Binary file 004/001/resources/spritesheet.png has changed
Binary file 004/001/resources/tilesheet.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/001/sprites.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+import pygame
+
+import resources
+
+
+FACING = {
+    (0, -1): 0,
+    (-1, 0): 1,
+    (0, 1): 2,
+    (1, 0): 3,
+}
+
+
+class ShadowSprite(pygame.sprite.Sprite):
+    def __init__(self, sprite):
+        super(ShadowSprite, self).__init__()
+        self.spritesheet = resources.load_sheet('effects.png', 32)
+        self.image = self.spritesheet[0]
+        self.rect = self.image.get_rect()
+        self.image.set_alpha(0.6 * 255)
+        self.sprite = sprite
+        self.rect.midbottom = self.sprite.rect.midbottom
+
+    def update(self):
+        self.rect.midbottom = self.sprite.rect.midbottom
+
+
+class HeroSprite(pygame.sprite.Sprite):
+    def __init__(self, (x, y)):
+        super(HeroSprite, self).__init__()
+        self.spritesheet = resources.load_sheet('spritesheet.png', 64)
+        self.facing = 2
+        self.image = self.spritesheet[self.facing]
+        self.rect = self.image.get_rect()
+        self.rect.midbottom = (x * 32 + 16, y * 32 + 32)
+        self.animations = []
+
+    def move(self, (dx, dy)):
+        self.facing = FACING[dx, dy]
+        self.animations.append(self.animation_walk((dx, dy)))
+
+    def animation_walk(self, (dx, dy)):
+        for step in xrange(8):
+            self.image = self.spritesheet[self.facing + step * 4 + 4]
+            self.rect.move_ip(dx * 4, dy * 4)
+            yield
+
+    def animation_stand(self):
+        self.image = self.spritesheet[self.facing]
+        return
+        yield
+
+    def update(self):
+        if not self.animations:
+            self.animations.append(self.animation_stand())
+        while self.animations:
+            try:
+                self.animations[0].next()
+                return
+            except StopIteration:
+                self.animations.pop(0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/002/__main__.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from game import Game
+
+
+game = Game()
+game.load_level('level.tmx')
+game.run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/002/game.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+import pygame
+import pygame.locals as pg
+
+import level
+import sprites
+
+
+class Game(object):
+    def __init__(self):
+        pygame.display.init()
+        pygame.font.init()
+        self.screen = pygame.display.set_mode((800, 600))
+        self.screen.fill((0x66, 0x66, 0x66))
+        self.keys = {
+            pg.K_UP: (self.move_hero, (0, -1)),
+            pg.K_DOWN: (self.move_hero, (0, 1)),
+            pg.K_LEFT: (self.move_hero, (-1, 0)),
+            pg.K_RIGHT: (self.move_hero, (1, 0)),
+            pg.K_ESCAPE: (self.quit,),
+        }
+        self.shadow_sprites = pygame.sprite.RenderUpdates()
+        self.sprites = pygame.sprite.RenderUpdates()
+        self.key = None
+
+    def load_level(self, filename):
+        self.level = level.Level(filename)
+        for name, pos in self.level.objects():
+            sprite = sprites.DecorationSprite(pos)
+            self.sprites.add(sprites.DecorationSprite(pos))
+            self.shadow_sprites.add(sprites.ShadowSprite(sprite))
+        self.hero_sprite = sprites.HeroSprite(self.level.hero_position)
+        self.sprites.add(self.hero_sprite)
+        self.shadow_sprites.add(sprites.ShadowSprite(self.hero_sprite))
+        self.background = self.screen.copy()
+        self.level.render(self.background)
+        self.screen.blit(self.background, (0, 0))
+        self.sprites.draw(self.screen)
+        pygame.display.update()
+
+    def quit(self):
+        self.game_over = True
+
+    def move_hero(self, direction):
+        if self.level.move_hero(direction):
+            self.hero_sprite.move(direction)
+
+    def control(self):
+        for event in pygame.event.get():
+            if event.type == pg.QUIT:
+                self.quit()
+            elif event.type == pg.KEYDOWN:
+                self.key = event.key
+        if not self.hero_sprite.animations:
+            keys = pygame.key.get_pressed()
+            for key, action in self.keys.iteritems():
+                if keys[key] or self.key == key:
+                    action[0](*action[1:])
+                    break
+            self.key = None
+
+    def display(self):
+        self.sprites.clear(self.screen, self.background)
+        self.shadow_sprites.clear(self.screen, self.background)
+        self.sprites.update()
+        self.shadow_sprites.update()
+        dirty = []
+        dirty.extend(self.shadow_sprites.draw(self.screen))
+        dirty.extend(self.sprites.draw(self.screen))
+        pygame.display.update(dirty)
+
+    def run(self):
+        self.clock = pygame.time.Clock()
+        self.game_over = False
+        while not self.game_over:
+            self.display()
+            self.control()
+            self.clock.tick(30)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/002/level.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+from pytmx import tmxloader
+
+import resources
+
+
+class Level(object):
+    def __init__(self, filename):
+        path = resources.file_path(filename)
+        self.tiled_map = tmxloader.load_pygame(path)
+        self.hero_position = 1, 1
+
+    def render(self, screen):
+        w = self.tiled_map.tilewidth
+        h = self.tiled_map.tileheight
+        get_image = self.tiled_map.getTileImage
+        for l, layer in enumerate(self.tiled_map.tilelayers):
+            for y in xrange(self.tiled_map.height):
+                for x in xrange(self.tiled_map.width):
+                    image = get_image(x, y, l)
+                    if image:
+                        screen.blit(image, (x * w, y * h))
+        x, y = self.hero_position
+
+    def get_properties(self, (x, y)):
+        properties = {}
+        for l, layer in enumerate(self.tiled_map.tilelayers):
+            properties.update(self.tiled_map.getTileProperties((x, y, l)) or {})
+        return properties
+
+    def is_blocked(self, (x, y)):
+        return self.get_properties((x, y)).get('block')
+
+    def move_hero(self, (dx, dy)):
+        x, y = self.hero_position
+        if not self.is_blocked((x + dx, y + dy)):
+            self.hero_position = x + dx, y + dy
+            return True
+        return False
+
+    def objects(self):
+        w = self.tiled_map.tilewidth
+        h = self.tiled_map.tileheight
+        for og in self.tiled_map.objectgroups:
+            for o in og:
+                yield o.name, (o.x // w, o.y // h)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/002/pytmx/__init__.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,5 @@
+from tmxloader import load_tmx, load_pygame
+from pytmx import TiledElement, TiledMap, TiledTileset, TiledLayer, \
+                  TiledObjectGroup, TiledObject
+from utils import buildDistributionRects
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/002/pytmx/constants.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,11 @@
+# internal flags
+TRANS_FLIPX = 1
+TRANS_FLIPY = 2
+TRANS_ROT = 4
+
+
+# Tiled gid flags
+GID_TRANS_FLIPX = 1<<31
+GID_TRANS_FLIPY = 1<<30
+GID_TRANS_ROT   = 1<<29
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/002/pytmx/pytmx.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,572 @@
+from itertools import chain, product
+from xml.etree import ElementTree
+from collections import defaultdict 
+from utils import decode_gid, types, parse_properties, read_points
+from constants import *
+
+
+
+"""
+Class declarations and other important stuff
+"""
+
+
+
+class TiledElement(object):
+    def set_properties(self, node):
+        """
+        read the xml attributes and tiled "properties" from a xml node and fill
+        in the values into the object's dictionary.  Names will be checked to
+        make sure that they do not conflict with reserved names.
+        """
+
+        # set the attributes reserved for tiled
+        [ setattr(self, k, types[str(k)](v)) for (k,v) in node.items() ] 
+
+        # set the attributes that are derived from tiled 'properties'
+        for k,v in parse_properties(node).items():
+            if k in self.reserved:
+                msg = "{} \"{}\" has a property called \"{}\""
+                print msg.format(self.__class__.__name__, self.name, k, self.__class__.__name__)
+                msg = "This name is reserved for {} objects and can cannot be used."
+                print msg.format(self.__class__.__name__)
+                print "Please change the name in Tiled and try again."
+                print v
+                raise ValueError
+            setattr(self, k, types[str(k)](v))
+
+
+class TiledMap(TiledElement):
+    """
+    Contains the tile layers, tile images, object groups, and objects from a
+    Tiled TMX map.
+    """
+
+    reserved = "version orientation width height tilewidth tileheight properties tileset layer objectgroup".split()
+
+
+    def __init__(self, filename):
+        from collections import defaultdict
+
+        TiledElement.__init__(self)
+        self.tilesets = []          # list of TiledTileset objects
+        self.tilelayers   = []      # list of TiledLayer objects
+        self.objectgroups = []      # list of TiledObjectGroup objects
+        self.tile_properties = {}   # dict of tiles that have metadata
+        self.filename = filename
+
+        # only used tiles are actually loaded, so there will be a difference
+        # between the GID's in the Tile map data (tmx) and the data in this
+        # class and the layers.  This dictionary keeps track of that difference.
+        self.gidmap = defaultdict(list)
+
+        # should be filled in by a loader function
+        self.images = []
+
+        # defaults from the TMX specification
+        self.version = 0.0
+        self.orientation = None
+        self.width = 0
+        self.height = 0
+        self.tilewidth = 0
+        self.tileheight = 0 
+
+        self.imagemap = {}  # mapping of gid and trans flags to real gids
+        self.maxgid = 1
+
+        self.load()
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.filename)
+
+
+    def getTileImage(self, x, y, layer):
+        """
+        return the tile image for this location
+        x and y must be integers and are in tile coordinates, not pixel
+
+        return value will be 0 if there is no tile with that location.
+        """
+
+        try:
+            gid = self.tilelayers[int(layer)].data[int(y)][int(x)]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is not valid."
+            raise Exception, msg.format(x, y, layer)
+        except TypeError:
+            msg = "Tiles must be specified in integers."
+            raise TypeError, msg
+
+        else:
+            try:
+                return self.images[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invalid GID: {3}"
+                raise Exception, msg.format(x, y, layer, gid)
+
+
+    def getTileGID(self, x, y, layer):
+        """
+        return GID of a tile in this location
+        x and y must be integers and are in tile coordinates, not pixel
+        """
+
+        try:
+            return self.tilelayers[int(layer)].data[int(y)][int(x)]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid"
+            raise Exception, msg.format(x, y, layer)
+
+
+    def getDrawOrder(self):
+        """
+        return a list of objects in the order that they should be drawn
+        this will also exclude any layers that are not set to visible
+
+        may be useful if you have objects and want to control rendering
+        from tiled
+        """
+
+        raise NotImplementedError
+
+
+    def getTileImages(self, r, layer):
+        """
+        return a group of tiles in an area
+        expects a pygame rect or rect-like list/tuple
+
+        useful if you don't want to repeatedly call getTileImage
+        """
+
+        raise NotImplementedError
+
+
+    def getObjects(self):
+        """
+        Return iterator of all the objects associated with this map
+        """
+
+        return chain(*(i for i in self.objectgroups))
+
+
+    def getTileProperties(self, (x, y, layer)):
+        """
+        return the properties for the tile, if any
+        x and y must be integers and are in tile coordinates, not pixel
+
+        returns a dict of there are properties, otherwise will be None
+        """
+
+        try:
+            gid = self.tilelayers[int(layer)].data[int(y)][int(x)]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid."
+            raise Exception, msg.format(x, y, layer)
+
+        else:
+            try:
+                return self.tile_properties[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invaid GID: {3}"
+                raise Exception, msg.format(x, y, layer, gid)
+            except KeyError:
+                return None
+
+
+    def getLayerData(self, layer):
+        """
+        Return the data for a layer.
+
+        Data is an array of arrays.
+
+        >>> pos = data[y][x]
+        """
+
+        try:
+            return self.tilelayers[layer].data
+        except IndexError:
+            msg = "Layer {} does not exist."
+            raise ValueError, msg.format(layer)
+
+
+    def getTileLocation(self, gid):
+        # experimental way to find locations of a tile by the GID
+
+        p = product(xrange(self.width),
+                    xrange(self.height),
+                    xrange(len(self.tilelayers)))
+
+        return [ (x,y,l) for (x,y,l) in p 
+               if self.tilelayers[l].data[y][x] == gid ]
+
+
+    def getTilePropertiesByGID(self, gid):
+        try:
+            return self.tile_properties[gid]
+        except KeyError:
+            return None
+
+
+    def setTileProperties(self, gid, d):
+        """
+        set the properties of a tile by GID.
+        must use a standard python dict as d
+        """
+
+        try:
+            self.tile_properties[gid] = d
+        except KeyError:
+            msg = "GID #{} does not exist."
+            raise ValueError, msg.format(gid)
+
+
+    def getTilePropertiesByLayer(self, layer):
+        """
+        Return a list of tile properties (dict) in use in this tile layer.
+        """
+
+        try:
+            layer = int(layer)
+        except:
+            msg = "Layer must be an integer.  Got {} instead."
+            raise ValueError, msg.format(type(layer))
+
+        p = product(range(self.width),range(self.height))
+        layergids = set( self.tilelayers[layer].data[y][x] for x, y in p )
+
+        props = []
+        for gid in layergids:
+            try:
+                props.append((gid, self.tile_properties[gid]))
+            except:
+                continue
+            
+        return props
+
+
+    def registerGID(self, real_gid, flags=0):
+        """
+        used to manage the mapping of GID between the tmx data and the internal
+        data.
+        
+        number returned is gid used internally
+        """
+
+        if real_gid:
+            try:
+                return self.imagemap[(real_gid, flags)][0]
+            except KeyError:
+                # this tile has not been encountered before, or it has been
+                # transformed in some way.  make a new GID for it.
+                gid = self.maxgid
+                self.maxgid += 1
+                self.imagemap[(real_gid, flags)] = (gid, flags)
+                self.gidmap[real_gid].append((gid, flags))
+                return gid
+
+        else:
+            return 0
+
+
+
+    def mapGID(self, real_gid):
+        """
+        used to lookup a GID read from a TMX file's data
+        """
+
+        try:
+            return self.gidmap[int(real_gid)]
+        except KeyError:
+            return None
+        except TypeError:
+            msg = "GID's must be an integer"
+            raise TypeError, msg
+
+
+    def loadTileImages(self, filename):
+        raise NotImplementedError
+
+
+    def load(self):
+        """
+        parse a map node from a tiled tmx file
+        """
+        etree = ElementTree.parse(self.filename).getroot()
+        self.set_properties(etree)
+
+        # initialize the gid mapping
+        self.imagemap[(0,0)] = 0
+
+        for node in etree.findall('layer'):
+            self.tilelayers.append(TiledLayer(self, node))
+
+        for node in etree.findall('objectgroup'):
+            self.objectgroups.append(TiledObjectGroup(self, node))
+
+        for node in etree.findall('tileset'):
+            self.tilesets.append(TiledTileset(self, node))
+
+        # "tile objects", objects with a GID, have need to have their
+        # attributes set after the tileset is loaded
+        for o in self.getObjects():
+            p = self.getTilePropertiesByGID(o.gid)
+            if p:
+                o.name = "TileObject"
+                o.__dict__.update(p)
+
+
+    @property
+    def visibleTileLayers(self):
+        return [layer for layer in self.tilelayers if layer.visible]
+
+
+class TiledTileset(TiledElement):
+    reserved = "firstgid source name tilewidth tileheight spacing margin image tile properties".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+
+        # defaults from the specification
+        self.firstgid = 0
+        self.source = None
+        self.name = None
+        self.tilewidth = 0
+        self.tileheight = 0
+        self.spacing = 0
+        self.margin = 0
+        self.tiles = {}
+
+        self.parse(node)
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        """
+        parse a tileset element and return a tileset object and properties for
+        tiles as a dict
+
+        a bit of mangling is doew here so that tilesets that have external
+        TSX files appear the same as those that don't
+        """
+        import os
+
+
+        # if true, then node references an external tileset
+        source = node.get('source', False)
+        if source:
+            if source[-4:].lower() == ".tsx":
+
+                # external tilesets don't save this, store it for later
+                self.firstgid = int(node.get('firstgid'))
+
+                # we need to mangle the path - tiled stores relative paths
+                dirname = os.path.dirname(self.parent.filename)
+                path = os.path.abspath(os.path.join(dirname, source))
+                try:
+                    node = ElementTree.parse(path).getroot()
+                except IOError:
+                    msg = "Cannot load external tileset: {}"
+                    raise Exception, msg.format(path)
+
+            else:
+                msg = "Found external tileset, but cannot handle type: {}"
+                raise Exception, msg.format(self.source)
+           
+        self.set_properties(node)
+
+        # since tile objects [probably] don't have a lot of metadata,
+        # we store it seperately in the parent (a TiledMap instance)
+        for child in node.iter('tile'):
+            real_gid = int(child.get("id"))
+            p = parse_properties(child)
+            p['width'] = self.tilewidth
+            p['height'] = self.tileheight
+            for gid, flags in self.parent.mapGID(real_gid + self.firstgid):
+                self.parent.setTileProperties(gid, p)
+
+
+        image_node = node.find('image')
+        self.source = image_node.get('source')
+        self.trans = image_node.get("trans", None)
+
+
+class TiledLayer(TiledElement):
+    reserved = "name x y width height opacity properties data".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+        self.data = []
+
+        # defaults from the specification
+        self.name = None
+        self.opacity = 1.0
+        self.visible = True
+       
+        self.parse(node)
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        """
+        parse a layer element
+        """
+        from utils import group
+        from itertools import product, imap
+        from struct import unpack
+        import array
+
+        self.set_properties(node)
+
+        data = None
+        next_gid = None
+
+        data_node = node.find('data')
+
+        encoding = data_node.get("encoding", None)
+        if encoding == "base64":
+            from base64 import decodestring
+            data = decodestring(data_node.text.strip())
+
+        elif encoding == "csv":
+            next_gid = imap(int, "".join(
+                line.strip() for line in data_node.text.strip()
+                ).split(","))
+
+        elif encoding:
+            msg = "TMX encoding type: {} is not supported."
+            raise Exception, msg.format(encoding)
+
+        compression = data_node.get("compression", None)
+        if compression == "gzip":
+            from StringIO import StringIO
+            import gzip
+            with gzip.GzipFile(fileobj=StringIO(data)) as fh:
+                data = fh.read()
+
+        elif compression == "zlib":
+            import zlib
+            data = zlib.decompress(data)
+
+        elif compression:
+            msg = "TMX compression type: {} is not supported."
+            raise Exception, msg.format(str(attr["compression"]))
+     
+        # if data is None, then it was not decoded or decompressed, so
+        # we assume here that it is going to be a bunch of tile elements
+        # TODO: this will probably raise an exception if there are no tiles
+        if encoding == next_gid == None:
+            def get_children(parent):
+                for child in parent.findall('tile'):
+                    yield int(child.get('gid'))
+
+            next_gid = get_children(data_node)
+
+        elif data:
+            # data is a list of gid's. cast as 32-bit ints to format properly
+            # create iterator to effeciently parse data
+            next_gid=imap(lambda i:unpack("<L", "".join(i))[0], group(data, 4))
+
+        # using bytes here limits the layer to 256 unique tiles
+        # may be a limitation for very detailed maps, but most maps are not
+        # so detailed. 
+        [ self.data.append(array.array("B")) for i in xrange(self.height) ]
+
+        for (y, x) in product(xrange(self.height), xrange(self.width)):
+            self.data[y].append(self.parent.registerGID(*decode_gid(next(next_gid))))
+
+
+class TiledObjectGroup(TiledElement, list):
+    """
+    Stores TiledObjects.  Supports any operation of a normal list.
+    """
+    reserved = "name color x y width height opacity object properties".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+
+        # defaults from the specification
+        self.name = None
+
+        self.parse(node)
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        """
+        parse a objectgroup element and return a object group
+        """
+
+        self.set_properties(node)
+
+        for child in node.findall('object'):
+            o = TiledObject(self.parent, child)
+            self.append(o)
+
+
+class TiledObject(TiledElement):
+    reserved = "name type x y width height gid properties polygon polyline image".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+
+        # defaults from the specification
+        self.name = None
+        self.type = None
+        self.x = 0
+        self.y = 0
+        self.width = 0
+        self.height = 0
+        self.gid = 0
+
+        self.parse(node)
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        self.set_properties(node)
+
+        # correctly handle "tile objects" (object with gid set)
+        if self.gid:
+            self.gid = self.parent.registerGID(self.gid)
+
+        polygon = node.find('polygon')
+        if polygon is not None:
+            x1 = x2 = y1 = y2 = 0
+            self.points = read_points(polygon.get('points'))
+            self.closed = 1
+            for x, y in self.points:
+                if x < x1: x1 = x
+                if x > x2: x2 = x
+                if y < y1: y1 = y
+                if y > y2: y2 = y
+            self.width = abs(x1) + abs(x2) 
+            self.height = abs(y1) + abs(y2)
+
+        polyline = node.find('polyline')
+        if polyline is not None:
+            x1 = x2 = y1 = y2 = 0
+            self.points = read_points(polyline.get('points'))
+            self.closed = 0
+            for x, y in self.points:
+                if x < x1: x1 = x
+                if x > x2: x2 = x
+                if y < y1: y1 = y
+                if y > y2: y2 = y
+            self.width = abs(x1) + abs(x2) 
+            self.height = abs(y1) + abs(y2)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/002/pytmx/tmxloader.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,304 @@
+"""
+Map loader for TMX Files
+bitcraft (leif dot theden at gmail.com)
+v.15 - for python 2.7
+
+If you have any problems or suggestions, please contact me via email.
+Tested with Tiled 0.8.1 for Mac.
+
+released under the LGPL v3
+
+===============================================================================
+
+This map loader can be used to load maps created in the Tiled map editor.  It
+provides a simple way to get tiles and associated metadata so that you can draw
+a map onto the screen.  It does not force you to draw your map in any
+particular way.
+
+This is *not* a rendering engine.  It will load the data that is necessary to
+render a map onto the screen.  All tiles will be loaded into in memory and
+available to blit onto the screen.
+
+
+Design Goals:
+    Simple api
+    Memory efficient and fast
+
+Features:
+    Loads data and "properties" metadata from Tile's TMX format
+    "Properties" for: maps, tilesets, layers, objectgroups, objects, and tiles
+    Point data for polygon and polyline objects
+    Automatic flipping and rotation of tiles
+    Supports base64, csv, gzip, zlib and uncompressed XML
+    Image loading with pygame
+
+New in .15:
+    loader: fixed issue where objects with tile gid did not load properties
+    loader: polygon and polyline objects
+    loader: new lookup methods use iterators
+    loader: loading function moved into classes
+    loader: data/images can be reloaded on the fly
+    loader: uses etree for faster xml parsing
+
+New in .14:
+    loader: Fixed gid lookup for "buildDistributionRects"
+    loader: Added useful output to a few classes "__repr__"
+    loader: Fixed a gid mapping issue that broke rotated tiles
+    pygame: fixed colorkey handling
+    pygame: correctly handles margins and spacing between tiles in tilesets
+    pygame: b/c of changes, now correctly renders tiled's example maps
+    added scrolling demo
+
+New in .13:
+    loader: Renamed "get_tile_image" to "getTileImage"
+    loader: Removed duplicates returned from getTilePropertiesByLayer
+    loader: Modified confusing messages for GID errors
+    loader: Fixed bug where transformed tile properties are not available
+    loader: No longer loads metadata for tiles that are not used
+    loader: Reduced tile cache to 256 unique tiles
+    loader: Removed 'visible' from list of reserved words
+    loader: Added 'buildDistributionRects' and maputils module
+    loader: Added some misc. functions for retrieving properties
+    pygame: Smarter tile management made tile loading cache useless; removed it
+    pygame: pygame.RLEACCEL flag added when appropriate
+
+New in .12:
+    loader: Fixed bug where tile properties could contain reserved words
+    loader: Reduced size of image index by only allocating space for used tiles
+
+New in .11:
+    loader: Added support for tileset properties
+    loader: Now checks for property names that are reserved for internal use
+    loader: Added support for rotated tiles
+    pygame: Only the tiles that are used in the map will be loaded into memory
+    pygame: Added support for rotated tiles
+    pygame: Added option to force a bitsize (depth) for surfaces
+    pygame: Added option to convert alpha transparency to colorkey transparency
+    pygame: Tilesets no longer load with per-pixel alphas by default
+    pygame: Colorkey transparency should be correctly handled now
+
+
+NOTES:
+
+* The Tiled "properties" have reserved names.
+
+If you use "properties" for any of the following object types, you cannot use
+any of theese words as a name for your property.  A ValueError will be raised
+if there are any conflicts.
+
+As of 0.8.1, these values are:
+
+map:        version, orientation, width, height, tilewidth, tileheight
+            properties, tileset, layer, objectgroup
+
+tileset:    firstgid, source, name, tilewidth, tileheight, spacing, margin,
+            image, tile, properties
+
+tile:       id, image, properties
+
+layer:      name, x, y, width, height, opacity, properties, data
+
+objectgroup: name, color, x, y, width, height, opacity, object, properties
+
+object:     name, type, x, y, width, height, gid, properties, polygon,
+            polyline, image
+
+
+
+I have been intentionally not including a module rendering utility since
+rendering a map will have different requirements for every situation.  However,
+I can appreciate that some poeple won't understand how it works unless they see
+it, so I am including a sample map and viewer.  It includes a scrolling/zooming
+renderer.  They are for demonstation purposes, and may not be suitable for all
+projects.  Use at your own risk.
+
+I've also included a copy of this loader that may work with python 3.x.  I
+personally do not think that python 3.x should be used with pygame, yet (and I
+am not the only person).  You can try it if you insist on using pygame with
+python 3.x, but I don't update that often.
+
+===============================================================================
+
+Basic usage sample:
+
+    >>> from pytmx import tmxloader
+    >>> tmxdata = tmxloader.load_pygame("map.tmx")
+
+
+When you want to draw tiles, you simply call "getTileImage":
+
+    >>> image = tmxdata.getTileImage(x, y, layer)    
+    >>> screen.blit(position, image)
+
+
+Maps, tilesets, layers, objectgroups, and objects all have a simple way to
+access metadata that was set inside tiled: they all become object attributes.
+
+    >>> layer = tmxdata.layers[0]
+    >>> print layer.tilewidth
+    32
+    >>> print layer.weather
+    'sunny'
+
+
+Tiles properties are the exception here, and must be accessed through
+"getTileProperties".  The data is a regular Python dictionary:
+
+    >>> tile = tmxdata.getTileProperties(x, y, layer)
+    >>> tile["name"]
+    'CobbleStone'
+
+
+
+Please see the TiledMap class for some api information.
+"""
+from utils import types
+from constants import *
+
+
+
+def load_tmx(filename, *args, **kwargs):
+    # for .14 compatibility
+    from pytmx import TiledMap
+
+    tiledmap = TiledMap(filename)
+    return tiledmap
+
+
+def load_images_pygame(tmxdata, mapping, *args, **kwargs):
+    """
+    due to the way the tiles are loaded, they will be in the same pixel format
+    as the display when it is loaded.  take this into consideration if you
+    intend to support different screen pixel formats.
+
+    by default, the images will not have per-pixel alphas.  this can be
+    changed by including "pixelalpha=True" in the keywords.  this will result
+    in much slower blitting speeds.
+
+    if the tileset's image has colorkey transparency set in Tiled, the loader
+    will return images that have their transparency already set.  using a
+    tileset with colorkey transparency will greatly increase the speed of
+    rendering the map.
+
+    optionally, you can force the loader to strip the alpha channel of the
+    tileset image and to fill in the missing areas with a color, then use that
+    new color as a colorkey.  the resulting tiles will render much faster, but
+    will not preserve the transparency of the tile if it uses partial
+    transparency (which you shouldn't be doing anyway, this is SDL).
+    """
+    from itertools import product
+    from pygame import Surface, mask
+    import pygame, os
+
+
+    def handle_transformation(tile, flags):
+        if flags:
+            fx = flags & TRANS_FLIPX == TRANS_FLIPX
+            fy = flags & TRANS_FLIPY == TRANS_FLIPY
+            r  = flags & TRANS_ROT == TRANS_ROT
+
+            if r:
+                # not sure why the flip is required...but it is.
+                newtile = pygame.transform.rotate(tile, 270)
+                newtile = pygame.transform.flip(newtile, 1, 0)
+
+                if fx or fy:
+                    newtile = pygame.transform.flip(newtile, fx, fy)
+
+            elif fx or fy:
+                newtile = pygame.transform.flip(tile, fx, fy)
+
+            # preserve any flags that may have been lost after the transformation
+            return newtile.convert(tile)
+
+        else:
+            return tile
+
+
+    pixelalpha     = kwargs.get("pixelalpha", False)
+    force_colorkey = kwargs.get("force_colorkey", False)
+    force_bitdepth = kwargs.get("depth", False)
+
+    if force_colorkey:
+        try:
+            force_colorkey = pygame.Color(*force_colorkey)
+        except:
+            msg = "Cannot understand color: {}"
+            raise Exception, msg.format(force_colorkey)
+
+    tmxdata.images = [0] * tmxdata.maxgid
+
+    for firstgid, t in sorted((t.firstgid, t) for t in tmxdata.tilesets):
+        path = os.path.join(os.path.dirname(tmxdata.filename), t.source)
+
+        image = pygame.image.load(path)
+
+        w, h = image.get_size()
+        tile_size = (t.tilewidth, t.tileheight)
+        real_gid = t.firstgid - 1
+
+        if t.trans:
+            tileset_colorkey = pygame.Color("#{}".format(t.trans)) 
+
+        # i dont agree with margins and spacing, but i'll support it anyway
+        # such is life.  okay.jpg
+        tilewidth = t.tilewidth + t.spacing
+        tileheight = t.tileheight + t.spacing
+
+        # some tileset images may be slightly larger than the tile area
+        # ie: may include a banner, copyright, ect.  this compensates for that
+        width = ((int((w-t.margin*2) + t.spacing) / tilewidth) * tilewidth) - t.spacing
+        height = ((int((h-t.margin*2) + t.spacing) / tileheight) * tileheight) - t.spacing
+
+        # using product avoids the overhead of nested loops
+        p = product(xrange(t.margin, height+t.margin, tileheight),
+                    xrange(t.margin, width+t.margin, tilewidth))
+
+        for (y, x) in p:
+            real_gid += 1
+            gids = tmxdata.mapGID(real_gid)
+            if gids == []: continue
+
+            # we do some tests to correctly handle the tile and set the right
+            # blitting flags.  just grab a section of it.
+            temp = image.subsurface(((x,y), tile_size))
+
+            # count the number of pixels in the tile that are not transparent
+            px = mask.from_surface(temp).count()
+
+            # there are no transparent pixels in the image
+            if px == tile_size[0] * tile_size[1]:
+                tile = temp.convert()
+
+            # there are transparent pixels, and set to force a colorkey
+            elif force_colorkey:
+                tile = Surface(tile_size)
+                tile.fill(force_colorkey)
+                tile.blit(temp, (0,0))
+                tile.set_colorkey(force_colorkey, pygame.RLEACCEL)
+
+            # there are transparent pixels, and tiled set a colorkey
+            elif t.trans:
+                tile = temp.convert()
+                tile.set_colorkey(tileset_colorkey, pygame.RLEACCEL)
+
+            # there are transparent pixels, and set for perpixel alpha
+            elif pixelalpha:
+                tile = temp.convert_alpha()
+
+            # there are transparent pixels, and we won't handle them
+            else:
+                tile = temp.convert()
+
+            for gid, flags in gids:
+                tmxdata.images[gid] = handle_transformation(tile, flags)
+
+
+def load_pygame(filename, *args, **kwargs):
+    tmxdata = load_tmx(filename, *args, **kwargs)
+    load_images_pygame(tmxdata, None, *args, **kwargs)
+
+    return tmxdata
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/002/pytmx/tmxloader3.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,612 @@
+"""
+Map loader for TMX Files
+bitcraft (leif.theden at gmail.com)
+v.7  -- for python 3.x
+
+If you have any problems, please contact me via email.
+Tested with Tiled 0.7.1 for Mac.
+
+released under the LGPL v3
+
+======================================================================
+Design Goals:
+    Simple api
+    Memory efficient and fast
+    Quick access to tiles, attributes, and properties
+
+Non-Goals:
+    Rendering
+
+Works:
+    Pygame image loading
+    Map loading with all required types
+    Properties for all types: maps, layers, objects, tiles
+    Automatic flipping of tiles
+
+Todo:
+    Pygame: test colorkey transparency
+
+    Optimized for maps that do not make heavy use of tile
+    properties.  If i find that it is used a lot then i can rework
+    it for better performance.
+
+======================================================================
+
+Basic usage sample:
+
+    >>> import tmxloader
+    >>> tiledmap = tmxloader.load_pygame("map.tmx")
+
+
+When you want to draw tiles, you simply call "get_tile_image":
+
+    >>> image = tiledmap.get_tile_image(x, y, layer)    
+    >>> screen.blit(position, image)
+
+
+Layers, objectgroups, tilesets, and maps all have a simple way to access
+metadata that was set inside tiled: they all become class attributes.
+
+    >>> print(layer.tilewidth)
+    32
+    >>> print(layer.weather)
+    'sunny'
+
+
+Tiles are the exception here, and must be accessed through "getTileProperties"
+and are regular Python dictionaries:
+
+    >>> tile = tiledmap.getTileProperties(x, y, layer)
+    >>> tile["name"]
+    'CobbleStone'
+
+"""
+
+from itertools import chain
+
+
+# internal flags
+FLIP_X = 1
+FLIP_Y = 2
+
+
+# Tiled gid flags
+GID_FLIP_X = 1<<31
+GID_FLIP_Y = 1<<30
+
+
+class TiledElement(object):
+    pass
+
+class TiledMap(TiledElement):
+    """
+    not really useful unless "loaded"  ie: don't instance directly.
+    see the pygame loader for inspiration
+    """
+
+    def __init__(self):
+        TiledElement.__init__(self)
+        self.layers = []            # list of all layer types (tile layers + object layers)
+        self.tilesets = []          # list of TiledTileset objects
+        self.tilelayers   = []      # list of TiledLayer objects
+        self.objectgroups = []      # list of TiledObjectGroup objects
+        self.tile_properties = {}   # dict of tiles that have additional metadata (properties)
+        self.filename = None
+
+        # this is a work around to tiled's strange way of storing gid's
+        self.images = [0]
+
+        # defaults from the TMX specification
+        self.version = 0.0
+        self.orientation = None
+        self.width = 0
+        self.height = 0
+        self.tilewidth = 0
+        self.tileheight = 0 
+
+    def get_tile_image(self, x, y, layer):
+        """
+        return the tile image for this location
+        x and y must be integers and are in tile coordinates, not pixel
+
+        return value will be 0 if there is no tile with that location.
+        """
+
+        try:
+            gid = self.tilelayers[layer].data[y][x]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer)
+            raise Exception(msg)
+
+        else:
+            try:
+                return self.images[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invalid GID: {3}/{4}.".format(x, y, layer, gid, len(self.images))
+                raise Exception(msg)
+
+    def getTileGID(self, x, y, layer):
+        """
+        return GID of a tile in this location
+        x and y must be integers and are in tile coordinates, not pixel
+        """
+
+        try:
+            return self.tilelayers[layer].data[y][x]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer)
+            raise Exception(msg)
+
+    def getDrawOrder(self):
+        """
+        return a list of objects in the order that they should be drawn
+        this will also exclude any layers that are not set to visible
+
+        may be useful if you have objects and want to control rendering
+        from tiled
+        """
+
+        raise NotImplementedError
+
+    def getTileImages(self, r, layer):
+        """
+        return a group of tiles in an area
+        expects a pygame rect or rect-like list/tuple
+
+        usefull if you don't want to repeatedly call get_tile_image
+        probably not the most effecient way of doing this, but oh well.
+        """
+
+        raise NotImplementedError
+
+    def getObjects(self):
+        """
+        Return iterator all of the objects associated with this map
+        """
+
+        return chain(*[ i.objects for i in self.objectgroups ])
+
+    def getTileProperties(self, x, y, layer):
+        """
+        return the properties for the tile, if any
+        x and y must be integers and are in tile coordinates, not pixel
+
+        returns a dict of there are properties, otherwise will be None
+        """
+
+        try:
+            gid = self.tilelayers[layer].data[y][x]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer)
+            raise Exception(msg)
+
+        else:
+            try:
+                return self.tile_properties[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invaid GID: {3}/{4}.".format(x, y, layer, gid, len(self.images))
+                raise Exception(msg)
+
+    def getTilePropertiesByGID(self, gid):
+        try:
+            return self.tile_properties[gid]
+        except KeyError:
+            return None
+
+# the following classes get their attributes filled in with the loader
+
+class TiledTileset(TiledElement):
+    def __init__(self):
+        TiledElement.__init__(self)
+
+        # defaults from the specification
+        self.firstgid = 0
+        self.lastgid = 0
+        self.name = None
+        self.tilewidth = 0
+        self.tileheight = 0
+        self.spacing = 0
+        self.margin = 0
+
+class TiledLayer(TiledElement):
+    def __init__(self):
+        TiledElement.__init__(self)
+        self.data = None
+
+        # defaults from the specification
+        self.name = None
+        self.opacity = 1.0
+        self.visible = 1
+        
+class TiledObjectGroup(TiledElement):
+    def __init__(self):
+        TiledElement.__init__(self)
+        self.objects = []
+
+        # defaults from the specification
+        self.name = None
+
+class TiledObject(TiledElement):
+    __slots__ = ['name', 'type', 'x', 'y', 'width', 'height', 'gid']
+
+    def __init__(self):
+        TiledElement.__init__(self)
+
+        # defaults from the specification
+        self.name = None
+        self.type = None
+        self.x = 0
+        self.y = 0
+        self.width = 0
+        self.height = 0
+        self.gid = 0
+
+
+def load_tmx(filename):
+    """
+    Utility function to parse a Tiled TMX and return a usable object.
+    Images will not be loaded, so probably not useful to call this directly
+
+    See the load_pygame func for an idea of what to do
+    """
+
+    from xml.dom.minidom import parse
+    from itertools import tee, islice, chain
+    from collections import defaultdict 
+    from struct import unpack
+    import array, os
+
+    # used to change the unicode string returned from minidom to
+    # proper python variable types.
+    types = {
+        "version": float,
+        "orientation": str,
+        "width": int,
+        "height": int,
+        "tilewidth": int,
+        "tileheight": int,
+        "firstgid": int,
+        "source": str,
+        "name": str,
+        "spacing": int,
+        "margin": int,
+        "source": str,
+        "trans": str,
+        "id": int,
+        "opacity": float,
+        "visible": bool,
+        "encoding": str,
+        "compression": str,
+        "gid": int,
+        "type": str,
+        "x": int,
+        "y": int,
+        "value": str,
+    }
+ 
+    def pairwise(iterable):
+        # return a list as a sequence of pairs
+        a, b = tee(iterable)
+        next(b, None)
+        return zip(a, b)
+
+    def group(l, n):
+        # return a list as a sequence of n tuples
+        return zip(*[islice(l, i, None, n) for i in range(n)])
+
+    def parse_properties(node):
+        """
+        parse a node and return a dict that represents a tiled "property"
+        """
+
+        d = {}
+
+        for child in node.childNodes:
+            if child.nodeName == "properties":
+                for subnode in child.getElementsByTagName("property"):
+                    # the "properties" from tiled's tmx have an annoying
+                    # quality that "name" and "value" is included as part of it.
+                    # so we mangle it to get that stuff out.
+                    d.update(dict(pairwise([ str(i.value) for i in list(subnode.attributes.values()) ])))
+
+        return d
+
+    def get_properties(node):
+        """
+        parses a node and returns a dict that contains the data from the node's
+        attributes and any data from "property" elements as well.
+        """
+
+        d = {}
+
+        # get tag attributes
+        d.update(get_attributes(node))
+
+        # get vlues of the properties element, if any
+        d.update(parse_properties(node))
+     
+        return d
+
+    def set_properties(obj, node):
+        """
+        read the xml attributes and tiled "properties" from a xml node and fill in
+        the values into an object's dictionary
+        """
+
+        [ setattr(obj, k, v) for k,v in list(get_properties(node).items()) ]
+
+    def get_attributes(node):
+        """
+        get the attributes from a node and fix them to the correct type
+        """
+
+        d = defaultdict(lambda:None)
+
+        for k, v in list(node.attributes.items()):
+            k = str(k)
+            d[k] = types[k](v)
+
+        return d
+
+    def decode_gid(raw_gid):
+        # gid's are encoded with extra information
+        # as of 0.7.0 it determines if the tile should be flipped when rendered
+
+        flags = 0
+        if raw_gid & GID_FLIP_X == GID_FLIP_X: flags += FLIP_X
+        if raw_gid & GID_FLIP_Y == GID_FLIP_Y: flags += FLIP_Y
+        gid = raw_gid & ~(GID_FLIP_X | GID_FLIP_Y)
+
+        return gid, flags
+
+
+    def parse_map(node):
+        """
+        parse a map node from a tiled tmx file
+        return a tiledmap
+        """
+
+        tiledmap = TiledMap()
+        tiledmap.filename = filename
+        set_properties(tiledmap, map_node)
+
+        for node in map_node.getElementsByTagName("tileset"):
+            t, tiles = parse_tileset(node)
+            tiledmap.tilesets.append(t)
+            tiledmap.tile_properties.update(tiles)
+
+        for node in dom.getElementsByTagName("layer"):
+            l = parse_layer(tiledmap.tilesets, node)
+            tiledmap.tilelayers.append(l)
+            tiledmap.layers.append(l)
+
+        for node in dom.getElementsByTagName("objectgroup"):
+            o = parse_objectgroup(node)
+            tiledmap.objectgroups.append(o)
+            tiledmap.layers.append(o)
+
+        return tiledmap
+
+
+    def parse_tileset(node, firstgid=None):
+        """
+        parse a tileset element and return a tileset object and properties for tiles as a dict
+        """
+
+        tileset = TiledTileset()
+        set_properties(tileset, node)
+        tiles = {}
+
+        if firstgid != None:
+            tileset.firstgid = firstgid
+
+        # since tile objects probably don't have a lot of metadata,
+        # we store it seperately from the class itself
+        for child in node.childNodes:
+            if child.nodeName == "tile":
+                p = get_properties(child)
+                gid = p["id"] + tileset.firstgid
+                del p["id"]
+                tiles[gid] = p
+
+        # check for tiled "external tilesets"
+        if hasattr(tileset, "source"):
+            if tileset.source[-4:].lower() == ".tsx":
+                try:
+                    # we need to mangle the path some because tiled stores relative paths
+                    path = os.path.join(os.path.dirname(filename), tileset.source)
+                    tsx = parse(path)
+                except IOError:
+                    raise IOError("Cannot load external tileset: " + path)
+
+                tileset_node = tsx.getElementsByTagName("tileset")[0]
+                tileset, tiles = parse_tileset(tileset_node, tileset.firstgid)
+            else:
+                raise Exception("Found external tileset, but cannot handle type: " + tileset.source)
+
+        # if we have an "image" tag, process it here
+        try:
+            image_node = node.getElementsByTagName("image")[0]
+        except IndexError:
+            pass
+        else:
+            attr = get_attributes(image_node)
+            tileset.source = attr["source"]
+            tileset.trans = attr["trans"]
+
+            # calculate the number of tiles in this tileset
+            x, r = divmod(attr["width"], tileset.tilewidth)
+            y, r = divmod(attr["height"], tileset.tileheight)
+
+            tileset.lastgid = tileset.firstgid + x + y
+          
+        return tileset, tiles
+
+
+    def parse_layer(tilesets, node):
+        """
+        parse a layer element and return a layer object
+
+        tilesets is required since we need to mangle gid's here
+        """
+
+        layer = TiledLayer()
+        layer.data = []
+        layer.flipped_tiles = []
+        set_properties(layer, node)
+
+        data = None
+        next_gid = None
+
+        data_node = node.getElementsByTagName("data")[0]
+        attr = get_attributes(data_node)
+
+        if attr["encoding"] == "base64":
+            from base64 import b64decode
+            data = b64decode(bytes(data_node.lastChild.nodeValue, 'ascii'))
+
+        elif attr["encoding"] == "csv":
+            next_gid = map(int, "".join([line.strip() for line in data_node.lastChild.nodeValue]).split(","))
+
+        elif not attr["encoding"] == None:
+            raise Exception("TMX encoding type: " + str(attr["encoding"]) + " is not supported.")
+      
+        if attr["compression"] == "gzip":
+            from io import BytesIO
+            import gzip
+            with gzip.GzipFile(fileobj=BytesIO(data)) as fh:
+                data = fh.read()
+
+        elif not attr["compression"] == None:
+            raise Exception("TMX compression type: " + str(attr["compression"]) + " is not supported.")
+     
+        # if data is None, then it was not decoded or decompressed, so
+        # we assume here that it is going to be a bunch of tile elements
+        if attr["encoding"] == next_gid == None:
+            def get_children(parent):
+                for child in parent.getElementsByTagName("tile"):
+                    yield int(child.getAttribute("gid"))
+
+            next_gid = get_children(data_node)
+
+        elif not data == None:
+            # cast the data as a list of 32-bit integers
+            def u(i): return unpack("<L", bytes(i))[0]
+            next_gid = map(u, group(data, 4))
+
+        gids = [ i.firstgid for i in tilesets ]
+
+        # fill up our 2D array of gid's.
+        for y in range(layer.height):
+
+            # store as 16-bit ints, since we will never use enough tiles to fill a 32-bit int
+            layer.data.append(array.array("H"))
+
+            for x in range(layer.width):
+                gid, flags = decode_gid(next(next_gid))
+                if not flags == 0: layer.flipped_tiles.append((x, y, gid, flags))
+                layer.data[y].append(gid)
+
+        return layer
+
+
+    def parse_objectgroup(node):
+        """
+        parse a objectgroup element and return a object group
+        """
+
+        objgroup = TiledObjectGroup()
+        set_properties(objgroup, node)
+
+        for subnode in node.getElementsByTagName("object"):
+            obj = TiledObject()
+            set_properties(obj, subnode)
+            objgroup.objects.append(obj)
+
+        return objgroup
+
+
+    # open and read our TMX (which is really just xml)
+    dom = parse(filename)
+    map_node =  dom.getElementsByTagName("map")[0]
+    return parse_map(map_node)
+
+
+def load_pygame(filename):
+    """
+    load a tiled TMX map for use with pygame
+    """
+
+    from pygame import Surface
+    import pygame, os
+
+    tiledmap = load_tmx(filename)
+
+    # cache will find duplicate tiles to reduce memory usage
+    # mostly this is a problem in the blank areas of a tilemap
+    cache = {}
+
+    # just a precaution to make sure tileset images are added in the correct order
+    for firstgid, t in sorted([ (t.firstgid, t) for t in tiledmap.tilesets ]):
+        path = os.path.join(os.path.dirname(tiledmap.filename), t.source)
+
+        image = pygame.image.load(path)
+
+        w, h = image.get_rect().size
+
+        tile_size = (t.tilewidth, t.tileheight)
+
+        # some tileset images may be slightly larger than the tiles area
+        # ie: may include a banner, copyright, ect.  this compensates for that
+        for y in range(0, int(h / t.tileheight) * t.tileheight, t.tileheight):
+            for x in range(0, int(w / t.tilewidth) * t.tilewidth, t.tilewidth):
+
+                # somewhat handle transparency, though colorkey handling is not tested
+                if t.trans == None:
+                    tile = Surface(tile_size, pygame.SRCALPHA)
+                else:
+                    tile = Surface(tile_size)
+
+                # blit the smaller portion onto a new image from the larger one
+                tile.blit(image, (0,0), ((x, y), tile_size))
+
+                # make a unique id for this image, not sure if this is the best way, but it works
+                key = pygame.image.tostring(tile, "RGBA")
+        
+                # make sure we don't have a duplicate tile
+                try:
+                    tile = cache[key]
+                except KeyError:
+                    if t.trans == None:
+                        tile = tile.convert_alpha()
+                    else:
+                        tile = tile.convert()
+                        tile.set_colorkey(t.trans)
+
+                    # update the cache
+                    cache[key] = tile
+
+                tiledmap.images.append(tile)
+
+    # correctly handle transformed tiles.  currently flipped tiles
+    # work by creating a new gid for the flipped tile and changing the gid
+    # in the layer to the new gid.
+    for layer in tiledmap.tilelayers:
+        for x, y, gid, trans in layer.flipped_tiles:
+            fx = trans & FLIP_X == FLIP_X
+            fy = trans & FLIP_Y == FLIP_Y
+
+            tile = pygame.transform.flip(tiledmap.images[gid], fx, fy)
+            tiledmap.images.append(tile)
+
+            # change the original gid in the layer data to the new gid
+            layer.data[y][x] = len(tiledmap.images) - 1
+
+        del layer.flipped_tiles
+    del cache
+
+    return tiledmap
+
+if __name__ == '__main__':
+    print('[tmxloader] starting built-in test')
+    load_tmx("../resources/maps/village.tmx")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/002/pytmx/utils.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,251 @@
+from pygame import Rect
+from itertools import tee, islice, izip, product
+from collections import defaultdict
+from constants import *
+
+
+
+"""
+Various code that doesn't really fit anywhere else
+"""
+
+
+def read_points(text):
+    return [ tuple(map(lambda x: int(x), i.split(',')))
+         for i in text.split() ]
+
+
+
+def parse_properties(node):
+    """
+    parse a node and return a dict that represents a tiled "property"
+    """
+
+    # the "properties" from tiled's tmx have an annoying quality that "name"
+    # and "value" is included. here we mangle it to get that junk out.
+
+    d = {}
+
+    for child in node.findall('properties'):
+        for subnode in child.findall('property'):
+            d[subnode.get('name')] = subnode.get('value')
+
+    return d
+
+
+def decode_gid(raw_gid):
+    # gid's are encoded with extra information
+    # as of 0.7.0 it determines if the tile should be flipped when rendered
+    # as of 0.8.0 bit 30 determines if GID is rotated
+
+    flags = 0
+    if raw_gid & GID_TRANS_FLIPX == GID_TRANS_FLIPX: flags += TRANS_FLIPX
+    if raw_gid & GID_TRANS_FLIPY == GID_TRANS_FLIPY: flags += TRANS_FLIPY
+    if raw_gid & GID_TRANS_ROT == GID_TRANS_ROT: flags += TRANS_ROT
+    gid = raw_gid & ~(GID_TRANS_FLIPX | GID_TRANS_FLIPY | GID_TRANS_ROT)
+
+    return gid, flags
+
+
+def handle_bool(text):
+    # properly convert strings to a bool
+    try:
+        return bool(int(text))
+    except:
+        pass
+
+    try:
+        text = str(text).lower()
+        if text == "true":   return True
+        if text == "yes":    return True
+        if text == "false":  return False
+        if text == "no":     return False
+    except:
+        pass
+        
+    raise ValueError
+
+
+# used to change the unicode string returned from xml to proper python 
+# variable types.
+types = defaultdict(lambda: str)
+types.update({
+    "version": float,
+    "orientation": str,
+    "width": int,
+    "height": int,
+    "tilewidth": int,
+    "tileheight": int,
+    "firstgid": int,
+    "source": str,
+    "name": str,
+    "spacing": int,
+    "margin": int,
+    "source": str,
+    "trans": str,
+    "id": int,
+    "opacity": float,
+    "visible": handle_bool,
+    "encoding": str,
+    "compression": str,
+    "gid": int,
+    "type": str,
+    "x": int,
+    "y": int,
+    "value": str,
+})
+
+
+def pairwise(iterable):
+    # return a list as a sequence of pairs
+    a, b = tee(iterable)
+    next(b, None)
+    return izip(a, b)
+
+
+def group(l, n):
+    # return a list as a sequence of n tuples
+    return izip(*(islice(l, i, None, n) for i in xrange(n)))
+
+
+def buildDistributionRects(tmxmap, layer, tileset=None, real_gid=None):
+    """
+    generate a set of non-overlapping rects that represents the distribution
+    of the specfied gid.
+
+    useful for generating rects for use in collision detection
+    """
+    
+    if isinstance(tileset, int):
+        try:
+            tileset = tmxmap.tilesets[tileset]
+        except IndexError:
+            msg = "Tileset #{} not found in map {}."
+            raise IndexError, msg.format(tileset, tmxmap)
+
+    elif isinstance(tileset, str):
+        try:
+            tileset = [ t for t in tmxmap.tilesets if t.name == tileset ].pop()
+        except IndexError:
+            msg = "Tileset \"{}\" not found in map {}."
+            raise ValueError, msg.format(tileset, tmxmap)
+
+    elif tileset:
+        msg = "Tileset must be either a int or string. got: {}"
+        raise ValueError, msg.format(type(tileset))
+
+    gid = None
+    if real_gid:
+        try:
+            gid, flags = tmxmap.mapGID(real_gid)[0]
+        except IndexError:
+            msg = "GID #{} not found"
+            raise ValueError, msg.format(real_gid)
+
+
+    if isinstance(layer, int):
+        layer_data = tmxmap.getLayerData(layer).data
+    elif isinstance(layer, str):
+        try:
+            layer = [ l for l in tmxmap.tilelayers if l.name == layer ].pop()
+            layer_data = layer.data
+        except IndexError:
+            msg = "Layer \"{}\" not found in map {}."
+            raise ValueError, msg.format(layer, tmxmap)
+
+    p = product(xrange(tmxmap.width), xrange(tmxmap.height))
+    if gid:
+        points = [ (x,y) for (x,y) in p if layer_data[y][x] == gid ]
+    else:
+        points = [ (x,y) for (x,y) in p if layer_data[y][x] ]
+
+    rects = simplify(points, tmxmap.tilewidth, tmxmap.tileheight)
+    return rects
+
+
+def simplify(all_points, tilewidth, tileheight):
+    """
+    klugde:
+
+    "A kludge (or kluge) is a workaround, a quick-and-dirty solution,
+    a clumsy or inelegant, yet effective, solution to a problem, typically
+    using parts that are cobbled together."
+
+    -- wikipedia
+   
+    turn a list of points into a rects 
+    adjacent rects will be combined.
+
+    plain english:
+        the input list must be a a list of tuples that represent
+        the areas to be combined into rects
+        the rects will be blended together over solid groups
+
+        so if data is something like:
+
+        0 1 1 1 0 0 0 
+        0 1 1 0 0 0 0 
+        0 0 0 0 0 4 0 
+        0 0 0 0 0 4 0
+        0 0 0 0 0 0 0 
+        0 0 1 1 1 1 1
+
+        you'll have the 4 rects that mask the area like this:
+
+        ..######......
+        ..####........
+        ..........##..
+        ..........##..
+        ..............
+        ....##########
+
+        pretty cool, right?
+
+    there may be cases where the number of rectangles is not as low as possible,
+    but i haven't found that it is excessively bad.  certainly much better than
+    making a list of rects, one for each tile on the map!
+
+    """
+
+    def pick_rect(points, rects):
+        ox, oy = sorted([ (sum(p), p) for p in points ])[0][1]
+        x = ox
+        y = oy
+        ex = None            
+
+        while 1:
+            x += 1
+            if not (x, y) in points:
+                if ex == None:
+                    ex = x - 1
+
+                if ((ox, y+1) in points):
+                    if x == ex + 1 :
+                        y += 1
+                        x = ox
+
+                    else:
+                        y -= 1
+                        break
+                else:
+                    if x <= ex: y-= 1
+                    break
+
+        c_rect = Rect(ox*tilewidth,oy*tileheight,\
+                     (ex-ox+1)*tilewidth,(y-oy+1)*tileheight)
+
+        rects.append(c_rect)
+
+        rect = Rect(ox,oy,ex-ox+1,y-oy+1)
+        kill = [ p for p in points if rect.collidepoint(p) ]
+        [ points.remove(i) for i in kill ]
+
+        if points:
+            pick_rect(points, rects)
+
+    rect_list = []
+    while all_points:
+        pick_rect(all_points, rect_list)
+
+    return rect_list
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/002/resources.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+import pygame
+import os
+
+
+def file_path(filename):
+    return os.path.join(
+            os.path.dirname(os.path.abspath(__file__)),
+            'resources',
+            filename
+        )
+
+
+def load_sheet(filename, width=32, height=None):
+    if height is None:
+        height = width
+    path = file_path(filename)
+    image = pygame.image.load(path)
+    image = image.convert()
+    image.set_colorkey((0xff, 0, 0xff))
+    w, h = image.get_size()
+    images = []
+    for x in xrange(w // width):
+        for y in xrange(h // height):
+            rect = pygame.rect.Rect(x * width, y * height, width, height)
+            images.append(image.subsurface(rect))
+    return images
+
+
+
Binary file 004/002/resources/control.png has changed
Binary file 004/002/resources/effects.png has changed
Binary file 004/002/resources/objects.png has changed
Binary file 004/002/resources/spritesheet.png has changed
Binary file 004/002/resources/tilesheet.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/002/sprites.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+import pygame
+
+import resources
+
+
+FACING = {
+    (0, -1): 0,
+    (-1, 0): 1,
+    (0, 1): 2,
+    (1, 0): 3,
+}
+
+
+class DecorationSprite(pygame.sprite.Sprite):
+    def __init__(self, (x, y)):
+        super(DecorationSprite, self).__init__()
+        self.spritesheet = resources.load_sheet('objects.png', 64)
+        self.image = self.spritesheet[0]
+        self.rect = self.image.get_rect()
+        self.rect.midbottom = (x * 32 + 16, y * 32 + 32)
+
+
+class ShadowSprite(pygame.sprite.Sprite):
+    def __init__(self, sprite):
+        super(ShadowSprite, self).__init__()
+        self.spritesheet = resources.load_sheet('effects.png', 32)
+        self.image = self.spritesheet[0]
+        self.rect = self.image.get_rect()
+        self.image.set_alpha(0.6 * 255)
+        self.sprite = sprite
+        self.rect.midbottom = self.sprite.rect.midbottom
+
+    def update(self):
+        self.rect.midbottom = self.sprite.rect.midbottom
+
+
+class HeroSprite(pygame.sprite.Sprite):
+    def __init__(self, (x, y)):
+        super(HeroSprite, self).__init__()
+        self.spritesheet = resources.load_sheet('spritesheet.png', 64)
+        self.facing = 2
+        self.image = self.spritesheet[self.facing]
+        self.rect = self.image.get_rect()
+        self.rect.midbottom = (x * 32 + 16, y * 32 + 32)
+        self.animations = []
+
+    def move(self, (dx, dy)):
+        self.facing = FACING[dx, dy]
+        self.animations.append(self.animation_walk((dx, dy)))
+
+    def animation_walk(self, (dx, dy)):
+        for step in xrange(8):
+            self.image = self.spritesheet[self.facing + step * 4 + 4]
+            self.rect.move_ip(dx * 4, dy * 4)
+            yield
+
+    def animation_stand(self):
+        self.image = self.spritesheet[self.facing]
+        return
+        yield
+
+    def update(self):
+        if not self.animations:
+            self.animations.append(self.animation_stand())
+        while self.animations:
+            try:
+                self.animations[0].next()
+                return
+            except StopIteration:
+                self.animations.pop(0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/003/__main__.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from game import Game
+
+
+game = Game()
+game.load_level('level.tmx')
+game.run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/003/game.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+import pygame
+import pygame.locals as pg
+
+import level
+import sprites
+
+
+class Game(object):
+    def __init__(self):
+        pygame.display.init()
+        pygame.font.init()
+        self.screen = pygame.display.set_mode((800, 600))
+        self.screen.fill((0x66, 0x66, 0x66))
+        self.keys = {
+            pg.K_UP: (self.move_hero, (0, -1)),
+            pg.K_DOWN: (self.move_hero, (0, 1)),
+            pg.K_LEFT: (self.move_hero, (-1, 0)),
+            pg.K_RIGHT: (self.move_hero, (1, 0)),
+            pg.K_ESCAPE: (self.quit,),
+        }
+        self.shadow_sprites = pygame.sprite.RenderUpdates()
+        self.sprites = sprites.SortedUpdates()
+        self.key = None
+
+    def load_level(self, filename):
+        self.level = level.Level(filename)
+        for name, pos in self.level.objects():
+            sprite = sprites.DecorationSprite(pos)
+            self.sprites.add(sprites.DecorationSprite(pos))
+            self.shadow_sprites.add(sprites.ShadowSprite(sprite))
+        self.hero_sprite = sprites.HeroSprite(self.level.hero_position)
+        self.sprites.add(self.hero_sprite)
+        self.shadow_sprites.add(sprites.ShadowSprite(self.hero_sprite))
+        self.background = self.screen.copy()
+        self.level.render(self.background)
+        self.screen.blit(self.background, (0, 0))
+        self.sprites.draw(self.screen)
+        pygame.display.update()
+
+    def quit(self):
+        self.game_over = True
+
+    def move_hero(self, direction):
+        if self.level.move_hero(direction):
+            self.hero_sprite.move(direction)
+
+    def control(self):
+        for event in pygame.event.get():
+            if event.type == pg.QUIT:
+                self.quit()
+            elif event.type == pg.KEYDOWN:
+                self.key = event.key
+        if not self.hero_sprite.animations:
+            keys = pygame.key.get_pressed()
+            for key, action in self.keys.iteritems():
+                if keys[key] or self.key == key:
+                    action[0](*action[1:])
+                    break
+            self.key = None
+
+    def display(self):
+        self.sprites.clear(self.screen, self.background)
+        self.shadow_sprites.clear(self.screen, self.background)
+        self.sprites.update()
+        self.shadow_sprites.update()
+        dirty = []
+        dirty.extend(self.shadow_sprites.draw(self.screen))
+        dirty.extend(self.sprites.draw(self.screen))
+        pygame.display.update(dirty)
+
+    def run(self):
+        self.clock = pygame.time.Clock()
+        self.game_over = False
+        while not self.game_over:
+            self.display()
+            self.control()
+            self.clock.tick(30)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/003/level.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+from pytmx import tmxloader
+
+import resources
+
+
+class Level(object):
+    def __init__(self, filename):
+        path = resources.file_path(filename)
+        self.tiled_map = tmxloader.load_pygame(path)
+        self.hero_position = 1, 1
+
+    def render(self, screen):
+        w = self.tiled_map.tilewidth
+        h = self.tiled_map.tileheight
+        get_image = self.tiled_map.getTileImage
+        for l, layer in enumerate(self.tiled_map.tilelayers):
+            for y in xrange(self.tiled_map.height):
+                for x in xrange(self.tiled_map.width):
+                    image = get_image(x, y, l)
+                    if image:
+                        screen.blit(image, (x * w, y * h))
+        x, y = self.hero_position
+
+    def get_properties(self, (x, y)):
+        properties = {}
+        for l, layer in enumerate(self.tiled_map.tilelayers):
+            properties.update(self.tiled_map.getTileProperties((x, y, l)) or {})
+        return properties
+
+    def is_blocked(self, (x, y)):
+        return self.get_properties((x, y)).get('block')
+
+    def move_hero(self, (dx, dy)):
+        x, y = self.hero_position
+        if not self.is_blocked((x + dx, y + dy)):
+            self.hero_position = x + dx, y + dy
+            return True
+        return False
+
+    def objects(self):
+        w = self.tiled_map.tilewidth
+        h = self.tiled_map.tileheight
+        for og in self.tiled_map.objectgroups:
+            for o in og:
+                yield o.name, (o.x // w, o.y // h)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/003/pytmx/__init__.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,5 @@
+from tmxloader import load_tmx, load_pygame
+from pytmx import TiledElement, TiledMap, TiledTileset, TiledLayer, \
+                  TiledObjectGroup, TiledObject
+from utils import buildDistributionRects
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/003/pytmx/constants.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,11 @@
+# internal flags
+TRANS_FLIPX = 1
+TRANS_FLIPY = 2
+TRANS_ROT = 4
+
+
+# Tiled gid flags
+GID_TRANS_FLIPX = 1<<31
+GID_TRANS_FLIPY = 1<<30
+GID_TRANS_ROT   = 1<<29
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/003/pytmx/pytmx.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,572 @@
+from itertools import chain, product
+from xml.etree import ElementTree
+from collections import defaultdict 
+from utils import decode_gid, types, parse_properties, read_points
+from constants import *
+
+
+
+"""
+Class declarations and other important stuff
+"""
+
+
+
+class TiledElement(object):
+    def set_properties(self, node):
+        """
+        read the xml attributes and tiled "properties" from a xml node and fill
+        in the values into the object's dictionary.  Names will be checked to
+        make sure that they do not conflict with reserved names.
+        """
+
+        # set the attributes reserved for tiled
+        [ setattr(self, k, types[str(k)](v)) for (k,v) in node.items() ] 
+
+        # set the attributes that are derived from tiled 'properties'
+        for k,v in parse_properties(node).items():
+            if k in self.reserved:
+                msg = "{} \"{}\" has a property called \"{}\""
+                print msg.format(self.__class__.__name__, self.name, k, self.__class__.__name__)
+                msg = "This name is reserved for {} objects and can cannot be used."
+                print msg.format(self.__class__.__name__)
+                print "Please change the name in Tiled and try again."
+                print v
+                raise ValueError
+            setattr(self, k, types[str(k)](v))
+
+
+class TiledMap(TiledElement):
+    """
+    Contains the tile layers, tile images, object groups, and objects from a
+    Tiled TMX map.
+    """
+
+    reserved = "version orientation width height tilewidth tileheight properties tileset layer objectgroup".split()
+
+
+    def __init__(self, filename):
+        from collections import defaultdict
+
+        TiledElement.__init__(self)
+        self.tilesets = []          # list of TiledTileset objects
+        self.tilelayers   = []      # list of TiledLayer objects
+        self.objectgroups = []      # list of TiledObjectGroup objects
+        self.tile_properties = {}   # dict of tiles that have metadata
+        self.filename = filename
+
+        # only used tiles are actually loaded, so there will be a difference
+        # between the GID's in the Tile map data (tmx) and the data in this
+        # class and the layers.  This dictionary keeps track of that difference.
+        self.gidmap = defaultdict(list)
+
+        # should be filled in by a loader function
+        self.images = []
+
+        # defaults from the TMX specification
+        self.version = 0.0
+        self.orientation = None
+        self.width = 0
+        self.height = 0
+        self.tilewidth = 0
+        self.tileheight = 0 
+
+        self.imagemap = {}  # mapping of gid and trans flags to real gids
+        self.maxgid = 1
+
+        self.load()
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.filename)
+
+
+    def getTileImage(self, x, y, layer):
+        """
+        return the tile image for this location
+        x and y must be integers and are in tile coordinates, not pixel
+
+        return value will be 0 if there is no tile with that location.
+        """
+
+        try:
+            gid = self.tilelayers[int(layer)].data[int(y)][int(x)]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is not valid."
+            raise Exception, msg.format(x, y, layer)
+        except TypeError:
+            msg = "Tiles must be specified in integers."
+            raise TypeError, msg
+
+        else:
+            try:
+                return self.images[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invalid GID: {3}"
+                raise Exception, msg.format(x, y, layer, gid)
+
+
+    def getTileGID(self, x, y, layer):
+        """
+        return GID of a tile in this location
+        x and y must be integers and are in tile coordinates, not pixel
+        """
+
+        try:
+            return self.tilelayers[int(layer)].data[int(y)][int(x)]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid"
+            raise Exception, msg.format(x, y, layer)
+
+
+    def getDrawOrder(self):
+        """
+        return a list of objects in the order that they should be drawn
+        this will also exclude any layers that are not set to visible
+
+        may be useful if you have objects and want to control rendering
+        from tiled
+        """
+
+        raise NotImplementedError
+
+
+    def getTileImages(self, r, layer):
+        """
+        return a group of tiles in an area
+        expects a pygame rect or rect-like list/tuple
+
+        useful if you don't want to repeatedly call getTileImage
+        """
+
+        raise NotImplementedError
+
+
+    def getObjects(self):
+        """
+        Return iterator of all the objects associated with this map
+        """
+
+        return chain(*(i for i in self.objectgroups))
+
+
+    def getTileProperties(self, (x, y, layer)):
+        """
+        return the properties for the tile, if any
+        x and y must be integers and are in tile coordinates, not pixel
+
+        returns a dict of there are properties, otherwise will be None
+        """
+
+        try:
+            gid = self.tilelayers[int(layer)].data[int(y)][int(x)]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid."
+            raise Exception, msg.format(x, y, layer)
+
+        else:
+            try:
+                return self.tile_properties[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invaid GID: {3}"
+                raise Exception, msg.format(x, y, layer, gid)
+            except KeyError:
+                return None
+
+
+    def getLayerData(self, layer):
+        """
+        Return the data for a layer.
+
+        Data is an array of arrays.
+
+        >>> pos = data[y][x]
+        """
+
+        try:
+            return self.tilelayers[layer].data
+        except IndexError:
+            msg = "Layer {} does not exist."
+            raise ValueError, msg.format(layer)
+
+
+    def getTileLocation(self, gid):
+        # experimental way to find locations of a tile by the GID
+
+        p = product(xrange(self.width),
+                    xrange(self.height),
+                    xrange(len(self.tilelayers)))
+
+        return [ (x,y,l) for (x,y,l) in p 
+               if self.tilelayers[l].data[y][x] == gid ]
+
+
+    def getTilePropertiesByGID(self, gid):
+        try:
+            return self.tile_properties[gid]
+        except KeyError:
+            return None
+
+
+    def setTileProperties(self, gid, d):
+        """
+        set the properties of a tile by GID.
+        must use a standard python dict as d
+        """
+
+        try:
+            self.tile_properties[gid] = d
+        except KeyError:
+            msg = "GID #{} does not exist."
+            raise ValueError, msg.format(gid)
+
+
+    def getTilePropertiesByLayer(self, layer):
+        """
+        Return a list of tile properties (dict) in use in this tile layer.
+        """
+
+        try:
+            layer = int(layer)
+        except:
+            msg = "Layer must be an integer.  Got {} instead."
+            raise ValueError, msg.format(type(layer))
+
+        p = product(range(self.width),range(self.height))
+        layergids = set( self.tilelayers[layer].data[y][x] for x, y in p )
+
+        props = []
+        for gid in layergids:
+            try:
+                props.append((gid, self.tile_properties[gid]))
+            except:
+                continue
+            
+        return props
+
+
+    def registerGID(self, real_gid, flags=0):
+        """
+        used to manage the mapping of GID between the tmx data and the internal
+        data.
+        
+        number returned is gid used internally
+        """
+
+        if real_gid:
+            try:
+                return self.imagemap[(real_gid, flags)][0]
+            except KeyError:
+                # this tile has not been encountered before, or it has been
+                # transformed in some way.  make a new GID for it.
+                gid = self.maxgid
+                self.maxgid += 1
+                self.imagemap[(real_gid, flags)] = (gid, flags)
+                self.gidmap[real_gid].append((gid, flags))
+                return gid
+
+        else:
+            return 0
+
+
+
+    def mapGID(self, real_gid):
+        """
+        used to lookup a GID read from a TMX file's data
+        """
+
+        try:
+            return self.gidmap[int(real_gid)]
+        except KeyError:
+            return None
+        except TypeError:
+            msg = "GID's must be an integer"
+            raise TypeError, msg
+
+
+    def loadTileImages(self, filename):
+        raise NotImplementedError
+
+
+    def load(self):
+        """
+        parse a map node from a tiled tmx file
+        """
+        etree = ElementTree.parse(self.filename).getroot()
+        self.set_properties(etree)
+
+        # initialize the gid mapping
+        self.imagemap[(0,0)] = 0
+
+        for node in etree.findall('layer'):
+            self.tilelayers.append(TiledLayer(self, node))
+
+        for node in etree.findall('objectgroup'):
+            self.objectgroups.append(TiledObjectGroup(self, node))
+
+        for node in etree.findall('tileset'):
+            self.tilesets.append(TiledTileset(self, node))
+
+        # "tile objects", objects with a GID, have need to have their
+        # attributes set after the tileset is loaded
+        for o in self.getObjects():
+            p = self.getTilePropertiesByGID(o.gid)
+            if p:
+                o.name = "TileObject"
+                o.__dict__.update(p)
+
+
+    @property
+    def visibleTileLayers(self):
+        return [layer for layer in self.tilelayers if layer.visible]
+
+
+class TiledTileset(TiledElement):
+    reserved = "firstgid source name tilewidth tileheight spacing margin image tile properties".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+
+        # defaults from the specification
+        self.firstgid = 0
+        self.source = None
+        self.name = None
+        self.tilewidth = 0
+        self.tileheight = 0
+        self.spacing = 0
+        self.margin = 0
+        self.tiles = {}
+
+        self.parse(node)
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        """
+        parse a tileset element and return a tileset object and properties for
+        tiles as a dict
+
+        a bit of mangling is doew here so that tilesets that have external
+        TSX files appear the same as those that don't
+        """
+        import os
+
+
+        # if true, then node references an external tileset
+        source = node.get('source', False)
+        if source:
+            if source[-4:].lower() == ".tsx":
+
+                # external tilesets don't save this, store it for later
+                self.firstgid = int(node.get('firstgid'))
+
+                # we need to mangle the path - tiled stores relative paths
+                dirname = os.path.dirname(self.parent.filename)
+                path = os.path.abspath(os.path.join(dirname, source))
+                try:
+                    node = ElementTree.parse(path).getroot()
+                except IOError:
+                    msg = "Cannot load external tileset: {}"
+                    raise Exception, msg.format(path)
+
+            else:
+                msg = "Found external tileset, but cannot handle type: {}"
+                raise Exception, msg.format(self.source)
+           
+        self.set_properties(node)
+
+        # since tile objects [probably] don't have a lot of metadata,
+        # we store it seperately in the parent (a TiledMap instance)
+        for child in node.iter('tile'):
+            real_gid = int(child.get("id"))
+            p = parse_properties(child)
+            p['width'] = self.tilewidth
+            p['height'] = self.tileheight
+            for gid, flags in self.parent.mapGID(real_gid + self.firstgid):
+                self.parent.setTileProperties(gid, p)
+
+
+        image_node = node.find('image')
+        self.source = image_node.get('source')
+        self.trans = image_node.get("trans", None)
+
+
+class TiledLayer(TiledElement):
+    reserved = "name x y width height opacity properties data".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+        self.data = []
+
+        # defaults from the specification
+        self.name = None
+        self.opacity = 1.0
+        self.visible = True
+       
+        self.parse(node)
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        """
+        parse a layer element
+        """
+        from utils import group
+        from itertools import product, imap
+        from struct import unpack
+        import array
+
+        self.set_properties(node)
+
+        data = None
+        next_gid = None
+
+        data_node = node.find('data')
+
+        encoding = data_node.get("encoding", None)
+        if encoding == "base64":
+            from base64 import decodestring
+            data = decodestring(data_node.text.strip())
+
+        elif encoding == "csv":
+            next_gid = imap(int, "".join(
+                line.strip() for line in data_node.text.strip()
+                ).split(","))
+
+        elif encoding:
+            msg = "TMX encoding type: {} is not supported."
+            raise Exception, msg.format(encoding)
+
+        compression = data_node.get("compression", None)
+        if compression == "gzip":
+            from StringIO import StringIO
+            import gzip
+            with gzip.GzipFile(fileobj=StringIO(data)) as fh:
+                data = fh.read()
+
+        elif compression == "zlib":
+            import zlib
+            data = zlib.decompress(data)
+
+        elif compression:
+            msg = "TMX compression type: {} is not supported."
+            raise Exception, msg.format(str(attr["compression"]))
+     
+        # if data is None, then it was not decoded or decompressed, so
+        # we assume here that it is going to be a bunch of tile elements
+        # TODO: this will probably raise an exception if there are no tiles
+        if encoding == next_gid == None:
+            def get_children(parent):
+                for child in parent.findall('tile'):
+                    yield int(child.get('gid'))
+
+            next_gid = get_children(data_node)
+
+        elif data:
+            # data is a list of gid's. cast as 32-bit ints to format properly
+            # create iterator to effeciently parse data
+            next_gid=imap(lambda i:unpack("<L", "".join(i))[0], group(data, 4))
+
+        # using bytes here limits the layer to 256 unique tiles
+        # may be a limitation for very detailed maps, but most maps are not
+        # so detailed. 
+        [ self.data.append(array.array("B")) for i in xrange(self.height) ]
+
+        for (y, x) in product(xrange(self.height), xrange(self.width)):
+            self.data[y].append(self.parent.registerGID(*decode_gid(next(next_gid))))
+
+
+class TiledObjectGroup(TiledElement, list):
+    """
+    Stores TiledObjects.  Supports any operation of a normal list.
+    """
+    reserved = "name color x y width height opacity object properties".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+
+        # defaults from the specification
+        self.name = None
+
+        self.parse(node)
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        """
+        parse a objectgroup element and return a object group
+        """
+
+        self.set_properties(node)
+
+        for child in node.findall('object'):
+            o = TiledObject(self.parent, child)
+            self.append(o)
+
+
+class TiledObject(TiledElement):
+    reserved = "name type x y width height gid properties polygon polyline image".split()
+
+    def __init__(self, parent, node):
+        TiledElement.__init__(self)
+        self.parent = parent
+
+        # defaults from the specification
+        self.name = None
+        self.type = None
+        self.x = 0
+        self.y = 0
+        self.width = 0
+        self.height = 0
+        self.gid = 0
+
+        self.parse(node)
+
+
+    def __repr__(self):
+        return "<{}: \"{}\">".format(self.__class__.__name__, self.name)
+
+
+    def parse(self, node):
+        self.set_properties(node)
+
+        # correctly handle "tile objects" (object with gid set)
+        if self.gid:
+            self.gid = self.parent.registerGID(self.gid)
+
+        polygon = node.find('polygon')
+        if polygon is not None:
+            x1 = x2 = y1 = y2 = 0
+            self.points = read_points(polygon.get('points'))
+            self.closed = 1
+            for x, y in self.points:
+                if x < x1: x1 = x
+                if x > x2: x2 = x
+                if y < y1: y1 = y
+                if y > y2: y2 = y
+            self.width = abs(x1) + abs(x2) 
+            self.height = abs(y1) + abs(y2)
+
+        polyline = node.find('polyline')
+        if polyline is not None:
+            x1 = x2 = y1 = y2 = 0
+            self.points = read_points(polyline.get('points'))
+            self.closed = 0
+            for x, y in self.points:
+                if x < x1: x1 = x
+                if x > x2: x2 = x
+                if y < y1: y1 = y
+                if y > y2: y2 = y
+            self.width = abs(x1) + abs(x2) 
+            self.height = abs(y1) + abs(y2)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/003/pytmx/tmxloader.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,304 @@
+"""
+Map loader for TMX Files
+bitcraft (leif dot theden at gmail.com)
+v.15 - for python 2.7
+
+If you have any problems or suggestions, please contact me via email.
+Tested with Tiled 0.8.1 for Mac.
+
+released under the LGPL v3
+
+===============================================================================
+
+This map loader can be used to load maps created in the Tiled map editor.  It
+provides a simple way to get tiles and associated metadata so that you can draw
+a map onto the screen.  It does not force you to draw your map in any
+particular way.
+
+This is *not* a rendering engine.  It will load the data that is necessary to
+render a map onto the screen.  All tiles will be loaded into in memory and
+available to blit onto the screen.
+
+
+Design Goals:
+    Simple api
+    Memory efficient and fast
+
+Features:
+    Loads data and "properties" metadata from Tile's TMX format
+    "Properties" for: maps, tilesets, layers, objectgroups, objects, and tiles
+    Point data for polygon and polyline objects
+    Automatic flipping and rotation of tiles
+    Supports base64, csv, gzip, zlib and uncompressed XML
+    Image loading with pygame
+
+New in .15:
+    loader: fixed issue where objects with tile gid did not load properties
+    loader: polygon and polyline objects
+    loader: new lookup methods use iterators
+    loader: loading function moved into classes
+    loader: data/images can be reloaded on the fly
+    loader: uses etree for faster xml parsing
+
+New in .14:
+    loader: Fixed gid lookup for "buildDistributionRects"
+    loader: Added useful output to a few classes "__repr__"
+    loader: Fixed a gid mapping issue that broke rotated tiles
+    pygame: fixed colorkey handling
+    pygame: correctly handles margins and spacing between tiles in tilesets
+    pygame: b/c of changes, now correctly renders tiled's example maps
+    added scrolling demo
+
+New in .13:
+    loader: Renamed "get_tile_image" to "getTileImage"
+    loader: Removed duplicates returned from getTilePropertiesByLayer
+    loader: Modified confusing messages for GID errors
+    loader: Fixed bug where transformed tile properties are not available
+    loader: No longer loads metadata for tiles that are not used
+    loader: Reduced tile cache to 256 unique tiles
+    loader: Removed 'visible' from list of reserved words
+    loader: Added 'buildDistributionRects' and maputils module
+    loader: Added some misc. functions for retrieving properties
+    pygame: Smarter tile management made tile loading cache useless; removed it
+    pygame: pygame.RLEACCEL flag added when appropriate
+
+New in .12:
+    loader: Fixed bug where tile properties could contain reserved words
+    loader: Reduced size of image index by only allocating space for used tiles
+
+New in .11:
+    loader: Added support for tileset properties
+    loader: Now checks for property names that are reserved for internal use
+    loader: Added support for rotated tiles
+    pygame: Only the tiles that are used in the map will be loaded into memory
+    pygame: Added support for rotated tiles
+    pygame: Added option to force a bitsize (depth) for surfaces
+    pygame: Added option to convert alpha transparency to colorkey transparency
+    pygame: Tilesets no longer load with per-pixel alphas by default
+    pygame: Colorkey transparency should be correctly handled now
+
+
+NOTES:
+
+* The Tiled "properties" have reserved names.
+
+If you use "properties" for any of the following object types, you cannot use
+any of theese words as a name for your property.  A ValueError will be raised
+if there are any conflicts.
+
+As of 0.8.1, these values are:
+
+map:        version, orientation, width, height, tilewidth, tileheight
+            properties, tileset, layer, objectgroup
+
+tileset:    firstgid, source, name, tilewidth, tileheight, spacing, margin,
+            image, tile, properties
+
+tile:       id, image, properties
+
+layer:      name, x, y, width, height, opacity, properties, data
+
+objectgroup: name, color, x, y, width, height, opacity, object, properties
+
+object:     name, type, x, y, width, height, gid, properties, polygon,
+            polyline, image
+
+
+
+I have been intentionally not including a module rendering utility since
+rendering a map will have different requirements for every situation.  However,
+I can appreciate that some poeple won't understand how it works unless they see
+it, so I am including a sample map and viewer.  It includes a scrolling/zooming
+renderer.  They are for demonstation purposes, and may not be suitable for all
+projects.  Use at your own risk.
+
+I've also included a copy of this loader that may work with python 3.x.  I
+personally do not think that python 3.x should be used with pygame, yet (and I
+am not the only person).  You can try it if you insist on using pygame with
+python 3.x, but I don't update that often.
+
+===============================================================================
+
+Basic usage sample:
+
+    >>> from pytmx import tmxloader
+    >>> tmxdata = tmxloader.load_pygame("map.tmx")
+
+
+When you want to draw tiles, you simply call "getTileImage":
+
+    >>> image = tmxdata.getTileImage(x, y, layer)    
+    >>> screen.blit(position, image)
+
+
+Maps, tilesets, layers, objectgroups, and objects all have a simple way to
+access metadata that was set inside tiled: they all become object attributes.
+
+    >>> layer = tmxdata.layers[0]
+    >>> print layer.tilewidth
+    32
+    >>> print layer.weather
+    'sunny'
+
+
+Tiles properties are the exception here, and must be accessed through
+"getTileProperties".  The data is a regular Python dictionary:
+
+    >>> tile = tmxdata.getTileProperties(x, y, layer)
+    >>> tile["name"]
+    'CobbleStone'
+
+
+
+Please see the TiledMap class for some api information.
+"""
+from utils import types
+from constants import *
+
+
+
+def load_tmx(filename, *args, **kwargs):
+    # for .14 compatibility
+    from pytmx import TiledMap
+
+    tiledmap = TiledMap(filename)
+    return tiledmap
+
+
+def load_images_pygame(tmxdata, mapping, *args, **kwargs):
+    """
+    due to the way the tiles are loaded, they will be in the same pixel format
+    as the display when it is loaded.  take this into consideration if you
+    intend to support different screen pixel formats.
+
+    by default, the images will not have per-pixel alphas.  this can be
+    changed by including "pixelalpha=True" in the keywords.  this will result
+    in much slower blitting speeds.
+
+    if the tileset's image has colorkey transparency set in Tiled, the loader
+    will return images that have their transparency already set.  using a
+    tileset with colorkey transparency will greatly increase the speed of
+    rendering the map.
+
+    optionally, you can force the loader to strip the alpha channel of the
+    tileset image and to fill in the missing areas with a color, then use that
+    new color as a colorkey.  the resulting tiles will render much faster, but
+    will not preserve the transparency of the tile if it uses partial
+    transparency (which you shouldn't be doing anyway, this is SDL).
+    """
+    from itertools import product
+    from pygame import Surface, mask
+    import pygame, os
+
+
+    def handle_transformation(tile, flags):
+        if flags:
+            fx = flags & TRANS_FLIPX == TRANS_FLIPX
+            fy = flags & TRANS_FLIPY == TRANS_FLIPY
+            r  = flags & TRANS_ROT == TRANS_ROT
+
+            if r:
+                # not sure why the flip is required...but it is.
+                newtile = pygame.transform.rotate(tile, 270)
+                newtile = pygame.transform.flip(newtile, 1, 0)
+
+                if fx or fy:
+                    newtile = pygame.transform.flip(newtile, fx, fy)
+
+            elif fx or fy:
+                newtile = pygame.transform.flip(tile, fx, fy)
+
+            # preserve any flags that may have been lost after the transformation
+            return newtile.convert(tile)
+
+        else:
+            return tile
+
+
+    pixelalpha     = kwargs.get("pixelalpha", False)
+    force_colorkey = kwargs.get("force_colorkey", False)
+    force_bitdepth = kwargs.get("depth", False)
+
+    if force_colorkey:
+        try:
+            force_colorkey = pygame.Color(*force_colorkey)
+        except:
+            msg = "Cannot understand color: {}"
+            raise Exception, msg.format(force_colorkey)
+
+    tmxdata.images = [0] * tmxdata.maxgid
+
+    for firstgid, t in sorted((t.firstgid, t) for t in tmxdata.tilesets):
+        path = os.path.join(os.path.dirname(tmxdata.filename), t.source)
+
+        image = pygame.image.load(path)
+
+        w, h = image.get_size()
+        tile_size = (t.tilewidth, t.tileheight)
+        real_gid = t.firstgid - 1
+
+        if t.trans:
+            tileset_colorkey = pygame.Color("#{}".format(t.trans)) 
+
+        # i dont agree with margins and spacing, but i'll support it anyway
+        # such is life.  okay.jpg
+        tilewidth = t.tilewidth + t.spacing
+        tileheight = t.tileheight + t.spacing
+
+        # some tileset images may be slightly larger than the tile area
+        # ie: may include a banner, copyright, ect.  this compensates for that
+        width = ((int((w-t.margin*2) + t.spacing) / tilewidth) * tilewidth) - t.spacing
+        height = ((int((h-t.margin*2) + t.spacing) / tileheight) * tileheight) - t.spacing
+
+        # using product avoids the overhead of nested loops
+        p = product(xrange(t.margin, height+t.margin, tileheight),
+                    xrange(t.margin, width+t.margin, tilewidth))
+
+        for (y, x) in p:
+            real_gid += 1
+            gids = tmxdata.mapGID(real_gid)
+            if gids == []: continue
+
+            # we do some tests to correctly handle the tile and set the right
+            # blitting flags.  just grab a section of it.
+            temp = image.subsurface(((x,y), tile_size))
+
+            # count the number of pixels in the tile that are not transparent
+            px = mask.from_surface(temp).count()
+
+            # there are no transparent pixels in the image
+            if px == tile_size[0] * tile_size[1]:
+                tile = temp.convert()
+
+            # there are transparent pixels, and set to force a colorkey
+            elif force_colorkey:
+                tile = Surface(tile_size)
+                tile.fill(force_colorkey)
+                tile.blit(temp, (0,0))
+                tile.set_colorkey(force_colorkey, pygame.RLEACCEL)
+
+            # there are transparent pixels, and tiled set a colorkey
+            elif t.trans:
+                tile = temp.convert()
+                tile.set_colorkey(tileset_colorkey, pygame.RLEACCEL)
+
+            # there are transparent pixels, and set for perpixel alpha
+            elif pixelalpha:
+                tile = temp.convert_alpha()
+
+            # there are transparent pixels, and we won't handle them
+            else:
+                tile = temp.convert()
+
+            for gid, flags in gids:
+                tmxdata.images[gid] = handle_transformation(tile, flags)
+
+
+def load_pygame(filename, *args, **kwargs):
+    tmxdata = load_tmx(filename, *args, **kwargs)
+    load_images_pygame(tmxdata, None, *args, **kwargs)
+
+    return tmxdata
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/003/pytmx/tmxloader3.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,612 @@
+"""
+Map loader for TMX Files
+bitcraft (leif.theden at gmail.com)
+v.7  -- for python 3.x
+
+If you have any problems, please contact me via email.
+Tested with Tiled 0.7.1 for Mac.
+
+released under the LGPL v3
+
+======================================================================
+Design Goals:
+    Simple api
+    Memory efficient and fast
+    Quick access to tiles, attributes, and properties
+
+Non-Goals:
+    Rendering
+
+Works:
+    Pygame image loading
+    Map loading with all required types
+    Properties for all types: maps, layers, objects, tiles
+    Automatic flipping of tiles
+
+Todo:
+    Pygame: test colorkey transparency
+
+    Optimized for maps that do not make heavy use of tile
+    properties.  If i find that it is used a lot then i can rework
+    it for better performance.
+
+======================================================================
+
+Basic usage sample:
+
+    >>> import tmxloader
+    >>> tiledmap = tmxloader.load_pygame("map.tmx")
+
+
+When you want to draw tiles, you simply call "get_tile_image":
+
+    >>> image = tiledmap.get_tile_image(x, y, layer)    
+    >>> screen.blit(position, image)
+
+
+Layers, objectgroups, tilesets, and maps all have a simple way to access
+metadata that was set inside tiled: they all become class attributes.
+
+    >>> print(layer.tilewidth)
+    32
+    >>> print(layer.weather)
+    'sunny'
+
+
+Tiles are the exception here, and must be accessed through "getTileProperties"
+and are regular Python dictionaries:
+
+    >>> tile = tiledmap.getTileProperties(x, y, layer)
+    >>> tile["name"]
+    'CobbleStone'
+
+"""
+
+from itertools import chain
+
+
+# internal flags
+FLIP_X = 1
+FLIP_Y = 2
+
+
+# Tiled gid flags
+GID_FLIP_X = 1<<31
+GID_FLIP_Y = 1<<30
+
+
+class TiledElement(object):
+    pass
+
+class TiledMap(TiledElement):
+    """
+    not really useful unless "loaded"  ie: don't instance directly.
+    see the pygame loader for inspiration
+    """
+
+    def __init__(self):
+        TiledElement.__init__(self)
+        self.layers = []            # list of all layer types (tile layers + object layers)
+        self.tilesets = []          # list of TiledTileset objects
+        self.tilelayers   = []      # list of TiledLayer objects
+        self.objectgroups = []      # list of TiledObjectGroup objects
+        self.tile_properties = {}   # dict of tiles that have additional metadata (properties)
+        self.filename = None
+
+        # this is a work around to tiled's strange way of storing gid's
+        self.images = [0]
+
+        # defaults from the TMX specification
+        self.version = 0.0
+        self.orientation = None
+        self.width = 0
+        self.height = 0
+        self.tilewidth = 0
+        self.tileheight = 0 
+
+    def get_tile_image(self, x, y, layer):
+        """
+        return the tile image for this location
+        x and y must be integers and are in tile coordinates, not pixel
+
+        return value will be 0 if there is no tile with that location.
+        """
+
+        try:
+            gid = self.tilelayers[layer].data[y][x]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer)
+            raise Exception(msg)
+
+        else:
+            try:
+                return self.images[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invalid GID: {3}/{4}.".format(x, y, layer, gid, len(self.images))
+                raise Exception(msg)
+
+    def getTileGID(self, x, y, layer):
+        """
+        return GID of a tile in this location
+        x and y must be integers and are in tile coordinates, not pixel
+        """
+
+        try:
+            return self.tilelayers[layer].data[y][x]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer)
+            raise Exception(msg)
+
+    def getDrawOrder(self):
+        """
+        return a list of objects in the order that they should be drawn
+        this will also exclude any layers that are not set to visible
+
+        may be useful if you have objects and want to control rendering
+        from tiled
+        """
+
+        raise NotImplementedError
+
+    def getTileImages(self, r, layer):
+        """
+        return a group of tiles in an area
+        expects a pygame rect or rect-like list/tuple
+
+        usefull if you don't want to repeatedly call get_tile_image
+        probably not the most effecient way of doing this, but oh well.
+        """
+
+        raise NotImplementedError
+
+    def getObjects(self):
+        """
+        Return iterator all of the objects associated with this map
+        """
+
+        return chain(*[ i.objects for i in self.objectgroups ])
+
+    def getTileProperties(self, x, y, layer):
+        """
+        return the properties for the tile, if any
+        x and y must be integers and are in tile coordinates, not pixel
+
+        returns a dict of there are properties, otherwise will be None
+        """
+
+        try:
+            gid = self.tilelayers[layer].data[y][x]
+        except (IndexError, ValueError):
+            msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer)
+            raise Exception(msg)
+
+        else:
+            try:
+                return self.tile_properties[gid]
+            except (IndexError, ValueError):
+                msg = "Coords: ({0},{1}) in layer {2} has invaid GID: {3}/{4}.".format(x, y, layer, gid, len(self.images))
+                raise Exception(msg)
+
+    def getTilePropertiesByGID(self, gid):
+        try:
+            return self.tile_properties[gid]
+        except KeyError:
+            return None
+
+# the following classes get their attributes filled in with the loader
+
+class TiledTileset(TiledElement):
+    def __init__(self):
+        TiledElement.__init__(self)
+
+        # defaults from the specification
+        self.firstgid = 0
+        self.lastgid = 0
+        self.name = None
+        self.tilewidth = 0
+        self.tileheight = 0
+        self.spacing = 0
+        self.margin = 0
+
+class TiledLayer(TiledElement):
+    def __init__(self):
+        TiledElement.__init__(self)
+        self.data = None
+
+        # defaults from the specification
+        self.name = None
+        self.opacity = 1.0
+        self.visible = 1
+        
+class TiledObjectGroup(TiledElement):
+    def __init__(self):
+        TiledElement.__init__(self)
+        self.objects = []
+
+        # defaults from the specification
+        self.name = None
+
+class TiledObject(TiledElement):
+    __slots__ = ['name', 'type', 'x', 'y', 'width', 'height', 'gid']
+
+    def __init__(self):
+        TiledElement.__init__(self)
+
+        # defaults from the specification
+        self.name = None
+        self.type = None
+        self.x = 0
+        self.y = 0
+        self.width = 0
+        self.height = 0
+        self.gid = 0
+
+
+def load_tmx(filename):
+    """
+    Utility function to parse a Tiled TMX and return a usable object.
+    Images will not be loaded, so probably not useful to call this directly
+
+    See the load_pygame func for an idea of what to do
+    """
+
+    from xml.dom.minidom import parse
+    from itertools import tee, islice, chain
+    from collections import defaultdict 
+    from struct import unpack
+    import array, os
+
+    # used to change the unicode string returned from minidom to
+    # proper python variable types.
+    types = {
+        "version": float,
+        "orientation": str,
+        "width": int,
+        "height": int,
+        "tilewidth": int,
+        "tileheight": int,
+        "firstgid": int,
+        "source": str,
+        "name": str,
+        "spacing": int,
+        "margin": int,
+        "source": str,
+        "trans": str,
+        "id": int,
+        "opacity": float,
+        "visible": bool,
+        "encoding": str,
+        "compression": str,
+        "gid": int,
+        "type": str,
+        "x": int,
+        "y": int,
+        "value": str,
+    }
+ 
+    def pairwise(iterable):
+        # return a list as a sequence of pairs
+        a, b = tee(iterable)
+        next(b, None)
+        return zip(a, b)
+
+    def group(l, n):
+        # return a list as a sequence of n tuples
+        return zip(*[islice(l, i, None, n) for i in range(n)])
+
+    def parse_properties(node):
+        """
+        parse a node and return a dict that represents a tiled "property"
+        """
+
+        d = {}
+
+        for child in node.childNodes:
+            if child.nodeName == "properties":
+                for subnode in child.getElementsByTagName("property"):
+                    # the "properties" from tiled's tmx have an annoying
+                    # quality that "name" and "value" is included as part of it.
+                    # so we mangle it to get that stuff out.
+                    d.update(dict(pairwise([ str(i.value) for i in list(subnode.attributes.values()) ])))
+
+        return d
+
+    def get_properties(node):
+        """
+        parses a node and returns a dict that contains the data from the node's
+        attributes and any data from "property" elements as well.
+        """
+
+        d = {}
+
+        # get tag attributes
+        d.update(get_attributes(node))
+
+        # get vlues of the properties element, if any
+        d.update(parse_properties(node))
+     
+        return d
+
+    def set_properties(obj, node):
+        """
+        read the xml attributes and tiled "properties" from a xml node and fill in
+        the values into an object's dictionary
+        """
+
+        [ setattr(obj, k, v) for k,v in list(get_properties(node).items()) ]
+
+    def get_attributes(node):
+        """
+        get the attributes from a node and fix them to the correct type
+        """
+
+        d = defaultdict(lambda:None)
+
+        for k, v in list(node.attributes.items()):
+            k = str(k)
+            d[k] = types[k](v)
+
+        return d
+
+    def decode_gid(raw_gid):
+        # gid's are encoded with extra information
+        # as of 0.7.0 it determines if the tile should be flipped when rendered
+
+        flags = 0
+        if raw_gid & GID_FLIP_X == GID_FLIP_X: flags += FLIP_X
+        if raw_gid & GID_FLIP_Y == GID_FLIP_Y: flags += FLIP_Y
+        gid = raw_gid & ~(GID_FLIP_X | GID_FLIP_Y)
+
+        return gid, flags
+
+
+    def parse_map(node):
+        """
+        parse a map node from a tiled tmx file
+        return a tiledmap
+        """
+
+        tiledmap = TiledMap()
+        tiledmap.filename = filename
+        set_properties(tiledmap, map_node)
+
+        for node in map_node.getElementsByTagName("tileset"):
+            t, tiles = parse_tileset(node)
+            tiledmap.tilesets.append(t)
+            tiledmap.tile_properties.update(tiles)
+
+        for node in dom.getElementsByTagName("layer"):
+            l = parse_layer(tiledmap.tilesets, node)
+            tiledmap.tilelayers.append(l)
+            tiledmap.layers.append(l)
+
+        for node in dom.getElementsByTagName("objectgroup"):
+            o = parse_objectgroup(node)
+            tiledmap.objectgroups.append(o)
+            tiledmap.layers.append(o)
+
+        return tiledmap
+
+
+    def parse_tileset(node, firstgid=None):
+        """
+        parse a tileset element and return a tileset object and properties for tiles as a dict
+        """
+
+        tileset = TiledTileset()
+        set_properties(tileset, node)
+        tiles = {}
+
+        if firstgid != None:
+            tileset.firstgid = firstgid
+
+        # since tile objects probably don't have a lot of metadata,
+        # we store it seperately from the class itself
+        for child in node.childNodes:
+            if child.nodeName == "tile":
+                p = get_properties(child)
+                gid = p["id"] + tileset.firstgid
+                del p["id"]
+                tiles[gid] = p
+
+        # check for tiled "external tilesets"
+        if hasattr(tileset, "source"):
+            if tileset.source[-4:].lower() == ".tsx":
+                try:
+                    # we need to mangle the path some because tiled stores relative paths
+                    path = os.path.join(os.path.dirname(filename), tileset.source)
+                    tsx = parse(path)
+                except IOError:
+                    raise IOError("Cannot load external tileset: " + path)
+
+                tileset_node = tsx.getElementsByTagName("tileset")[0]
+                tileset, tiles = parse_tileset(tileset_node, tileset.firstgid)
+            else:
+                raise Exception("Found external tileset, but cannot handle type: " + tileset.source)
+
+        # if we have an "image" tag, process it here
+        try:
+            image_node = node.getElementsByTagName("image")[0]
+        except IndexError:
+            pass
+        else:
+            attr = get_attributes(image_node)
+            tileset.source = attr["source"]
+            tileset.trans = attr["trans"]
+
+            # calculate the number of tiles in this tileset
+            x, r = divmod(attr["width"], tileset.tilewidth)
+            y, r = divmod(attr["height"], tileset.tileheight)
+
+            tileset.lastgid = tileset.firstgid + x + y
+          
+        return tileset, tiles
+
+
+    def parse_layer(tilesets, node):
+        """
+        parse a layer element and return a layer object
+
+        tilesets is required since we need to mangle gid's here
+        """
+
+        layer = TiledLayer()
+        layer.data = []
+        layer.flipped_tiles = []
+        set_properties(layer, node)
+
+        data = None
+        next_gid = None
+
+        data_node = node.getElementsByTagName("data")[0]
+        attr = get_attributes(data_node)
+
+        if attr["encoding"] == "base64":
+            from base64 import b64decode
+            data = b64decode(bytes(data_node.lastChild.nodeValue, 'ascii'))
+
+        elif attr["encoding"] == "csv":
+            next_gid = map(int, "".join([line.strip() for line in data_node.lastChild.nodeValue]).split(","))
+
+        elif not attr["encoding"] == None:
+            raise Exception("TMX encoding type: " + str(attr["encoding"]) + " is not supported.")
+      
+        if attr["compression"] == "gzip":
+            from io import BytesIO
+            import gzip
+            with gzip.GzipFile(fileobj=BytesIO(data)) as fh:
+                data = fh.read()
+
+        elif not attr["compression"] == None:
+            raise Exception("TMX compression type: " + str(attr["compression"]) + " is not supported.")
+     
+        # if data is None, then it was not decoded or decompressed, so
+        # we assume here that it is going to be a bunch of tile elements
+        if attr["encoding"] == next_gid == None:
+            def get_children(parent):
+                for child in parent.getElementsByTagName("tile"):
+                    yield int(child.getAttribute("gid"))
+
+            next_gid = get_children(data_node)
+
+        elif not data == None:
+            # cast the data as a list of 32-bit integers
+            def u(i): return unpack("<L", bytes(i))[0]
+            next_gid = map(u, group(data, 4))
+
+        gids = [ i.firstgid for i in tilesets ]
+
+        # fill up our 2D array of gid's.
+        for y in range(layer.height):
+
+            # store as 16-bit ints, since we will never use enough tiles to fill a 32-bit int
+            layer.data.append(array.array("H"))
+
+            for x in range(layer.width):
+                gid, flags = decode_gid(next(next_gid))
+                if not flags == 0: layer.flipped_tiles.append((x, y, gid, flags))
+                layer.data[y].append(gid)
+
+        return layer
+
+
+    def parse_objectgroup(node):
+        """
+        parse a objectgroup element and return a object group
+        """
+
+        objgroup = TiledObjectGroup()
+        set_properties(objgroup, node)
+
+        for subnode in node.getElementsByTagName("object"):
+            obj = TiledObject()
+            set_properties(obj, subnode)
+            objgroup.objects.append(obj)
+
+        return objgroup
+
+
+    # open and read our TMX (which is really just xml)
+    dom = parse(filename)
+    map_node =  dom.getElementsByTagName("map")[0]
+    return parse_map(map_node)
+
+
+def load_pygame(filename):
+    """
+    load a tiled TMX map for use with pygame
+    """
+
+    from pygame import Surface
+    import pygame, os
+
+    tiledmap = load_tmx(filename)
+
+    # cache will find duplicate tiles to reduce memory usage
+    # mostly this is a problem in the blank areas of a tilemap
+    cache = {}
+
+    # just a precaution to make sure tileset images are added in the correct order
+    for firstgid, t in sorted([ (t.firstgid, t) for t in tiledmap.tilesets ]):
+        path = os.path.join(os.path.dirname(tiledmap.filename), t.source)
+
+        image = pygame.image.load(path)
+
+        w, h = image.get_rect().size
+
+        tile_size = (t.tilewidth, t.tileheight)
+
+        # some tileset images may be slightly larger than the tiles area
+        # ie: may include a banner, copyright, ect.  this compensates for that
+        for y in range(0, int(h / t.tileheight) * t.tileheight, t.tileheight):
+            for x in range(0, int(w / t.tilewidth) * t.tilewidth, t.tilewidth):
+
+                # somewhat handle transparency, though colorkey handling is not tested
+                if t.trans == None:
+                    tile = Surface(tile_size, pygame.SRCALPHA)
+                else:
+                    tile = Surface(tile_size)
+
+                # blit the smaller portion onto a new image from the larger one
+                tile.blit(image, (0,0), ((x, y), tile_size))
+
+                # make a unique id for this image, not sure if this is the best way, but it works
+                key = pygame.image.tostring(tile, "RGBA")
+        
+                # make sure we don't have a duplicate tile
+                try:
+                    tile = cache[key]
+                except KeyError:
+                    if t.trans == None:
+                        tile = tile.convert_alpha()
+                    else:
+                        tile = tile.convert()
+                        tile.set_colorkey(t.trans)
+
+                    # update the cache
+                    cache[key] = tile
+
+                tiledmap.images.append(tile)
+
+    # correctly handle transformed tiles.  currently flipped tiles
+    # work by creating a new gid for the flipped tile and changing the gid
+    # in the layer to the new gid.
+    for layer in tiledmap.tilelayers:
+        for x, y, gid, trans in layer.flipped_tiles:
+            fx = trans & FLIP_X == FLIP_X
+            fy = trans & FLIP_Y == FLIP_Y
+
+            tile = pygame.transform.flip(tiledmap.images[gid], fx, fy)
+            tiledmap.images.append(tile)
+
+            # change the original gid in the layer data to the new gid
+            layer.data[y][x] = len(tiledmap.images) - 1
+
+        del layer.flipped_tiles
+    del cache
+
+    return tiledmap
+
+if __name__ == '__main__':
+    print('[tmxloader] starting built-in test')
+    load_tmx("../resources/maps/village.tmx")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/003/pytmx/utils.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,251 @@
+from pygame import Rect
+from itertools import tee, islice, izip, product
+from collections import defaultdict
+from constants import *
+
+
+
+"""
+Various code that doesn't really fit anywhere else
+"""
+
+
+def read_points(text):
+    return [ tuple(map(lambda x: int(x), i.split(',')))
+         for i in text.split() ]
+
+
+
+def parse_properties(node):
+    """
+    parse a node and return a dict that represents a tiled "property"
+    """
+
+    # the "properties" from tiled's tmx have an annoying quality that "name"
+    # and "value" is included. here we mangle it to get that junk out.
+
+    d = {}
+
+    for child in node.findall('properties'):
+        for subnode in child.findall('property'):
+            d[subnode.get('name')] = subnode.get('value')
+
+    return d
+
+
+def decode_gid(raw_gid):
+    # gid's are encoded with extra information
+    # as of 0.7.0 it determines if the tile should be flipped when rendered
+    # as of 0.8.0 bit 30 determines if GID is rotated
+
+    flags = 0
+    if raw_gid & GID_TRANS_FLIPX == GID_TRANS_FLIPX: flags += TRANS_FLIPX
+    if raw_gid & GID_TRANS_FLIPY == GID_TRANS_FLIPY: flags += TRANS_FLIPY
+    if raw_gid & GID_TRANS_ROT == GID_TRANS_ROT: flags += TRANS_ROT
+    gid = raw_gid & ~(GID_TRANS_FLIPX | GID_TRANS_FLIPY | GID_TRANS_ROT)
+
+    return gid, flags
+
+
+def handle_bool(text):
+    # properly convert strings to a bool
+    try:
+        return bool(int(text))
+    except:
+        pass
+
+    try:
+        text = str(text).lower()
+        if text == "true":   return True
+        if text == "yes":    return True
+        if text == "false":  return False
+        if text == "no":     return False
+    except:
+        pass
+        
+    raise ValueError
+
+
+# used to change the unicode string returned from xml to proper python 
+# variable types.
+types = defaultdict(lambda: str)
+types.update({
+    "version": float,
+    "orientation": str,
+    "width": int,
+    "height": int,
+    "tilewidth": int,
+    "tileheight": int,
+    "firstgid": int,
+    "source": str,
+    "name": str,
+    "spacing": int,
+    "margin": int,
+    "source": str,
+    "trans": str,
+    "id": int,
+    "opacity": float,
+    "visible": handle_bool,
+    "encoding": str,
+    "compression": str,
+    "gid": int,
+    "type": str,
+    "x": int,
+    "y": int,
+    "value": str,
+})
+
+
+def pairwise(iterable):
+    # return a list as a sequence of pairs
+    a, b = tee(iterable)
+    next(b, None)
+    return izip(a, b)
+
+
+def group(l, n):
+    # return a list as a sequence of n tuples
+    return izip(*(islice(l, i, None, n) for i in xrange(n)))
+
+
+def buildDistributionRects(tmxmap, layer, tileset=None, real_gid=None):
+    """
+    generate a set of non-overlapping rects that represents the distribution
+    of the specfied gid.
+
+    useful for generating rects for use in collision detection
+    """
+    
+    if isinstance(tileset, int):
+        try:
+            tileset = tmxmap.tilesets[tileset]
+        except IndexError:
+            msg = "Tileset #{} not found in map {}."
+            raise IndexError, msg.format(tileset, tmxmap)
+
+    elif isinstance(tileset, str):
+        try:
+            tileset = [ t for t in tmxmap.tilesets if t.name == tileset ].pop()
+        except IndexError:
+            msg = "Tileset \"{}\" not found in map {}."
+            raise ValueError, msg.format(tileset, tmxmap)
+
+    elif tileset:
+        msg = "Tileset must be either a int or string. got: {}"
+        raise ValueError, msg.format(type(tileset))
+
+    gid = None
+    if real_gid:
+        try:
+            gid, flags = tmxmap.mapGID(real_gid)[0]
+        except IndexError:
+            msg = "GID #{} not found"
+            raise ValueError, msg.format(real_gid)
+
+
+    if isinstance(layer, int):
+        layer_data = tmxmap.getLayerData(layer).data
+    elif isinstance(layer, str):
+        try:
+            layer = [ l for l in tmxmap.tilelayers if l.name == layer ].pop()
+            layer_data = layer.data
+        except IndexError:
+            msg = "Layer \"{}\" not found in map {}."
+            raise ValueError, msg.format(layer, tmxmap)
+
+    p = product(xrange(tmxmap.width), xrange(tmxmap.height))
+    if gid:
+        points = [ (x,y) for (x,y) in p if layer_data[y][x] == gid ]
+    else:
+        points = [ (x,y) for (x,y) in p if layer_data[y][x] ]
+
+    rects = simplify(points, tmxmap.tilewidth, tmxmap.tileheight)
+    return rects
+
+
+def simplify(all_points, tilewidth, tileheight):
+    """
+    klugde:
+
+    "A kludge (or kluge) is a workaround, a quick-and-dirty solution,
+    a clumsy or inelegant, yet effective, solution to a problem, typically
+    using parts that are cobbled together."
+
+    -- wikipedia
+   
+    turn a list of points into a rects 
+    adjacent rects will be combined.
+
+    plain english:
+        the input list must be a a list of tuples that represent
+        the areas to be combined into rects
+        the rects will be blended together over solid groups
+
+        so if data is something like:
+
+        0 1 1 1 0 0 0 
+        0 1 1 0 0 0 0 
+        0 0 0 0 0 4 0 
+        0 0 0 0 0 4 0
+        0 0 0 0 0 0 0 
+        0 0 1 1 1 1 1
+
+        you'll have the 4 rects that mask the area like this:
+
+        ..######......
+        ..####........
+        ..........##..
+        ..........##..
+        ..............
+        ....##########
+
+        pretty cool, right?
+
+    there may be cases where the number of rectangles is not as low as possible,
+    but i haven't found that it is excessively bad.  certainly much better than
+    making a list of rects, one for each tile on the map!
+
+    """
+
+    def pick_rect(points, rects):
+        ox, oy = sorted([ (sum(p), p) for p in points ])[0][1]
+        x = ox
+        y = oy
+        ex = None            
+
+        while 1:
+            x += 1
+            if not (x, y) in points:
+                if ex == None:
+                    ex = x - 1
+
+                if ((ox, y+1) in points):
+                    if x == ex + 1 :
+                        y += 1
+                        x = ox
+
+                    else:
+                        y -= 1
+                        break
+                else:
+                    if x <= ex: y-= 1
+                    break
+
+        c_rect = Rect(ox*tilewidth,oy*tileheight,\
+                     (ex-ox+1)*tilewidth,(y-oy+1)*tileheight)
+
+        rects.append(c_rect)
+
+        rect = Rect(ox,oy,ex-ox+1,y-oy+1)
+        kill = [ p for p in points if rect.collidepoint(p) ]
+        [ points.remove(i) for i in kill ]
+
+        if points:
+            pick_rect(points, rects)
+
+    rect_list = []
+    while all_points:
+        pick_rect(all_points, rect_list)
+
+    return rect_list
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/003/resources.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+import pygame
+import os
+
+
+def file_path(filename):
+    return os.path.join(
+            os.path.dirname(os.path.abspath(__file__)),
+            'resources',
+            filename
+        )
+
+
+def load_sheet(filename, width=32, height=None):
+    if height is None:
+        height = width
+    path = file_path(filename)
+    image = pygame.image.load(path)
+    image = image.convert()
+    image.set_colorkey((0xff, 0, 0xff))
+    w, h = image.get_size()
+    images = []
+    for x in xrange(w // width):
+        for y in xrange(h // height):
+            rect = pygame.rect.Rect(x * width, y * height, width, height)
+            images.append(image.subsurface(rect))
+    return images
+
+
+
Binary file 004/003/resources/control.png has changed
Binary file 004/003/resources/effects.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/003/resources/level.tmx	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" width="25" height="18" tilewidth="32" tileheight="32">
+ <tileset firstgid="1" name="control" tilewidth="32" tileheight="32">
+  <properties>
+   <property name="control" value="1"/>
+  </properties>
+  <image source="../../002/resources/control.png" trans="ff00ff" width="64" height="32"/>
+  <tile id="0">
+   <properties>
+    <property name="block" value="1"/>
+   </properties>
+  </tile>
+  <tile id="1">
+   <properties>
+    <property name="block" value="1"/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="3" name="tilesheet" tilewidth="32" tileheight="32">
+  <image source="../../002/resources/tilesheet.png" trans="ff00ff" width="288" height="192"/>
+ </tileset>
+ <layer name="Control" width="25" height="18" visible="0">
+  <data encoding="base64" compression="zlib">
+   eJxjZGBgYKQDpiUY7nYwUYDpaQeMPdT9gcsOJiSaFnYgqxnqYUWp+YPJH7S2A5saSgGu+KC1HdQG9KibQBgAcvcBjQ==
+  </data>
+ </layer>
+ <layer name="Background" width="25" height="18">
+  <data encoding="base64" compression="zlib">
+   eJyTZ2BgkKcDVqIhHu52UDPcR6Id+MJ61A762zFc0hW17aBHWTJU7aA1BgDmfzlJ
+  </data>
+ </layer>
+ <layer name="Corners" width="25" height="18">
+  <data encoding="base64" compression="zlib">
+   eJy1VdtqwzAMFWQbFHaBwNq1DyFhD92lD7Zr//+v1WY65ERTRyFMIOzKro6OJCtJRJL8SNA1qi3KLNG5x8L/xd1E+7uqH/+g94Q7OnF1tM/mrNBanDt91XPVQeZcjHT/tep2he4Ia9A1EI9i7EJ7rk3642yk/UB+rN1KvNEGX4+Or2YveoYcIta2f6u6d/RAPsDnVh5BMXA+mVizxvRu4mFfmeJFPc4OP+RjonPwFcXge168ydiBLbJ8g5P63hAOYzw4voABns3ey9xXQZb8OVdFOWXFsPMAGB3xQJ3whry++qr67ehJzzE/PB4Wm+0v9H87n7jO3BuoLXLqYXTyu+acMwj7tdjwhXeAvDOPo6ybg5+y7FGuB3qlSSa+ntgZCWk+evrN7+NJ1s3Ba/qsWODC3xbuRc47vjtB5tlh5yMk0noBeX8qEg==
+  </data>
+ </layer>
+ <objectgroup name="objects" width="25" height="18">
+  <object name="barell" x="128" y="480"/>
+  <object x="96" y="448"/>
+  <object x="128" y="352"/>
+  <object x="96" y="288"/>
+  <object x="64" y="288"/>
+  <object x="96" y="256"/>
+  <object x="704" y="160"/>
+  <object x="288" y="320"/>
+  <object x="512" y="192"/>
+  <object x="448" y="128"/>
+  <object x="480" y="128"/>
+  <object x="512" y="128"/>
+  <object x="544" y="544"/>
+ </objectgroup>
+</map>
Binary file 004/003/resources/objects.png has changed
Binary file 004/003/resources/spritesheet.png has changed
Binary file 004/003/resources/tilesheet.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/004/003/sprites.py	Fri Jun 29 23:06:48 2012 +0200
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import pygame
+
+import resources
+
+
+FACING = {
+    (0, -1): 0,
+    (-1, 0): 1,
+    (0, 1): 2,
+    (1, 0): 3,
+}
+
+
+class SortedUpdates(pygame.sprite.RenderUpdates):
+    def sprites(self):
+        return sorted(self.spritedict.keys(),
+                      key=lambda sprite: getattr(sprite, "depth", 0))
+
+
+class DecorationSprite(pygame.sprite.Sprite):
+    def __init__(self, (x, y)):
+        super(DecorationSprite, self).__init__()
+        self.spritesheet = resources.load_sheet('objects.png', 64)
+        self.image = self.spritesheet[0]
+        self.rect = self.image.get_rect()
+        self.rect.midbottom = (x * 32 + 16, y * 32 + 28)
+        self.depth = self.rect.bottom
+
+
+class ShadowSprite(pygame.sprite.Sprite):
+    def __init__(self, sprite):
+        super(ShadowSprite, self).__init__()
+        self.spritesheet = resources.load_sheet('effects.png', 32)
+        self.image = self.spritesheet[0]
+        self.rect = self.image.get_rect()
+        self.image.set_alpha(0.6 * 255)
+        self.sprite = sprite
+        self.rect.midbottom = self.sprite.rect.midbottom
+
+    def update(self):
+        self.rect.midbottom = self.sprite.rect.midbottom
+
+
+class HeroSprite(pygame.sprite.Sprite):
+    def __init__(self, (x, y)):
+        super(HeroSprite, self).__init__()
+        self.spritesheet = resources.load_sheet('spritesheet.png', 64)
+        self.facing = 2
+        self.image = self.spritesheet[self.facing]
+        self.rect = self.image.get_rect()
+        self.rect.midbottom = (x * 32 + 16, y * 32 + 32)
+        self.depth = self.rect.bottom
+        self.animations = []
+
+    def move(self, (dx, dy)):
+        self.facing = FACING[dx, dy]
+        self.animations.append(self.animation_walk((dx, dy)))
+
+    def animation_walk(self, (dx, dy)):
+        for step in xrange(8):
+            self.image = self.spritesheet[self.facing + step * 4 + 4]
+            self.rect.move_ip(dx * 4, dy * 4)
+            self.depth = self.rect.bottom
+            yield
+
+    def animation_stand(self):
+        self.image = self.spritesheet[self.facing]
+        return
+        yield
+
+    def update(self):
+        if not self.animations:
+            self.animations.append(self.animation_stand())
+        while self.animations:
+            try:
+                self.animations[0].next()
+                return
+            except StopIteration:
+                self.animations.pop(0)