#!/usr/bin/env python # -*- coding: iso-8859-15 -*- # # Mines of Elderlore # An Ascii roguelike with : # * Permanent levels # * Simple and easy gameplay # * High scores that you can compare with others # http://landsof.elderlore.com # # Released under the GNU General Public License # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ Mines of Elderlore """ import random import time import Numeric as N import curses import ConfigParser import pickle import os ############################################################################### # Constants ############################################################################### CURSES_DIRS = {curses.KEY_UP : (0, -1), # North curses.KEY_DOWN : (0, 1), # South curses.KEY_LEFT : (-1, 0), # West curses.KEY_RIGHT : (1, 0), # East ord(' ') : (0, 0), # to wait curses.KEY_B2 : (0, 0)} """ curses.KEY_A1 : (-1, -1), # North West curses.KEY_A3 : (1, -1), # North East curses.KEY_C1 : (-1, 1), # South West curses.KEY_C3 : (1, 1), # South East """ # Multipliers for transforming coordinates to other octants: MULT = [[1, 0, 0, -1, -1, 0, 0, 1], [0, 1, -1, 0, 0, -1, 1, 0], [0, 1, 1, 0, 0, -1, -1, 0], [1, 0, 0, 1, -1, 0, 0, -1]] # DIRS = ((-1,0), (-1,-1), (0,-1), (1,-1), (1,0), (1,1), (0,1), (-1,1)) DIRS = [(-1,0), (0,-1), (1,0), (0,1)] SPECIAL_NONE = 0 SPECIAL_DOOR = 1 SPECIAL_UPSTAIRS = 2 SPECIAL_DOWNSTAIRS1 = 3 SPECIAL_DOWNSTAIRS2 = 4 SPECIAL_MUSHROOM = 5 SPECIAL_POTION = 6 DUNGEON_FLOOR = 0 DUNGEON_WALL = 1 WEAPON_SWORD = 10 WEAPON_AXE = 20 WEAPON_SPEAR = 30 WEAPON_WARHAMMER = 40 WEAPON_NONE = -1 WEAPON_FLAVOR0 = 0 WEAPON_FLAVOR1 = 1 WEAPON_FLAVOR2 = 2 WEAPON_FLAVOR3 = 3 WEAPON_FLAVOR4 = 4 WEAPON_FLAVOR5 = 5 WEAPON_NAME = ("sword", "axe", "spear", "warhammer") WEAPON_TILE = ("/", "P", "|", "T") WEAPON_FLAVOR = ("training", "iron", "steel", "hardenned steel", "crystal", "vorpal") ############################################################################### # Weapon class ############################################################################### class Weapon: def __init__(self, type, flavor): self.type = type self.flavor = flavor def tile(self): return WEAPON_TILE[self.tile] def damage(self, l): l1 = self.flavor l2 = l + self.flavor return 1 + random.randint(l1, l2) + random.randint(l1, l2) + random.randint(l1, l2) ############################################################################### # Monster class ############################################################################### class Monster: ASLEEP = 0 AWAKEN = 1 def __init__(self, pos, level): self.pos = pos self.level = level if 1 < self.level < 10: while random.randint(1, 5) == 1: self.level +=1 self.level = min(self.level, 9) self.hp = 5 * level self.state = Monster.ASLEEP def is_awake(self): return self.state == Monster.AWAKEN def awake(self): """ Change state to aggressive """ self.state = min(self.state + 1, Monster.AWAKEN) def tile(self): """ Monster tile on two characters """ if self.is_awake(): if self.level < 10: return "M%s" % self.level else: return "ME" else: return "M~" def move(self, pos): """ Moves to a new pos """ self.pos = pos def upgrade(self): """ The monster gains a level """ self.level = min(self.level+1, 9) self.hp = 5 * self.level def damage(self): """ How much damage the monster inflicts """ l = self.level return random.randint(1, l) + random.randint(1, l) + random.randint(1, l) def stun(self, n): """ Stun the monster """ self.state = -n ############################################################################### # Dungeon class ############################################################################### class Dungeon: def __init__(self, size = (32, 32), ratio = 60, name = None, level = 1): """ Initialization of a dungeon floor size : (width, height) ratio : ratio of rooms compared to the full surface (in %) from 10 to 60 name : random seed in a string """ self.size = size self.name = name self.level = level # Set the seed if name == "random" or name is None: random.seed(time.time()) else: random.seed(name) # Creating the floor array # 0: the floor # 1: the walls self.floor = N.ones(size) * DUNGEON_WALL # rooms array self.rooms = N.ones(size) # number of cells rooms occupy self.surf_rooms = 0 # doors, stairs, ... array self.special = N.ones(size) * SPECIAL_NONE # Dead end list self.list_de = [] # Monsters dictionary self.monster = {} self.corridor_v(False) r = self.size[0] * self.size[1] * ratio / 100 n = 0 nmax = r * 10 while n < r: n += 1 self.corridor_h() self.corridor_v() if self.surf_rooms < r: self.room() # Addition of doors to rooms self.special *= (1 - self.floor) * SPECIAL_DOOR # Addition of stairs and treasures to dead ends self.dead_end() # Back to a true random seed random.seed(time.time()) def corridor_h(self, test = True): """ A horizontal corridor """ l = random.randint(2, 6) * 2 + 1 x = (random.randint(2, self.size[0] - l - 2) / 2) * 2 + 1 y = (random.randint(6, self.size[1] - 6) / 2) * 2 + 1 if l / 5 > N.sum(N.sum(1 - self.floor[x:x+l, y])) > 0 or not test: self.floor[x:x+l, y] = DUNGEON_FLOOR self.list_de.append((x, y)) self.list_de.append((x+l-1, y)) return True return False def corridor_v(self, test = True): """ A vertical corridor """ l = random.randint(2, 6) * 2 + 1 x = (random.randint(6, self.size[0] - 6) / 2) * 2 + 1 y = (random.randint(2, self.size[1] - l - 2) / 2) * 2 + 1 if l / 5 > N.sum(N.sum(1 - self.floor[x, y:y+l])) > 0 or not test: self.floor[x, y:y+l] = DUNGEON_FLOOR self.list_de.append((x, y)) self.list_de.append((x, y+l-1)) return True return False def room(self, pos = None, test = True): """ A room """ lx = random.randint(2, 5) * 2 + 1 ly = random.randint(2, 5) * 2 + 1 if pos is None: x = (random.randint(0, self.size[0] - lx - 2) / 2) * 2 + 1 y = (random.randint(0, self.size[1] - ly - 2) / 2) * 2 + 1 else: x, y = pos if (N.sum(N.sum(1 - self.floor[x:x+lx, y:y+ly])) > 0 \ and N.sum(N.sum(1 - self.rooms[x:x+lx, y:y+ly])) == 0) \ or not test: # The room is above one corridor or more # but not above another room self.surf_rooms += lx * ly self.floor[x:x+lx, y:y+ly] = DUNGEON_FLOOR self.rooms[x:x+lx, y:y+ly] = 0 self.special[x-1:x+lx+1, y-1:y+ly+1] = SPECIAL_DOOR self.special[x:x+lx, y:y+ly] = SPECIAL_NONE n = random.randint(1, 5 + self.level) posn = (x+random.randint(1, lx-1), y+random.randint(1, ly-1)) if n == 1: self.add_special(posn, SPECIAL_POTION) elif n < 4: self.add_special(posn, SPECIAL_MUSHROOM) else: self.add_monster(posn, self.level) return True return False def add_special(self, pos, type): """ Add something special to pos """ if self.special[pos] == SPECIAL_NONE: self.special[pos] = type def has_special(self, pos, type): """ True is there is a special type at pos, False either """ return self.special[pos] == type def grab_special(self, pos, type): """ Gets the special type at pos """ if self.has_special(pos, type): self.special[pos] = SPECIAL_NONE return 1 return 0 def add_monster(self, pos, level): """ Add a monster at pos """ if self.is_reachable(pos): self.monster[pos] = Monster(pos, level) def has_monster(self, pos): """ True is there is a monster at pos, False either """ return self.monster.has_key(pos) def bash_monster(self, pos, n): """ Remove n points to monster health points """ if self.has_monster(pos): if self.monster[pos].hp <= n: xp = self.monster[pos].level self.monster.pop(pos) return xp else: self.monster[pos].hp -= n return 0 def dead_end(self): """ Dealing dead ends """ nb_sp = 1 for i in range(len(self.list_de)): x, y = self.list_de[i] if N.sum(N.sum(self.floor[x-1:x+2, y-1:y+2])) <> 7: # Add one potion every 3 monster if i % 3 == 0: self.add_special((x, y), SPECIAL_POTION) else: self.add_monster((x, y), self.level) else: # It is a real dead end nb_sp += 1 if nb_sp < 5: if nb_sp not in (SPECIAL_DOWNSTAIRS1, SPECIAL_DOWNSTAIRS2) or self.level < 9: # Down stairs only before level 9 self.special[x, y] = nb_sp # Record starting position if nb_sp == 2: self.pos_start = self.list_de[i] else: if nb_sp % 2 == 1: # Add a mushroom self.special[x, y] = 5 else: # Add a weapon if self.level == 9: weap_type = WEAPON_SWORD weap_flav = WEAPON_FLAVOR5 else: weap_type = random.choice((WEAPON_SWORD, WEAPON_AXE, WEAPON_SPEAR, WEAPON_WARHAMMER)) weap_flav = min(random.randint(0, self.level-1) + random.randint(0, self.level-1), WEAPON_FLAVOR4) self.special[x, y] = weap_type + weap_flav self.add_monster((x-1, y), self.level + 1) self.add_monster((x+1, y), self.level + 1) self.add_monster((x, y-1), self.level + 1) self.add_monster((x, y+1), self.level + 1) if nb_sp == 1: # No dead ends found # We add stairs manually self.special[self.list_de[0]] = SPECIAL_UPSTAIRS # Record starting position self.pos_start = self.list_de[0] if self.level < 9: self.special[self.list_de[1]] = SPECIAL_DOWNSTAIRS1 elif nb_sp == 2: #print "Bottom reached !" pass def is_reachable(self, pos): """ Return True is pos can be accessed False either """ if self.has_special(pos, SPECIAL_DOOR): return False else: return self.floor[pos[0], pos[1]] == DUNGEON_FLOOR and \ not self.monster.has_key(pos) ############################################################################### # Player class ############################################################################### class Player: def __init__(self, size = (32, 32), dungeon_name = "Moria", scores = {}): self.fov = max(size[0], size[1]) self.dname = dungeon_name self.dsize = size self.dname_full = "%s (%s, %s)" % (dungeon_name, size[0], size[1]) # Dictionary of visited floors self.dict_dungeon = {} self.xp = 0 self.level = 1 self.hp, self.hpmax = 10, 10 self.deepness = 1 self.mushroom = 0 # List of mushroom pos seen by the player self.mush_seen = [] self.potion = 1 # List of owned weapons self.weapon_flavor = [WEAPON_FLAVOR0, WEAPON_NONE, WEAPON_NONE, WEAPON_NONE] self.active_weapon = 0 # Flag for axe self.hit_by_monster = False # List of starting pos in levels self.start_pos = [(0, 0)] # Loading the first floor self.load_dungeon_floor(self.start_pos[-1], False) self.pos = self.dungeon.pos_start self.known[self.pos] = 1 # Messages of text self.message = ["", "", ""] # Flag to test the end of the program # 0 : in game # 1 : killed # 2 : exited the mines self.exit = 0 self.line = " " * curses.tigetnum('cols') # A dictionary where key is dungeon name, and value is a tuple # each tuple contains all scores for one dungeon self.scores = scores def change_active_weapon(self, type): """ change the active weapon of the player """ n = type / 10 - 1 if self.weapon_flavor[n] <> WEAPON_NONE: if self.active_weapon == n: self.add_message("You already hold your %s %s." % (WEAPON_FLAVOR[self.weapon_flavor[n]], WEAPON_NAME[n])) else: self.active_weapon = n self.add_message("You take your %s %s." % (WEAPON_FLAVOR[self.weapon_flavor[n]], WEAPON_NAME[n])) else: self.add_message("You have no %s in your equipment." % WEAPON_NAME[n]) def inventory(self): """ List all the player belongings """ mess = "You have a" for n in range(len(self.weapon_flavor)): if self.weapon_flavor[n] <> WEAPON_NONE: if WEAPON_FLAVOR[n][0] in "aeiou": mess = "%sn" % mess mess = "%s %s %s, a" % (mess, WEAPON_FLAVOR[self.weapon_flavor[n]], WEAPON_NAME[n]) self.add_message("%s." % mess[:-3]) def dist(self, pos1, pos2): """ calculate the distance between pos1 and pos2 """ dx = abs(pos1[0] - pos2[0]) dy = abs(pos1[1] - pos2[1]) return dx + dy def blocked(self, pos): """ True is (x, y) cannot be accessed, False either """ if self.dsize[0] >= pos[0] >= 0 and self.dsize[1] >= pos[1] >= 0: return self.dungeon.floor[pos] == DUNGEON_WALL \ or self.dungeon.has_special(pos, SPECIAL_DOOR) return False def lit(self, pos): """ True if cell at pos is lit, False either """ return self.seen[pos] == 1 def set_lit(self, pos): """ Light the cell at pos """ if 0 <= pos[0] <= self.dsize[0] and 0 <= pos[1] <= self.dsize[1]: self.seen[pos] = 1 self.known[pos] = 1 def _cast_light(self, cx, cy, row, start, end, radius, xx, xy, yx, yy, id): """ Recursive lightcasting function """ if start < end: return radius_squared = radius*radius for j in range(row, radius+1): dx, dy = -j-1, -j blocked = False while dx <= 0: dx += 1 # Translate the dx, dy coordinates into map coordinates: X, Y = cx + dx * xx + dy * xy, cy + dx * yx + dy * yy # l_slope and r_slope store the slopes of the left and right # extremities of the square we're considering: l_slope, r_slope = (dx-0.5)/(dy+0.5), (dx+0.5)/(dy-0.5) if start < r_slope: continue elif end > l_slope: break else: # Our light beam is touching this square; light it: if dx*dx + dy*dy < radius_squared: self.set_lit((X, Y)) if blocked: # we're scanning a row of blocked squares: if self.blocked((X, Y)): new_start = r_slope continue else: blocked = False start = new_start else: if self.blocked((X, Y)) and j < radius: # This is a blocking square, start a child scan: blocked = True self._cast_light(cx, cy, j+1, start, l_slope, radius, xx, xy, yx, yy, id+1) new_start = r_slope # Row is scanned; do next row unless last square was blocked: if blocked: break def do_fov(self, radius): """ Calculate lit squares from the given location and radius """ self.seen = N.zeros(self.dsize) x, y = self.pos for oct in range(8): self._cast_light(x, y, 1, 1.0, 0.0, radius, MULT[0][oct], MULT[1][oct], MULT[2][oct], MULT[3][oct], 0) def deal_damage(self, tile, n): """ The player suffers n HPs of damage """ self.hp -= n hit = random.choice(("bites", "claws", "slashes", "scratches", "chomps", \ "gnaws", "gnarls", "kicks", "tears", "rips", "stings")) if self.hp > 0: self.add_message("%s %s you for %s point(s) of damage !" % (tile, hit, n)) else: self.add_message("%s %s you for %s point(s) of damage ! Maybe next time..." % (tile, hit, n)) self.exit = 1 def add_mushroom_seen(self, pos): """ Remembers a mushroom seen by the player """ try: n = self.mush_seen.index(pos) except ValueError: self.mush_seen.append(pos) def remove_mushroom_seen(self, pos): """ Removes a mushroom from the list self.mush_seen """ try: n = self.mush_seen.index(pos) self.mush_seen.pop(n) except ValueError: pass def closest_mushroom(self, pos): """ Finds the closest mushroom to the pos of a monster """ if len(self.mush_seen) == 0: # No mushroom seen return (0, 0), 1000 else: d_mush = 1000 for pos_found in self.mush_seen: d = self.dist(pos_found, pos) if d < d_mush: d_mush = d pos_mush = pos_found return pos_mush, d_mush def move_monsters(self): """ Browse all monsters and move awaken ones """ # Temporary dictionary monsters = {} # Flag for axe fight flag_hit = False for pos, monster in self.dungeon.monster.items(): move = False if monster.is_awake(): # Choose the closest between closest mushroom and player pos_mush, d_mush = self.closest_mushroom(pos) d_player = self.dist(pos, self.pos) if d_player < d_mush: spos, d = self.pos, d_player else: spos, d = pos_mush, d_mush if d == 1 and d_player < d_mush: flag_hit = True self.deal_damage(monster.tile(), monster.damage()) if self.exit > 0: break elif d == 0 and not d_player < d_mush: self.dungeon.grab_special(pos_mush, SPECIAL_MUSHROOM) monster.upgrade() self.remove_mushroom_seen(pos_mush) self.add_message("The monster eats a mushroom and progress to level %s." % monster.level) else: # The monster moves to the target d_new = d random.shuffle(DIRS) for dir in DIRS: pos_temp = (pos[0] + dir[0], pos[1] + dir[1]) if self.dungeon.is_reachable(pos_temp) and \ not monsters.has_key(pos_temp): d_temp = self.dist(pos_temp, spos) if d_temp <= d_new: d_new, pos_new = d_temp, pos_temp move = d_new < d if move: monsters[pos_new] = monster else: monsters[pos] = monster # Hack (cannot change dict while browsing it) self.dungeon.monster = monsters if flag_hit: self.hit_by_monster = True else: self.hit_by_monster = False def tile(self): """ Player tile on the screen """ return "&%s" % WEAPON_TILE[self.active_weapon] def display(self, s): """ Display the dungeon floor on the given curses """ # Screen init w, h = curses.tigetnum('cols'), curses.tigetnum('lines') s.addstr(1, 1, '_' * (w - 2)) s.addstr(h - 5, 1, '_' * (w - 2)) s.vline(2,0, '|', h - 6) s.vline(2, w - 1, '|', h - 6) # Clear the screen for i in range(2, h - 5): s.addstr(i, 1, " " * (w - 2)) for x in range(self.dsize[0]): xx = (x - self.pos[0] + w / 4) * 2 + 1 if 0 < xx < w - 1: for y in range(self.dsize[1]): yy = y - self.pos[1] + h / 2 - 2 if 1 < yy < h - 5: ch = " " if self.known[x, y] == 1: if x == self.pos[0] and y == self.pos[1]: ch = self.tile() else: if self.lit((x, y)) and self.dungeon.monster.has_key((x, y)): ch = self.dungeon.monster[(x, y)].tile() self.dungeon.monster[(x, y)].awake() elif self.dungeon.special[x, y] >= WEAPON_SWORD: weap_flav = self.dungeon.special[x, y] % 10 weap_type = self.dungeon.special[x, y] - weap_flav ch = "%s%s" % (WEAPON_TILE[weap_type / 10 - 1], weap_flav) elif self.dungeon.has_special((x, y), SPECIAL_DOOR): ch = "++" elif self.dungeon.has_special((x, y), SPECIAL_UPSTAIRS): ch = "< " elif self.dungeon.has_special((x, y), SPECIAL_DOWNSTAIRS1) or \ self.dungeon.has_special((x, y), SPECIAL_DOWNSTAIRS2): ch = "> " elif self.dungeon.has_special((x, y), SPECIAL_MUSHROOM): ch = "* " self.add_mushroom_seen((x, y)) elif self.dungeon.has_special((x, y), SPECIAL_POTION): ch = "! " elif self.dungeon.floor[x, y] == DUNGEON_WALL: ch = "##" else: if self.lit((x, y)): ch = ". " #s.addstr(y+2, x*2+1, ch, curses.color_pair(curses.COLOR_WHITE)) s.addstr(yy, xx, ch, curses.color_pair(curses.COLOR_WHITE)) info = "Level %s | HP %s / %s | XP %s (%s for lvl %s) | Potion %s | Mushroom %s" % \ (self.deepness, self.hp, self.hpmax, self.xp, self.level * (self.level + 1) * (self.level + 2), self.level+1, self.potion, self.mushroom) s.addstr(0, 0, self.line) s.addstr(0, 0, info, curses.color_pair(curses.COLOR_WHITE)) for i in range(3): s.addstr(h + i - 4, 0, self.line) if self.message[2 - i].find("!") > 0: color = curses.color_pair(curses.COLOR_YELLOW) else: color = curses.color_pair(curses.COLOR_WHITE) s.addstr(h + i - 4, 0, self.message[2 - i], color) s.refresh() def load_dungeon_floor(self, pos, save = True): """ Loads a dungeon from its name, its level and a position """ self.mush_seen = [] if save: # We save the information of the dungeon self.dict_dungeon[self.dungeon.name] = (self.dungeon, self.known) name = "%s-%s (%s, %s)" % (self.dname, self.deepness, pos[0], pos[1]) if self.dict_dungeon.has_key(name): # This floor has already been visited self.dungeon, self.known = self.dict_dungeon[name] else: self.dungeon = Dungeon(self.dsize, 50, name, self.deepness) self.known = N.zeros(self.dsize) def add_message(self, message): """ Add a message """ self.message.insert(0, message) def progress(self, xp): """ Player gets xp """ self.xp += xp if self.xp >= self.level * (self.level + 1) * (self.level + 2): self.level += 1 self.hpmax += self.level self.hp = self.hpmax self.add_message("You gain %s XP points and progress to level %s !" % (xp, self.level)) else: self.add_message("You gain %s XP." % xp) def drink(self): """ drinks a potion """ if self.potion > 0: self.potion -= 1 self.add_message("You drink a health potion and recover all your health points !") self.hp = self.hpmax def rest(self): """ rest to recover HP by eating mushrooms """ for pos, monster in self.dungeon.monster.items(): if monster.is_awake(): self.add_message("You cannot rest while there are awaken monsters around.") return if self.hp == self.hpmax: self.add_message("You are already fully rested.") return hp_rest = min(self.mushroom, self.hpmax - self.hp) self.hp += hp_rest self.mushroom -= hp_rest if hp_rest == 0: self.add_message("You rest for a while but recover no health points. You need mushrooms !") elif self.hp == self.hpmax: self.add_message("You rest for a while and eat enough mushrooms to recover all your health points.") else: self.add_message("You rest for a while and eat enough mushrooms to recover %s health point(s)." % hp_rest) def damage(self): """ How much damage the player inflicts """ l1 = self.weapon_flavor[self.active_weapon] l2 = l1 + self.level if self.active_weapon == WEAPON_AXE: base = self.level + l1 elif l1 == WEAPON_FLAVOR5: base = self.level + 2 * l1 else: base = 1 return 1 + random.randint(l1, l2) + random.randint(l1, l2) + random.randint(l1, l2) def save_score(self): """ Save score in dictionary """ print "You scored %s." % player.xp print "---------------" if not self.scores.has_key(self.dname_full): self.scores[self.dname_full] = [] new_entry = "Score %s - %s" % (self.xp, time.strftime("%c")) self.scores[self.dname_full].append((self.xp, new_entry)) self.scores[self.dname_full].sort(lambda x, y: cmp(y[0],x[0])) print "%s ranks :" % self.dname_full print "-" * (len(self.dname_full) + 8) for i in range(len(self.scores[self.dname_full])): print "%s : %s" % (i+1, self.scores[self.dname_full][i][1]) def move(self, pos): """ Move the player to the new pos """ if pos[0] == self.pos[0] and pos[1] == self.pos[1]: player.add_message("You wait...") elif self.dungeon.has_monster(pos): if self.hit_by_monster and self.active_weapon == 1: # Axe self.add_message("You are too fuzzy to use your axe.") elif self.active_weapon == 3: # Warhammer n = self.weapon_flavor[self.active_weapon] + 1 if random.randint(0, n + self.dungeon.monster[pos].level) <= n: self.dungeon.monster[pos].stun(n) self.add_message("You stun the monster for %s rounds." % n) else: self.add_message("You try to stun the monster, but fail.") else: n = self.damage() xp = self.dungeon.bash_monster(pos, n) if xp > 0: self.add_message("You hit the monster for %s points of damage, killing him." % n) self.progress(xp) if self.active_weapon == 2: # Spear pos_next = (2 * (pos[0] - self.pos[0]) + self.pos[0], 2 * (pos[1] - self.pos[1]) + self.pos[1]) self.pos = (pos[0], pos[1]) self.move(pos_next) else: self.add_message("You hit the monster for %s points of damage." % n) elif self.dungeon.has_special(pos, SPECIAL_DOOR): self.add_message("You open the door.") self.dungeon.grab_special(pos, SPECIAL_DOOR) elif self.dungeon.is_reachable(pos): self.pos = (pos[0], pos[1]) if self.dungeon.special[pos] >= WEAPON_SWORD: weap_flav = self.dungeon.special[pos] % 10 weap_type = self.dungeon.special[pos] - weap_flav weap_num = weap_type / 10 - 1 if weap_flav > self.weapon_flavor[weap_num]: mess = "You grab a" if WEAPON_FLAVOR[weap_flav][0] in "aeiou": mess = "%sn" % mess self.add_message("%s %s %s." % (mess, WEAPON_FLAVOR[weap_flav], WEAPON_NAME[weap_num])) self.weapon_flavor[weap_num] = weap_flav self.dungeon.special[pos] = 0 else: self.add_message("You already have a better %s." % WEAPON_NAME[weap_num]) if self.dungeon.has_special(pos, SPECIAL_MUSHROOM): # mushroom self.add_message("You grab a mushroom.") self.mushroom += self.dungeon.grab_special(pos, SPECIAL_MUSHROOM) self.remove_mushroom_seen(pos) elif self.dungeon.has_special(pos, SPECIAL_POTION): # potion self.add_message("You grab a health potion.") self.potion += self.dungeon.grab_special(pos, SPECIAL_POTION) elif self.dungeon.has_special(pos, SPECIAL_DOWNSTAIRS1) or \ self.dungeon.has_special(pos, SPECIAL_DOWNSTAIRS2): # Stairs down self.add_message("You go down the stairs.") self.deepness += 1 self.load_dungeon_floor(pos) self.pos = self.dungeon.pos_start self.start_pos.append(pos) self.known[self.pos] = 1 elif self.dungeon.has_special(pos, SPECIAL_UPSTAIRS): # Stairs up if self.deepness == 1: self.add_message("You exit the mines.") self.exit = 2 else: self.add_message("You go up the stairs.") self.deepness -= 1 self.pos = self.start_pos[-1] self.start_pos.pop() self.load_dungeon_floor(self.start_pos[-1]) self.known[self.pos] = 1 else: self.add_message("You cannot go there.") ############################################################################### # Main ############################################################################### def load_config(): """ Loads moe.ini file """ conf = ConfigParser.ConfigParser() try: conf.read('moe.ini') name = conf.get("Game", "Name") dirs = conf.get("Game", "Dirs") width = conf.getint("Game", "Width") height = conf.getint("Game", "Height") return name, eval(dirs), width, height except: try: os.remove('moe.ini') except: pass conf.add_section('Game') conf.set('Game', 'Name', 'Moria') conf.set('Game', 'Width', 40) conf.set('Game', 'Height', 40) conf.set('Game', 'Dirs', CURSES_DIRS) # Save the config file conf.write(open('moe.ini', 'w')) return "Moria", CURSES_DIRS, 40, 40 def save(player): """ save to a file """ output = open('moe.sav', 'wb') # Pickle the player pickle.dump(player, output, pickle.HIGHEST_PROTOCOL) output.close() if __name__ == "__main__": try: import psyco psyco.full() except ImportError: pass # Curses init s = curses.initscr() curses.start_color() curses.noecho() curses.cbreak() c = [] for i in range(1, 16): curses.init_pair(i, i % 8, 0) if i < 8: c.append(curses.color_pair(i)) else: c.append(curses.color_pair(i) | curses.A_BOLD) s.keypad(1) curses.curs_set(0) # Player init mine_name, dirs, w, h = load_config() try: pkl_file = open('moe.sav', 'rb') player = pickle.load(pkl_file) pkl_file.close() if player.hp <= 0 or player.exit == 2: player = Player((w, h), mine_name, player.scores) except: player = Player((w, h), mine_name) player.do_fov(player.fov) player.display(s) try: while True: k = s.getch() if k == 27: break elif CURSES_DIRS.has_key(k): new_pos = (player.pos[0] + dirs[k][0], player.pos[1] + dirs[k][1]) player.move(new_pos) player.do_fov(player.fov) elif k == ord('d'): player.drink() elif k == ord('r'): player.rest() elif k == ord('1'): player.change_active_weapon(WEAPON_SWORD) elif k == ord('2'): player.change_active_weapon(WEAPON_AXE) elif k == ord('3'): player.change_active_weapon(WEAPON_SPEAR) elif k == ord('4'): player.change_active_weapon(WEAPON_WARHAMMER) elif k == ord('i'): player.inventory() player.move_monsters() player.display(s) if player.exit > 0: break finally: # Curses ending s.keypad(0) curses.echo() curses.nocbreak() curses.endwin() # Exit with some words print "Last messages :" print "---------------" n = min(10, len(player.message)) for i in range(n): print player.message[n-i-1] print "---------------" if player.hp <= 0 or player.exit == 2: player.save_score() save(player)