changeset 0:0de4cbe2e6a1 default tip

initial commit
author Radomir Dopieralski <sheep-devel@sheep.art.pl>
date Sat, 13 Oct 2012 20:54:24 +0200
parents
children
files zday.py
diffstat 1 files changed, 1878 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zday.py	Sat Oct 13 20:54:24 2012 +0200
@@ -0,0 +1,1878 @@
+#!/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.
+
+import sys, time, random, math
+
+# --- 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 -
+
+# --- World data -------------------
+
+C_GRAY  =   0
+C_WHITE =   1
+C_BLACK =   2
+C_LIME =    3
+C_GREEN =   4
+C_YELLOW =  5
+C_BROWN =   6
+C_LRED =    7
+C_RED   =   8
+C_AQUA =    9
+C_CYAN  =   10
+C_MAGENTA = 11
+
+effects = (
+    {
+        "char":     '*',
+        "color":    C_LRED,
+    }, {
+        "char":     '+',
+        "color":    C_WHITE,
+    }, {
+        "char":     'x',
+        "color":    C_YELLOW,
+    },
+)
+
+EFF_HIT = 0
+EFF_BURN = 1
+EFF_MISS = 2
+
+tiles = (
+    {
+        "name":     'floor',
+        "char":     '.',
+        "color":    C_GRAY,
+        "shadow":   1,
+        "change":   4,
+    }, {
+        "name":     'wall',
+        "char":     '#',
+        "color":    C_GRAY,
+        "block":    1,
+        "opaque":   1,
+        "change":   5,
+    }, {
+        "name":     'corpse',
+        "char":     '%',
+        "color":    C_LRED,
+        "item":     1,
+        "change":   3,
+        "highlight":1,
+    }, {
+        "name":     'pile of flesh',
+        "char":     '+',
+        "color":    C_RED,
+    }, {
+        "name":     'pile of rubble',
+        "char":     ':',
+        "color":    C_BLACK,
+        "change":   0,
+    }, {
+        "name":     'ruined wall',
+        "char":     '#',
+        "color":    C_BLACK,
+        "block":    1,
+        "opaque":   1,
+        "change":   4,
+    }, {
+        "name":     'blood pool',
+        "char":     ':',
+        "color":    C_RED,
+        "change":   0,
+    }, {
+        "name":     'blood stain',
+        "char":     '.',
+        "color":    C_RED,
+        "change":   0,
+    }, {
+        "name":     'bloody wall',
+        "char":     '#',
+        "color":    C_LRED,
+        "block":    1,
+        "opaque":   1,
+        "change":   5,
+    }, {
+        "name":     'crate',
+        "char":     '&',
+        "color":    C_YELLOW,
+        "item":     1,
+        "change":   4,
+        "highlight":1,
+    }, {
+        "name":     'escalator down',
+        "char":     '>',
+        "color":    C_AQUA,
+        "operate":   1,
+        "highlight":1,
+    }, {
+        "name":     'escalator up',
+        "char":     '<',
+        "color":    C_CYAN,
+        "operate":   1,
+    }, {
+        "name":     'closed door',
+        "char":     '+',
+        "color":    C_BROWN,
+        "block":    1,
+        "opaque":   1,
+        "change":   13,
+        "operate":   1,
+    }, {
+        "name":     'open door',
+        "char":     "'",
+        "color":    C_BROWN,
+        "change":   12,
+        "operate":   1,
+    }, {
+        "name":     'machinery',
+        "char":     "$",
+        "color":    C_CYAN,
+        "block":    1,
+        "opaque":   1,
+        "change":   5,
+        "highlight":1,
+        "lethal":   20,
+    }, {
+        "name":     'bloody machinery',
+        "char":     "$",
+        "color":    C_LRED,
+        "block":    1,
+        "opaque":   1,
+        "change":   5,
+        "highlight":1,
+        "lethal":   20,
+    }, {
+        "name":     'exit',
+        "char":     "X",
+        "color":    C_LIME,
+        "block":    1,
+        "opaque":   1,
+        "highlight":1,
+    },
+)
+
+hero_char = '@'
+hero_color = C_WHITE
+zombie_char = 'Z'
+zombie_color = C_GREEN
+burning_char = '*'
+burning_color = C_YELLOW
+
+MAP_FLOOR =     0
+MAP_WALL =      1
+MAP_CORPSE =    2
+MAP_BLOOD =     6
+MAP_CRATE =     9
+MAP_ELEV =      10
+MAP_DOOR =      12
+MAP_MACHINE =   14
+MAP_EXIT =   16
+
+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 ------------
+
+
+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
+
+
+def game_win(disp):
+    disp.clear()
+    disp.pause()
+    sys.exit()
+    
+def game_over(disp):
+    disp.clear()
+    disp.pause()
+    sys.exit()
+
+
+class Mesg:
+    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):
+        for msg in self.last:
+            print msg
+        for msg in self.mesgs:
+            print msg
+
+class Light:
+    maxrays = 255
+    maxradius = 12
+    ray_max = []
+    ray_min = []
+    def __init__(self, world):
+        self.world = world
+        self.lit = []
+        self.last = []
+        self.radius = 12
+        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.flags[xpos][ypos] & FLAG_OPAQUE
+                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)
+
+class World:
+    def __init__(self):
+        self.width, self.height = 64, 64
+        self.hero = None
+        self.level = 0
+        self.new_level()
+
+    def new_level(self):
+        self.level+=1
+        self.map = []
+        self.flags = []
+        for x in range(self.width):
+            line_map = []
+            line_flags = []
+            for y in range(self.height):
+                line_map.append(MAP_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):
+        for creat in self.creatures:
+            if creat.pos == pos:
+                return creat
+        return None
+
+    def add_tile(self, pos, tile):
+        x,y = pos
+        if x<0 or y<0 or x>=self.width or y>=self.height:
+            return
+        self.map[x][y] = tile
+        try:
+            tmp = tiles[tile]['block']
+            self.flags[x][y] |= FLAG_BLOCKED
+        except KeyError:
+            self.flags[x][y] &= ~FLAG_BLOCKED
+        try:
+            tmp = tiles[tile]['opaque']
+            self.flags[x][y] |= FLAG_OPAQUE
+        except KeyError:
+            self.flags[x][y] &= ~FLAG_OPAQUE
+        try:
+            tmp = tiles[tile]['item']
+            self.flags[x][y] |= FLAG_ITEM
+        except KeyError:
+            self.flags[x][y] &= ~FLAG_ITEM
+        try:
+            tmp = tiles[tile]['lethal']
+            self.flags[x][y] |= FLAG_LETHAL
+        except KeyError:
+            self.flags[x][y] &= ~FLAG_LETHAL
+        
+    def add_rect(self, topleft, bottomright, tile):
+        x1, y1 = topleft
+        x2, y2 = bottomright
+        for y in range(y1, y2):
+            self.add_tile((x1, y), tile)
+            self.add_tile((x2, y), tile)
+        for x in range(x1, x2):
+            self.add_tile((x, y1), tile)
+            self.add_tile((x, y2), tile)
+
+    def fill_rect(self, topleft, bottomright, tile):
+        x1, y1 = topleft
+        x2, y2 = bottomright
+        for y in range(y1, y2):
+            for x in range(x1, x2):
+                self.add_tile((x, y), tile)
+
+    def decorate(self):
+        for i in range(random.randrange(5, 20)):
+            x = random.randrange(1, self.width-2)
+            y = random.randrange(1, self.width-2)
+            self.add_tile((x, y), MAP_CORPSE)
+        for i in range(random.randrange(25, 40)):
+            x = random.randrange(1, self.width-2)
+            y = random.randrange(1, self.width-2)
+            self.add_tile((x, y), MAP_CRATE)
+        for i in range(5):
+            while 1:
+                x = random.randrange(1, self.width-2)
+                y = random.randrange(1, self.width-2)
+                if self.map[x][y] == MAP_FLOOR:
+                    self.add_tile((x, y), MAP_ELEV)
+                    break
+
+
+    def erode(self):
+        for i in range(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):
+        if self.hero:
+            x,y = self.hero.pos
+            self.add_tile((x, y), MAP_ELEV + 1)
+            self.flags[x][y] |= FLAG_CREATURE
+        for i in range(random.randrange(50+5*self.level, 90+20*self.level)):
+            Zombie(self)
+
+
+    def put_doors(self, pos1, pos2, tile):
+        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):
+        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), MAP_FLOOR)
+            self.add_rect((x1,y1), (x2,y2), MAP_WALL)
+            self.add_rect((x1-1,y1-1), (x2+1,y2+1), MAP_FLOOR)
+            for j in range(random.randrange(4, 8)):
+                self.put_doors((x1,y1),(x2,y2), MAP_FLOOR)
+        for x in range(self.width):
+            self.add_tile((x, 0), MAP_WALL)
+            self.add_tile((x, self.height-1), MAP_WALL)
+        for y in range(self.width):
+            self.add_tile((0, y), MAP_WALL)
+            self.add_tile((self.width-1, y), MAP_WALL)
+        for j in range(random.randrange(8, 15)):
+            self.put_doors((0,0),(self.width,self.height), MAP_EXIT)
+        
+    def generate(self):
+        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), MAP_FLOOR)
+            self.add_rect((x1,y1), (x2,y2), MAP_WALL)
+            self.add_rect((x1-1,y1-1), (x2+1,y2+1), MAP_FLOOR)
+            for j in range(random.randrange(2, 4)):
+                self.put_doors((x1,y1),(x2,y2), MAP_FLOOR)
+            for j in range(random.randrange(4)):
+                self.put_doors((x1,y1),(x2,y2), MAP_FLOOR+1)
+            for j in range(random.randrange(4)):
+                self.put_doors((x1,y1),(x2,y2), MAP_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), MAP_FLOOR)
+        for x in range(self.width):
+            self.add_tile((x, 0), MAP_WALL)
+            self.add_tile((x, self.height-1), MAP_WALL)
+        for y in range(self.width):
+            self.add_tile((0, y), MAP_WALL)
+            self.add_tile((self.width-1, y), MAP_WALL)
+
+    def change(self, pos):
+        x, y = pos
+        try:
+            changed = tiles[self.map[x][y]]["change"]
+        except KeyError:
+            return
+        self.add_tile(pos, changed)
+
+    def noise(self):
+        for creat in self.creatures:
+            if distance(creat.pos, self.hero.pos)<10:
+                if not creat.target:
+                    creat.target = self.hero.pos
+        
+
+class Display:
+    def __init__(self):
+        pass
+
+    def clear(self):
+        pass
+
+    def msg_draw(self, msg):
+        pass
+            
+    def creat_draw(self, creat):
+        pass
+
+    def effect_draw(self, creat):
+        pass
+
+    def tile_draw(self, pos, world):
+        pass
+            
+    def light_draw(self, light):
+        pass
+            
+    def inv_draw(self, inv):
+        pass
+
+    def draw_stats(self, hero):
+        pass
+
+    def world_draw(self, world):
+        pass
+
+    def select(self, pos):
+        pass
+
+    def centermap(self, pos):
+        pass
+
+    def refresh(self):
+        pass
+
+    def scroll_map(self, pos, world):
+        pass
+    
+    def get_command(self, animate=0):
+        return (None, None)
+
+    def pause(self):
+        cmd = None
+        msg.write("More...")
+        self.msg_draw(msg)
+        self.refresh()
+        while cmd!="more":
+            cmd, parm = self.get_command()
+        
+class DisplayPygame(Display):
+    def __init__(self):
+        self.screen = pygame.display.set_mode((800, 600))
+        pygame.key.set_repeat(500, 30)
+        self.font = pygame.font.Font(None, 16)
+        self.maxmapx = 39
+        self.maxmapy = 35
+        self.msgh = 32
+        self.xtile = 16
+        self.ytile = 16
+        self.dirty =[]
+        self.colors = (
+            (0x66, 0x66, 0x66),
+            (0xff, 0xff, 0xff),
+            (0x44, 0x44, 0x44),
+            (0x00, 0xff, 0x00),
+            (0x00, 0x66, 0x00),
+            (0xff, 0xff, 0x00),
+            (0x66, 0x44, 0x22),
+            (0xff, 0x00, 0x00),
+            (0x66, 0x00, 0x00),
+            (0x00, 0xff, 0xff),
+            (0x00, 0x66, 0x66)
+        )
+        self.empty = pygame.Surface((self.xtile,self.ytile))
+        self.empty.fill((0x00, 0x00, 0x00))
+        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()
+        
+        for tile in tiles:
+            tile["image"] = self.font.render(tile["char"], 1, self.colors[tile["color"]], (0x00, 0x00, 0x00))
+
+    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]
+        tile = tiles[world.map[x][y]]
+        if flags & FLAG_VISIBLE or flags & FLAG_EXPLORED:
+            img = tile["image"]
+            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]), 1, color)
+            else:
+                img = self.font.render("%s %s" % (chr(ord('a')+i), items[inv.inv[i]]['name']), 1, 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), 1, (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, 1, (0x00, 0x66, 0x00))
+        self.screen.blit(img, (0, 0))
+        text = ""
+        for m in msg.mesgs:
+            text += m + " "
+        img = self.font.render(text, 1, (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
+        img = self.font.render(creat.char, 1, self.colors[creat.color], (0x00, 0x00, 0x00))
+        self._blit_map(x, y, img)
+
+    def centermap(self, pos):
+        print 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 pos!=None:
+            x,y = pos
+            self._blit_map(x, y, self.cursor, 0)
+
+    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:
+                    msg.write("What does %s mean?" % name)
+                    return (None, None)
+
+class DisplayCurses(Display):
+    def __init__(self, stdscr):
+        maxy, maxx = stdscr.getmaxyx()
+        self.msg = curses.newwin(1, maxx-16, 0, 0)
+        self.inv = curses.newwin(maxy-1, 16, 1, maxx-16)
+        self.stat = curses.newwin(1, 16, 0, maxx-16)
+        self.map = curses.newwin(maxy-1, maxx-16, 1, 0)
+        self.map.keypad(1)
+        self.map.immedok(0)
+        self.map.leaveok(0)
+        self.inv.immedok(0)
+        self.stat.immedok(0)
+        self.msg.immedok(0)
+        self.stat.scrollok(0)
+        self.msg.scrollok(1)
+        self.xmap = 0
+        self.ymap = 0
+        self.maxmapy, self.maxmapx = self.map.getmaxyx()
+        curses.meta(1)
+        if curses.has_colors():
+            curses.start_color()
+            curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_BLACK)
+            curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
+            curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK)
+            curses.init_pair(4, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
+            curses.init_pair(5, curses.COLOR_RED, curses.COLOR_BLACK)
+            curses.init_pair(6, curses.COLOR_CYAN, curses.COLOR_BLACK)
+            self.colors = [
+                curses.color_pair(0),
+                curses.color_pair(0) | curses.A_BOLD,
+                curses.color_pair(1) | curses.A_BOLD,
+                curses.color_pair(2) | curses.A_BOLD,
+                curses.color_pair(2),
+                curses.color_pair(3) | curses.A_BOLD,
+                curses.color_pair(3),
+                curses.color_pair(5) | curses.A_BOLD,
+                curses.color_pair(5),
+                curses.color_pair(6) | curses.A_BOLD,
+                curses.color_pair(6),
+                curses.color_pair(4),
+            ]
+        else:
+            self.colors = [
+                curses.A_NORMAL,
+                curses.A_NORMAL,
+                curses.A_NORMAL,
+                curses.A_NORMAL,
+                curses.A_NORMAL,
+                curses.A_NORMAL,
+                curses.A_NORMAL,
+                curses.A_NORMAL,
+                curses.A_NORMAL,
+                curses.A_NORMAL,
+                curses.A_NORMAL,
+                curses.A_NORMAL,
+            ]
+
+    def msg_draw(self, msg):
+        self.msg.clear()
+        for m in msg.last:
+            self.msg.addstr(m, self.colors[C_GREEN])
+            self.msg.addstr(" ")
+        for m in msg.mesgs:
+            self.msg.addstr(m, self.colors[C_LIME])
+            self.msg.addstr(" ")
+            
+    def _get_color(self, col):
+        if not self.colors:
+            return 0
+        return self.colors[col]
+
+    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
+        c = ord(creat.char) | self._get_color(creat.color)
+        try:
+            self.map.addch(y-self.ymap, x-self.xmap, c)
+        except:
+            pass
+
+    def inv_draw(self, inv):
+        self.inv.clear()
+        for i in range(inv.max_inv):
+            if i in inv.eqp:
+                color = curses.A_BOLD | self.colors[C_LIME]
+            else:
+                color = curses.A_NORMAL | self.colors[C_GREEN]
+            if inv.charges[i]>0:
+                self.inv.addstr("%s %s %d\n" % (curses.unctrl(ord('a')+i), items[inv.inv[i]]['name'], inv.charges[i]), color)
+            else:
+                self.inv.addstr("%s %s\n" % (curses.unctrl(ord('a')+i), items[inv.inv[i]]['name']), color)
+
+    def effect_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.effect!=None:
+            c = ord(effects[creat.effect]["char"]) | self._get_color(effects[creat.effect]["color"])
+        else:
+            c = ord(creat.char) | self._get_color(creat.color)
+        try:
+            self.map.addch(y-self.ymap, x-self.xmap, c)
+        except:
+            pass
+
+    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]
+        tile = world.map[x][y]
+        c = ' '
+        if flags & FLAG_VISIBLE:
+            c = ord(tiles[tile]["char"])
+            try:
+                s = tiles[tile]["shadow"]
+                if world.flags[x-1][y-1] & FLAG_OPAQUE:
+                    c |= self.colors[C_BLACK]
+            except KeyError:
+                c |= self._get_color(tiles[tile]["color"])
+        elif flags & FLAG_EXPLORED:
+            c = ord(tiles[tile]["char"]) | self.colors[C_MAGENTA]
+            try:
+                if tiles[tile]["highlight"]:
+                    c |= curses.A_BOLD
+            except KeyError:
+                pass
+            try:
+                s = tiles[tile]["shadow"]
+                if not world.flags[x-1][y-1] & FLAG_OPAQUE:
+                    c |= curses.A_BOLD
+            except KeyError:
+                pass
+        try:
+            self.map.addch(y-self.ymap, x-self.xmap, c)
+        except:
+            pass
+            
+    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 draw_stats(self, hero):
+        self.stat.clear()
+        wounds = ''
+        for i in range(hero.wounds):
+            wounds += '*'
+        self.stat.addstr(0, 0, "S:%3d W:%s" % (hero.stamina, wounds), self.colors[C_LRED] | curses.A_BOLD)
+
+    def select(self, pos):
+        if pos==None:
+            self.map.move(0, 0)
+            try:
+                curses.curs_set(0)
+            except:
+                pass
+        else:
+            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:
+                self.map.move(0, 0)
+                try:
+                    curses.curs_set(0)
+                except:
+                    pass
+            else:
+                try:
+                    curses.curs_set(1)
+                except:
+                    pass
+                self.map.move(y-self.ymap, x-self.xmap)
+        
+    def centermap(self, pos):
+        x, y = pos
+        sy, sx = self.map.getmaxyx()
+        self.xmap = -sx/2+x
+        self.ymap = -sy/2+y
+
+    def scroll_map(self, pos, world):
+        x, y = pos
+        maxy, maxx = self.map.getmaxyx()
+        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 clear(self):
+        self.msg.clear()
+        self.map.clear()
+        self.inv.clear()
+        self.stat.clear()
+
+    def refresh(self):
+        self.msg.noutrefresh()
+        self.inv.noutrefresh()
+        self.stat.noutrefresh()
+        self.map.noutrefresh()
+        curses.doupdate()
+
+    def world_draw(self, world):
+        self.map.clear()
+        for x in range(world.width):
+            for y in range(world.height):
+                self.tile_draw((x, y), world)
+    
+    def get_command(self, animate=0):
+        if animate:
+            self.map.timeout(300)
+        else:
+            self.map.timeout(-1)
+        try:
+            key = self.map.getkey()
+        except:
+            return (None, None)
+        if key=="-1":
+            return (None, None)
+        try:
+            return command_keys[key]
+        except KeyError:
+            msg.write("What does %s mean?" % key)
+            return (None, None)
+
+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
+        self.char = zombie_char
+        self.color = zombie_color
+        world.creatures.append(self)
+        self.world.flags[self.pos[0]][self.pos[1]] |= FLAG_CREATURE
+        self.burning = 0
+        self.dead = 0
+        self.tripped = 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
+        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, MAP_CORPSE)
+                else:
+                    self.world.add_tile(self.pos, MAP_CORPSE+1)
+        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):
+        for dy in (-1, 0, 1):
+            for dx in (-1, 0, 1):
+                x = self.pos[0] + dx
+                y = self.pos[1] + dy
+                if self.world.map[x][y]==MAP_FLOOR:
+                    if random.randrange(100)>110-amount:
+                        self.world.add_tile((x,y), MAP_BLOOD)
+                    elif random.randrange(100)>80-amount:
+                        self.world.add_tile((x,y), MAP_BLOOD+1)
+                elif self.world.map[x][y]==MAP_WALL:
+                    if random.randrange(100)>130-amount:
+                        self.world.add_tile((x,y), MAP_BLOOD+2)
+
+    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):
+        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):
+        self.burning = 1
+        self.char = burning_char
+        self.color = burning_color
+
+    def pushback(self):
+        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:
+                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.char = hero_char
+        self.color = hero_color
+        self.light = Light(world)
+        self.light.cast(self.pos[0], self.pos[1])
+        self.inventory = Inventory()
+        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
+
+    def kill(self):
+        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:
+            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:
+            msg.write("Ouch!")
+            self.bleed(20)
+            self.wound()
+
+    def burn(self):
+        self.stamina -= random.randrange(10, 40)
+        msg.write("Burns!")
+        if self.stamina<0:
+            self.stamina = 0
+
+    def act_shoot(self):
+        if not self.target:
+            msg.write("Nobody here.")
+            raise ActError
+        gun_slot = self.inventory.eqp[KIND_GUN]
+        if gun_slot==None:
+            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:
+                msg.write("Spak!")
+                creat.effect = EFF_MISS
+            self.inventory.useup_item(gun_slot)
+            self.disp.inv_draw(self.inventory)
+        else:
+            msg.write("Sneaky bastard!")
+            raise ActError
+
+    def act_use(self, slot):
+        if self.inventory.inv[slot]==0:
+            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:
+            msg.write("Useless.")
+            raise ActError
+
+    def act_trash(self, slot):
+        if self.inventory.inv[slot]==0:
+            msg.write("Gone already.")
+            raise ActError
+        self.inventory.del_item(slot)
+        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:
+            msg.write("There's nothing useful here.")
+            raise ActError
+        if self.world.map[x][y] == MAP_CRATE:
+            msg.write("Gently opening the crate...")
+            item = self.inventory.search_crate()
+        elif self.world.map[x][y] == MAP_CORPSE:
+            msg.write("Searching the corpse....")
+            item = self.inventory.search_corpse()
+        self.world.change(self.pos)
+        if item:
+            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:
+            msg.write("Finally made it to safety!")
+            game_win(self.disp)
+        if self.world.flags[nx][ny] & FLAG_LETHAL:
+            msg.write("Careful! Those gears can shred you into pieces.")
+            raise ActError
+        if self.world.flags[nx][ny] & FLAG_BLOCKED:
+            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:
+                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:
+            msg.write('Pant, pant...')
+
+    def act_aim(self):
+        if self.aim==None:
+            self.aim = 0
+        start = self.aim
+        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:
+                msg.write("Here.")
+                self.target = (x,y)
+                break
+            if self.aim == start:
+                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(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(msg)
+                self.disp.refresh()
+                cmd, param = self.disp.get_command()
+            try:
+                if cmd == 'quit':
+                    sys.exit()
+                elif cmd == 'refresh':
+                    done = 0
+                elif cmd == 'wait':
+                    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
+                    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)
+        msg.clear()
+
+class Inventory:
+    def __init__(self):
+        self.max_inv = 21
+        self.inv = []
+        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]:
+            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:
+                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:
+            msg.write(items[self.inv[slot]]["sound"])
+        except KeyError:
+            pass
+                
+
+class GameError(Exception):
+    pass
+
+class ActError(Exception):
+    pass
+
+    
+def main(stdscr=None):
+    if stdscr:
+        maxy, maxx = stdscr.getmaxyx()
+        if maxy<24 or maxx<80:
+            raise GameError, "Screen too small, you need at last 80x24 characters."
+        disp = DisplayCurses(stdscr)
+    else:
+        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()
+
+msg = Mesg()
+try:
+#    raise ImportError
+    import curses
+    curses.wrapper(main)
+except ImportError:
+    try:
+        import pygame
+        from pygame.locals import *
+        pygame.init()
+        main()
+    except ImportError:
+        raise GameError, "You need either PyGame or PyCurses."
+msg.output()