#!/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()

