Advertisement
Guest User

Untitled

a guest
Feb 4th, 2017
206
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 47.35 KB | None | 0 0
  1. import libtcodpy as libtcod
  2. import math
  3. import textwrap
  4. import random
  5. import shelve
  6. from bearlibterminal import terminal
  7.  
  8. #size of window
  9. SCREEN_WIDTH = 80
  10. SCREEN_HEIGHT = 50
  11.  
  12. #size of map
  13. MAP_WIDTH = 80
  14. MAP_HEIGHT = 43
  15.  
  16. FOV_ALGO = 0  #default FOV algorithm
  17. FOV_LIGHT_WALLS = True  #light walls or not
  18. TORCH_RADIUS = 5
  19.  
  20. #sizes and coordinates relevant for the GUI
  21. BAR_WIDTH = 20
  22. PANEL_HEIGHT = 7
  23. PANEL_Y = SCREEN_HEIGHT - PANEL_HEIGHT
  24. MSG_X = BAR_WIDTH + 2
  25. MSG_WIDTH = SCREEN_WIDTH - BAR_WIDTH - 2
  26. MSG_HEIGHT = PANEL_HEIGHT - 1
  27. BAR_CHAR = 20
  28. INVENTORY_WIDTH = 50
  29. MENU_BACKGROUND_COLOR = 3716001477
  30. LEVEL_SCREEN_WIDTH = 40
  31. CHARACTER_SCREEN_WIDTH = 30
  32.  
  33. MONSTER_SMELL_RANGE = 20
  34.  
  35. #parameters for dungeon generator
  36. ROOM_MAX_SIZE = 10
  37. ROOM_MIN_SIZE = 6
  38. MAX_ROOMS = 30
  39.  
  40. #Spell Values
  41. HEAL_AMOUNT = 4
  42. LIGHTNING_DAMAGE = 20
  43. LIGHTNING_RANGE = 5
  44. CONFUSE_RANGE = 8
  45. CONFUSE_NUM_TURNS = 10
  46. FIREBALL_RADIUS = 3
  47. FIREBALL_DAMAGE = 12
  48.  
  49. #Experience and level-ups
  50. LEVEL_UP_BASE = 200
  51. LEVEL_UP_FACTOR = 150
  52.  
  53. LIMIT_FPS = 10  #10 frames-per-second maximum
  54.  
  55. tile_dark_wall = 14
  56. tile_light_wall = 13
  57. tile_dark_ground = 0
  58. tile_light_ground = 1
  59.  
  60. class Tile:
  61.     #a tile of the map and its properties
  62.     def __init__(self, blocked, block_sight = None):
  63.         self.blocked = blocked
  64.  
  65.         #all tiles start unexplored
  66.         self.explored = False
  67.  
  68.         #by default, if a tile is blocked, it also blocks sight
  69.         if block_sight is None: block_sight = blocked
  70.         self.block_sight = block_sight
  71.  
  72. class Rect:
  73.     #a rectangle on the map. used to characterize a room.
  74.     def __init__(self, x, y, w, h):
  75.         self.x1 = x
  76.         self.y1 = y
  77.         self.x2 = x + w
  78.         self.y2 = y + h
  79.  
  80.     def center(self):
  81.         center_x = (self.x1 + self.x2) / 2
  82.         center_y = (self.y1 + self.y2) / 2
  83.         return (center_x, center_y)
  84.  
  85.     def intersect(self, other):
  86.         #returns true if this rectangle intersects with another one
  87.         return (self.x1 <= other.x2 and self.x2 >= other.x1 and
  88.                 self.y1 <= other.y2 and self.y2 >= other.y1)
  89.  
  90. class Object:
  91.     #this is a generic object: the player, a monster, an item, the stairs...
  92.     #it's always represented by a character on screen.
  93.     def __init__(self, x, y, char, name, blocks=False, always_visible=False, fighter=None, ai=None, item=None, equipment=None):
  94.         self.x = x
  95.         self.y = y
  96.         self.char = char
  97.         self.name = name
  98.         self.blocks = blocks
  99.         self.always_visible = always_visible
  100.        
  101.         self.fighter = fighter
  102.         if self.fighter:  #let the fighter component know who owns it
  103.             self.fighter.owner = self
  104.  
  105.         self.ai = ai
  106.         if self.ai:  #let the AI component know who owns it
  107.             self.ai.owner = self
  108.  
  109.         self.item = item
  110.         if self.item:  #let the Item component know who owns it
  111.             self.item.owner = self
  112.  
  113.         self.equipment = equipment
  114.         if self.equipment:  #let the Equipment component know who owns it
  115.             self.equipment.owner = self
  116.             #there must be an Item component for the Equipment component to work properly
  117.             self.item = Item()
  118.             self.item.owner = self
  119.  
  120.  
  121.     def move(self, dx, dy):
  122.         #move by the given amount, if the destination is not blocked
  123.         if not is_blocked(self.x + dx, self.y + dy):
  124.  
  125.             self.x += dx
  126.             self.y += dy
  127.  
  128.     def move_astar(self, target):
  129.         #Create a FOV map that has the dimensions of the map
  130.         fov = libtcod.map_new(MAP_WIDTH, MAP_HEIGHT)
  131.  
  132.         #Scan the current map each turn and set all the walls as unwalkable
  133.         for y1 in range(MAP_HEIGHT):
  134.             for x1 in range(MAP_WIDTH):
  135.                 libtcod.map_set_properties(fov, x1, y1, not map[x1][y1].block_sight, not map[x1][y1].blocked)
  136.  
  137.         #Scan all the objects to see if there are objects that must be navigated around
  138.         #Check also that the object isn't self or the target (so that the start and the end points are free)
  139.         #The AI class handles the situation if self is next to the target so it will not use this A* function anyway  
  140.         for obj in objects:
  141.             if obj.blocks and obj != self and obj != target:
  142.                 #Set the tile as a wall so it must be navigated around
  143.                 libtcod.map_set_properties(fov, obj.x, obj.y, True, False)
  144.  
  145.         #Allocate a A* path
  146.         #The 1.41 is the normal diagonal cost of moving, it can be set as 0.0 if diagonal moves are prohibited
  147.         my_path = libtcod.path_new_using_map(fov, 1.41)
  148.  
  149.         #Compute the path between self's coordinates and the target's coordinates
  150.         libtcod.path_compute(my_path, self.x, self.y, target.x, target.y)
  151.  
  152.         #Check if the path exists, and in this case, also the path is shorter than 25 tiles
  153.         #The path size matters if you want the monster to use alternative longer paths (for example through other rooms) if for example the player is in a corridor
  154.         #It makes sense to keep path size relatively low to keep the monsters from running around the map if there's an alternative path really far away        
  155.         if not libtcod.path_is_empty(my_path) and libtcod.path_size(my_path) < 25:
  156.             #Find the next coordinates in the computed full path
  157.             x, y = libtcod.path_walk(my_path, True)
  158.             if x or y:
  159.                 #Set self's coordinates to the next path tile
  160.                 self.x = x
  161.                 self.y = y
  162.         else:
  163.             #Keep the old move function as a backup so that if there are no paths (for example another monster blocks a corridor)
  164.             #it will still try to move towards the player (closer to the corridor opening)
  165.             self.move_towards(target.x, target.y)  
  166.  
  167.         #Delete the path to free memory
  168.         libtcod.path_delete(my_path)
  169.            
  170.     def move_towards(self, target_x, target_y):
  171.         #vector from this object to the target, and distance
  172.         dx = target_x - self.x
  173.         dy = target_y - self.y
  174.         distance = math.sqrt(dx ** 2 + dy ** 2)
  175.  
  176.         #normalize it to length 1 (preserving direction), then round it and
  177.         #convert to integer so the movement is restricted to the map grid
  178.         dx = int(round(dx / distance))
  179.         dy = int(round(dy / distance))
  180.         self.move(dx, dy)
  181.  
  182.     def distance_to(self, other):
  183.         #return the distance to another object
  184.         dx = other.x - self.x
  185.         dy = other.y - self.y
  186.         return math.sqrt(dx ** 2 + dy ** 2)
  187.  
  188.     def distance(self, x, y):
  189.         #return the distance to some coordinates
  190.         return math.sqrt((x - self.x) ** 2 + (y - self.y) ** 2)    
  191.  
  192.     def send_to_back(self):
  193.         #make this object be drawn first, so all others appear above it if they're in the same tile.
  194.         global objects
  195.         objects.remove(self)
  196.         objects.insert(0, self)
  197.  
  198.     def draw(self):
  199.         #set the color and then draw the character that represents this object at its position
  200.         if (libtcod.map_is_in_fov(fov_map, self.x, self.y) or
  201.             (self.always_visible and map[self.x][self.y].explored)):
  202.             #set color and then draw character at location
  203.             if self.fighter:
  204.                 terminal.layer(4)
  205.             else:
  206.                 terminal.layer(3)
  207.             terminal.color("#FFFFFF")
  208.             terminal.put(self.x, self.y, self.char)
  209.  
  210.     def clear(self):
  211.         #erase the character that represents this object
  212.         if self.fighter:
  213.             terminal.layer(4)
  214.         else:
  215.             terminal.layer(3)
  216.         terminal.put(self.x, self.y, ' ')
  217.        
  218. class Fighter:
  219.     #combat-related properties and methods (monster, player, NPC).
  220.     def __init__(self, hp, defense, power, xp, death_function=None):
  221.         self.base_max_hp = hp
  222.         self.hp = hp
  223.         self.base_defense = defense
  224.         self.base_power = power
  225.         self.xp = xp
  226.         self.death_function = death_function
  227.  
  228.     @property
  229.     def power(self):  #return actual power, by summing up the bonuses from all equipped items
  230.         bonus = sum(equipment.power_bonus for equipment in get_all_equipped(self.owner))
  231.         return self.base_power + bonus
  232.  
  233.     @property
  234.     def defense(self):  #return actual defense, by summing up the bonuses from all equipped items
  235.         bonus = sum(equipment.defense_bonus for equipment in get_all_equipped(self.owner))
  236.         return self.base_defense + bonus
  237.  
  238.     @property
  239.     def max_hp(self):  #return actual max_hp, by summing up the bonuses from all equipped items
  240.         bonus = sum(equipment.max_hp_bonus for equipment in get_all_equipped(self.owner))
  241.         return self.base_max_hp + bonus
  242.  
  243.     def attack(self, target):
  244.         #random-rolled attack damage, with crits
  245.         ran_hit = libtcod.random_get_float(0, 0, 1)
  246.         ran_def = libtcod.random_get_float(0, 0, 1)
  247.         if libtcod.random_get_float(0, 0, 100) > 90:
  248.             ran_hit = 2
  249.             message(self.owner.name.capitalize() + ' PERFORMS A CRITICAL HIT!', 4294901887)
  250.         damage = int(ran_hit * self.power - ran_def * target.fighter.defense)
  251.         if damage > 0:
  252.             #print self.owner.name.capitalize() + ' attacks ' + target.name + ' for ' + str(damage) + ' hit points.'
  253.             message(self.owner.name.capitalize() + ' attacks ' + target.name + ' for ' + str(damage) + ' hit points.',
  254.                 4286578559)
  255.             target.fighter.take_damage(damage)
  256.         else:
  257.             #print self.owner.name.capitalize() + ' attacks ' + target.name + ' but has no effect.'
  258.             message(self.owner.name.capitalize() + ' attacks ' + target.name + ' but has no effect.', 4282335231)
  259.        
  260.     def take_damage(self, damage):
  261.         #apply damage if possible
  262.         if damage > 0:
  263.             self.hp -= damage
  264.             #check for death. if there's a death function, call it
  265.             if self.hp <= 0:
  266.                 function = self.death_function
  267.                 if function is not None:
  268.                     function(self.owner)
  269.                 if self.owner != player:  #yield experience to the player
  270.                     player.fighter.xp += self.xp
  271.  
  272.     def heal(self, amount):
  273.         #heal by the given amount, without going over the maximum
  274.         self.hp += amount
  275.         if self.hp > self.max_hp:
  276.             self.hp = self.max_hp
  277.  
  278. class BasicMonster:
  279.     def __init__(self, has_seen_player=False):
  280.         self.has_seen_player = has_seen_player
  281.     #AI routine for a basic monster !!AI (for object)
  282.     def take_turn(self):
  283.         #basic mosnter takes turn. if you can see it, it can see you.
  284.         monster = self.owner
  285.         if libtcod.map_is_in_fov(fov_map, monster.x, monster.y):
  286.  
  287.             self.has_seen_player = True
  288.             #move towards the player if far away
  289.             if monster.distance_to(player) >= 2:
  290.                 monster.move_astar(player)
  291.  
  292.             #close enough to attack (if player is alive still)
  293.             elif player.fighter.hp > 0:
  294.                 monster.fighter.attack(player)
  295.                 #print 'The attack of the ' + monster.name + ' bounces on your shiny carapace.'
  296.         elif (self.owner.distance_to(player) <= MONSTER_SMELL_RANGE) and self.has_seen_player:
  297.             monster.move_towards(player.x, player.y)
  298.  
  299. class ConfusedMonster:
  300.     #AI for a temporarily confused monster (reverts to previous AI after a while).
  301.     def __init__(self, old_ai, num_turns=CONFUSE_NUM_TURNS):
  302.         self.old_ai = old_ai
  303.         self.num_turns = num_turns
  304.  
  305.     def take_turn(self):
  306.         if self.num_turns > 0:  #still confused...
  307.             #move in a random direction, and decrease the number of turns confused
  308.             self.owner.move(libtcod.random_get_int(0, -1, 1), libtcod.random_get_int(0, -1, 1))
  309.             self.num_turns -= 1
  310.  
  311.         else:  #restore the previous AI (this one will be deleted because it's not referenced anymore)
  312.             self.owner.ai = self.old_ai
  313.             message('The ' + self.owner.name + ' is no longer confused!', 'red')
  314.  
  315. class Item:
  316.     #an item that can be picked up and used.
  317.     def __init__(self, use_function=None):
  318.         self.use_function = use_function
  319.  
  320.     def pick_up(self):
  321.         #add to the player's inventory and remove from the map
  322.         if len(inventory) >= 26:
  323.             message('Your inventory is full, cannot pick up ' + self.owner.name + '.', 'red')
  324.         else:
  325.             inventory.append(self.owner)
  326.             objects.remove(self.owner)
  327.             message('You picked up a ' + self.owner.name + '!', 'green')
  328.  
  329.             #special case: automatically equip, if the corresponding equipment slot is unused
  330.             equipment = self.owner.equipment
  331.             if equipment and get_equipped_in_slot(equipment.slot) is None:
  332.                 equipment.equip()
  333.  
  334.     def drop(self):
  335.         #special case: if the object has the Equipment component, dequip it before dropping
  336.         if self.owner.equipment:
  337.             self.owner.equipment.dequip
  338.         #add to the map and remove from the player's inventory. also, place it at the player's coordinates
  339.         objects.append(self.owner)
  340.         inventory.remove(self.owner)
  341.         self.owner.x = player.x
  342.         self.owner.y = player.y
  343.         message('You dropped a ' + self.owner.name + '.', 4294967040)
  344.  
  345.  
  346.     def use(self):
  347.         #special case: if the object has the Equipment component, the "use" action is to equip/dequip
  348.         if self.owner.equipment:
  349.             self.owner.equipment.toggle_equip()
  350.             return
  351.  
  352.         #just call the "use_function" if it is defined
  353.         if self.use_function is None:
  354.             message('The ' + self.owner.name + ' cannot be used.')
  355.         else:
  356.             if self.use_function() != 'cancelled':
  357.                 inventory.remove(self.owner)  #destroy after use, unless it was cancelled for some reason
  358.  
  359. class Equipment:
  360.     #an object that can be equipped, yielding bonuses. automatically adds the Item component.
  361.     def __init__(self, slot, power_bonus=0, defense_bonus=0, max_hp_bonus=0):
  362.         self.power_bonus = power_bonus
  363.         self.defense_bonus = defense_bonus
  364.         self.max_hp_bonus = max_hp_bonus
  365.         self.slot = slot
  366.         self.is_equipped = False
  367.  
  368.     def toggle_equip(self):  #toggle equip/dequip status
  369.         if self.is_equipped:
  370.             self.dequip()
  371.         else:
  372.             self.equip()
  373.  
  374.     def equip(self):
  375.         #if the slot is already being used, dequip whatever is there first
  376.         old_equipment = get_equipped_in_slot(self.slot)
  377.         if old_equipment is not None:
  378.             old_equipment.dequip()
  379.  
  380.         #equip object and show a message about it
  381.         self.is_equipped = True
  382.         message('Equipped ' + self.owner.name + ' on ' + self.slot + '.', 'green')
  383.  
  384.     def dequip(self):
  385.         #dequip object and show a message about it
  386.         if not self.is_equipped: return
  387.         self.is_equipped = False
  388.         message('Dequipped ' + self.owner.name + ' from ' + self.slot + '.', 'yellow')
  389.  
  390. def get_equipped_in_slot(slot):  #returns the equipment in a slot, or None if it's empty
  391.     for obj in inventory:
  392.         if obj.equipment and obj.equipment.slot == slot and obj.equipment.is_equipped:
  393.             return obj.equipment
  394.     return None
  395.  
  396. def get_all_equipped(obj):  #returns a list of equipped items
  397.     if obj == player:
  398.         equipped_list = []
  399.         for item in inventory:
  400.             if item.equipment and item.equipment.is_equipped:
  401.                 equipped_list.append(item.equipment)
  402.         return equipped_list
  403.     else:
  404.         return []  #other objects have no equipment
  405.  
  406. def is_blocked(x, y):
  407.     #first test the map tile
  408.     if map[x][y].blocked:
  409.         return True
  410.  
  411.     #now check for any blocking objects
  412.     for object in objects:
  413.         if object.blocks and object.x == x and object.y == y:
  414.             return True
  415.  
  416.     return False
  417.        
  418. def create_room(room):
  419.     global map
  420.     #go through the tiles in the rectangle and make them passable
  421.     for x in range(room.x1 + 1, room.x2):
  422.         for y in range(room.y1 + 1, room.y2):
  423.             map[x][y].blocked = False
  424.             map[x][y].block_sight = False
  425.  
  426. def create_h_tunnel(x1, x2, y):
  427.     global map
  428.     #horizontal tunnel. min() and max() are used in case x1>x2
  429.     for x in range(min(x1, x2), max(x1, x2) + 1):
  430.         map[x][y].blocked = False
  431.         map[x][y].block_sight = False
  432.  
  433. def create_v_tunnel(y1, y2, x):
  434.     global map
  435.     #vertical tunnel
  436.     for y in range(min(y1, y2), max(y1, y2) + 1):
  437.         map[x][y].blocked = False
  438.         map[x][y].block_sight = False
  439.        
  440. def make_map():
  441.     global map, objects, stairs
  442.  
  443.     #the list of objects with just the player
  444.     objects = [player]
  445.     #fill map with "blocked" tiles
  446.     map = [[ Tile(True)
  447.         for y in range(MAP_HEIGHT) ]
  448.             for x in range(MAP_WIDTH) ]
  449.  
  450.     rooms = []
  451.     num_rooms = 0
  452.  
  453.     for r in range(MAX_ROOMS):
  454.         #random width and height
  455.         w = libtcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)
  456.         h = libtcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)
  457.         #random position without going out of the boundaries of the map
  458.         x = libtcod.random_get_int(0, 0, MAP_WIDTH - w - 1)
  459.         y = libtcod.random_get_int(0, 0, MAP_HEIGHT - h - 1)
  460.  
  461.         #"Rect" class makes rectangles easier to work with
  462.         new_room = Rect(x, y, w, h)
  463.  
  464.         #run through the other rooms and see if they intersect with this one
  465.         failed = False
  466.         for other_room in rooms:
  467.             if new_room.intersect(other_room):
  468.                 failed = True
  469.                 break
  470.  
  471.         if not failed:
  472.             #this means there are no intersections, so this room is valid
  473.  
  474.             #"paint" it to the map's tiles
  475.             create_room(new_room)
  476.  
  477.             #center coordinates of new room, will be useful later
  478.             (new_x, new_y) = new_room.center()
  479.  
  480.             if num_rooms == 0:
  481.                 #this is the first room, where the player starts at
  482.                 player.x = new_x
  483.                 player.y = new_y
  484.  
  485.             else:
  486.                 #all rooms after the first:
  487.                 #connect it to the previous room with a tunnel
  488.  
  489.                 #center coordinates of previous room
  490.                 (prev_x, prev_y) = rooms[num_rooms-1].center()
  491.  
  492.                 #draw a coin (random number that is either 0 or 1)
  493.                 if libtcod.random_get_int(0, 0, 1) == 1:
  494.                     #first move horizontally, then vertically
  495.                     create_h_tunnel(prev_x, new_x, prev_y)
  496.                     create_v_tunnel(prev_y, new_y, new_x)
  497.                 else:
  498.                     #first move vertically, then horizontally
  499.                     create_v_tunnel(prev_y, new_y, prev_x)
  500.                     create_h_tunnel(prev_x, new_x, new_y)
  501.  
  502.             #add some contents to this room, such as monsters
  503.             place_objects(new_room)
  504.            
  505.             #finally, append the new room to the list
  506.             rooms.append(new_room)
  507.             num_rooms += 1
  508.            
  509.     #create stairs at the center of the last room
  510.     stairs = Object(new_x, new_y, 7, 'stairs', always_visible=True)
  511.     objects.append(stairs)
  512.     stairs.send_to_back()  #so it's drawn below the monsters
  513.        
  514. def random_choice_index(chances): #Choose one option from a list of chances
  515.     #The dice land on a number between 1 and sum
  516.     dice = libtcod.random_get_int(0, 1, sum(chances))
  517.    
  518.     #Go through all chances
  519.     running_sum = 0
  520.     choice = 0
  521.     for w in chances:
  522.         running_sum += w
  523.        
  524.         #Checking i dice landed in the part that corresponds to this choice
  525.         if dice <= running_sum:
  526.             return choice
  527.         choice +=1
  528.        
  529. def random_choice(chances_dict):
  530.     #choose one option from dictionary of chances, returning its key
  531.     chances = chances_dict.values()
  532.     strings = chances_dict.keys()
  533.     return strings[random_choice_index(chances)]
  534.        
  535. def from_dungeon_level(table):
  536.     #returns a value that depends on level. the table specifies what value occurs after each level, default is 0.
  537.     for (value, level) in reversed(table):
  538.         if dungeon_level >= level:
  539.             return value
  540.     return 0
  541.        
  542. def place_objects(room):
  543.     #this is where we decide the chance of each monster or item appearing.
  544.  
  545.     #maximum number of monsters per room
  546.     max_monsters = from_dungeon_level([[2, 1], [3, 4], [5, 6]])
  547.  
  548.     #chance of each monster
  549.     monster_chances = {}
  550.     monster_chances['orc'] = 80  #orc always shows up, even if all other monsters have 0 chance
  551.     monster_chances['troll'] = from_dungeon_level([[15, 3], [30, 5], [60, 7]])
  552.  
  553.     #maximum number of items per room
  554.     max_items = from_dungeon_level([[1, 1], [2, 4]])
  555.  
  556.     #chance of each item (by default they have a chance of 0 at level 1, which then goes up)
  557.     item_chances = {}
  558.     item_chances['heal'] = 35  #healing potion always shows up, even if all other items have 0 chance
  559.     item_chances['lightning'] = from_dungeon_level([[25, 4]])
  560.     item_chances['fireball'] =  from_dungeon_level([[25, 6]])
  561.     item_chances['confuse'] =   from_dungeon_level([[10, 2]])
  562.     item_chances['sword'] =     from_dungeon_level([[5, 4]])
  563.     item_chances['shield'] =    from_dungeon_level([[15, 8]])
  564.  
  565.  
  566.     #choose random number of monsters
  567.     num_monsters = libtcod.random_get_int(0, 0, max_monsters)
  568.  
  569.     for i in range(num_monsters):
  570.         #choose random spot for this monster
  571.         x = libtcod.random_get_int(0, room.x1+1, room.x2-1)
  572.         y = libtcod.random_get_int(0, room.y1+1, room.y2-1)
  573.  
  574.         #only place it if the tile is not blocked
  575.         if not is_blocked(x, y):
  576.             choice = random_choice(monster_chances)
  577.             if choice == 'orc':
  578.                 #create an orc
  579.                 fighter_component = Fighter(hp=5, defense=0, power=3, xp=35, death_function=monster_death)
  580.                 ai_component = BasicMonster()
  581.  
  582.                 monster = Object(x, y, 169, 'orc', blocks=True, fighter=fighter_component, ai=ai_component)
  583.  
  584.             elif choice == 'troll':
  585.                 #create a troll
  586.                 fighter_component = Fighter(hp=30, defense=2, power=8, xp=100, death_function=monster_death)
  587.                 ai_component = BasicMonster()
  588.  
  589.                 monster = Object(x, y, 162, 'slime', blocks=True, fighter=fighter_component, ai=ai_component)
  590.  
  591.             objects.append(monster)
  592.  
  593.     #choose random number of items
  594.     num_items = libtcod.random_get_int(0, 0, max_items)
  595.  
  596.     for i in range(num_items):
  597.         #choose random spot for this item
  598.         x = libtcod.random_get_int(0, room.x1+1, room.x2-1)
  599.         y = libtcod.random_get_int(0, room.y1+1, room.y2-1)
  600.  
  601.         #only place it if the tile is not blocked
  602.         if not is_blocked(x, y):
  603.             choice = random_choice(item_chances)
  604.             if choice == 'heal':
  605.                 #create a healing potion
  606.                 item_component = Item(use_function=cast_heal)
  607.                 item = Object(x, y, 24, 'healing potion', item=item_component, always_visible=True)
  608.  
  609.             elif choice == 'lightning':
  610.                 #create a lightning bolt scroll
  611.                 item_component = Item(use_function=cast_lightning)
  612.                 item = Object(x, y, 21, 'scroll of lightning bolt', item=item_component, always_visible=True)
  613.  
  614.             elif choice == 'fireball':
  615.                 #create a fireball scroll
  616.                 item_component = Item(use_function=cast_fireball)
  617.                 item = Object(x, y, 21, 'scroll of fireball', 'light yellow', item=item_component, always_visible=True)
  618.  
  619.             elif choice == 'confuse':
  620.                 #create a confuse scroll
  621.                 item_component = Item(use_function=cast_confuse)
  622.                 item = Object(x, y, 21, 'scroll of confusion', 'light yellow', item=item_component, always_visible=True)
  623.  
  624.             elif choice == 'sword':
  625.                 #create a sword
  626.                 equipment_component = Equipment(slot='right hand', power_bonus=3)
  627.                 item = Object(x, y, 25, 'sword of might', equipment=equipment_component)
  628.  
  629.             elif choice == 'shield':
  630.                 #create a shield
  631.                 equipment_component = Equipment(slot='left hand', defense_bonus=1)
  632.                 item = Object(x, y, 134, 'shield', equipment=equipment_component)
  633.  
  634.             objects.append(item)
  635.             item.send_to_back()  #items appear below other objects
  636.             item.always_visible = True  #items are visible even out-of-FOV, if in an explored area
  637.    
  638. def render_bar(x, y, total_width, name, value, maximum, bar_color, back_color):
  639.     #adjust: move panel to desired location (bottom of console)
  640.     y = y + PANEL_Y
  641.     #set console to GUI layer
  642.     terminal.layer(5)
  643.     #render a bar (HP, experience, etc). first calculate the width of the bar
  644.     bar_width = int(float(value) / maximum * total_width)
  645.     #set total-width back bar color
  646.     terminal.color(back_color)
  647.     #terminal: draw a row of given length
  648.     a = 0
  649.     for b in range(total_width):
  650.         terminal.put(x + a, y, BAR_CHAR)
  651.         a += 1
  652.     #now render the bar on top
  653.     #set bar itself color
  654.     terminal.color(bar_color)
  655.     #libtcod.console_set_default_background(panel, bar_color)
  656.     if bar_width > 0:
  657.         a = 0
  658.         for b in range(bar_width):
  659.             terminal.put(x + a, y, BAR_CHAR)
  660.             a += 1
  661.     #finally, some centered text with the values
  662.     #first clear previous text, then draw new text, centred.
  663.     terminal.color("#FFFFFF")
  664.     terminal.layer(6)
  665.     terminal.clear_area(x, y, total_width, 1)
  666.     bar_center = len(name + ': ' + str(value) + '/' + str(maximum))/2
  667.     terminal.print_(x + total_width/2 - bar_center, y, name + ': ' + str(value) + '/' + str(maximum))
  668.        
  669. def get_names_under_mouse():
  670.     #return a string with the names of all objects under the mouse
  671.     (x, y) = (terminal.state(terminal.TK_MOUSE_X), terminal.state(terminal.TK_MOUSE_Y))
  672.    
  673.     #create a list with the names of all objects at the mouse's coordinates and in FOV
  674.     names = [obj.name for obj in objects
  675.         if obj.x == x and obj.y == y and libtcod.map_is_in_fov(fov_map, obj.x, obj.y)]
  676.     names = ', '.join(names)  #join the names, separated by commas
  677.     return names.capitalize()
  678.        
  679. def render_all():
  680.     global fov_map, color_dark_wall, color_light_wall
  681.     global color_dark_ground, color_light_ground
  682.     global fov_recompute, game_msgs
  683.  
  684.     if fov_recompute:
  685.         #recompute FOV if needed (the player moved or something)
  686.         fov_recompute = False
  687.         libtcod.map_compute_fov(fov_map, player.x, player.y, TORCH_RADIUS, FOV_LIGHT_WALLS, FOV_ALGO)
  688.         terminal.color("#FFFFFF")
  689.         #go through all tiles, and set their background color according to the FOV
  690.         for y in range(MAP_HEIGHT):
  691.             for x in range(MAP_WIDTH):
  692.                 visible = libtcod.map_is_in_fov(fov_map, x, y)
  693.                 wall = map[x][y].block_sight
  694.                 if not visible:
  695.                     #if it's not visible right now, the player can only see it if it's explored
  696.                     terminal.layer(2)
  697.                     if map[x][y].explored:
  698.                         if wall:
  699.                             terminal.put(x, y, tile_dark_wall)
  700.                         else:
  701.                             terminal.put(x, y, tile_dark_ground)
  702.                 else:
  703.                     #it's visible
  704.                     if wall:
  705.                         terminal.put(x, y, tile_light_wall)
  706.                     else:
  707.                         terminal.put(x, y, tile_light_ground)
  708.                     #since it's visible, explore it
  709.                     map[x][y].explored = True
  710.  
  711.     #draw all objects in the list, except the player. we want it to
  712.     for object in objects:
  713.         object.draw()
  714.  
  715.     #prepare to render the GUI text
  716.     terminal.layer(6)
  717.     terminal.clear_area(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
  718.     #print the game messages, one line at a time
  719.     y = 1
  720.     for (line, color) in game_msgs:
  721.         terminal.color(color)
  722.         terminal.print_(MSG_X, y+PANEL_Y, line)
  723.         y += 1
  724.  
  725.     #show the player's hp
  726.     render_bar(1, 3, BAR_WIDTH, 'HP', player.fighter.hp, player.fighter.max_hp, 4294917951, 4286513152)
  727.  
  728.     #show xp
  729.     level_up_xp = LEVEL_UP_BASE + player.level * LEVEL_UP_FACTOR
  730.     render_bar( 1, 4, BAR_WIDTH, 'XP', player.fighter.xp, level_up_xp, 4282384191, 4278222592)
  731.  
  732.     #print dungeon level
  733.     terminal.layer(6)
  734.     terminal.color(4294967295)
  735.     terminal.print_(1,PANEL_Y + 5, 'Dungeon level ' + str(dungeon_level))
  736.  
  737.     #display names of objects under the mouse
  738.     #still on layer 6 ***** (text layer)
  739.     terminal.print_(1, PANEL_Y, get_names_under_mouse())
  740.        
  741. def message(new_msg, color = 'white'):
  742.     #split the message if necessary, among multiple lines
  743.     new_msg_lines = textwrap.wrap(new_msg, MSG_WIDTH)
  744.  
  745.     for line in new_msg_lines:
  746.         #if the buffer is full, remove the first line to make room for the new one
  747.         if len(game_msgs) == MSG_HEIGHT:
  748.             del game_msgs[0]
  749.  
  750.         #add the new line as a tuple, with the text and the color
  751.         game_msgs.append( (line, color) )
  752.        
  753. def player_move_or_attack(dx, dy):
  754.     global fov_recompute
  755.     #the coordinates the player is moving to/attacking
  756.     x = player.x + dx
  757.     y = player.y + dy
  758.     #try to find an attackable object there
  759.     target = None
  760.     for object in objects:
  761.         if object.fighter and object.x == x and object.y == y:
  762.             target = object
  763.             break
  764.     #attack if target found, move otherwise
  765.     if target is not None:
  766.         player.fighter.attack(target)
  767.     else:
  768.         player.move(dx, dy)
  769.         fov_recompute = True
  770.        
  771. def menu(header, options, width):
  772.     if len(options) > 26: raise ValueError('Cannot have a menu with more than 26 options.')
  773.    
  774.     #calculate total height for the header (after auto-wrap) and one line per option
  775.     header_height = libtcod.console_get_height_rect(0, 0, 0, width, SCREEN_HEIGHT, header)
  776.     if header == '':
  777.         header_height = 0
  778.     height = len(options) + header_height + 2
  779.  
  780.     #create an off-screen console that represents the menu's window
  781.     terminal.layer(2)
  782.     terminal.clear_area(0,0,SCREEN_WIDTH,SCREEN_HEIGHT)
  783.     #cursors position
  784.     c_pos = 0
  785.    
  786.     output = None
  787.  
  788.     x = SCREEN_WIDTH/2 - width/2
  789.     y = SCREEN_HEIGHT/2 - height/2
  790.    
  791.     while True:
  792.         terminal.print_(x+1,y, '[color=white]' + header)
  793.        
  794.         #print all the options
  795.         h = header_height
  796.         letter_index = ord('a')
  797.         run = 0
  798.         for option_text in options:
  799.             text = option_text
  800.            
  801.             if run == c_pos:
  802.                 terminal.print_(x+1,h+y+1, '[color=yellow]' + text)
  803.                
  804.             else:    
  805.                 terminal.print_(x+1,h+y+1, '[color=white]' + text)
  806.             h += 1
  807.             letter_index += 1
  808.             run += 1
  809.            
  810.         #present the root console to the player and wait for a key-press
  811.         terminal.refresh()
  812.        
  813.         key = terminal.read()
  814.         if key == terminal.TK_ESCAPE:
  815.             break
  816.         elif key == terminal.TK_UP or key == terminal.TK_KP_8:
  817.             c_pos -= 1
  818.             if c_pos < 0:
  819.                 c_pos = len(options)-1
  820.                
  821.         elif key == terminal.TK_DOWN or key == terminal.TK_KP_2:
  822.             c_pos += 1
  823.             if c_pos == len(options):
  824.                 c_pos = 0
  825.        
  826.         elif key == terminal.TK_ENTER:              
  827.             #convert the ASCII code to an index; if it corresponds to an option, return it
  828.             index = c_pos
  829.             #if index >= 0 and index < len(options):
  830.             output = index
  831.             break
  832.            
  833.     terminal.clear_area(0,0,SCREEN_WIDTH,SCREEN_HEIGHT)
  834.     terminal.layer(0)
  835.     return output
  836.        
  837. def inventory_menu(header):
  838.     global fov_recompute
  839.     #show a menu with each item of the inventory as an option
  840.     if len(inventory) == 0:
  841.         options = ['Inventory is empty.']
  842.     else:
  843.         options = []
  844.         for item in inventory:
  845.             text = item.name
  846.             #show additional info, in case it's equipped
  847.             if item.equipment and item.equipment.is_equipped:
  848.                 text = text + ' (on ' + item.equipment.slot + ')'
  849.             options.append(text)
  850.         #options = [item.name for item in inventory]
  851.     index = menu(header, options, INVENTORY_WIDTH)
  852.     #if an item was chosen, return it
  853.     if index is None or len(inventory) == 0: return None
  854.     return inventory[index].item
  855.  
  856.     terminal.layer(6)
  857.     terminal.clear_area(0, 0, MAP_WIDTH, MAP_HEIGHT)
  858.     fov_recompute = True
  859.  
  860. def msgbox(text, width=50):
  861.     menu(text, [], width)  #use menu() as a sort of "message box"
  862.  
  863. def handle_keys():
  864.     terminal.set('window.resizeable=true')
  865.     global fov_recompute
  866.     # Read keyboard input
  867.     global key
  868.  
  869.     key = terminal.read()
  870.     if key == terminal.TK_ESCAPE:
  871.         # Close terminal
  872.         return 'exit'
  873.     if game_state == 'playing':
  874.         #??? it was 'exit()'
  875.         a = 'player moved'
  876.         if key == terminal.TK_KP_2 or key == terminal.TK_DOWN:
  877.             player_move_or_attack(0, 1)
  878.             return a
  879.         elif key == terminal.TK_KP_8 or key == terminal.TK_UP:
  880.             player_move_or_attack(0, -1)
  881.             return a
  882.         elif key == terminal.TK_KP_6 or key == terminal.TK_RIGHT:
  883.             player_move_or_attack(1, 0)
  884.             return a
  885.         elif key == terminal.TK_KP_4 or key == terminal.TK_LEFT:
  886.             player_move_or_attack(-1, 0)
  887.             return a
  888.         elif key == terminal.TK_KP_7:
  889.             player_move_or_attack(-1, -1)
  890.             return a
  891.         elif key == terminal.TK_KP_9:
  892.             player_move_or_attack(1, -1)
  893.             return a
  894.         elif key == terminal.TK_KP_1:
  895.             player_move_or_attack(-1, 1)
  896.             return a
  897.         elif key == terminal.TK_KP_3:
  898.             player_move_or_attack(1, 1)
  899.             return a
  900.         elif key == terminal.TK_KP_5:
  901.             return a
  902.         else: #test for other keys
  903.             if key == terminal.TK_G:
  904.                 #pick up an item
  905.                 for object in objects:  #look for an item in the player's tile
  906.                     if object.x == player.x and object.y == player.y and object.item:
  907.                         object.item.pick_up()
  908.                         break
  909.  
  910.             if key == terminal.TK_I:
  911.                 #show the inventory; if an item is selected, use it
  912.                 chosen_item = inventory_menu('Press the key next to an item to use it, or any other to cancel.\n')
  913.                 if chosen_item is not None:
  914.                     chosen_item.use()
  915.  
  916.             if key == terminal.TK_D:
  917.                 #show the inventory; if an item is selected, drop it
  918.                 chosen_item = inventory_menu('Press the key next to an item to drop it, or any other to cancel.\n')
  919.                 if chosen_item is not None:
  920.                     chosen_item.drop()
  921.  
  922.             if key == terminal.TK_C:
  923.                 #show character info
  924.                 level_up_xp = LEVEL_UP_BASE + player.level * LEVEL_UP_FACTOR
  925.                 msgbox('Character Information\n\nLevel: ' + str(player.level) +
  926.                        '\nExperience: ' + str(player.fighter.xp) + ' / ' + str(level_up_xp) +
  927.                        '\n\nMaximum HP: ' + str(player.fighter.max_hp) +
  928.                     '\nAttack: ' + str(player.fighter.power) + '\nDefense: ' + str(player.fighter.defense), CHARACTER_SCREEN_WIDTH)
  929.  
  930.             if key == terminal.TK_SHIFT:
  931.                 key = terminal.read()
  932.                 if key == terminal.TK_PERIOD and stairs.x == player.x and stairs.y == player.y:
  933.                     #go down stairs, if the player is on them
  934.                         next_level()
  935.            
  936.             key = terminal.read()
  937.             if key == terminal.TK_RESIZED:
  938.                 new_columns = terminal.state(terminal.TK_WIDTH)
  939.                 new_rows = terminal.state(terminal.TK_HEIGHT)
  940.            
  941.  
  942.     return 'didnt-take-turn'
  943.            
  944. def check_level_up():
  945.     #see if the player's experience is enough to level up
  946.     level_up_xp = LEVEL_UP_BASE + player.level * LEVEL_UP_FACTOR
  947.     if player.fighter.xp >= level_up_xp:
  948.         #it is! level up
  949.         player.level += 1
  950.         player.fighter.xp -= level_up_xp
  951.         message('Your battle skills have improved! You reached level ' + str(player.level) + '!', 4294967040)
  952.         choice = None
  953.         while choice == None:
  954.             choice = menu('Level up! Choose a stat to raise:\n',
  955.                           ['Constitution (+20 HP, from ' + str(player.fighter.max_hp) + ')',
  956.                 'Strength (+1 attack, from ' + str(player.fighter.power) + ')',
  957.                 'Agility (+1 defense, from ' + str(player.fighter.defense) + ')'], LEVEL_SCREEN_WIDTH)
  958.         if choice == 0:
  959.             player.fighter.base_max_hp += 20
  960.             player.fighter.hp += 20
  961.  
  962.         elif choice == 1:
  963.             player.fighter.base_power += 1
  964.  
  965.         elif choice == 2:
  966.             player.fighter.base_defense += 1
  967.  
  968.         #clear level menu before refresh
  969.         terminal.layer(6)
  970.         terminal.clear_area(0, 0, MAP_WIDTH, MAP_HEIGHT)
  971.  
  972. def player_death(player):
  973.     #the game ended!
  974.     global game_state
  975.     message('You died!', 4294901760)
  976.     game_state = 'dead'
  977.  
  978.     #for added effect, transform the player into a corpse!
  979.     player.char = 22
  980.  
  981. def monster_death(monster):
  982.     #transform it into a nasty corpse! it doesn't block, can't be
  983.     #attacked and doesn't move
  984.     message(monster.name.capitalize() + ' is dead! You gain ' + str(monster.fighter.xp) + 'xp.', 4294901760)
  985.     monster.char = 22
  986.     monster.blocks = False
  987.     monster.fighter = None
  988.     monster.ai = None
  989.     monster.name = 'remains of ' + monster.name
  990.     monster.send_to_back()
  991.     #clearing monster sprite from its layer, since corpse is not 'fighter' type
  992.     terminal.layer(4)
  993.     terminal.clear_area(monster.x, monster.y, 1, 1)
  994.  
  995. #&&&&&&&&&&&&&&&TARGETTING TYPES&&&&&&&&&&&&&&&&&&&&    
  996. def target_tile(max_range=None):
  997.     global key
  998.     #return the position of a tile left-clicked in player's FOV (optionally in a range), or (None,None) if right-clicked.
  999.     while True:
  1000.         #render the screen. this erases the inventory and shows the names of objects under the mouse.
  1001.         terminal.refresh()
  1002.         key = terminal.read()
  1003.         render_all()
  1004.         (x, y) = (terminal.state(terminal.TK_MOUSE_X), terminal.state(terminal.TK_MOUSE_Y))
  1005.  
  1006.         if key == terminal.TK_MOUSE_RIGHT or key == terminal.TK_ESCAPE:
  1007.             message('Cancelled')
  1008.             return (None, None)  #cancel if the player right-clicked or pressed Escape
  1009.  
  1010.         #accept the target if the player clicked in FOV, and in case a range is specified, if it's in that range
  1011.         if (key == terminal.TK_MOUSE_LEFT and libtcod.map_is_in_fov(fov_map, x, y) and
  1012.                 (max_range is None or player.distance(x, y) <= max_range)):
  1013.             return (x, y)
  1014.  
  1015. def target_monster(max_range=None):
  1016.     #returns a clicked monster inside FOV up to a range, or None if right-clicked
  1017.     while True:
  1018.         (x, y) = target_tile(max_range)
  1019.         if x is None:  #player cancelled
  1020.             return None
  1021.  
  1022.         #return the first clicked monster, otherwise continue looping
  1023.         for obj in objects:
  1024.             if obj.x == x and obj.y == y and obj.fighter and obj != player:
  1025.                 return obj
  1026.  
  1027. def closest_monster(max_range):
  1028.     #find closest enemy, up to a maximum range, and in the player's FOV
  1029.     closest_enemy = None
  1030.     closest_dist = max_range + 1  #start with (slightly more than) maximum range
  1031.  
  1032.     for object in objects:
  1033.         if object.fighter and not object == player and libtcod.map_is_in_fov(fov_map, object.x, object.y):
  1034.             #calculate distance between this object and the player
  1035.             dist = player.distance_to(object)
  1036.             if dist < closest_dist:  #it's closer, so remember it
  1037.                 closest_enemy = object
  1038.                 closest_dist = dist
  1039.     return closest_enemy
  1040. #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&   
  1041.  
  1042. #################SPELL TYPES#########################  
  1043. def cast_heal():
  1044.     #heal the player
  1045.     if player.fighter.hp == player.fighter.max_hp:
  1046.         message('You are already at full health.', 'red')
  1047.         return 'cancelled'
  1048.  
  1049.     message('Your wounds start to feel better!', 'blue')
  1050.     player.fighter.heal(HEAL_AMOUNT)
  1051.  
  1052. def cast_lightning():
  1053.     #find closest enemy (inside a maximum range) and damage it
  1054.     monster = closest_monster(LIGHTNING_RANGE)
  1055.     if monster is None:  #no enemy found within maximum range
  1056.         message('No enemy is close enough to strike.', 'red')
  1057.         return 'cancelled'
  1058.  
  1059.     #zap it!
  1060.     message('A lighting bolt strikes the ' + monster.name + ' with a loud thunder! The damage is '
  1061.             + str(LIGHTNING_DAMAGE) + ' hit points.', 'blue')
  1062.     monster.fighter.take_damage(LIGHTNING_DAMAGE)
  1063.  
  1064. def cast_fireball():
  1065.     #ask the player for a target tile to throw a fireball at
  1066.     message('Left-click a target tile for the fireball, or right-click to cancel.', 'blue')
  1067.     (x, y) = target_tile()
  1068.     if x is None: return 'cancelled'
  1069.     message('The fireball explodes, burning everything within ' + str(FIREBALL_RADIUS) + ' tiles!', 'yellow')
  1070.  
  1071.     for obj in objects:  #damage every fighter in range, including the player
  1072.         if obj.distance(x, y) <= FIREBALL_RADIUS and obj.fighter:
  1073.             message('The ' + obj.name + ' gets burned for ' + str(FIREBALL_DAMAGE) + ' hit points.', 'yellow')
  1074.             obj.fighter.take_damage(FIREBALL_DAMAGE)
  1075.  
  1076. def cast_confuse():
  1077.     #ask the player for a target to confuse
  1078.     message('Left-click an enemy to confuse it, or right-click to cancel.', 'blue')
  1079.     monster = target_monster(CONFUSE_RANGE)
  1080.     if monster is None: return 'cancelled'
  1081.  
  1082.     #replace the monster's AI with a "confused" one; after some turns it will restore the old AI
  1083.     old_ai = monster.ai
  1084.     monster.ai = ConfusedMonster(old_ai)
  1085.     monster.ai.owner = monster  #tell the new component who owns it
  1086.     message('The eyes of the ' + monster.name + ' look vacant, as he starts to stumble around!', 'blue')
  1087. #####################################################
  1088.    
  1089. #%%%%%%%%%%%%%%%MENU FUNCTIONS%%%%%%%%%%%%%%%%%%%%%%%
  1090. def save_game():
  1091.     #open a new empty shelve (possibly overwriting an old one) to write the game data
  1092.     file = shelve.open('savegame', 'n')
  1093.     file['map'] = map
  1094.     file['objects'] = objects
  1095.     file['player_index'] = objects.index(player)  #index of player in objects list
  1096.     file['inventory'] = inventory
  1097.     file['game_msgs'] = game_msgs
  1098.     file['game_state'] = game_state
  1099.     file['stairs_index'] = objects.index(stairs)
  1100.     file['dungeon_level'] = dungeon_level
  1101.     file.close()
  1102.    
  1103. def load_game():
  1104.     #open the previously saved shelve and load the game data
  1105.     global map, objects, player, stairs, inventory, game_msgs, game_state, dungeon_level
  1106.  
  1107.     file = shelve.open('savegame', 'r')
  1108.     map = file['map']
  1109.     objects = file['objects']
  1110.     player = objects[file['player_index']]  #get index of player in objects list and access it
  1111.     stairs = objects[file['stairs_index']]  #same for the stairs
  1112.     inventory = file['inventory']
  1113.     game_msgs = file['game_msgs']
  1114.     game_state = file['game_state']
  1115.     dungeon_level = file['dungeon_level']
  1116.     file.close()
  1117.     terminal.clear()
  1118.     initialize_fov()
  1119.  
  1120. def new_game():
  1121.     global player, inventory, game_msgs, game_state, dungeon_level
  1122.  
  1123.     #create object representing the player
  1124.     fighter_component = Fighter(hp=100, defense=1, power=2, xp=0, death_function=player_death)
  1125.     player = Object(0, 0, 17, 'player', blocks=True, fighter=fighter_component)
  1126.  
  1127.     player.level = 1
  1128.  
  1129.     #generate map (at this point it's not drawn to the screen)
  1130.     dungeon_level = 1
  1131.     make_map()
  1132.     initialize_fov()
  1133.  
  1134.     game_state = 'playing'
  1135.     inventory = []
  1136.  
  1137.     #create the list of game messages and their colors, starts empty
  1138.     game_msgs = []
  1139.  
  1140.     #a warm welcoming message!
  1141.     message('You rise, your hair matted with blood, dazed and confused as to where you are.', 'red')
  1142.  
  1143.     #initial equipment: a loincloth
  1144.     equipment_component = Equipment(slot='right hand')
  1145.     obj = Object(0, 0, '-', 'dagger', 'blue', equipment=equipment_component)
  1146.     inventory.append(obj)
  1147.     equipment_component.equip()
  1148.     obj.always_visible = True
  1149.    
  1150. def next_level():
  1151.     #advance to the next level
  1152.     global dungeon_level
  1153.     message('You take a moment to rest, and recover your strength.', 'blue')
  1154.     player.fighter.heal(player.fighter.max_hp / 2)  #heal the player by 50%
  1155.  
  1156.     dungeon_level += 1
  1157.     message('After a rare moment of peace, you descend deeper into the heart of the dungeon...', 'red')
  1158.     make_map()  #create a fresh new level!
  1159.     initialize_fov()
  1160.    
  1161. def initialize_fov():
  1162.     global fov_recompute, fov_map
  1163.     fov_recompute = True
  1164.  
  1165.     #create the FOV map, according to the generated map
  1166.     fov_map = libtcod.map_new(MAP_WIDTH, MAP_HEIGHT)
  1167.     for y in range(MAP_HEIGHT):
  1168.         for x in range(MAP_WIDTH):
  1169.             libtcod.map_set_properties(fov_map, x, y, not map[x][y].block_sight, not map[x][y].blocked)
  1170.        
  1171. def play_game():
  1172.     player_action = None
  1173.  
  1174.     #main loop
  1175.     while True:
  1176.         render_all()
  1177.         check_level_up()
  1178.  
  1179.         #terminal.layer(5)
  1180.         #terminal.put(10, 10, 24) #place individual tile slime: 160:, troll=162: .
  1181.         terminal.refresh()
  1182.  
  1183.         for object in objects:
  1184.             object.clear()
  1185.  
  1186.         player_action = handle_keys()
  1187.         if player_action == 'exit':
  1188.             save_game()
  1189.             break
  1190.  
  1191.         #let monsters take their turn
  1192.         if game_state == 'playing' and player_action != 'didnt-take-turn':
  1193.             for object in objects:
  1194.                 if object.ai:
  1195.                     object.ai.take_turn()
  1196.                    
  1197. def main_menu():
  1198.    
  1199.     while True:
  1200.         terminal.clear()
  1201.         for y in range(50):
  1202.             for x in range(80):
  1203.                 terminal.color(1678238975 - x)
  1204.                 terminal.put(x, y, 20)
  1205.  
  1206.         terminal.refresh()
  1207.  
  1208.         #show the game's title and some credits
  1209.         terminal.layer(6)
  1210.         terminal.color(4294967103)
  1211.         terminal.print_(SCREEN_WIDTH/2 - 15, SCREEN_HEIGHT/2-8, 'GRIT AND STEEL: ATLANTIS RISING')
  1212.         terminal.print_(SCREEN_WIDTH/2, SCREEN_HEIGHT-2, 'By KM')
  1213.  
  1214.         #show options and wait for player's choice
  1215.         choice = menu('', ['Play a new game', 'Continue last game', 'Save & Quit'], 24)
  1216.  
  1217.         if choice == 0:  #new game
  1218.             terminal.clear()
  1219.             new_game()
  1220.             play_game()
  1221.         if choice == 1:  #load last game
  1222.             try:
  1223.                 load_game()
  1224.             except:
  1225.                 msgbox('\n No saved game to load.\n', 24)
  1226.                 continue
  1227.             play_game()
  1228.         elif choice == 2:  #quit
  1229.  
  1230.             terminal.close()
  1231.             break
  1232.            
  1233. #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  1234.        
  1235. #####################################################
  1236. # Initialization & Main Loop
  1237. #####################################################
  1238.  
  1239. #the new starting sequence
  1240. terminal.open()
  1241. terminal.set("window: size=" + str(SCREEN_WIDTH) + "x" + str(SCREEN_HEIGHT) + "; window.title='Grit and Steel: Atlantis Rising'; font: tiles.png, size=16x16; input: filter=[keyboard, mouse_left]")
  1242.  
  1243. main_menu()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement