changeset 1:22ddffd61be9

Initial import
author Radomir Dopieralski <moindev@sheep.art.pl>
date Sun, 16 Dec 2007 14:22:03 +0100
parents cec5a5dbd62c
children 0b0d0e806f33
files creatures.png creatures.py data.py display.py effects.png light.py prefs.py revolver.png tiles.png util.py world.py zday.py
diffstat 12 files changed, 1555 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
Binary file creatures.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/creatures.py	Sun Dec 16 14:22:03 2007 +0100
@@ -0,0 +1,649 @@
+import time, random, sys
+from data import *
+from util import *
+from light import Light
+
+def game_win(disp):
+    disp.clear()
+    disp.pause()
+    sys.exit()
+    
+def game_over(disp):
+    disp.clear()
+    disp.pause()
+    sys.exit()
+
+class ActError(Exception):
+    pass
+
+class Zombie:
+    def __init__(self, world, pos = None):
+        self.world = world
+        if pos:
+            self.pos = pos
+        else:
+            x = random.randrange(1, world.width-2)
+            y = random.randrange(1, world.height-2)
+            while world.flags[x][y] & (FLAG_BLOCKED | FLAG_CREATURE):
+                x += 1
+                if x>=world.width-1:
+                    x = 1
+                    y += 1
+            self.pos = (x, y)
+        self.target = None
+        self.effect = None
+        world.creatures.append(self)
+        self.world.flags[self.pos[0]][self.pos[1]] |= FLAG_CREATURE
+        self.burning = 0
+        self.dead = 0
+        self.tripped = 0
+        self.hero = 0
+
+    def trip(self):
+        if not self.burning:
+            self.tripped = 1
+        
+    def untrip(self):
+        if not self.burning:
+            self.tripped = 0
+
+    def kill(self):
+        x, y = self.pos
+        self.world.msg.write("Splat!")
+        self.dead = 1
+        self.world.flags[x][y] &= ~FLAG_CREATURE
+        if not self.world.map[self.pos[0]][self.pos[1]] in (MAP_CRATE, MAP_CORPSE, MAP_WALL, MAP_DOOR, MAP_ELEV):
+            if self.world.flags[x][y] & FLAG_LETHAL:
+                self.world.add_tile(self.pos, MAP_MACHINE+1)
+            else:
+                if random.randrange(100)>20:
+                    self.world.add_tile(self.pos, self.world.corpse)
+                else:
+                    self.world.blood(self.pos, 4)
+        for i in range(len(self.world.creatures)):
+            if self.world.creatures[i]==self:
+                del self.world.creatures[i]
+                break
+
+    def bleed(self, amount=50):
+        """Fills the map around the creature with blood."""
+
+        for dy in (-1, 0, 1):
+            for dx in (-1, 0, 1):
+                x = self.pos[0] + dx
+                y = self.pos[1] + dy
+                splatter = (random.randrange(100)+amount)/50
+                self.world.blood((x,y), splatter)
+
+    def wound(self, lethality):
+        if lethality>0:
+            self.effect = EFF_HIT
+            if random.randrange(100)<lethality:
+                self.kill()
+            elif lethality>50:
+                self.trip()
+
+    def metabolism(self):
+        """Get called every turn."""
+
+        if self.world.flags[self.pos[0]][self.pos[1]] & FLAG_LETHAL:
+            self.bleed(80)
+            self.wound(20)
+            self.trip()
+        if self.burning:
+            self.effect = EFF_BURN
+            if random.randrange(100)>90:
+                self.kill()
+
+    def burn(self):
+        """Called when the creature touches fire."""
+
+        self.burning = 1
+
+    def pushback(self):
+        """Called when the creature is pushed."""
+
+        hx, hy = self.world.hero.pos
+        x, y = self.pos
+        dx, dy = 0, 0
+        if hx < x:
+            dx = 1
+        elif hx > x:
+            dx = -1
+        if hy < y:
+            dy = 1
+        elif hy > y:
+            dy = -1
+        try:
+            self.push(dx, dy)
+            self.trip()
+        except ActError:
+            pass
+
+    def push(self, dx, dy):
+        if dx==0 and dy==0:
+            return
+        x, y = self.pos
+        npos = (x+dx, y+dy)
+        nx, ny = npos
+        if nx<0 or nx>=self.world.width or ny<0 or ny>=self.world.height:
+            raise ActError
+        if self.world.flags[nx][ny] & FLAG_LETHAL:
+            self.trip()
+        elif self.world.flags[nx][ny] & FLAG_BLOCKED:
+            raise ActError
+        if self.world.flags[nx][ny] & FLAG_CREATURE:
+            raise ActError
+        self.world.flags[x][y] &= ~FLAG_CREATURE
+        self.pos = npos
+        self.world.flags[nx][ny] |= FLAG_CREATURE
+    
+    def act_go(self, dx, dy):
+        if dx==0 and dy==0:
+            return
+        x, y = self.pos
+        npos = (x+dx, y+dy)
+        nx, ny = npos
+        if nx<0 or nx>=self.world.width or ny<0 or ny>=self.world.height:
+            raise ActError
+        if self.world.flags[nx][ny] & FLAG_BLOCKED:
+            raise ActError
+        if self.world.flags[nx][ny] & FLAG_CREATURE:
+            raise ActError
+        self.world.flags[x][y] &= ~FLAG_CREATURE
+        self.pos = npos
+        self.world.flags[nx][ny] |= FLAG_CREATURE
+
+    def act_bite(self):
+        first = random.choice((0, 1))
+        if first:
+            self.world.hero.defend(self)
+        if random.randrange(100)>10:
+            if distance(self.world.hero.pos, self.pos)<=1 and not self.dead:
+                self.world.msg.write("Chomp!")
+                self.world.hero.hit_bite()
+        if not first:
+            self.world.hero.defend(self)
+
+    def act(self):
+        x, y = self.pos
+        if self.burning:
+            dx, dy = random.choice((-1, 0, 1)), random.choice((-1,0,1))
+            for ny in range(self.pos[1]-1, self.pos[1]+1):
+                for nx in range(self.pos[0]-1, self.pos[0]+1):
+                    creat = self.world.find_creature((nx, ny))
+                    if creat and creat!=self:
+                        creat.burn()
+            try:
+                self.act_go(dx, dy)
+            except ActError:
+                pass
+        elif self.tripped:
+            self.untrip()
+        else:
+            if self.pos == self.target:
+                self.target = None
+            if self.world.flags[x][y] & FLAG_VISIBLE:
+                self.target = self.world.hero.pos
+            if self.target:
+                hx, hy = self.target
+                if distance(self.world.hero.pos, self.pos)<=1:
+                    self.act_bite()
+                else:
+                    dx = 0
+                    dy = 0
+                    if hx < x:
+                        dx = -1
+                    elif hx > x:
+                        dx = 1
+                    if hy < y:
+                        dy = -1
+                    elif hy > y:
+                        dy = 1
+                    try:
+                        self.act_go(dx, dy)
+                    except ActError:
+                        try:
+                            if random.choice((0, 1)):
+                                self.act_go(dx, 0)
+                            else:
+                                self.act_go(0, dy)
+                        except ActError:
+                            pass
+        
+class Hero(Zombie):
+    def __init__(self, world, disp, pos=None):
+        Zombie.__init__(self, world, pos)
+        self.disp = disp
+        self.disp.centermap(self.pos)
+        self.light = Light(world)
+        self.light.cast(self.pos[0], self.pos[1])
+        self.inventory = Inventory(world)
+        self.inventory.add_item(1)
+        self.inventory.add_item(6)
+        self.inventory.add_item(12)
+        self.disp.inv_draw(self.inventory)
+        self.stamina = 100
+        self.wounds = 0
+        self.dead = 0
+        self.food = 0
+        self.aim = None
+        self.hero = 1
+
+
+    def kill(self):
+        self.world.msg.write("Aaargh! They got me! Plant an orchid on my grave...")
+        game_over(self.disp)
+
+    def wound(self, lethality=0):
+        self.effect = EFF_HIT
+        if self.stamina==0:
+            self.kill()
+        if self.wounds<5:
+            self.wounds+=1
+        self.stamina -= random.randrange(1,10);
+        if self.stamina<0:
+            self.stamina = 0
+
+    def defend(self, creat):
+        slot = self.inventory.eqp[KIND_WEAPON]
+        if slot==None or self.stamina<=0:
+            return
+        weapon = items[self.inventory.inv[slot]]
+        try:
+            accuracy = weapon["accuracy"]
+        except KeyError:
+            accuracy = 100
+        if random.randrange(100)<=accuracy:
+            self.inventory.emit_sound(slot)
+            try:
+                if items[self.inventory.inv[slot]]["noisy"]:
+                    self.world.noise()
+            except KeyError:
+                pass
+            try:
+                creat.bleed(weapon["blood"])
+            except KeyError:
+                pass
+            try:
+                if weapon['burn']:
+                    creat.burn()
+            except KeyError:
+                pass
+            try:
+                if items[self.inventory.inv[slot]]["push"]:
+                    creat.pushback()
+            except KeyError:
+                pass
+            try:
+                lethal = weapon["lethal"]
+            except KeyError:
+                lethal = 40
+            creat.wound(lethal)
+        else:
+            creat.effect = EFF_MISS
+        self.inventory.useup_item(slot)
+        if random.randrange(100)<15:
+            self.world.msg.write("Broken.")
+            self.inventory.del_item(slot)
+        self.disp.inv_draw(self.inventory)
+
+    def metabolism(self):
+        if self.food>0:
+            amount = min(random.randrange(3,6), self.food)
+            self.food -= amount
+            self.stamina += amount
+        self.stamina -= self.wounds;
+        if self.stamina<0:
+            self.stamina = 0
+        if self.stamina>100:
+            self.stamina = 100
+
+    def hit_bite(self):
+        if random.randrange(100)>10:
+            self.world.msg.write("Ouch!")
+            self.bleed(20)
+            self.wound()
+
+    def burn(self):
+        self.stamina -= random.randrange(10, 40)
+        self.world.msg.write("Burns!")
+        if self.stamina<0:
+            self.stamina = 0
+
+    def act_shoot(self):
+        if not self.target:
+            self.world.msg.write("Nobody here.")
+            raise ActError
+        gun_slot = self.inventory.eqp[KIND_GUN]
+        if gun_slot==None:
+            self.world.msg.write("Click!")
+            raise ActError
+        gun = items[self.inventory.inv[gun_slot]]
+        creat = self.world.find_creature(self.target)
+        if creat:
+            try:
+                accuracy = gun["accuracy"]
+            except KeyError:
+                accuracy = 100
+            self.inventory.emit_sound(gun_slot)
+            try:
+                if items[self.inventory.inv[gun_slot]]["noisy"]:
+                    self.world.noise()
+            except KeyError:
+                pass
+            if random.randrange(100)<=accuracy:
+                try:
+                    creat.bleed(gun["blood"])
+                except KeyError:
+                    pass
+                try:
+                    if gun['burn']:
+                        creat.burn()
+                except KeyError:
+                    pass
+                try:
+                    if gun["push"]:
+                        creat.pushback()
+                except KeyError:
+                    pass
+                try:
+                    lethal = gun["lethal"]
+                except KeyError:
+                    lethal = 30
+                creat.wound(lethal)
+            else:
+                self.world.msg.write("Spak!")
+                creat.effect = EFF_MISS
+            self.inventory.useup_item(gun_slot)
+            self.disp.inv_draw(self.inventory)
+        else:
+            self.world.msg.write("Sneaky bastard!")
+            raise ActError
+
+    def act_use(self, slot):
+        if self.inventory.inv[slot]==0:
+            self.world.msg.write("It's not there.")
+            raise ActError
+        item = items[self.inventory.inv[slot]]
+        kind = item["kind"]
+        if kind in (KIND_WEAPON, KIND_GUN):
+            if self.inventory.eqp[kind]==slot:
+                self.inventory.eqp[kind] = None
+            else:
+                self.inventory.eqp[kind] = slot
+            self.disp.inv_draw(self.inventory)
+        elif kind == KIND_FOOD:
+            self.act_apply(slot)
+        else:
+            self.world.msg.write("Useless.")
+            raise ActError
+
+    def act_trash(self, slot):
+        if self.inventory.inv[slot]==0:
+            self.world.msg.write("Gone already.")
+            raise ActError
+        self.inventory.del_item(slot)
+        self.world.msg.write("Useless crap!")
+        self.disp.inv_draw(self.inventory)
+
+    def act_loot(self):
+        x, y = self.pos
+        if not self.world.flags[x][y] & FLAG_ITEM:
+            self.world.msg.write("There's nothing useful here.")
+            raise ActError
+        if self.world.map[x][y] == MAP_CRATE:
+            self.world.msg.write("Gently opening the crate...")
+            item = self.inventory.search_crate()
+        elif self.world.map[x][y] == MAP_CORPSE:
+            self.world.msg.write("Searching the corpse....")
+            item = self.inventory.search_corpse()
+        self.world.change(self.pos)
+        if item:
+            self.world.msg.write("Found something.")
+            self.disp.inv_draw(self.inventory)
+
+    def act_go(self, dx, dy):
+        x, y = self.pos
+        npos = (x+dx, y+dy)
+        nx, ny = npos
+        if nx<0 or nx>self.world.width or ny<0 or ny>self.world.height:
+            raise ActError
+        if self.world.map[nx][ny] == MAP_EXIT:
+            self.world.msg.write("Finally made it to safety!")
+            game_win(self.disp)
+        if self.world.flags[nx][ny] & FLAG_LETHAL:
+            self.world.msg.write("Careful! Those gears can shred you into pieces.")
+            raise ActError
+        if self.world.flags[nx][ny] & FLAG_BLOCKED:
+            self.world.msg.write("Bonk!")
+            raise ActError
+        if self.world.flags[nx][ny] & FLAG_CREATURE:
+            creat = self.world.find_creature((nx, ny))
+            if not creat:
+                self.world.flags[nx][ny] &= ~FLAG_CREATURE
+            else:
+                self.world.msg.write("Pardon me.")
+                creat.push(dx, dy)
+                creat.trip()
+        self.world.flags[x][y] &= ~FLAG_CREATURE
+        self.pos = npos
+        self.world.flags[nx][ny] |= FLAG_CREATURE
+
+    def act_apply(self, slot):
+        food = items[self.inventory.inv[slot]]
+        try:
+            self.wounds -= food["heal"]
+            if self.wounds<0:
+                self.wounds = 0
+        except KeyError:
+            pass
+        try:
+            self.food += food["food"]
+            if self.food>200:
+                self.food = 200
+        except KeyError:
+            pass
+        self.inventory.emit_sound(slot)
+        try:
+            if items[self.inventory.inv[slot]]["noisy"]:
+                self.world.noise()
+        except KeyError:
+            pass
+        self.inventory.useup_item(slot)
+        self.disp.inv_draw(self.inventory)
+
+    def act_run(self, dx, dy):
+        self.act_go(dx, dy)
+        if self.stamina>0:
+            try:
+                self.act_go(dx, dy)
+                self.stamina -= 3
+                if self.stamina<0:
+                    self.stamina = 0
+            except ActError:
+                pass
+        else:
+            self.world.msg.write('Pant, pant...')
+
+    def act_aim(self):
+        if self.aim==None:
+            self.aim = 0
+        start = self.aim
+        self.world.msg.write("Changed your mind, eh?")
+        while 1:
+            self.aim+=1
+            if self.aim>=len(self.world.creatures):
+                self.aim = 0
+            x, y = self.world.creatures[self.aim].pos
+            if self.world.flags[x][y] & FLAG_VISIBLE and self.world.creatures[self.aim]!=self:
+                self.world.msg.write("Here.")
+                self.target = (x,y)
+                break
+            if self.aim == start:
+                self.world.msg.write("No more targets.")
+                self.aim = None
+                break
+        
+    def act_operate(self):
+        x, y = self.pos;
+        if self.world.map[x][y] == MAP_ELEV:
+            self.world.new_level()
+            self.disp.world_draw(self.world)
+
+    def act(self):
+        x,y = self.pos
+        running = 0
+        done = 0
+#        self.target = None
+        
+                
+        if self.target and (not self.world.flags[self.target[0]][self.target[1]] & FLAG_CREATURE or
+                not self.world.flags[self.target[0]][self.target[1]] & FLAG_VISIBLE or
+                self.target==self.pos):
+            self.target=None
+        if self.target==None:
+            for creat in self.world.creatures:
+                if self!=creat and self.world.flags[creat.pos[0]][creat.pos[1]] & FLAG_VISIBLE and not creat.burning:
+                    if not self.target or distance(self.pos, creat.pos) < distance(self.pos, self.target):
+                        self.target = creat.pos
+        self.disp.light_draw(self.light)
+        self.disp.draw_stats(self)
+        while not done:
+            done = 1
+            cmd = None
+            for creat in self.world.creatures:
+                self.disp.effect_draw(creat)
+                creat.effect = None
+            self.disp.msg_draw(self.world.msg)
+            self.disp.select(None)
+            self.disp.refresh()
+            cmd, param = self.disp.get_command(animate=1)
+            for creat in self.world.creatures:
+                self.disp.creat_draw(creat)
+            self.disp.select(self.target)
+            while not cmd:
+                self.disp.msg_draw(self.world.msg)
+                self.disp.refresh()
+                cmd, param = self.disp.get_command()
+            try:
+                if cmd == 'quit':
+                    sys.exit()
+                elif cmd == 'refresh':
+                    done = 0
+                elif cmd == 'wait':
+                    self.world.msg.write("Yawn...")
+                elif cmd == 'loot':
+                    self.act_loot()
+                elif cmd == 'use':
+                    self.act_use(param)
+                elif cmd == 'shoot':
+                    self.act_shoot()
+                elif cmd == 'trash':
+                    self.act_trash(param)
+                elif cmd == 'aim':
+                    self.act_aim()
+                    done = 0
+                elif cmd == 'operate':
+                    self.act_operate()
+                elif cmd == 'run' and param==None:
+                    done = 0
+                    running = 1
+                    self.world.msg.write("Where to run?")
+                elif cmd=='go':
+                  if running:
+                    running = 0
+                    self.act_run(param[0], param[1])
+                  else:
+                      self.act_go(param[0], param[1])
+            except ActError:
+                done = 0
+            self.disp.scroll_map(self.pos, self.world)
+            x,y = self.pos
+            self.light.cast(x, y)
+        self.world.msg.clear()
+
+class Inventory:
+    def __init__(self, world):
+        self.max_inv = 21
+        self.inv = []
+        self.world = world
+        self.charges = []
+        self.eqp = [0, 1, None]
+        for i in range(self.max_inv):
+            self.inv.append(0)
+            self.charges.append(0)
+        self.crate_maxprob = 0
+        for item in items:
+            try:
+                self.crate_maxprob += item["crate_prob"]
+            except KeyError:
+                pass
+        self.corpse_maxprob = 0
+        for item in items:
+            try:
+                self.corpse_maxprob += item["corpse_prob"]
+            except KeyError:
+                pass
+
+
+    def add_item(self, item):
+        for i in range(self.max_inv):
+            if self.inv[i]==0:
+                break
+        if self.inv[i]:
+            self.world.msg.write("No more room.")
+            raise ActError
+        self.inv[i] = item
+        try:
+            self.charges[i] = items[self.inv[i]]['charges']
+        except KeyError:
+            pass
+    
+    def del_item(self, item):
+        self.inv[item] = 0
+        self.charges[item] = 0
+        for i in range(len(self.eqp)):
+            if self.eqp[i]==item:
+                self.eqp[i] = None
+
+    def useup_item(self, slot):
+        if self.charges[slot]>0:
+            self.charges[slot] -= 1
+            if self.charges[slot]<=0:
+                self.world.msg.write("Empty.")
+                self.del_item(slot)
+        elif items[self.inv[slot]]["kind"]!=KIND_WEAPON:
+            self.del_item(slot)
+            
+    def search_crate(self):
+        total = 0
+        spot = random.randrange(self.crate_maxprob)
+        for i in range(len(items)):
+            try:
+                total += items[i]['crate_prob']
+            except KeyError:
+                pass
+            if total>spot:
+                self.add_item(i)
+                return i
+        return 0
+
+    def search_corpse(self):
+        total = 0
+        spot = random.randrange(self.corpse_maxprob)
+        for i in range(len(items)):
+            try:
+                total += items[i]['corpse_prob']
+            except KeyError:
+                pass
+            if total>spot:
+                self.add_item(i)
+                return i
+        return 0
+
+    def emit_sound(self, slot):
+#        try:
+#            self.world.msg.write(items[self.inv[slot]]["sound"])
+#        except KeyError:
+            pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data.py	Sun Dec 16 14:22:03 2007 +0100
@@ -0,0 +1,178 @@
+# --- World data -------------------
+
+EFF_HIT = 0
+EFF_BURN = 1
+EFF_MISS = 2
+
+def d(**kw):
+    return kw
+# The order must match the order in the tiles.png image file.
+tiles = (
+    d(name="floor", shadow=1, change=4, blood="stain"),
+    d(name='stain', change=0, blood="blood"),
+    d(name='blood', change=1, blood="flesh"),
+    d(name='flesh', change=2),
+    d(name='corpse', item=1, change=3),
+    d(name='rubble', change=0, blood="stain"),
+    d(name='wall', block=1, opaque=1, change=7, blood="bloody wall"),
+    d(name='crack', block=1, opaque=1, change=5, blood="bloody wall"),
+    d(name='bloody wall', block=1, opaque=1, change=6),
+    d(name='cart', item=1, change=0),
+    d(name='machine', block=1, opaque=1, change=11, lethal=20, blood="bloody machine"),
+    d(name='bloody machine', block=1, opaque=1, change=10, lethal=20),
+    d(name='down', operate=1),
+    d(name='exit', block=1, opaque=1),
+    d(name='up'),
+)
+
+# XXX Obsolete
+MAP_FLOOR =     0
+MAP_WALL =      6
+MAP_CORPSE =    4
+MAP_BLOOD =     1
+MAP_CRATE =     9
+MAP_ELEV =      12
+MAP_MACHINE =   10
+MAP_EXIT =   14
+MAP_DOOR =  12
+
+FLAG_EXPLORED = 1
+FLAG_VISIBLE =  2
+FLAG_CREATURE = 4
+FLAG_ITEM =     8
+FLAG_BLOCKED =  16
+FLAG_OPAQUE =   32
+FLAG_LETHAL =   64
+FLAG_NONE =     0
+
+KIND_WEAPON =   0
+KIND_GUN =      1
+
+KIND_AMMO =     2
+KIND_FOOD =     3
+KIND_TRASH =    4
+
+items = (
+    {   "name":     '---',
+        "kind":     KIND_TRASH,
+        "corpse_prob":     8,
+    },
+    {   "name":     'umbrella',
+        "kind":     KIND_WEAPON,
+        "crate_prob":     2,
+        "corpse_prob":    6,
+        "accuracy": 70,
+        "sound":    "Whack",
+        "blood":    20,
+        "lethal":    30,
+    },
+    {   "name":     'axe',
+        "kind":     KIND_WEAPON,
+        "crate_prob":     8,
+        "accuracy": 85,
+        "sound":    "Hack",
+        "push":     1,
+        "blood":    60,
+        "lethal":    50,
+    },
+    {   "name":     'blowtorch',
+        "kind":     KIND_WEAPON,
+        "charges":  3,
+        "crate_prob":     4,
+        "accuracy": 75,
+        "lethal":    0,
+        "burn":     1,
+        "sound":    "Sslsh",
+    },
+    {   "name":     'chainsaw',
+        "kind":     KIND_WEAPON,
+        "crate_prob":     2,
+        "charges":  6,
+        "sound":    "Wrrrooaaam!",
+        "noisy":    1,
+        "push":     1,
+        "blood":    200,
+        "lethal":    100,
+    },
+    {   "name":     'revolver',
+        "kind":     KIND_GUN,
+        "corpse_prob":     6,
+        "charges":  6,
+        "accuracy": 95,
+        "sound":    "Bang!",
+        "noisy":    1,
+        "lethal":    60,
+        "blood":    40,
+    },
+    {   "name":     'nail gun',
+        "kind":     KIND_GUN,
+        "crate_prob":     8,
+        "charges":  24,
+        "accuracy": 70,
+        "sound":    "Zing!",
+        "blood":    20,
+        "lethal":    40,
+    },
+    {   "name":     'shotgun',
+        "kind":     KIND_GUN,
+        "corpse_prob":     4,
+        "charges":  8,
+        "sound":    "Blam!",
+        "push":     1,
+        "noisy":    1,
+        "lethal":    70,
+        "blood":    80,
+    },
+    {   "name":     'molotov',
+        "kind":     KIND_GUN,
+        "corpse_prob":    15,
+        "accuracy":  90,
+        "lethal":    10,
+        "burn":     1,
+        "sound":    "Swoosh!",
+    },
+    {   "name":     'chocolates',
+        "kind":     KIND_FOOD,
+        "corpse_prob":  12,
+        "charges":  6,
+        "food":     10,
+        "sound":    "Yummy, my favorite.",
+    },
+    {   "name":     'ration',
+        "kind":     KIND_FOOD,
+        "corpse_prob":     12,
+        "food":     40,
+        "sound":    "Munch, munch!",
+    },
+    {   "name":     'med pack',
+        "kind":     KIND_FOOD,
+        "crate_prob":     4,
+        "corpse_prob":     2,
+        "heal":     3,
+        "sound":    "Much beter!",
+    },
+    {   "name":     'wound spray',
+        "kind":     KIND_FOOD,
+        "corpse_prob":     5,
+        "heal":     1,
+        "charges":  3,
+        "sound":    "Psshh!",
+    },
+    {   "name":     'can of spam',
+        "kind":     KIND_FOOD,
+        "corpse_prob":     8,
+        "food":     60,
+        "sound":    "Lovely spam.",
+    },
+    {   "name":     'teddy bear',
+        "kind":     KIND_WEAPON,
+        "lethal":    0,
+        "corpse_prob":     4,
+        "push":     1,
+        "sound":    "Hug me!",
+    },
+)
+        
+
+# --- End of world data ------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/display.py	Sun Dec 16 14:22:03 2007 +0100
@@ -0,0 +1,210 @@
+#!/usr/bin/python
+import pygame
+from pygame.locals import *
+import sys
+
+from prefs import *
+from data import *
+
+
+class DisplayPygame:
+    def __init__(self):
+        pygame.init()
+        self.screen = pygame.display.set_mode((800, 600))
+        pygame.key.set_repeat(500, 30)
+        self.font = pygame.font.SysFont("DejaVu Sans Mono, Bitstream Vera Sans Mono, Lucida Console, Lucida Typewriter, monospace", 16, bold=True, italic=False)
+        self.maxmapx = 39
+        self.maxmapy = 35
+        self.msgh = 32
+        self.xtile = 16
+        self.ytile = 16
+        self.dirty =[]
+        self.empty = pygame.Surface((self.xtile,self.ytile))
+        self.empty.fill((0x0f, 0x0f, 0x0f))
+        self.fog = pygame.Surface((self.xtile,self.ytile))
+        self.fog.fill((0x44, 0x00, 0x44))
+        self.fog.set_alpha(90, RLEACCEL)
+        self.cursor = pygame.Surface((self.xtile,self.ytile))
+        self.cursor.fill((0x00, 0x66, 0x00))
+        self.cursor.set_alpha(90, RLEACCEL)
+        self.screen.fill((0x0f, 0x0f, 0x0f))
+        pygame.display.update()
+       
+        self.creatures = self._read_frames("creatures.png")
+        self.effects = self._read_frames("effects.png")
+        self.tiles = self._read_frames("tiles.png")
+        self.cursor = self.effects[1][0]
+ 
+    def _read_frames(self, filename, size=16):
+        '''Read an image and split it into tiles.'''
+
+        image = pygame.image.load(filename).convert()
+        w, h = image.get_size()
+        frames = []
+        for x in range(0, w/size):
+            frames.append([])
+            for y in range(0, h/size):
+                frames[x].append(image.subsurface((x*size, y*size, size, size)))
+        return frames
+
+
+    def pause(self):
+        cmd = None
+        self.refresh()
+        while cmd!="more":
+            cmd, parm = self.get_command()
+
+    def clear(self):
+        self.screen.fill((0x0f, 0x0f, 0x0f))
+
+    def _blit_map(self, x, y, img, clear=1):
+        ax, ay = x - self.xmap, y - self.ymap
+        if (ax<0) or (ax>=self.maxmapx) or (ay<0) or (ay>=self.maxmapy):
+            return
+        r = Rect(self.xtile*ax, self.msgh+self.ytile*ay, self.xtile, self.ytile)
+        if clear:
+            self.screen.fill((0x00, 0x00, 0x00), r)
+        self.screen.blit(img, r)
+        self.dirty.append(r)
+
+    def tile_draw(self, pos, world):
+        x, y = pos
+        if (x-self.xmap<0) or (x-self.xmap>=self.maxmapx) or (y-self.ymap<0) or (y-self.ymap>=self.maxmapy):
+            return
+        flags = world.flags[x][y]
+        maptile = world.map[x][y]
+        tile = tiles[maptile]
+        if flags & FLAG_VISIBLE or flags & FLAG_EXPLORED:
+            try:
+                img = self.tiles[maptile][0]
+            except IndexError:
+                img = self.empty
+            self.font.render("%d"%maptile, True, (0,0,0)).blit(img, (0,0,16,16))
+            self._blit_map(x, y, img)
+            if not (flags & FLAG_VISIBLE):
+                self._blit_map(x, y, self.fog, clear=0)
+        else:
+            self._blit_map(x, y, self.empty, clear = 0)
+
+    def world_draw(self, world):
+        r =Rect(0, self.msgh, self.xtile*self.maxmapx, self.ytile*self.maxmapy)
+        self.dirty.append(r)
+        self.screen.fill((0x00, 0x00, 0x00), Rect(0, self.msgh, self.xtile*self.maxmapx, self.ytile*self.maxmapy))
+        for x in range(world.width):
+            for y in range(world.height):
+                self.tile_draw((x, y), world)
+
+    def inv_draw(self, inv):
+        r =Rect(self.xtile*self.maxmapx, self.msgh, 800-self.xtile*self.maxmapx, self.ytile*self.maxmapy)
+        self.screen.fill((0x00, 0x00, 0x00), r)
+        for i in range(inv.max_inv):
+            if i in inv.eqp:
+                color = (0x00, 0xff, 0x00)
+            else:
+                color = (0x00, 0x66, 0x00)
+            if inv.charges[i]>0:
+                img = self.font.render("%s %s %d" % (chr(ord('a')+i), items[inv.inv[i]]['name'], inv.charges[i]), True, color)
+            else:
+                img = self.font.render("%s %s" % (chr(ord('a')+i), items[inv.inv[i]]['name']), True, color)
+            self.screen.blit(img, (self.xtile*self.maxmapx, self.msgh+i*self.ytile))
+        self.dirty.append(r)
+
+    def draw_stats(self, hero):
+        r = Rect(self.xtile*self.maxmapx, 0, 800-self.xtile*self.maxmapx, self.msgh)
+        self.screen.fill((0x00, 0x00, 0x00),r)
+        wounds = ''
+        for i in range(hero.wounds):
+            wounds += '*'
+        img = self.font.render("S:%3d W:%s" % (hero.stamina, wounds), True, (0xff, 0x22, 0x22), (0x00, 0x00, 0x00))
+        self.screen.blit(img, r)
+        self.dirty.append(r)
+
+    def refresh(self):
+        pygame.display.update(self.dirty)
+        self.dirty = []
+
+    def msg_draw(self, msg):
+        self.screen.fill((0x00, 0x00, 0x00), (0,0,self.xtile*self.maxmapx , self.msgh))
+        text = ""
+        for m in msg.last:
+            text += m + " "
+        img = self.font.render(text, True, (0x00, 0x66, 0x00))
+        self.screen.blit(img, (0, 0))
+        text = ""
+        for m in msg.mesgs:
+            text += m + " "
+        img = self.font.render(text, True, (0x00, 0xff, 0x00))
+        self.screen.blit(img, (0, 16))
+        self.dirty.append(Rect(0,0, 800, self.msgh))
+
+    def light_draw(self, light):
+        for pos in light.last:
+            if not pos in light.lit:
+                self.tile_draw(pos, light.world)
+        for pos in light.lit:
+            self.tile_draw(pos, light.world)
+            
+    def creat_draw(self, creat):
+        x, y = creat.pos
+        if x-self.xmap<0 or x-self.xmap>=self.maxmapx or y-self.ymap<0 or y-self.ymap>=self.maxmapy:
+            return
+        if not creat.world.flags[x][y] & FLAG_VISIBLE:
+            return
+        if creat.hero:
+            img = self.creatures[0][0]
+        elif creat.burning:
+            img = self.creatures[2][0]
+        else:
+            img = self.creatures[1][0]
+        self._blit_map(x, y, img)
+
+    def centermap(self, pos):
+        x, y = pos
+        self.xmap = - (self.maxmapx / 2 - x)
+        self.ymap = - (self.maxmapy / 2 - y)
+
+    def scroll_map(self, pos, world):
+        x, y = pos
+        maxy, maxx = self.maxmapy, self.maxmapx
+        if x-self.xmap<margin:
+            self.xmap-=margin
+            self.world_draw(world)
+        elif y-self.ymap<margin:
+            self.ymap-=margin
+            self.world_draw(world)
+        elif x-self.xmap>self.maxmapx-margin:
+            self.xmap+=margin
+            self.world_draw(world)
+        elif y-self.ymap>self.maxmapy-margin:
+            self.ymap+=margin
+            self.world_draw(world)
+
+    def select(self, pos):
+        if not pos:
+            return
+        x, y = pos
+        self._blit_map(x, y, self.cursor, False)
+
+    def get_command(self, animate=0):
+        shift = 0
+        if animate:
+            return (None, None)
+        while 1:
+            event=pygame.event.wait()
+            if event.type == QUIT:
+                sys.exit()
+            elif event.type == KEYDOWN:
+                name = pygame.key.name(event.key)
+                if len(name)==1:
+                    mods = pygame.key.get_mods()
+                    if mods & KMOD_LSHIFT or mods & KMOD_RSHIFT:
+                        name = chr(ord(name)-32) # XXX A hack for uppercase :)
+                try:
+                    cmd, parm = command_keys[name]
+                    return (cmd, parm)
+                except KeyError:
+                    #self.msg.write("What does %s mean?" % name)
+                    return (None, None)
+
+    def effect_draw(self, creat):
+        pass
Binary file effects.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light.py	Sun Dec 16 14:22:03 2007 +0100
@@ -0,0 +1,86 @@
+import math
+from data import *
+from util import *
+
+class Light:
+    maxrays = 255
+    maxradius = 12
+    ray_max = []
+    ray_min = []
+
+
+    def __init__(self, world):
+        self.world = world
+        self.lit = []
+        self.last = []
+        self.radius = 12
+        self.visible = set()
+        self.new = set()
+        self.explored = set()
+        if not self.ray_max:
+            for y in range(self.maxradius):
+                line_max = []
+                line_min = []
+                for x in range(self.maxradius):
+                    line_max.append(0)
+                    line_min.append(self.maxrays)
+                self.ray_max.append(line_max)
+                self.ray_min.append(line_min)
+            for ray in range(self.maxrays):
+                an = (math.pi*ray)/(self.maxrays*2)
+                s = math.sin(an)/4
+                c = math.cos(an)/4
+                d =0
+                x =0
+                y =0
+                while 1:
+                    x = int(math.floor(s*d))
+                    y = int(math.floor(c*d))
+                    d += 1
+                    if x<self.radius and y<self.radius:
+                        self.ray_min[x][y] = min(self.ray_min[x][y], ray)
+                        self.ray_max[x][y] = max(self.ray_max[x][y], ray)
+                    else:
+                        break
+
+    def _quart(self, xpos, ypos, dx, dy):
+        rays = []
+        ox = xpos
+        for i in range(self.maxrays):
+            rays.append(0)
+        corner = self.radius
+        for y in range(self.radius):
+            if (2*y>=self.radius):
+                corner -= 1
+            for x in range(corner):
+                opaque = self.world.is_opaque((xpos,ypos))
+                shadow = 0
+                rayweight = self.maxrays / (self.ray_max[x][y] - self.ray_min[x][y])
+                for ray in range(self.ray_min[x][y], self.ray_max[x][y]):
+                    if rays[ray]:
+                        shadow += rayweight
+                if 3*shadow<2*self.maxrays:
+                    self.lit.append((xpos, ypos))
+                    self.world.flags[xpos][ypos] |= FLAG_VISIBLE
+                if opaque:
+                    for ray in range(self.ray_min[x][y], self.ray_max[x][y]):
+                        rays[ray] = 1
+                xpos += dx
+                if xpos<0 or xpos>=self.world.width:
+                    break
+            ypos += dy
+            if ypos<0 or ypos>=self.world.width:
+                break
+            xpos = ox
+                
+    def cast(self, xpos, ypos):
+        self.last = self.lit
+        r = self.radius
+        for (x,y) in self.last:
+            self.world.flags[x][y] &= ~FLAG_VISIBLE
+            self.world.flags[x][y] |= FLAG_EXPLORED
+        self.lit = [(xpos,ypos)]
+        self._quart(xpos, ypos, 1, 1)
+        self._quart(xpos, ypos, -1, 1)
+        self._quart(xpos, ypos, 1, -1)
+        self._quart(xpos, ypos, -1, -1)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/prefs.py	Sun Dec 16 14:22:03 2007 +0100
@@ -0,0 +1,98 @@
+# --- User preferences data --------
+margin = 9
+command_keys = {
+    "KEY_UP":      ("go", ( 0,-1)),
+    "KEY_DOWN":    ("go", ( 0, 1)),
+    "KEY_LEFT":    ("go", (-1, 0)),
+    "KEY_RIGHT":   ("go", ( 1, 0)),
+    "KEY_A1":      ("go", (-1,-1)),
+    "KEY_A3":      ("go", ( 1,-1)),
+    "KEY_C1":      ("go", (-1, 1)),
+    "KEY_C3":      ("go", ( 1, 1)),
+    "KEY_HOME":      ("go", (-1,-1)),
+    "KEY_FIND":      ("go", (-1,-1)),
+    "KEY_PPAGE":      ("go", ( 1,-1)),
+    "KEY_END":      ("go", (-1, 1)),
+    "KEY_SELECT":      ("go", (-1, 1)),
+    "KEY_NPAGE":      ("go", ( 1, 1)),
+
+    '8':           ("go", ( 0,-1)),
+    '2':           ("go", ( 0, 1)),
+    '4':           ("go", (-1, 0)),
+    '6':           ("go", ( 1, 0)),
+    '7':           ("go", (-1,-1)),
+    '9':           ("go", ( 1,-1)),
+    '1':           ("go", (-1, 1)),
+    '3':           ("go", ( 1, 1)),
+
+    'a':           ("use", 0),
+    'b':           ("use", 1),
+    'c':           ("use", 2),
+    'd':           ("use", 3),
+    'e':           ("use", 4),
+    'f':           ("use", 5),
+    'g':           ("use", 6),
+    'h':           ("use", 7),
+    'i':           ("use", 8),
+    'j':           ("use", 9),
+    'k':           ("use", 10),
+    'l':           ("use", 11),
+    'm':           ("use", 12),
+    'n':           ("use", 13),
+    'o':           ("use", 14),
+    'p':           ("use", 15),
+    'q':           ("use", 16),
+    'r':           ("use", 17),
+    's':           ("use", 18),
+    't':           ("use", 19),
+    'u':           ("use", 20),
+
+    'A':           ("trash", 0),
+    'B':           ("trash", 1),
+    'C':           ("trash", 2),
+    'D':           ("trash", 3),
+    'E':           ("trash", 4),
+    'F':           ("trash", 5),
+    'G':           ("trash", 6),
+    'H':           ("trash", 7),
+    'I':           ("trash", 8),
+    'J':           ("trash", 9),
+    'K':           ("trash", 10),
+    'L':           ("trash", 11),
+    'M':           ("trash", 12),
+    'N':           ("trash", 13),
+    'O':           ("trash", 14),
+    'P':           ("trash", 15),
+    'Q':           ("trash", 16),
+    'R':           ("trash", 17),
+    'S':           ("trash", 18),
+    'T':           ("trash", 19),
+    'U':           ("trash", 20),
+
+    "KEY_B2":      ("wait", None),
+    '5':           ("wait", None),
+
+    'v':           ("run", None),
+    'x':           ("loot", None),
+    'z':           ("shoot", None),
+    'Z':           ("aim", None),
+    'X':           ("operate", None),
+    
+    '!':           ("quit", None),
+    'KEY_ESC':     ("quit", None),
+    ' ':           ("more", None),
+
+# for SDL:
+    'escape':      ("quit", None),
+    "[8]":         ("go", ( 0,-1)),
+    "[2]":         ("go", ( 0, 1)),
+    "[4]":         ("go", (-1, 0)),
+    "[6]":         ("go", ( 1, 0)),
+    "[7]":         ("go", (-1,-1)),
+    "[9]":         ("go", ( 1,-1)),
+    "[1]":         ("go", (-1, 1)),
+    "[3]":         ("go", ( 1, 1)),
+    '[5]':         ("wait", None),
+    'space':       ("more", None),
+}
+# --- End of user preferences data -
Binary file revolver.png has changed
Binary file tiles.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util.py	Sun Dec 16 14:22:03 2007 +0100
@@ -0,0 +1,5 @@
+def distance(a, b):
+    x1, y1 = a
+    x2, y2 = b
+    return (max(abs(x2-x1), abs(y2-y1)) + abs(x2-x1) + abs(y2-y1))/2
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/world.py	Sun Dec 16 14:22:03 2007 +0100
@@ -0,0 +1,295 @@
+import time, random
+from data import *
+from creatures import Zombie
+from util import *
+
+class TileError:
+    """Exception rised when a named tile is not found."""
+    pass
+
+class Mesg:
+    """Keep all the messages to be displayed."""
+
+    def __init__(self):
+        self.mesgs = []
+        self.last = []
+        self.history = []
+
+    def clear(self):
+        self.last = self.mesgs
+        self.history += self.mesgs
+        self.mesgs = []
+
+    def write(self, msg):
+        self.mesgs.append(msg)
+
+    def output(self):
+        """Output the messages to stdout, for debugging."""
+
+        for msg in self.last:
+            print msg
+        for msg in self.mesgs:
+            print msg
+
+class World:
+    """Hold all the information about the current state of
+    the game, the map and the creatures."""
+
+    def __init__(self):
+        self.width, self.height = 64, 64
+        self.hero = None
+        self.msg = Mesg()
+        self.level = 0
+        # The basic tile names
+        self.wall = self.named_tile("wall")
+        self.floor = self.named_tile("floor")
+        self.rubble = self.named_tile("rubble")
+        self.cart = self.named_tile("cart")
+        self.corpse = self.named_tile("corpse")
+        self.machine = self.named_tile("machine")
+        # Generate the first level
+        self.new_level()
+
+    def new_level(self):
+        """Discard the old map and create a new one."""
+
+        self.level += 1
+        self.map = []
+        self.flags = []
+        for x in xrange(self.width):
+            line_map = []
+            line_flags = []
+            for y in xrange(self.height):
+                line_map.append(self.floor)
+                line_flags.append(FLAG_NONE)
+            self.map.append(line_map)
+            self.flags.append(line_flags)
+        if self.level<5:
+            self.generate()
+            self.decorate()
+            self.erode()
+        else:
+            self.final()
+        if self.hero:
+            self.creatures = [self.hero]
+        else:
+            self.creatures = []
+        self.populate()
+
+    def find_creature(self, pos):
+        """Return the creature that stands on the specified
+        position, or None if the position is unoccupied."""
+
+        for creat in self.creatures:
+            if creat.pos == pos:
+                return creat
+        return None
+
+    def named_tile(self, name):
+        """Returns the number of the tile that has specified name.
+           If no tile with this name exists, throws an exception."""
+        for tile, i in zip(tiles, xrange(100)):
+            if tile["name"]==name:
+                return i
+        raise TileError
+                
+    def add_tile(self, pos, tile):
+        """Puts the specified tile in the specified position
+        of the map."""
+        
+        x,y = pos
+        self.map[x][y] = tile
+        info = tiles[tile]
+        if 'block' in info:
+            self.flags[x][y] |= FLAG_BLOCKED
+        else:
+            self.flags[x][y] &= ~FLAG_BLOCKED
+        if 'opaque' in info:
+            self.flags[x][y] |= FLAG_OPAQUE
+        else:
+            self.flags[x][y] &= ~FLAG_OPAQUE
+        if 'item' in info:
+            self.flags[x][y] |= FLAG_ITEM
+        else:
+            self.flags[x][y] &= ~FLAG_ITEM
+        if 'lethal' in info:
+            self.flags[x][y] |= FLAG_LETHAL
+        else:
+            self.flags[x][y] &= ~FLAG_LETHAL
+        
+    def add_rect(self, topleft, bottomright, tile):
+        """Draws a rectange with specified tile kind."""
+
+        x1, y1 = topleft
+        x2, y2 = bottomright
+        for y in xrange(y1, y2):
+            self.add_tile((x1, y), tile)
+            self.add_tile((x2, y), tile)
+        for x in xrange(x1, x2):
+            self.add_tile((x, y1), tile)
+            self.add_tile((x, y2), tile)
+
+    def fill_rect(self, topleft, bottomright, tile):
+        """Draws a fileld rectangle on the map."""
+
+        x1, y1 = topleft
+        x2, y2 = bottomright
+        for y in xrange(y1, y2):
+            for x in xrange(x1, x2):
+                self.add_tile((x, y), tile)
+
+    def decorate(self):
+        """Scatters various "decorations" around the map."""
+    
+        for i in xrange(random.randrange(5, 16)):
+            x = random.randrange(1, self.width-2)
+            y = random.randrange(1, self.width-2)
+            self.add_tile((x, y), self.corpse)
+        for i in xrange(random.randrange(25, 40)):
+            x = random.randrange(1, self.width-2)
+            y = random.randrange(1, self.width-2)
+            self.add_tile((x, y), self.cart)
+        for i in xrange(5):
+            while 1: # XXX this assumes there's always at least one floor tile!
+                x = random.randrange(1, self.width-2)
+                y = random.randrange(1, self.width-2)
+                if self.map[x][y] == self.floor:
+                    self.add_tile((x, y), self.named_tile("down"))
+                    break
+
+
+    def erode(self):
+        """Changes some tiles of the map into a more "destroyed" kind."""
+
+        for i in xrange(random.randrange(350, 700)):
+            x = random.randrange(1, self.width-2)
+            y = random.randrange(1, self.width-2)
+            self.change((x, y))
+
+    def populate(self):
+        """Fills the map with zombies and the hero."""
+
+        if self.hero:
+            x, y = self.hero.pos
+            self.add_tile((x, y), self.named_tile("up"))
+            self.flags[x][y] |= FLAG_CREATURE
+        for i in xrange(random.randrange(50+5*self.level, 90+20*self.level)):
+            Zombie(self)
+
+
+    def put_doors(self, pos1, pos2, tile):
+        """Puts specified tile in a one, random wall of the 
+            specified rectangle."""
+
+        x1,y1= pos1
+        x2,y2=pos2
+        if random.choice((0,1)):
+            if x2-x1>2:
+                x = random.randrange(x1+1, x2-1)
+                if random.choice((0,1)):
+                    y = y1
+                else:
+                    y = y2
+                self.add_tile((x, y), tile)
+        else:
+            if y2-y1>2:
+                y = random.randrange(y1+1, y2-1)
+                if random.choice((0,1)):
+                    x = x1
+                else:
+                    x = x2
+                self.add_tile((x, y), tile)
+
+    def final(self):
+        """Create the final level."""
+
+        for i in range(10):
+            x1 = random.randrange(1, self.width-2)
+            y1 = random.randrange(1, self.height-2)
+            x2 = min(random.randrange(x1+1, self.width-1), x1+10)
+            y2 = min(random.randrange(y1+1, self.height-1), y1+10)
+            self.add_rect((x1+1,y1+1), (x2-1,y2-1), self.floor)
+            self.add_rect((x1,y1), (x2,y2), self.wall)
+            self.add_rect((x1-1,y1-1), (x2+1,y2+1), self.floor)
+            for j in range(random.randrange(4, 8)):
+                self.put_doors((x1,y1),(x2,y2), self.floor)
+        for x in range(self.width):
+            self.add_tile((x, 0), self.wall)
+            self.add_tile((x, self.height-1), self.wall)
+        for y in range(self.width):
+            self.add_tile((0, y), self.wall)
+            self.add_tile((self.width-1, y), self.wall)
+        for j in range(random.randrange(8, 15)):
+            self.put_doors((0,0),(self.width,self.height), self.named_tile("exit"))
+        
+    def generate(self):
+        """Create a new level."""
+
+        for i in range(130):
+            x1 = random.randrange(1, self.width-2)
+            y1 = random.randrange(1, self.height-2)
+            x2 = min(random.randrange(x1+1, self.width-1), x1+10)
+            y2 = min(random.randrange(y1+1, self.height-1), y1+10)
+            self.add_rect((x1+1,y1+1), (x2-1,y2-1), self.floor)
+            self.add_rect((x1,y1), (x2,y2), self.wall)
+            self.add_rect((x1-1,y1-1), (x2+1,y2+1), self.floor)
+            for j in range(random.randrange(2, 4)):
+                self.put_doors((x1,y1),(x2,y2), self.floor)
+            for j in range(random.randrange(4)):
+                self.put_doors((x1,y1),(x2,y2), self.rubble)
+            for j in range(random.randrange(4)):
+                self.put_doors((x1,y1),(x2,y2), self.machine)
+        for i in range(20):
+            x1 = random.randrange(1, self.width-2)
+            y1 = random.randrange(1, self.height-2)
+            x2 = min(random.randrange(x1+1, self.width-1), x1+5)
+            y2 = min(random.randrange(y1+1, self.height-1), y1+5)
+            self.fill_rect((x1,y1), (x2,y2), self.floor)
+        for x in range(self.width):
+            self.add_tile((x, 0), self.wall)
+            self.add_tile((x, self.height-1), self.wall)
+        for y in range(self.width):
+            self.add_tile((0, y), self.wall)
+            self.add_tile((self.width-1, y), self.wall)
+
+    def change(self, pos):
+        """Changes the tile at specified position into a more
+           destroyed version."""
+
+        x, y = pos
+        changed = tiles[self.map[x][y]].get("change", None)
+        if changed:
+            self.add_tile(pos, changed)
+
+    def blood(self, pos, amount=1):
+        """Changes the tile at specified position into a more
+           bloody version. Optional amount adds more blood."""
+
+        x, y = pos
+        t = self.map[x][y]
+        if t==self.wall and amount<3:
+            return
+        for i in xrange(amount):
+            changed = tiles[t].get("blood", None)
+            if changed:
+                self.add_tile(pos, self.named_tile(changed))
+
+    def noise(self):
+        """Make all the creature close to the hero aware of him."""
+
+        for creat in self.creatures:
+            if distance(creat.pos, self.hero.pos)<10:
+                if not creat.target:
+                    creat.target = self.hero.pos
+
+    def is_opaque(self, pos):
+        """Return true if the tile at the specified position blocks light."""
+            
+        x, y = pos
+        return bool(tiles[self.map[x][y]].get("opaque", False))
+
+    def is_blocked(self, pos):
+        """Return true if the tile at the specified postion blocks movement."""
+
+        x,y = pos
+        return bool(tiles[self.map[x][y]].get("block", False))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zday.py	Sun Dec 16 14:22:03 2007 +0100
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+
+#------------------------------------------
+#   @@@@@@@@    @@@@@@@    @@@@   @@@  @@@ 
+#   YYYYYYY     YYYYYYYY YYY  YYY YYY YYY  
+#      TTT  TT  TT   TTT TT   TTT  TTTT    
+#    !!         !!   !!  !!!!!!!   !!      
+#  ::::::       :::::    :    :    :       
+#------------------------------------------
+
+# Licence:
+#
+# You're permitted do do anything with this code, provided
+# you contact the author and get his permission.
+# You can contact the author at sheep@sheep.prv.pl
+#
+# Copyright 2005 by Radomir 'The Sheep' Dopieralski.
+
+from display import DisplayPygame
+from world import World
+from creatures import Hero
+
+def main():
+    disp = DisplayPygame()
+    world = World()
+    world.hero = Hero(world, disp)
+    disp.world_draw(world)
+    while 1:
+        for creat in world.creatures:
+            creat.metabolism()
+            if not creat.dead:
+                creat.act()
+
+main()