Mercurial > scrounge
changeset 47:fa351463d204
reorganized files
| author | Radomir Dopieralski <sheep-devel@sheep.art.pl> |
|---|---|
| date | Sat, 27 Aug 2011 23:18:10 +0200 |
| parents | 93ad08ed2a33 |
| children | 006f9df7bf25 |
| files | display.py effect.py generator.py hero.png hero.py level.png level.py lib/hero.png lib/level.png lib/monsters.png lib/pf_ronda_seven_bold.ttf lib/things.png main.py menu.py monster.py monsters.png pf_ronda_seven_bold.ttf src/display.py src/effect.py src/generator.py src/hero.py src/level.py src/main.py src/menu.py src/monster.py src/thing.py start thing.py things.png |
| diffstat | 29 files changed, 1203 insertions(+), 1181 deletions(-) [+] |
line wrap: on
line diff
--- a/display.py Wed Aug 24 21:24:01 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,217 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -"""A pygame helper that implements a scrolled layer with animated sprites.""" - -import pygame -import pygame.locals - - -class GameQuit(Exception): - pass - - -class TileCache(object): - """Load the tilesets lazily into cache.""" - - def __init__(self, width=32, height=None): - self.width = width - self.height = height or width - self.cache = {} - - def __getitem__(self, filename): - """Return a table of tiles, load it from disk if needed.""" - - key = (filename, self.width, self.height) - try: - return self.cache[key] - except KeyError: - tile_table = self._load_tile_table(filename, self.width, - self.height) - self.cache[key] = tile_table - return tile_table - - def _load_tile_table(self, filename, width, height): - """Load an image and split it into tiles.""" - - image = pygame.image.load(filename).convert() - image_width, image_height = image.get_size() - tile_table = [] - for tile_x in range(0, image_width / width): - line = [] - tile_table.append(line) - for tile_y in range(0, image_height / height): - rect = (tile_x * width, tile_y * height, width, height) - line.append(image.subsurface(rect)) - return tile_table - - -class Sprite(pygame.sprite.Sprite): - def __init__(self, x, y, filename, tx=0, ty=0): - super(Sprite, self).__init__() - self.frames = sprites[filename] - self.image = self.frames[tx][ty] - self.rect = self.image.get_rect() - self.rect.move_ip(x * 24 - 4, y * 16 - 16) - self.depth = self.rect.midbottom[1] - - def move(self, x, y): - self.rect.topleft = (x * 24 - 4, y * 16 - 16) - self.depth = self.rect.midbottom[1] - -class Overlay(pygame.sprite.Sprite): - def __init__(self, x, y, image): - super(Overlay, self).__init__() - self.image = image - self.rect = self.image.get_rect() - self.rect.move_ip(x * 24, y * 16) - - -class SortedUpdates(pygame.sprite.RenderUpdates): - """Sprite group with a depth for controlling drawing order.""" - - def sprites(self): - return sorted(self.spritedict.keys(), - key=lambda sprite: getattr(sprite, "depth", 0)) - - -class Display(object): - def __init__(self, size=(384, 384), caption=""): - pygame.display.init() - pygame.font.init() - pygame.display.set_mode(size) - pygame.display.set_caption(caption) - self.clock = pygame.time.Clock() - self.screen = pygame.display.get_surface() - self.sprites = SortedUpdates() - self.shadows = pygame.sprite.Group() - self.overlay = pygame.sprite.Group() - self.effects = pygame.sprite.RenderUpdates() - self.menus = SortedUpdates() - self.empty() - - def empty(self): - self.key = None - self.scroll_offset = (0, 0) - self.background = pygame.Surface((self.screen.get_width(), - self.screen.get_height())) - self.background.fill((0, 0, 0)) - self.sprites.empty() - self.shadows.empty() - self.overlay.empty() - self.effects.empty() - - def _sprite_clear(self, screen, rect): - """Helper function for cleaning the sprites.""" - - scroll_x, scroll_y = self.scroll_offset - source = rect.move(-scroll_x, -scroll_y) - if not self.background.get_rect().contains(source): - screen.fill((0, 0, 0), rect) - screen.blit(self.background, rect, source) - - def _sprite_shift(self, sprites, offset, reverse=False): - """Moves all the sprites in a group by an offset.""" - - dx, dy = offset - if reverse: - dx = -dx - dy = -dy - for sprite in sprites: - sprite.rect.move_ip(dx, dy) - - def redraw(self): - """Redraw the whole screen. This is slow.""" - - self.screen.fill((0, 0, 0)) - self.screen.blit(self.background, (0, 0), - pygame.rect.Rect( - (-self.scroll_offset[0], -self.scroll_offset[1]), - (self.screen.get_width(), self.screen.get_height()))) - self.sprites.clear(self.screen, self._sprite_clear) - self.effects.clear(self.screen, self._sprite_clear) - self.menus.clear(self.screen, self._sprite_clear) - self.shadows.update() - self._sprite_shift(self.shadows, self.scroll_offset) - self._sprite_shift(self.sprites, self.scroll_offset) - self._sprite_shift(self.overlay, self.scroll_offset) - self._sprite_shift(self.effects, self.scroll_offset) - self.shadows.draw(self.screen) - self.sprites.draw(self.screen) - self.overlay.draw(self.screen) - self.effects.draw(self.screen) - self._sprite_shift(self.shadows, self.scroll_offset, reverse=True) - self._sprite_shift(self.sprites, self.scroll_offset, reverse=True) - self._sprite_shift(self.overlay, self.scroll_offset, reverse=True) - self._sprite_shift(self.effects, self.scroll_offset, reverse=True) - self.menus.draw(self.screen) - pygame.display.flip() - - def update(self): - """Update all the animated parts""" - - self.sprites.clear(self.screen, self._sprite_clear) - self.effects.clear(self.screen, self._sprite_clear) - self.menus.clear(self.screen, self._sprite_clear) - self.sprites.update() - self.effects.update() - self.shadows.update() - self.menus.update() - self._sprite_shift(self.shadows, self.scroll_offset) - self._sprite_shift(self.sprites, self.scroll_offset) - self._sprite_shift(self.overlay, self.scroll_offset) - self._sprite_shift(self.effects, self.scroll_offset) - self.shadows.draw(self.screen) - dirty = self.sprites.draw(self.screen) - self.overlay.draw(self.screen) - dirty += self.effects.draw(self.screen) - self._sprite_shift(self.shadows, self.scroll_offset, reverse=True) - self._sprite_shift(self.sprites, self.scroll_offset, reverse=True) - self._sprite_shift(self.overlay, self.scroll_offset, reverse=True) - self._sprite_shift(self.effects, self.scroll_offset, reverse=True) - dirty += self.menus.draw(self.screen) - pygame.display.update(dirty) - - def tick(self, tick=20): - """Handles a single frame of the game.""" - - self.update() - self.clock.tick(tick) - for event in pygame.event.get(): - if event.type == pygame.locals.QUIT: - raise GameQuit() - elif event.type == pygame.locals.KEYDOWN: - self.key = event.key - - def get_pressed(self): - """Get the array of pressed keys.""" - - keys = pygame.key.get_pressed() - keys = set(key for (key, pressed) in enumerate(keys) - if pressed or key == self.key) - self.key = None - return keys - - def scroll_to(self, rect): - self.scroll_offset = (self.screen.get_width() / 2 - rect.center[0], - self.screen.get_height() / 2 - rect.center[1]) - self.redraw() - - def scroll_after(self, rect): - x, y = self.scroll_offset - hscroll, vscroll = 0, 0 - if rect.left < 48-x: - hscroll = 3 - if rect.right > self.screen.get_width()-48-x: - hscroll = -3 - if rect.top < 48-y: - vscroll = 2 - if rect.bottom > self.screen.get_height()-48-y: - vscroll = -2 - if hscroll or vscroll: - self.scroll_offset = x + hscroll, y + vscroll - self.redraw() - - -sprites = TileCache() -tiles = TileCache(24, 16)
--- a/effect.py Wed Aug 24 21:24:01 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import pygame - - -def get_font(): - return pygame.font.Font('pf_ronda_seven_bold.ttf', 8) - -def outlined_text(text, color, border_color): - font = get_font() - notcolor = [c^0xFF for c in border_color] - base = font.render(text, 0, border_color, notcolor) - size = base.get_width() + 2, base.get_height() + 2 - image = pygame.Surface(size, 16) - image.fill(notcolor) - base.set_colorkey(0) - image.blit(base, (0, 0)) - image.blit(base, (2, 0)) - image.blit(base, (0, 2)) - image.blit(base, (2, 2)) - base.set_colorkey(0) - base.set_palette_at(1, color) - image.blit(base, (1, 1)) - image.set_colorkey(notcolor) - return image - -class CaptionSprite(pygame.sprite.Sprite): - def __init__(self, x, y, text, color, delay, ttl): - super(CaptionSprite, self).__init__() - self.text = text - self.color = color - bgcolor = (32, 32, 32) - self.image = outlined_text(text, color, bgcolor) - self.rect = self.image.get_rect() - self.x = x - self.y = y - self.rect.center = (x * 24 + 12, y * 16 + 8 - 24) - self.ttl = ttl - self.delay = delay - - def update(self): - if self.delay: - self.delay -= 1 - self.image.set_alpha(max(0, 255 - self.delay * 24)) - return - self.rect.move_ip(0, -2) - self.ttl -= 1 - if self.ttl < 5: - self.image.set_alpha(self.ttl * 51) - if self.ttl <= 0: - self.kill() - -class Caption(object): - def __init__(self, x, y, text, color=(255, 255, 255), ttl=16): - self.x = x - self.y = y - self.text = text - self.color = color - self.ttl = ttl - - def render(self, to_display, delay=0): - self.sprite = CaptionSprite(self.x, self.y, self.text, - self.color, delay, self.ttl) - to_display.effects.add(self.sprite) -
--- a/generator.py Wed Aug 24 21:24:01 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,184 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import random -import thing -import monster - - -class LevelGenerator(object): - depth_monsters = [ - [monster.Bat], - [monster.Bat, monster.Skeleton], - [monster.Bat, monster.Skeleton, monster.Zombie], - [monster.Skeleton, monster.Zombie], - [monster.Skeleton, monster.Zombie, monster.Jelly], - [monster.Skeleton, monster.Zombie, monster.Jelly, monster.Ghost], - [monster.Jelly, monster.Zombie, monster.Ghost], - [monster.Jelly, monster.Ghost, monster.Jack], - [monster.Ghost, monster.Jack], - [monster.Ghost, monster.Jack, monster.Eye], - [monster.Jack, monster.Eye], - [monster.Bat, monster.Jack, monster.Eye], - [monster.Bat, monster.Skeleton, monster.Zombie, monster.Jack, - monster.Eye, monster.Jelly, monster.Ghost], - ] - - def __init__(self, depth): - self.depth = depth - self.map = [['x'] * 30 for i in range(30)] - self.things = {} - self.monsters = {} - self.rooms = [[self.make_room(x, y) for x in range(3)] - for y in range(3)] - for line in self.rooms: - for x1, y1, x2, y2 in line: - self.rect(x1, y1, x2, y2, 'X', '.+:') - self.make_connections() - for line in self.map: - for i, c in enumerate(line): - if c == 'X': - line[i] = 'x' - self.make_monsters() - self.make_trash() - self.make_stairs() - - def _random_place(self): - r1 = random.choice(random.choice(self.rooms)) - x1 = random.randint(r1[0] + 1, r1[2] - 1) - y1 = random.randint(r1[1] + 1, r1[3] - 1) - return x1, y1 - - def make_trash(self): - trash = [thing.Plant, thing.Chest] - for i in range(random.randint(5, 15)): - p = self._random_place() - if p not in self.things: - self.things[p] = random.choice(trash)(*p) - - def make_monsters(self): - for i in range(15): - p = self._random_place() - if (p not in self.monsters and - not isinstance(self.things.get(p), thing.Entrance)): - monsters = self.depth_monsters[self.depth] - self.monsters[p] = random.choice(monsters)(*p) - - def make_stairs(self): - p = self._random_place() - while p in self.things: - p = self._random_place() - self.things[p] = thing.Entrance(*p) - p = self._random_place() - while p in self.things: - p = self._random_place() - x1, y1 = p - self.map[y1][x1] = '>' - - def make_connections(self): - dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - connections = [] - connected = [(random.randint(0, 2), random.randint(0, 2))] - while len(connected) < 9: - x1, y1 = random.choice(connected) - try: - dx, dy = random.choice([(dx, dy) for (dx, dy) - in dirs if 0 <= x1 + dx <= 2 and 0 <= y1 + dy <= 2 and - (x1 + dx, y1 + dy) not in connected]) - except IndexError: - # pick another room - continue - r1 = self.rooms[y1][x1] - x2, y2 = x1 + dx, y1 + dy - r2 = self.rooms[y2][x2] - self.connect_rooms(r1, r2) - connections.append((x1, y1, x2, y2)) - connected.append((x2, y2)) - for loop in range(3): - x1 = random.randint(0, 2) - y1 = random.randint(0, 2) - possible = [(dx, dy) for (dx, dy) - in dirs if 0 <= x1 + dx <= 2 and 0 <= y1 + dy <= 2 and - not ((x1, y1, x1 + dx, y1 + dy) in connections or - (x1 + dx, y1 + dy, x1, y1) in connections)] - if not possible: - continue - dx, dy = random.choice(possible) - x2, y2 = x1 + dx, y1 + dy - r1 = self.rooms[y1][x1] - r2 = self.rooms[y2][x2] - self.connect_rooms(r1, r2) - connections.append((x1, y1, x2, y2)) - - def make_room(self, x1, y1): - w = random.randint(3, 8) - h = random.randint(3, 8) - x = 10 * x1 + random.randint(0, 8 - w) - y = 10 * y1 + random.randint(0, 8 - h) - return (x, y, x + w, y + h) - - def connect_rooms(self, r1, r2): - if abs(r1[0] - r2[0]) < abs(r1[1] - r2[1]): - # vertical - v = True - if r1[1] > r2[1]: - r1, r2 = r2, r1 - x1 = random.randint(r1[0] + 1, r1[2] - 1) - y1 = r1[3] - x2 = random.randint(r2[0] + 1, r2[2] - 1) - y2 = r2[1] - if y1 + 1 != y2 - 1: - m = random.randint(y1 + 1, y2 - 1) - else: - m = y1 + 1 - for y in range(y1, m): - self.map[y][x1] = random.choice(',;') - if x1 < x2: - for x in range(x1, x2 + 1): - self.map[m][x] = random.choice(';"') - else: - for x in range(x2, x1 + 1): - self.map[m][x] = random.choice(';"') - for y in range(m, y2): - self.map[y][x2] = random.choice(',;') - else: - # horizontal - v = False - if r1[0] > r2[0]: - r1, r2 = r2, r1 - x1 = r1[2] - y1 = random.randint(r1[1] + 1, r1[3] - 1) - x2 = r2[0] - y2 = random.randint(r2[1] + 1, r2[3] - 1) - if x1 + 1 != x2 - 1: - m = random.randint(x1 + 1, x2 - 1) - else: - m = x1 + 1 - for x in range(x1, m): - self.map[y1][x] = random.choice(',;') - if y1 < y2: - for y in range(y1, y2 + 1): - self.map[y][m] = random.choice(';"') - else: - for y in range(y2, y1 + 1): - self.map[y][m] = random.choice(';"') - for x in range(m, x2): - self.map[y2][x] = random.choice(',;') - self.map[y1][x1] = '+' - self.map[y2][x2] = '+' - if random.randint(0, 10) > 7: - self.things[(x1, y1)] = thing.Door(x1, y1, not v) - if random.randint(0, 10) > 7: - self.things[(x2, y2)] = thing.Door(x2, y2, not v) - - def rect(self, x1, y1, x2, y2, c, fill=None): - if fill: - for y in range(y1 + 1, y2): - for x in range(x1 + 1, x2): - self.map[y][x] = random.choice(fill) - for y in range(y1, y2 + 1): - self.map[y][x1] = c - self.map[y][x2] = c - for x in range(x1, x2 + 1): - self.map[y1][x] = c - self.map[y2][x] = c
--- a/hero.py Wed Aug 24 21:24:01 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import display -import thing -import effect - - -class HeroSprite(display.Sprite): - faces = { - -1: { -1: 1, 0: 1, 1: 0 }, - 0: { -1: 2, 0: 0, 1: 0 }, - 1: { -1: 2, 0: 3, 1: 3}, - } - - def __init__(self): - super(HeroSprite, self).__init__(0, 0, 'hero.png') - self.facing = 2 - self.animation = None - - def update(self): - if self.animation: - try: - self.animation.next() - except StopIteration: - self.animation = None - else: - self.image = self.frames[self.facing][0] - - def anim_walk(self, dx, dy): - self.facing = self.faces[dx][dy] - self.depth = self.rect.midbottom[1] + 1 - for frame in range(4): - self.image = self.frames[self.facing][frame % 4] - for step in range(2): - yield - self.rect.move_ip(dx * 3, dy * 2) - self.depth = self.rect.midbottom[1] + 1 - - def anim_blocked(self, dx, dy): - self.facing = self.faces[dx][dy] - self.depth = self.rect.midbottom[1] + 1 - for frame in range(4): - yield - self.image = self.frames[self.facing][frame % 4] - yield - - def anim_enter(self): - self.depth = self.rect.midbottom[1] + 1 - self.image = self.frames[2][0] - self.rect.move_ip(0, -15) - for step in range(6): - yield - self.rect.move_ip(0, step) - - def anim_exit(self): - self.depth = self.rect.midbottom[1] + 1 - image = self.frames[2][0].copy() - self.image = image - move = 0 - for step in range(6): - yield - move += step - self.image = image.subsurface(0, 0, 32, 32 - move) - self.rect.move_ip(0, step) - - def anim_fight(self, dx, dy): - self.facing = self.faces[dx][dy] - self.image = self.frames[self.facing][4] - h = [1, 2, -3] - for frame in range(3): - yield - self.rect.move_ip(dx * 3 * frame, dy * 2 * frame - h[frame]) - self.depth = self.rect.midbottom[1] + 1 + h[frame] - self.depth = self.rect.midbottom[1] + 1 - for frame in range(3): - yield - self.image = self.frames[self.facing][1] - self.rect.move_ip(-dx * 3 * frame, -dy * 2 * frame) - self.depth = self.rect.midbottom[1] + 1 - self.image = self.frames[self.facing][0] - for frame in range(2): - yield - - -class Hero(thing.Thing): - def __init__(self, x=0, y=0): - self.x, self.y = x, y - self.effects = [] - self.sprite = HeroSprite() - self.max_hp = 10 - self.hp = self.max_hp - self.regen = 10 - self.regen_tick = 0 - - def render(self, to_display): - to_display.sprites.add(self.sprite) - to_display.sprites.add(thing.ShadowSprite(self.sprite)) - - def tick(self): - self.regen_tick += 1 - if self.regen_tick > self.regen: - if self.hp < self.max_hp: - self.hp += 1 - self.effects.append( - effect.Caption(self.x, self.y, '+1', (32, 128, 0))) - self.regen_tick = 0 - - def enter(self, level, x, y): - self.x = x - self.y = y - self.sprite.move(x, y) - self.sprite.facing = 2 - self.sprite.animation = self.sprite.anim_enter() - - def exit(self, level): - self.sprite.animation = self.sprite.anim_exit() - level.hero = None - - def walk(self, level, dx, dy): - block = level.is_blocked(self.x + dx, self.y + dy) - if not block: - self.x += dx - self.y += dy - self.sprite.animation = self.sprite.anim_walk(dx, dy) - elif hasattr(block, 'hit'): - self.attack(level, dx, dy, block) - else: - self.sprite.animation = self.sprite.anim_blocked(dx, dy) - self.sprite.animation.next() - - def attack(self, level, dx, dy, what=None): - if what is None: - what = level.is_blocked(self.x + dx, self.y + dy) - if not hasattr(what, 'hit'): - return - what.hit() - self.sprite.animation = self.sprite.anim_fight(dx, dy) - if isinstance(what, thing.Chest): - self.effects.append( - effect.Caption(self.x + dx, self.y + dy, 'empty', (64, 192, 0))) - - def hit(self, damage): - self.hp -= damage - self.regen_tick = 0 - self.effects.append( - effect.Caption(self.x, self.y, '%d' % (-damage), (255, 32, 32))) -
--- a/level.py Wed Aug 24 21:24:01 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,146 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import display -import thing -import generator -import pygame - - -class Tile(object): - filename = 'level.png' - tx, ty = 0, 3 - blocks = False - - def __init__(self, x, y, map): - self.x = x - self.y = y - self.map = map - - def render(self, to_display): - tile = display.tiles[self.filename][self.tx][self.ty] - to_display.background.blit(tile, (self.x * 24, self.y * 16)) - - -class BrokenTile(Tile): - tx, ty = 2, 3 - - -class GroundTile(Tile): - tx, ty = 0, 4 - - -class PlantsTile(Tile): - tx, ty = 1, 4 - - -class WeedTile(Tile): - tx, ty = 2, 4 - - -class GrassTile(Tile): - tx, ty = 3, 4 - - -class DoorTile(Tile): - tx, ty = 3, 3 - - -class StairsTile(Tile): - tx, ty = 1, 3 - - -class WallTile(Tile): - blocks = True - - def render(self, to_display): - map = self.map - x = self.x - y = self.y - c = map[y][x] - tileset = display.tiles[self.filename] - def f(x, y): - return map[y][x] == c - def sides(x, y): - if y < 0: - return 3 - if x <= 0 or f(x - 1, y): - if x >= len(map[y])-1 or f(x + 1, y): - return 1 - else: - return 2 - elif x >= len(map[y])-1 or f(x + 1, y): - return 0 - return 3 - if y > 0 and not f(x, y - 1): - overlay = display.Overlay(x, y - 1, tileset[sides(x, y)][0]) - to_display.overlay.add(overlay) - if y < len(map) - 1: - if f(x, y + 1): - tile = tileset[sides(x, y + 1)][1] - else: - tile = tileset[sides(x, y)][2] - to_display.background.blit(tile, (self.x * 24, self.y * 16)) - - -class Level(object): - key = { - '.': Tile, - '+': DoorTile, - ':': BrokenTile, - '>': StairsTile, - 'x': WallTile, - ',': GroundTile, - ';': PlantsTile, - '"': WeedTile, - '*': GrassTile, - } - BLOCKED = 1 - - def __init__(self, hero, depth): - self.hero = hero - self.depth = depth - gen = generator.LevelGenerator(depth) - self.map = gen.map - self.things = {} - for t in gen.things.itervalues(): - self.things[(t.x, t.y)] = t - if isinstance(t, thing.Entrance): - self.hero.enter(self, t.x, t.y) - self.monsters = {} - for m in gen.monsters.itervalues(): - self.monsters[(m.x, m.y)] = m - - def is_blocked(self, x, y): - if (x, y) in self.monsters: - return self.monsters[(x, y)] - t = self.things.get((x, y)) - if t and t.blocks: - return t - if x<0 or y<0: - return True - try: - return self.flags[y][x] & self.BLOCKED - except IndexError: - return True - - def render(self, display): - display.background = pygame.Surface((len(self.map[0]) * 24, - len(self.map) * 16)) - display.background.fill((0, 0, 0)) - self.flags = [] - for y, line in enumerate(self.map): - self.flags.append([]) - for x, c in enumerate(line): - tile_class = self.key.get(c) - if tile_class: - tile = tile_class(x, y, self.map) - tile.render(display) - self.flags[-1].append(0) - if tile_class is None or tile.blocks: - self.flags[-1][-1] |= self.BLOCKED - self.hero.render(display) - for t in self.things.itervalues(): - t.render(display) - for m in self.monsters.itervalues(): - m.render(display)
--- a/main.py Wed Aug 24 21:24:01 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import pygame.locals - -import display -import level -import hero -import menu - -class Game(object): - def __init__(self): - self.depth = 0 - self.display = display.Display((320, 200)) - self.hero = hero.Hero() - self.start_level() - self.health = menu.GameInfo(288, 8, 'HP %d/10' % self.hero.hp, - (32, 192, 0)) - self.display.menus.add(self.health) - - def start_level(self): - self.display.empty() - self.level = level.Level(self.hero, self.depth) - self.depth += 1 - self.level.render(self.display) - self.display.scroll_to(self.hero.sprite.rect) - - def control(self): - if self.display.key == pygame.locals.K_ESCAPE: - raise display.GameQuit() - end_turn = False - if not self.hero.sprite.animation: - if self.level.hero is None: - self.start_level() - if self.level.map[self.hero.y][self.hero.x] == '>': - self.hero.exit(self.level) - return - pressed = self.display.get_pressed() - if pygame.locals.K_UP in pressed: - self.hero.walk(self.level, 0, -1) - end_turn = True - elif pygame.locals.K_DOWN in pressed: - self.hero.walk(self.level, 0, 1) - end_turn = True - elif pygame.locals.K_LEFT in pressed: - self.hero.walk(self.level, -1, 0) - end_turn = True - elif pygame.locals.K_RIGHT in pressed: - self.hero.walk(self.level, 1, 0) - end_turn = True - if end_turn: - self.hero.tick() - self.health.change('HP %d/10' % self.hero.hp) - monsters = self.level.monsters.values() - for m in monsters: - if m.dead: - del self.level.monsters[(m.x, m.y)] - for m in monsters: - if not m.dead: - m.act(self.level) - for m in monsters + [self.hero]: - for d, effect in enumerate(m.effects): - effect.render(self.display, delay=d * 12) - m.effects = [] - - def main(self): - try: - while True: - self.display.tick(tick=24) - self.control() - self.display.scroll_after(self.hero.sprite.rect) - except display.GameQuit: - pass - -if __name__ == '__main__': - Game().main()
--- a/menu.py Wed Aug 24 21:24:01 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import pygame -import effect - - -class GameInfo(pygame.sprite.Sprite): - def __init__(self, x, y, text, color): - super(GameInfo, self).__init__() - self.x, self.y = x, y - self.text = text - self.color = color - self.image = effect.outlined_text(text, color, (32, 32, 32)) - self.rect = self.image.get_rect() - self.rect.center = self.x, self.y - - def change(self, text=None, color=None): - if text is not None: - self.text = text - if color is not None: - self.color = color - self.image = effect.outlined_text(self.text, self.color, (32, 32, 32)) - self.rect = self.image.get_rect() - self.rect.center = self.x, self.y
--- a/monster.py Wed Aug 24 21:24:01 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,233 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import random - -import thing -import effect -import display - - -dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - - -def distance(x1, y1, x2, y2): - return abs(x1 - x2) + abs(y1 - y2) - - -class MonsterSprite(display.Sprite): - def __init__(self, x, y, tx): - self.tx = tx - self.ty = 0 - super(MonsterSprite, self).__init__(x, y, 'monsters.png') - self.animation = self.anim_default() - self.frame = 0 - - def update(self): - try: - self.animation.next() - except StopIteration: - self.animation = self.anim_default() - self.animation.next() - - def anim_default(self): - self.depth = self.rect.midbottom[1] + 1 - while True: - yield - self.frame = (self.frame + 1) % 4 - self.image = self.frames[self.tx][self.frame % 4] - yield - yield - - def anim_walk(self, dx, dy): - for frame in range(2): - self.frame = (self.frame + 1) % 4 - for step in range(4): - yield - self.image = self.frames[self.tx][self.frame] - self.rect.move_ip(dx * 3, dy * 2) - self.depth = self.rect.midbottom[1] + 1 - - def anim_fight(self, dx, dy): - self.image = self.frames[self.tx][1] - h = [1, 2, -3] - latitude = 0 - for frame in range(2): - yield - for frame in range(3): - yield - self.rect.move_ip(dx * 3 * frame, dy * 2 * frame - h[frame]) - latitude += h[frame] - self.depth = self.rect.midbottom[1] - latitude + 1 - self.depth = self.rect.midbottom[1] - for frame in range(3): - yield - self.image = self.frames[self.tx][1] - self.rect.move_ip(-dx * 3 * frame, -dy * 2 * frame) - self.depth = self.rect.midbottom[1] + 1 - self.image = self.frames[self.tx][0] - - def anim_die(self): - image = self.frames[self.tx][0] - edge = 4 - yield - yield - for frame in range(8): - yield - self.image = image.subsurface(0, 0, 32, 32 - edge) - edge += frame - self.image.set_alpha(256 - frame * 32) - self.rect.move_ip(0, frame - 1) - self.kill() - - -class Monster(thing.Thing): - tx = 0 - max_hp = 4 - name = "monster" - alpha = None - - def __init__(self, x, y): - self.x, self.y = x, y - self.dead = False - self.effects = [] - self.hp = self.max_hp - self.asleep = False - - def render(self, to_display): - self.sprite = MonsterSprite(self.x, self.y, self.tx) - if self.alpha: - for frame in self.sprite.frames[self.tx]: - frame.set_alpha(self.alpha) - to_display.sprites.add(self.sprite) - to_display.sprites.add(thing.ShadowSprite(self.sprite)) - - def hit(self, damage=1): - self.asleep = False - self.effects.append( - effect.Caption(self.x, self.y, '%d' % (-damage), (255, 128, 16))) - self.hp -= damage - if self.hp <= 0: - self.die() - - def die(self): - c = effect.Caption(self.x, self.y, self.name, (192, 192, 192), ttl=8) - self.effects.append(c) - self.dead = True - self.sprite.animation = self.sprite.anim_die() - self.sprite.animation.next() - - def act(self, level): - # Attack if standing next to the hero - if distance(level.hero.x, level.hero.y, self.x, self.y) == 1: - self.attack(level, level.hero.x - self.x, level.hero.y - self.y) - return - - if self.asleep: - return - - # Walk towards the hero - possible = [(dx, dy) for (dx, dy) in dirs if - not self.is_blocked(level, dx, dy)] - if not possible: - return - random.shuffle(possible) - dx, dy = min(possible, - key=lambda d: distance(level.hero.x, level.hero.y, - self.x+d[0], self.y+d[1])) - self.walk(level, dx, dy) - - def is_blocked(self, level, dx, dy): - block = level.is_blocked(self.x + dx, self.y + dy) - if block: - return True - - def walk(self, level, dx, dy): - if self.is_blocked(level, dx, dy): - return - if (level.hero.x, level.hero.y) == (self.x + dx, self.y + dy): - self.attack(level, dx, dy) - return - del level.monsters[(self.x, self.y)] - self.x += dx - self.y += dy - level.monsters[(self.x, self.y)] = self - self.sprite.animation = self.sprite.anim_walk(dx, dy) - self.sprite.animation.next() - - def attack(self, level, dx, dy): - self.asleep = False - if not (level.hero.x, level.hero.y) == (self.x + dx, self.y + dy): - return - self.sprite.animation = self.sprite.anim_fight(dx, dy) - self.sprite.animation.next() - level.hero.hit(1) - - -class Skeleton(Monster): - tx = 0 - name = "skeleton" - max_hp = 4 - - -class Zombie(Monster): - tx = 1 - name = "zombie" - max_hp = 7 - - -class Jack(Monster): - tx = 2 - name = "jack" - max_hp = 8 - - -class Bat(Monster): - tx = 3 - name = "bat" - max_hp = 2 - - def act(self, level): - # Attack if we feel like it - if random.choice([True, False]): - if distance(level.hero.x, level.hero.y, self.x, self.y) == 1: - self.attack(level, level.hero.x - self.x, level.hero.y - self.y) - return - - if self.asleep: - return - - # Move randomly (very annoying) - possible = [(dx, dy) for (dx, dy) in dirs if - not self.is_blocked(level, dx, dy)] - if not possible: - return - dx, dy = random.choice(possible) - self.walk(level, dx, dy) - - -class Ghost(Monster): - tx = 4 - name = "ghost" - max_hp = 5 - alpha = 96 - - def is_blocked(self, level, dx, dy): - # Ghosts can pass through obstacles - block = level.is_blocked(self.x + dx, self.y + dy) - if block and isinstance(block, Monster) or block == True: - return True - - -class Eye(Monster): - tx = 5 - name = "eye" - max_hp = 7 - asleep = True - - -class Jelly(Monster): - tx = 6 - name = "jelly" - max_hp = 6 - alpha = 192
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/display.py Sat Aug 27 23:18:10 2011 +0200 @@ -0,0 +1,221 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +"""A pygame helper that implements a scrolled layer with animated sprites.""" + +import os +import pygame +import pygame.locals + + +class GameQuit(Exception): + pass + + +class TileCache(object): + """Load the tilesets lazily into cache.""" + + def __init__(self, width=32, height=None): + self.dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), + '../lib') + self.width = width + self.height = height or width + self.cache = {} + + def __getitem__(self, filename): + """Return a table of tiles, load it from disk if needed.""" + + key = (filename, self.width, self.height) + try: + return self.cache[key] + except KeyError: + tile_table = self._load_tile_table(filename, self.width, + self.height) + self.cache[key] = tile_table + return tile_table + + def _load_tile_table(self, filename, width, height): + """Load an image and split it into tiles.""" + + image = pygame.image.load(os.path.join(self.dir, filename)).convert() + image_width, image_height = image.get_size() + tile_table = [] + for tile_x in range(0, image_width / width): + line = [] + tile_table.append(line) + for tile_y in range(0, image_height / height): + rect = (tile_x * width, tile_y * height, width, height) + line.append(image.subsurface(rect)) + return tile_table + + +class Sprite(pygame.sprite.Sprite): + def __init__(self, x, y, filename, tx=0, ty=0): + super(Sprite, self).__init__() + self.frames = sprites[filename] + self.image = self.frames[tx][ty] + self.rect = self.image.get_rect() + self.rect.move_ip(x * 24 - 4, y * 16 - 16) + self.depth = self.rect.midbottom[1] + + def move(self, x, y): + self.rect.topleft = (x * 24 - 4, y * 16 - 16) + self.depth = self.rect.midbottom[1] + +class Overlay(pygame.sprite.Sprite): + def __init__(self, x, y, image): + super(Overlay, self).__init__() + self.image = image + self.rect = self.image.get_rect() + self.rect.move_ip(x * 24, y * 16) + + +class SortedUpdates(pygame.sprite.RenderUpdates): + """Sprite group with a depth for controlling drawing order.""" + + def sprites(self): + return sorted(self.spritedict.keys(), + key=lambda sprite: getattr(sprite, "depth", 0)) + + +class Display(object): + def __init__(self, size=(384, 384), caption=""): + pygame.display.init() + pygame.display.set_mode(size) + pygame.display.set_caption(caption) + self.clock = pygame.time.Clock() + self.screen = pygame.display.get_surface() + self.sprites = SortedUpdates() + self.shadows = pygame.sprite.Group() + self.overlay = pygame.sprite.Group() + self.effects = pygame.sprite.RenderUpdates() + self.menus = SortedUpdates() + self.empty() + + def empty(self): + self.key = None + self.scroll_offset = (0, 0) + self.background = pygame.Surface((self.screen.get_width(), + self.screen.get_height())) + self.background.fill((0, 0, 0)) + self.sprites.empty() + self.shadows.empty() + self.overlay.empty() + self.effects.empty() + + def _sprite_clear(self, screen, rect): + """Helper function for cleaning the sprites.""" + + scroll_x, scroll_y = self.scroll_offset + source = rect.move(-scroll_x, -scroll_y) + if not self.background.get_rect().contains(source): + screen.fill((0, 0, 0), rect) + screen.blit(self.background, rect, source) + + def _sprite_shift(self, sprites, offset, reverse=False): + """Moves all the sprites in a group by an offset.""" + + dx, dy = offset + if reverse: + dx = -dx + dy = -dy + for sprite in sprites: + sprite.rect.move_ip(dx, dy) + + def redraw(self): + """Redraw the whole screen. This is slow.""" + + self.screen.fill((0, 0, 0)) + self.screen.blit(self.background, (0, 0), + pygame.rect.Rect( + (-self.scroll_offset[0], -self.scroll_offset[1]), + (self.screen.get_width(), self.screen.get_height()))) + self.sprites.clear(self.screen, self._sprite_clear) + self.effects.clear(self.screen, self._sprite_clear) + self.menus.clear(self.screen, self._sprite_clear) + self.shadows.update() + self._sprite_shift(self.shadows, self.scroll_offset) + self._sprite_shift(self.sprites, self.scroll_offset) + self._sprite_shift(self.overlay, self.scroll_offset) + self._sprite_shift(self.effects, self.scroll_offset) + self.shadows.draw(self.screen) + self.sprites.draw(self.screen) + self.overlay.draw(self.screen) + self.effects.draw(self.screen) + self._sprite_shift(self.shadows, self.scroll_offset, reverse=True) + self._sprite_shift(self.sprites, self.scroll_offset, reverse=True) + self._sprite_shift(self.overlay, self.scroll_offset, reverse=True) + self._sprite_shift(self.effects, self.scroll_offset, reverse=True) + self.menus.draw(self.screen) + pygame.display.flip() + + def update(self): + """Update all the animated parts""" + + self.sprites.clear(self.screen, self._sprite_clear) + self.effects.clear(self.screen, self._sprite_clear) + self.menus.clear(self.screen, self._sprite_clear) + self.sprites.update() + self.effects.update() + self.shadows.update() + self.menus.update() + self._sprite_shift(self.shadows, self.scroll_offset) + self._sprite_shift(self.sprites, self.scroll_offset) + self._sprite_shift(self.overlay, self.scroll_offset) + self._sprite_shift(self.effects, self.scroll_offset) + self.shadows.draw(self.screen) + dirty = self.sprites.draw(self.screen) + self.overlay.draw(self.screen) + dirty += self.effects.draw(self.screen) + self._sprite_shift(self.shadows, self.scroll_offset, reverse=True) + self._sprite_shift(self.sprites, self.scroll_offset, reverse=True) + self._sprite_shift(self.overlay, self.scroll_offset, reverse=True) + self._sprite_shift(self.effects, self.scroll_offset, reverse=True) + dirty += self.menus.draw(self.screen) + pygame.display.update(dirty) + + def tick(self, tick=20): + """Handles a single frame of the game.""" + + self.update() + self.clock.tick(tick) + for event in pygame.event.get(): + if event.type == pygame.locals.QUIT: + raise GameQuit() + elif event.type == pygame.locals.KEYDOWN: + self.key = event.key + + def get_pressed(self): + """Get the array of pressed keys.""" + + keys = pygame.key.get_pressed() + keys = set(key for (key, pressed) in enumerate(keys) + if pressed or key == self.key) + self.key = None + return keys + + def scroll_to(self, rect): + self.scroll_offset = (self.screen.get_width() / 2 - rect.center[0], + self.screen.get_height() / 2 - rect.center[1]) + self.redraw() + + def scroll_after(self, rect): + x, y = self.scroll_offset + hscroll, vscroll = 0, 0 + if rect.left < 48-x: + hscroll = 3 + if rect.right > self.screen.get_width()-48-x: + hscroll = -3 + if rect.top < 48-y: + vscroll = 2 + if rect.bottom > self.screen.get_height()-48-y: + vscroll = -2 + if hscroll or vscroll: + self.scroll_offset = x + hscroll, y + vscroll + self.redraw() + + +sprites = TileCache(32, 32) +tiles = TileCache(24, 16) +pygame.font.init() +font = pygame.font.Font(os.path.join(tiles.dir, 'pf_ronda_seven_bold.ttf'), 8)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/effect.py Sat Aug 27 23:18:10 2011 +0200 @@ -0,0 +1,64 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import pygame +import display + + +def outlined_text(text, color, border_color): + font = display.font + notcolor = [c^0xFF for c in border_color] + base = font.render(text, 0, border_color, notcolor) + size = base.get_width() + 2, base.get_height() + 2 + image = pygame.Surface(size, 16) + image.fill(notcolor) + base.set_colorkey(0) + image.blit(base, (0, 0)) + image.blit(base, (2, 0)) + image.blit(base, (0, 2)) + image.blit(base, (2, 2)) + base.set_colorkey(0) + base.set_palette_at(1, color) + image.blit(base, (1, 1)) + image.set_colorkey(notcolor) + return image + +class CaptionSprite(pygame.sprite.Sprite): + def __init__(self, x, y, text, color, delay, ttl): + super(CaptionSprite, self).__init__() + self.text = text + self.color = color + bgcolor = (32, 32, 32) + self.image = outlined_text(text, color, bgcolor) + self.rect = self.image.get_rect() + self.x = x + self.y = y + self.rect.center = (x * 24 + 12, y * 16 + 8 - 24) + self.ttl = ttl + self.delay = delay + + def update(self): + if self.delay: + self.delay -= 1 + self.image.set_alpha(max(0, 255 - self.delay * 24)) + return + self.rect.move_ip(0, -2) + self.ttl -= 1 + if self.ttl < 5: + self.image.set_alpha(self.ttl * 51) + if self.ttl <= 0: + self.kill() + +class Caption(object): + def __init__(self, x, y, text, color=(255, 255, 255), ttl=16): + self.x = x + self.y = y + self.text = text + self.color = color + self.ttl = ttl + + def render(self, to_display, delay=0): + self.sprite = CaptionSprite(self.x, self.y, self.text, + self.color, delay, self.ttl) + to_display.effects.add(self.sprite) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/generator.py Sat Aug 27 23:18:10 2011 +0200 @@ -0,0 +1,184 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import random +import thing +import monster + + +class LevelGenerator(object): + depth_monsters = [ + [monster.Bat], + [monster.Bat, monster.Skeleton], + [monster.Bat, monster.Skeleton, monster.Zombie], + [monster.Skeleton, monster.Zombie], + [monster.Skeleton, monster.Zombie, monster.Jelly], + [monster.Skeleton, monster.Zombie, monster.Jelly, monster.Ghost], + [monster.Jelly, monster.Zombie, monster.Ghost], + [monster.Jelly, monster.Ghost, monster.Jack], + [monster.Ghost, monster.Jack], + [monster.Ghost, monster.Jack, monster.Eye], + [monster.Jack, monster.Eye], + [monster.Bat, monster.Jack, monster.Eye], + [monster.Bat, monster.Skeleton, monster.Zombie, monster.Jack, + monster.Eye, monster.Jelly, monster.Ghost], + ] + + def __init__(self, depth): + self.depth = depth + self.map = [['x'] * 30 for i in range(30)] + self.things = {} + self.monsters = {} + self.rooms = [[self.make_room(x, y) for x in range(3)] + for y in range(3)] + for line in self.rooms: + for x1, y1, x2, y2 in line: + self.rect(x1, y1, x2, y2, 'X', '.+:') + self.make_connections() + for line in self.map: + for i, c in enumerate(line): + if c == 'X': + line[i] = 'x' + self.make_monsters() + self.make_trash() + self.make_stairs() + + def _random_place(self): + r1 = random.choice(random.choice(self.rooms)) + x1 = random.randint(r1[0] + 1, r1[2] - 1) + y1 = random.randint(r1[1] + 1, r1[3] - 1) + return x1, y1 + + def make_trash(self): + trash = [thing.Plant, thing.Chest] + for i in range(random.randint(5, 15)): + p = self._random_place() + if p not in self.things: + self.things[p] = random.choice(trash)(*p) + + def make_monsters(self): + for i in range(15): + p = self._random_place() + if (p not in self.monsters and + not isinstance(self.things.get(p), thing.Entrance)): + monsters = self.depth_monsters[self.depth] + self.monsters[p] = random.choice(monsters)(*p) + + def make_stairs(self): + p = self._random_place() + while p in self.things: + p = self._random_place() + self.things[p] = thing.Entrance(*p) + p = self._random_place() + while p in self.things: + p = self._random_place() + x1, y1 = p + self.map[y1][x1] = '>' + + def make_connections(self): + dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + connections = [] + connected = [(random.randint(0, 2), random.randint(0, 2))] + while len(connected) < 9: + x1, y1 = random.choice(connected) + try: + dx, dy = random.choice([(dx, dy) for (dx, dy) + in dirs if 0 <= x1 + dx <= 2 and 0 <= y1 + dy <= 2 and + (x1 + dx, y1 + dy) not in connected]) + except IndexError: + # pick another room + continue + r1 = self.rooms[y1][x1] + x2, y2 = x1 + dx, y1 + dy + r2 = self.rooms[y2][x2] + self.connect_rooms(r1, r2) + connections.append((x1, y1, x2, y2)) + connected.append((x2, y2)) + for loop in range(3): + x1 = random.randint(0, 2) + y1 = random.randint(0, 2) + possible = [(dx, dy) for (dx, dy) + in dirs if 0 <= x1 + dx <= 2 and 0 <= y1 + dy <= 2 and + not ((x1, y1, x1 + dx, y1 + dy) in connections or + (x1 + dx, y1 + dy, x1, y1) in connections)] + if not possible: + continue + dx, dy = random.choice(possible) + x2, y2 = x1 + dx, y1 + dy + r1 = self.rooms[y1][x1] + r2 = self.rooms[y2][x2] + self.connect_rooms(r1, r2) + connections.append((x1, y1, x2, y2)) + + def make_room(self, x1, y1): + w = random.randint(3, 8) + h = random.randint(3, 8) + x = 10 * x1 + random.randint(0, 8 - w) + y = 10 * y1 + random.randint(0, 8 - h) + return (x, y, x + w, y + h) + + def connect_rooms(self, r1, r2): + if abs(r1[0] - r2[0]) < abs(r1[1] - r2[1]): + # vertical + v = True + if r1[1] > r2[1]: + r1, r2 = r2, r1 + x1 = random.randint(r1[0] + 1, r1[2] - 1) + y1 = r1[3] + x2 = random.randint(r2[0] + 1, r2[2] - 1) + y2 = r2[1] + if y1 + 1 != y2 - 1: + m = random.randint(y1 + 1, y2 - 1) + else: + m = y1 + 1 + for y in range(y1, m): + self.map[y][x1] = random.choice(',;') + if x1 < x2: + for x in range(x1, x2 + 1): + self.map[m][x] = random.choice(';"') + else: + for x in range(x2, x1 + 1): + self.map[m][x] = random.choice(';"') + for y in range(m, y2): + self.map[y][x2] = random.choice(',;') + else: + # horizontal + v = False + if r1[0] > r2[0]: + r1, r2 = r2, r1 + x1 = r1[2] + y1 = random.randint(r1[1] + 1, r1[3] - 1) + x2 = r2[0] + y2 = random.randint(r2[1] + 1, r2[3] - 1) + if x1 + 1 != x2 - 1: + m = random.randint(x1 + 1, x2 - 1) + else: + m = x1 + 1 + for x in range(x1, m): + self.map[y1][x] = random.choice(',;') + if y1 < y2: + for y in range(y1, y2 + 1): + self.map[y][m] = random.choice(';"') + else: + for y in range(y2, y1 + 1): + self.map[y][m] = random.choice(';"') + for x in range(m, x2): + self.map[y2][x] = random.choice(',;') + self.map[y1][x1] = '+' + self.map[y2][x2] = '+' + if random.randint(0, 10) > 7: + self.things[(x1, y1)] = thing.Door(x1, y1, not v) + if random.randint(0, 10) > 7: + self.things[(x2, y2)] = thing.Door(x2, y2, not v) + + def rect(self, x1, y1, x2, y2, c, fill=None): + if fill: + for y in range(y1 + 1, y2): + for x in range(x1 + 1, x2): + self.map[y][x] = random.choice(fill) + for y in range(y1, y2 + 1): + self.map[y][x1] = c + self.map[y][x2] = c + for x in range(x1, x2 + 1): + self.map[y1][x] = c + self.map[y2][x] = c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hero.py Sat Aug 27 23:18:10 2011 +0200 @@ -0,0 +1,148 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import display +import thing +import effect + + +class HeroSprite(display.Sprite): + faces = { + -1: { -1: 1, 0: 1, 1: 0 }, + 0: { -1: 2, 0: 0, 1: 0 }, + 1: { -1: 2, 0: 3, 1: 3}, + } + + def __init__(self): + super(HeroSprite, self).__init__(0, 0, 'hero.png') + self.facing = 2 + self.animation = None + + def update(self): + if self.animation: + try: + self.animation.next() + except StopIteration: + self.animation = None + else: + self.image = self.frames[self.facing][0] + + def anim_walk(self, dx, dy): + self.facing = self.faces[dx][dy] + self.depth = self.rect.midbottom[1] + 1 + for frame in range(4): + self.image = self.frames[self.facing][frame % 4] + for step in range(2): + yield + self.rect.move_ip(dx * 3, dy * 2) + self.depth = self.rect.midbottom[1] + 1 + + def anim_blocked(self, dx, dy): + self.facing = self.faces[dx][dy] + self.depth = self.rect.midbottom[1] + 1 + for frame in range(4): + yield + self.image = self.frames[self.facing][frame % 4] + yield + + def anim_enter(self): + self.depth = self.rect.midbottom[1] + 1 + self.image = self.frames[2][0] + self.rect.move_ip(0, -15) + for step in range(6): + yield + self.rect.move_ip(0, step) + + def anim_exit(self): + self.depth = self.rect.midbottom[1] + 1 + image = self.frames[2][0].copy() + self.image = image + move = 0 + for step in range(6): + yield + move += step + self.image = image.subsurface(0, 0, 32, 32 - move) + self.rect.move_ip(0, step) + + def anim_fight(self, dx, dy): + self.facing = self.faces[dx][dy] + self.image = self.frames[self.facing][4] + h = [1, 2, -3] + for frame in range(3): + yield + self.rect.move_ip(dx * 3 * frame, dy * 2 * frame - h[frame]) + self.depth = self.rect.midbottom[1] + 1 + h[frame] + self.depth = self.rect.midbottom[1] + 1 + for frame in range(3): + yield + self.image = self.frames[self.facing][1] + self.rect.move_ip(-dx * 3 * frame, -dy * 2 * frame) + self.depth = self.rect.midbottom[1] + 1 + self.image = self.frames[self.facing][0] + for frame in range(2): + yield + + +class Hero(thing.Thing): + def __init__(self, x=0, y=0): + self.x, self.y = x, y + self.effects = [] + self.sprite = HeroSprite() + self.max_hp = 10 + self.hp = self.max_hp + self.regen = 10 + self.regen_tick = 0 + + def render(self, to_display): + to_display.sprites.add(self.sprite) + to_display.sprites.add(thing.ShadowSprite(self.sprite)) + + def tick(self): + self.regen_tick += 1 + if self.regen_tick > self.regen: + if self.hp < self.max_hp: + self.hp += 1 + self.effects.append( + effect.Caption(self.x, self.y, '+1', (32, 128, 0))) + self.regen_tick = 0 + + def enter(self, level, x, y): + self.x = x + self.y = y + self.sprite.move(x, y) + self.sprite.facing = 2 + self.sprite.animation = self.sprite.anim_enter() + + def exit(self, level): + self.sprite.animation = self.sprite.anim_exit() + level.hero = None + + def walk(self, level, dx, dy): + block = level.is_blocked(self.x + dx, self.y + dy) + if not block: + self.x += dx + self.y += dy + self.sprite.animation = self.sprite.anim_walk(dx, dy) + elif hasattr(block, 'hit'): + self.attack(level, dx, dy, block) + else: + self.sprite.animation = self.sprite.anim_blocked(dx, dy) + self.sprite.animation.next() + + def attack(self, level, dx, dy, what=None): + if what is None: + what = level.is_blocked(self.x + dx, self.y + dy) + if not hasattr(what, 'hit'): + return + what.hit() + self.sprite.animation = self.sprite.anim_fight(dx, dy) + if isinstance(what, thing.Chest): + self.effects.append( + effect.Caption(self.x + dx, self.y + dy, 'empty', (64, 192, 0))) + + def hit(self, damage): + self.hp -= damage + self.regen_tick = 0 + self.effects.append( + effect.Caption(self.x, self.y, '%d' % (-damage), (255, 32, 32))) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/level.py Sat Aug 27 23:18:10 2011 +0200 @@ -0,0 +1,152 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import display +import thing +import generator +import pygame + + +class Tile(object): + filename = 'level.png' + tx, ty = 0, 3 + blocks = False + + def __init__(self, x, y, map): + self.x = x + self.y = y + self.map = map + + def render(self, to_display): + tile = display.tiles[self.filename][self.tx][self.ty] + to_display.background.blit(tile, (self.x * 24, self.y * 16)) + + +class BrokenTile(Tile): + tx, ty = 2, 3 + + +class GroundTile(Tile): + tx, ty = 0, 4 + + +class PlantsTile(Tile): + tx, ty = 1, 4 + + +class WeedTile(Tile): + tx, ty = 2, 4 + + +class GrassTile(Tile): + tx, ty = 3, 4 + + +class DoorTile(Tile): + tx, ty = 3, 3 + + +class StairsTile(Tile): + tx, ty = 1, 3 + + +class WallTile(Tile): + blocks = True + + def render(self, to_display): + map = self.map + x = self.x + y = self.y + c = map[y][x] + tileset = display.tiles[self.filename] + def f(x, y): + return map[y][x] == c + def sides(x, y): + if y < 0: + return 3 + if x <= 0 or f(x - 1, y): + if x >= len(map[y])-1 or f(x + 1, y): + return 1 + else: + return 2 + elif x >= len(map[y])-1 or f(x + 1, y): + return 0 + return 3 + if y > 0 and not f(x, y - 1): + overlay = display.Overlay(x, y - 1, tileset[sides(x, y)][0]) + to_display.overlay.add(overlay) + if y < len(map) - 1: + if f(x, y + 1): + tile = tileset[sides(x, y + 1)][1] + else: + tile = tileset[sides(x, y)][2] + to_display.background.blit(tile, (self.x * 24, self.y * 16)) + + +class Level(object): + key = { + '.': Tile, + '+': DoorTile, + ':': BrokenTile, + '>': StairsTile, + 'x': WallTile, + ',': GroundTile, + ';': PlantsTile, + '"': WeedTile, + '*': GrassTile, + } + BLOCKED = 1 + + def __init__(self, hero, depth): + self.hero = hero + self.depth = depth + gen = generator.LevelGenerator(depth) + self.map = gen.map + self.things = {} + for t in gen.things.itervalues(): + self.things[(t.x, t.y)] = t + if isinstance(t, thing.Entrance): + self.hero.enter(self, t.x, t.y) + self.monsters = {} + for m in gen.monsters.itervalues(): + self.monsters[(m.x, m.y)] = m + + def is_blocked(self, x, y): + if (x, y) in self.monsters: + return self.monsters[(x, y)] + t = self.things.get((x, y)) + if t and t.blocks: + return t + if x<0 or y<0: + return True + try: + return self.flags[y][x] & self.BLOCKED + except IndexError: + return True + + def render(self, display, area=None): + if area is None: + area = (0, 0, len(self.map[0]), len(self.map)) + display.background = pygame.Surface((len(self.map[0]) * 24, + len(self.map) * 16)) + self.flags = [] + for y, line in enumerate(self.map[area[1]:area[3]]): + self.flags.append([]) + for x, c in enumerate(line[area[0]:area[2]]): + tile_class = self.key.get(c) + if tile_class: + tile = tile_class(x, y, self.map) + tile.render(display) + self.flags[-1].append(0) + if tile_class is None or tile.blocks: + self.flags[-1][-1] |= self.BLOCKED + self.hero.render(display) + for t in self.things.itervalues(): + t.render(display) + for m in self.monsters.itervalues(): + m.render(display) + + def render_map(self, display, area): + display.background.fill((0, 0, 0), rect=Rect(area[0]*24, area[1]*16, + (area[2] - area[0]) * 24, (area[3] - area[1]) * 16)) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.py Sat Aug 27 23:18:10 2011 +0200 @@ -0,0 +1,76 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import pygame.locals + +import display +import level +import hero +import menu + +class Game(object): + def __init__(self): + self.depth = 0 + self.display = display.Display((320, 200)) + self.hero = hero.Hero() + self.start_level() + self.health = menu.GameInfo(288, 8, 'HP %d/10' % self.hero.hp, + (32, 192, 0)) + self.display.menus.add(self.health) + + def start_level(self): + self.display.empty() + self.level = level.Level(self.hero, self.depth) + self.depth += 1 + self.level.render(self.display) + self.display.scroll_to(self.hero.sprite.rect) + + def control(self): + if self.display.key == pygame.locals.K_ESCAPE: + raise display.GameQuit() + end_turn = False + if not self.hero.sprite.animation: + if self.level.hero is None: + self.start_level() + if self.level.map[self.hero.y][self.hero.x] == '>': + self.hero.exit(self.level) + return + pressed = self.display.get_pressed() + if pygame.locals.K_UP in pressed: + self.hero.walk(self.level, 0, -1) + end_turn = True + elif pygame.locals.K_DOWN in pressed: + self.hero.walk(self.level, 0, 1) + end_turn = True + elif pygame.locals.K_LEFT in pressed: + self.hero.walk(self.level, -1, 0) + end_turn = True + elif pygame.locals.K_RIGHT in pressed: + self.hero.walk(self.level, 1, 0) + end_turn = True + if end_turn: + self.hero.tick() + self.health.change('HP %d/10' % self.hero.hp) + monsters = self.level.monsters.values() + for m in monsters: + if m.dead: + del self.level.monsters[(m.x, m.y)] + for m in monsters: + if not m.dead: + m.act(self.level) + for m in monsters + [self.hero]: + for d, effect in enumerate(m.effects): + effect.render(self.display, delay=d * 12) + m.effects = [] + + def main(self): + try: + while True: + self.display.tick(tick=24) + self.control() + self.display.scroll_after(self.hero.sprite.rect) + except display.GameQuit: + pass + +if __name__ == '__main__': + Game().main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/menu.py Sat Aug 27 23:18:10 2011 +0200 @@ -0,0 +1,25 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import pygame +import effect + + +class GameInfo(pygame.sprite.Sprite): + def __init__(self, x, y, text, color): + super(GameInfo, self).__init__() + self.x, self.y = x, y + self.text = text + self.color = color + self.image = effect.outlined_text(text, color, (32, 32, 32)) + self.rect = self.image.get_rect() + self.rect.center = self.x, self.y + + def change(self, text=None, color=None): + if text is not None: + self.text = text + if color is not None: + self.color = color + self.image = effect.outlined_text(self.text, self.color, (32, 32, 32)) + self.rect = self.image.get_rect() + self.rect.center = self.x, self.y
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/monster.py Sat Aug 27 23:18:10 2011 +0200 @@ -0,0 +1,233 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import random + +import thing +import effect +import display + + +dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + + +def distance(x1, y1, x2, y2): + return abs(x1 - x2) + abs(y1 - y2) + + +class MonsterSprite(display.Sprite): + def __init__(self, x, y, tx): + self.tx = tx + self.ty = 0 + super(MonsterSprite, self).__init__(x, y, 'monsters.png') + self.animation = self.anim_default() + self.frame = 0 + + def update(self): + try: + self.animation.next() + except StopIteration: + self.animation = self.anim_default() + self.animation.next() + + def anim_default(self): + self.depth = self.rect.midbottom[1] + 1 + while True: + yield + self.frame = (self.frame + 1) % 4 + self.image = self.frames[self.tx][self.frame % 4] + yield + yield + + def anim_walk(self, dx, dy): + for frame in range(2): + self.frame = (self.frame + 1) % 4 + for step in range(4): + yield + self.image = self.frames[self.tx][self.frame] + self.rect.move_ip(dx * 3, dy * 2) + self.depth = self.rect.midbottom[1] + 1 + + def anim_fight(self, dx, dy): + self.image = self.frames[self.tx][1] + h = [1, 2, -3] + latitude = 0 + for frame in range(2): + yield + for frame in range(3): + yield + self.rect.move_ip(dx * 3 * frame, dy * 2 * frame - h[frame]) + latitude += h[frame] + self.depth = self.rect.midbottom[1] - latitude + 1 + self.depth = self.rect.midbottom[1] + for frame in range(3): + yield + self.image = self.frames[self.tx][1] + self.rect.move_ip(-dx * 3 * frame, -dy * 2 * frame) + self.depth = self.rect.midbottom[1] + 1 + self.image = self.frames[self.tx][0] + + def anim_die(self): + image = self.frames[self.tx][0] + edge = 4 + yield + yield + for frame in range(8): + yield + self.image = image.subsurface(0, 0, 32, 32 - edge) + edge += frame + self.image.set_alpha(256 - frame * 32) + self.rect.move_ip(0, frame - 1) + self.kill() + + +class Monster(thing.Thing): + tx = 0 + max_hp = 4 + name = "monster" + alpha = None + + def __init__(self, x, y): + self.x, self.y = x, y + self.dead = False + self.effects = [] + self.hp = self.max_hp + self.asleep = False + + def render(self, to_display): + self.sprite = MonsterSprite(self.x, self.y, self.tx) + if self.alpha: + for frame in self.sprite.frames[self.tx]: + frame.set_alpha(self.alpha) + to_display.sprites.add(self.sprite) + to_display.sprites.add(thing.ShadowSprite(self.sprite)) + + def hit(self, damage=1): + self.asleep = False + self.effects.append( + effect.Caption(self.x, self.y, '%d' % (-damage), (255, 128, 16))) + self.hp -= damage + if self.hp <= 0: + self.die() + + def die(self): + c = effect.Caption(self.x, self.y, self.name, (192, 192, 192), ttl=8) + self.effects.append(c) + self.dead = True + self.sprite.animation = self.sprite.anim_die() + self.sprite.animation.next() + + def act(self, level): + # Attack if standing next to the hero + if distance(level.hero.x, level.hero.y, self.x, self.y) == 1: + self.attack(level, level.hero.x - self.x, level.hero.y - self.y) + return + + if self.asleep: + return + + # Walk towards the hero + possible = [(dx, dy) for (dx, dy) in dirs if + not self.is_blocked(level, dx, dy)] + if not possible: + return + random.shuffle(possible) + dx, dy = min(possible, + key=lambda d: distance(level.hero.x, level.hero.y, + self.x+d[0], self.y+d[1])) + self.walk(level, dx, dy) + + def is_blocked(self, level, dx, dy): + block = level.is_blocked(self.x + dx, self.y + dy) + if block: + return True + + def walk(self, level, dx, dy): + if self.is_blocked(level, dx, dy): + return + if (level.hero.x, level.hero.y) == (self.x + dx, self.y + dy): + self.attack(level, dx, dy) + return + del level.monsters[(self.x, self.y)] + self.x += dx + self.y += dy + level.monsters[(self.x, self.y)] = self + self.sprite.animation = self.sprite.anim_walk(dx, dy) + self.sprite.animation.next() + + def attack(self, level, dx, dy): + self.asleep = False + if not (level.hero.x, level.hero.y) == (self.x + dx, self.y + dy): + return + self.sprite.animation = self.sprite.anim_fight(dx, dy) + self.sprite.animation.next() + level.hero.hit(1) + + +class Skeleton(Monster): + tx = 0 + name = "skeleton" + max_hp = 4 + + +class Zombie(Monster): + tx = 1 + name = "zombie" + max_hp = 7 + + +class Jack(Monster): + tx = 2 + name = "jack" + max_hp = 8 + + +class Bat(Monster): + tx = 3 + name = "bat" + max_hp = 2 + + def act(self, level): + # Attack if we feel like it + if random.choice([True, False]): + if distance(level.hero.x, level.hero.y, self.x, self.y) == 1: + self.attack(level, level.hero.x - self.x, level.hero.y - self.y) + return + + if self.asleep: + return + + # Move randomly (very annoying) + possible = [(dx, dy) for (dx, dy) in dirs if + not self.is_blocked(level, dx, dy)] + if not possible: + return + dx, dy = random.choice(possible) + self.walk(level, dx, dy) + + +class Ghost(Monster): + tx = 4 + name = "ghost" + max_hp = 5 + alpha = 96 + + def is_blocked(self, level, dx, dy): + # Ghosts can pass through obstacles + block = level.is_blocked(self.x + dx, self.y + dy) + if block and isinstance(block, Monster) or block == True: + return True + + +class Eye(Monster): + tx = 5 + name = "eye" + max_hp = 7 + asleep = True + + +class Jelly(Monster): + tx = 6 + name = "jelly" + max_hp = 6 + alpha = 192
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/thing.py Sat Aug 27 23:18:10 2011 +0200 @@ -0,0 +1,86 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import display + + +class ShadowSprite(display.Sprite): + def __init__(self, parent): + super(ShadowSprite, self).__init__(0, 0, 'things.png', 3, 3) + self.image.set_alpha(64) + self.parent = parent + + def update(self): + if not self.parent.alive(): + self.kill() + return + depth = getattr(self.parent, 'depth', None) + self.rect.midbottom = (self.parent.rect.midbottom[0], depth) + +class DoorSprite(display.Sprite): + def __init__(self, x, y, vertical): + if vertical: + tx = 1 + else: + tx = 0 + super(DoorSprite, self).__init__(x, y, 'things.png', tx, 0) + self.animation = None + + def update(self): + if self.animation: + try: + self.animation.next() + except StopIteration: + self.animation = None + + def anim_open(self): + self.depth += 2 + for step in range(6): + yield + self.rect.move_ip(0, -2) + + + +class Thing(object): + tx, ty = 0, 1 + blocks = False + + def __init__(self, x, y): + self.x = x + self.y = y + self.sprite = display.Sprite(self.x, self.y, 'things.png', + self.tx, self.ty) + + def render(self, to_display): + to_display.sprites.add(self.sprite) + to_display.shadows.add(ShadowSprite(self.sprite)) + + +class Door(Thing): + def __init__(self, x, y, vertical): + self.x = x + self.y = y + self.blocks = True + self.vertical = vertical + self.sprite = DoorSprite(self.x, self.y, self.vertical) + + def hit(self, damage=0): + self.sprite.animation = self.sprite.anim_open() + self.blocks = False + + +class Entrance(Thing): + tx, ty = 3, 0 + + +class Chest(Thing): + tx, ty = 1, 1 + blocks = True + + def hit(self, damage=0): + self.sprite.image = self.sprite.frames[2][self.ty] + self.blocks = False + + +class Plant(Thing): + tx, ty = 2, 2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/start Sat Aug 27 23:18:10 2011 +0200 @@ -0,0 +1,14 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import os +import sys + +base_dir = os.path.dirname(os.path.abspath(__file__)) +src_dir = os.path.join(base_dir, 'src') + +sys.path.insert(0, src_dir) + +import main + +main.Game().main()
--- a/thing.py Wed Aug 24 21:24:01 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import display - - -class ShadowSprite(display.Sprite): - def __init__(self, parent): - super(ShadowSprite, self).__init__(0, 0, 'things.png', 3, 3) - self.image.set_alpha(64) - self.parent = parent - - def update(self): - if not self.parent.alive(): - self.kill() - return - depth = getattr(self.parent, 'depth', None) - self.rect.midbottom = (self.parent.rect.midbottom[0], depth) - -class DoorSprite(display.Sprite): - def __init__(self, x, y, vertical): - if vertical: - tx = 1 - else: - tx = 0 - super(DoorSprite, self).__init__(x, y, 'things.png', tx, 0) - self.animation = None - - def update(self): - if self.animation: - try: - self.animation.next() - except StopIteration: - self.animation = None - - def anim_open(self): - self.depth += 2 - for step in range(6): - yield - self.rect.move_ip(0, -2) - - - -class Thing(object): - tx, ty = 0, 1 - blocks = False - - def __init__(self, x, y): - self.x = x - self.y = y - self.sprite = display.Sprite(self.x, self.y, 'things.png', - self.tx, self.ty) - - def render(self, to_display): - to_display.sprites.add(self.sprite) - to_display.shadows.add(ShadowSprite(self.sprite)) - - -class Door(Thing): - def __init__(self, x, y, vertical): - self.x = x - self.y = y - self.blocks = True - self.vertical = vertical - self.sprite = DoorSprite(self.x, self.y, self.vertical) - - def hit(self, damage=0): - self.sprite.animation = self.sprite.anim_open() - self.blocks = False - - -class Entrance(Thing): - tx, ty = 3, 0 - - -class Chest(Thing): - tx, ty = 1, 1 - blocks = True - - def hit(self, damage=0): - self.sprite.image = self.sprite.frames[2][self.ty] - self.blocks = False - - -class Plant(Thing): - tx, ty = 2, 2
