view mamba/level.py @ 162:7fbbe27120a9

Rodents! Amphibians! Import juggling!
author Jeremy Thurgood <firxen@gmail.com>
date Tue, 13 Sep 2011 23:24:20 +0200
parents 5bade7867d04
children 41e8d4ce3af2
line wrap: on
line source

"""
Level for our shiny game.
"""

from pygame.surface import Surface
from pygame.sprite import RenderUpdates

from mamba.data import load_file
from mamba import sprites
from mamba.snake import Snake


class InvalidMapError(Exception):
    pass


def mktile(cls, **kw):
    return (cls, kw)


TILE_MAP = {
    '.': None,
    'X': mktile(sprites.TileSprite, image_name='wall', solid=True),
    'R': mktile(sprites.DoorSprite, colour='red'),
    'B': mktile(sprites.DoorSprite, colour='blue'),
    '^': mktile(sprites.EntrySprite, direction=Snake.UP),
    'v': mktile(sprites.EntrySprite, direction=Snake.DOWN),
    '<': mktile(sprites.EntrySprite, direction=Snake.LEFT),
    '>': mktile(sprites.EntrySprite, direction=Snake.RIGHT),
    'E': mktile(sprites.ExitSprite),
    '~': mktile(sprites.PuddleSprite),
    'M': mktile(sprites.BigMouse),
    'm': mktile(sprites.SmallMouse),
    'f': mktile(sprites.Frog),
    }

THING_MAP = {
    'a': mktile(sprites.BaseSprite, image_name='red'),
    }


class Tileset(object):
    def __init__(self, tileset_name):
        self.name = tileset_name
        self.load_tiles()

    def load_tiles(self):
        self.tiles = {}
        self.floor = sprites.TileSprite(tileset=self.name,
                                        image_name='floor').image
        for name, value in TILE_MAP.items():
            if value is not None:
                value[1]['tileset'] = self.name
            self.tiles[name] = value
        self.tiles.update(THING_MAP)

    def __getitem__(self, key):
        try:
            tilespec = self.tiles[key]
        except KeyError:
            raise InvalidMapError("Unknown tile type: '%s'" % key)
        if not tilespec:
            return None
        cls, params = tilespec
        params['tile_char'] = key
        return cls(**params)

    def get_tile(self, key, tile_pos, *groups):
        tile = self[key]
        if tile:
            tile.add(*groups)
            tile.set_tile_pos(tile_pos)
        return tile


class Level(object):
    def __init__(self, level_name):
        self.level_name = level_name
        self.tiles_ascii = ''
        self.load_level_data()

    def load_level_data(self):
        """
        This file format is potentially yucky.
        """
        level_data = load_file('levels/%s.txt' % (self.level_name,))
        self.name = level_data.readline().strip()
        tileset_name = level_data.readline().strip()
        self.tileset = Tileset(tileset_name)
        tiles_ascii = [line.strip() for line in level_data.readlines()]
        self.tiles_ascii = tiles_ascii
        self.setup_tiles(tiles_ascii)
        self.make_background()

    def setup_tiles(self, tiles_ascii):
        self.sprites = RenderUpdates()
        self.tiles = []
        self.entry = None
        self.tile_size = (len(tiles_ascii[0]), len(tiles_ascii))
        for y, row in enumerate(tiles_ascii):
            if len(row) != self.tile_size[0]:
                raise InvalidMapError("Map not rectangular.")
            tile_row = []
            for x, tile_char in enumerate(row):
                #tile_orientation = self.get_tile_orientation(y, x, row,
                #        tile_char)
                tile = self.tileset.get_tile(tile_char, (x, y), self.sprites)
                tile_row.append(tile)
                if isinstance(tile, sprites.EntrySprite):
                    if self.entry is not None:
                        raise InvalidMapError("Too many entry points.")
                    self.entry = tile
            self.tiles.append(tile_row)
        if self.entry is None:
            raise InvalidMapError("Not enough entry points.")
        self.set_tile_orientations()

    def set_tile_orientations(self):
        tiles = [tile  # This is a scary listcomp. It makes me happy.
                 for row in self.tiles
                 for tile in row
                 if tile is not None]
        for tile in tiles:
            orientation = self.get_tile_orientation(tile)
            tile.use_variant(*orientation)

    def is_same_tile(self, tile, x, y):
        """Is there a tile of the same type?"""
        if tile.tile_char is None:
            # This isn't really a tile, so bail
            return False
        try:
            other_tile = self.tiles[y][x]
        except IndexError:
            # We're over the edge of the map
            return False
        if other_tile is None:
            # Emptiness.
            return False
        return tile.tile_char == other_tile.tile_char

    def get_tile_orientation(self, tile):
        if tile is None:
            return (False, False, False, False)
        map_x, map_y = tile.tile_pos
        return (
            self.is_same_tile(tile, map_x, map_y - 1),  # above
            self.is_same_tile(tile, map_x, map_y + 1),  # below
            self.is_same_tile(tile, map_x - 1, map_y),  # left
            self.is_same_tile(tile, map_x + 1, map_y),  # right
            )

    def get_tile_size(self):
        return self.tile_size

    def get_size(self):
        x, y = self.get_tile_size()
        return sprites.tile_sizify((x, y))

    def make_background(self):
        sx, sy = self.get_tile_size()
        self.background = Surface(self.get_size())
        [self.background.blit(self.tileset.floor, sprites.tile_sizify((x, y)))
         for x in range(sx) for y in range(sy)]

    def get_entry(self):
        return (self.entry.tile_pos, self.entry.direction)

    def draw(self, surface):
        surface.blit(self.background, (0, 0))
        self.sprites.draw(surface)