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