Pastebin launched a little side project called VERYVIRAL.com, check it out ;-) Want more features on Pastebin? Sign Up, it's FREE!
Guest

Fistmaker Yes! Stairs almost?!

By: a guest on Sep 9th, 2010  |  syntax: Python  |  size: 20.62 KB  |  views: 83  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. import libtcodpy as libtcod
  2. import math
  3. import textwrap
  4.  
  5. #actual size of the window was 75/40
  6. SCREEN_WIDTH = 75
  7. SCREEN_HEIGHT = 40
  8.  
  9. #size of the map 75/23
  10. MAP_WIDTH = 75
  11. MAP_HEIGHT = 23
  12.  
  13. #sizes and coordinates relevant for the GUI
  14. BAR_WIDTH = 20
  15. MSG_X = BAR_WIDTH + 2
  16. MSG_WIDTH = SCREEN_WIDTH - BAR_WIDTH - 2
  17. PANEL_HEIGHT = 6
  18. PANEL_Y = SCREEN_HEIGHT - PANEL_HEIGHT
  19.  
  20. #parameters for dungeon generator
  21. ROOM_MAX_SIZE = 8
  22. ROOM_MIN_SIZE = 6
  23. MAX_ROOMS = 30
  24. MAX_ROOM_MONSTERS = 3
  25.  
  26.  
  27. FOV_ALGO = 0  #default FOV algorithm
  28. FOV_LIGHT_WALLS = True  #light walls or not
  29. TORCH_RADIUS = 10
  30.  
  31. LIMIT_FPS = 20  #20 frames-per-second maximum
  32.  
  33.  
  34. color_dark_wall = libtcod.Color(0, 0, 100)
  35. color_light_wall = libtcod.Color(130, 110, 50)
  36. color_dark_ground = libtcod.Color(50, 50, 150)
  37. color_light_ground = libtcod.Color(200, 180, 50)
  38.  
  39.  
  40.  
  41. class Tile:
  42.     #a tile of the map and its properties
  43.     def __init__(self, blocked, block_sight = None):
  44.         self.blocked = blocked
  45.  
  46.         #all tiles start unexplored
  47.         self.explored = False
  48.  
  49.         #by default, if a tile is blocked, it also blocks sight
  50.         if block_sight is None: block_sight = blocked
  51.         self.block_sight = block_sight
  52.  
  53. class Rect:
  54.     #a rectangle on the map. used to characterize a room.
  55.     def __init__(self, x, y, w, h):
  56.         self.x1 = x
  57.         self.y1 = y
  58.         self.x2 = x + w
  59.         self.y2 = y + h
  60.  
  61.     def center(self):
  62.         center_x = (self.x1 + self.x2) / 2
  63.         center_y = (self.y1 + self.y2) / 2
  64.         return (center_x, center_y)
  65.  
  66.     def intersect(self, other):
  67.         #returns true if this rectangle intersects with another one
  68.         return (self.x1 <= other.x2 and self.x2 >= other.x1 and
  69.                 self.y1 <= other.y2 and self.y2 >= other.y1)
  70.  
  71. class Object:
  72.     #this is a generic object: the player, a monster, an item, the stairs...
  73.     #it's always represented by a character on screen.
  74.     def __init__(self, x, y, char, name, color, blocks=False, fighter=None, ai=None):
  75.         self.x = x
  76.         self.y = y
  77.         self.char = char
  78.         self.name = name
  79.         self.color = color
  80.         self.blocks = blocks
  81.         self.fighter = fighter
  82.         if self.fighter:  #let the fighter component know who owns it
  83.             self.fighter.owner = self
  84.  
  85.         self.ai = ai
  86.         if self.ai:  #let the AI component know who owns it
  87.             self.ai.owner = self
  88.  
  89.     def move(self, dx, dy):
  90.         #move by the given amount, if the destination is not blocked
  91.         if not is_blocked(self.x + dx, self.y + dy):
  92.             self.x += dx
  93.             self.y += dy
  94.  
  95.     def move_towards(self, target_x, target_y):
  96.         #vector from this object to the target, and distance
  97.         dx = target_x - self.x
  98.         dy = target_y - self.y
  99.         distance = math.sqrt(dx ** 2 + dy ** 2)
  100.  
  101.         #normalize it to length 1 (preserving direction), then round it and
  102.         #convert to integer so the movement is restricted to the map grid
  103.         dx = int(round(dx / distance))
  104.         dy = int(round(dy / distance))
  105.         self.move(dx, dy)
  106.  
  107.     def distance_to(self, other):
  108.         #return the distance to another object
  109.         dx = other.x - self.x
  110.         dy = other.y - self.y
  111.         return math.sqrt(dx ** 2 + dy ** 2)
  112.  
  113.     def send_to_back(self):
  114.         #make this object be drawn first, so all others appear above it if they're in the same tile.
  115.         global objects
  116.         objects.remove(self)
  117.         objects.insert(0, self)
  118.  
  119.     def draw(self):
  120.         #only show if it's visible to the player
  121.         if libtcod.map_is_in_fov(fov_map, self.x, self.y):
  122.             #set the color and then draw the character that represents this object at its position
  123.             libtcod.console_set_foreground_color(con, self.color)
  124.             libtcod.console_put_char(con, self.x, self.y, self.char, libtcod.BKGND_NONE)
  125.  
  126.     def clear(self):
  127.         #erase the character that represents this object
  128.         libtcod.console_put_char(con, self.x, self.y, ' ', libtcod.BKGND_NONE)
  129.  
  130. class Stairs:
  131.     def __init__(char, stairsx, stairsy):
  132.         char='>'
  133.         stairsx=x
  134.         stairsy=y
  135.        
  136. class Fighter:
  137.     #combat-related properties and methods (monster, player, NPC).
  138.     def __init__(self, hp, defense, power, xp, death_function=None):
  139.         self.max_hp = hp
  140.         self.hp = hp
  141.         self.defense = defense
  142.         self.power = power
  143.         self.death_function = death_function
  144.         self.xp = xp
  145.        
  146.  
  147.     def attack(self, target):
  148.         #a simple formula for attack damage
  149.         damage = self.power - target.fighter.defense
  150.  
  151.         if damage > 0:
  152.             #make the target take some damage
  153.             message(self.owner.name.capitalize() + ' attacks ' + target.name + ' for ' + str(damage) + ' hit points.')
  154.             target.fighter.take_damage(damage)
  155.             #xp=xp+1
  156.         else:
  157.             message(self.owner.name.capitalize() + ' attacks ' + target.name + ' but it has no effect!')
  158.  
  159.     def take_damage(self, damage):
  160.         #apply damage if possible
  161.         if damage > 0:
  162.             self.hp -= damage
  163.  
  164.             #check for death. if there's a death function, call it
  165.             if self.hp <= 0:
  166.                 function = self.death_function
  167.                 if function is not None:
  168.                     function(self.owner)
  169.  
  170. class BasicMonster:
  171.     #AI for a basic monster.
  172.     def take_turn(self):
  173.         #a basic monster takes its turn. if you can see it, it can see you
  174.         monster = self.owner
  175.         if libtcod.map_is_in_fov(fov_map, monster.x, monster.y):
  176.  
  177.             #move towards player if far away
  178.             if monster.distance_to(player) >= 2:
  179.                 monster.move_towards(player.x, player.y)
  180.  
  181.             #close enough, attack! (if the player is still alive.)
  182.             elif player.fighter.hp > 0:
  183.                 monster.fighter.attack(player)
  184.  
  185.  
  186. def is_blocked(x, y):
  187.     #first test the map tile
  188.     if map[x][y].blocked:
  189.         return True
  190.  
  191.     #now check for any blocking objects
  192.     for object in objects:
  193.         if object.blocks and object.x == x and object.y == y:
  194.             return True
  195.  
  196.     return False
  197.  
  198. def create_room(room):
  199.     global map
  200.     #go through the tiles in the rectangle and make them passable
  201.     for x in range(room.x1 + 1, room.x2):
  202.         for y in range(room.y1 + 1, room.y2):
  203.             map[x][y].blocked = False
  204.             map[x][y].block_sight = False
  205.  
  206. def create_h_tunnel(x1, x2, y):
  207.     global map
  208.     #horizontal tunnel. min() and max() are used in case x1>x2
  209.     for x in range(min(x1, x2), max(x1, x2) + 1):
  210.         map[x][y].blocked = False
  211.         map[x][y].block_sight = False
  212.  
  213. def create_v_tunnel(y1, y2, x):
  214.     global map
  215.     #vertical tunnel
  216.     for y in range(min(y1, y2), max(y1, y2) + 1):
  217.         map[x][y].blocked = False
  218.         map[x][y].block_sight = False
  219.  
  220. def make_map():
  221.     global map, player, stairsx, stairsy
  222.  
  223.     #fill map with "blocked" tiles
  224.     map = [[ Tile(True)
  225.         for y in range(MAP_HEIGHT) ]
  226.             for x in range(MAP_WIDTH) ]
  227.  
  228.     rooms = []
  229.     num_rooms = 0
  230.  
  231.     for r in range(MAX_ROOMS):
  232.         #random width and height
  233.         w = libtcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)
  234.         h = libtcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)
  235.         #random position without going out of the boundaries of the map
  236.         x = libtcod.random_get_int(0, 0, MAP_WIDTH - w - 1)
  237.         y = libtcod.random_get_int(0, 0, MAP_HEIGHT - h - 1)
  238.  
  239.         #"Rect" class makes rectangles easier to work with
  240.         new_room = Rect(x, y, w, h)
  241.  
  242.         #run through the other rooms and see if they intersect with this one
  243.         failed = False
  244.         for other_room in rooms:
  245.             if new_room.intersect(other_room):
  246.                 failed = True
  247.                 break
  248.  
  249.         if not failed:
  250.             #this means there are no intersections, so this room is valid
  251.  
  252.             #"paint" it to the map's tiles
  253.             create_room(new_room)
  254.  
  255.             #add some contents to this room, such as monsters
  256.             place_objects(new_room)
  257.  
  258.             #center coordinates of new room, will be useful later
  259.             (new_x, new_y) = new_room.center()
  260.  
  261.             if num_rooms == 0:
  262.                 #this is the first room, where the player starts at
  263.                 player.x = new_x
  264.                 player.y = new_y
  265.             else:
  266.                 #all rooms after the first:
  267.                 #connect it to the previous room with a tunnel
  268.  
  269.                 #center coordinates of previous room
  270.                 (prev_x, prev_y) = rooms[num_rooms-1].center()
  271.  
  272.                 #draw a coin (random number that is either 0 or 1)
  273.                 if libtcod.random_get_int(0, 0, 1) == 1:
  274.                     #first move horizontally, then vertically
  275.                     create_h_tunnel(prev_x, new_x, prev_y)
  276.                     create_v_tunnel(prev_y, new_y, new_x)
  277.                 else:
  278.                     #first move vertically, then horizontally
  279.                     create_v_tunnel(prev_y, new_y, prev_x)
  280.                     create_h_tunnel(prev_x, new_x, new_y)
  281.  
  282.             #finally, append the new room to the list
  283.             rooms.append(new_room)
  284.             num_rooms += 1
  285.                 #after that, place the stairs at the last room
  286.             stairsx = new_x
  287.             stairsy = new_y
  288.  
  289.  
  290. def place_objects(room):
  291.     #choose random number of monsters
  292.     num_monsters = libtcod.random_get_int(0, 0, MAX_ROOM_MONSTERS)
  293.  
  294.     for i in range(num_monsters):
  295.         #choose random spot for this monster
  296.         x = libtcod.random_get_int(0, room.x1, room.x2)
  297.         y = libtcod.random_get_int(0, room.y1, room.y2)
  298.  
  299.         #only place it if the tile is not blocked
  300.         if not is_blocked(x, y):
  301.  
  302.             # create stairs
  303.             if libtcod.random_get_int(0, 0, 100) < 100:
  304.                 stairs= Object(x,y, '>', 'stairs', libtcod.grey, blocks=False)
  305.            
  306.                
  307.             if libtcod.random_get_int(0, 0, 100) < 80:  #80% chance of getting an orc
  308.                 #create an orc
  309.                 fighter_component = Fighter(hp=10, defense=0, power=3, xp=0, death_function=monster_death)
  310.                 ai_component = BasicMonster()
  311.  
  312.                 monster = Object(x, y, '#', 'orc', libtcod.desaturated_green,
  313.                     blocks=True, fighter=fighter_component, ai=ai_component)
  314.             else:
  315.                 #create a troll
  316.                 fighter_component = Fighter(hp=16, defense=1, power=4, xp=0, death_function=monster_death)
  317.                 ai_component = BasicMonster()
  318.  
  319.                 monster = Object(x, y, '&', 'troll', libtcod.darker_green,
  320.                     blocks=True, fighter=fighter_component, ai=ai_component)
  321.  
  322.             objects.append(monster)
  323.             objects.append(stairs)
  324.  
  325.  
  326. def render_bar(x, y, total_width, name, value, maximum, bar_color, back_color):
  327.     #render a bar (HP, experience, etc). first calculate the width of the bar
  328.     bar_width = int(float(value) / maximum * total_width)
  329.  
  330.     #render the background first
  331.     libtcod.console_set_background_color(panel, back_color)
  332.     libtcod.console_rect(panel, x, y, total_width, 1, False)
  333.  
  334.     #now render the bar on top
  335.     libtcod.console_set_background_color(panel, bar_color)
  336.     if bar_width > 0:
  337.         libtcod.console_rect(panel, x, y, bar_width, 1, False)
  338.  
  339.     #finally, some centered text with the values
  340.     libtcod.console_set_foreground_color(panel, libtcod.black)
  341.     libtcod.console_print_center(panel, x + total_width / 2, y, libtcod.BKGND_NONE,
  342.         name + ': ' + str(value) + '/' + str(maximum))
  343.     #libtcod.console_print_left(0, 1, SCREEN_HEIGHT - 1, libtcod.BKGND_NONE,
  344.         #'XP: ' + str(xp))
  345.  
  346.  
  347. def render_all():
  348.     global fov_map, color_dark_wall, color_light_wall
  349.     global color_dark_ground, color_light_ground
  350.     global fov_recompute
  351.  
  352.     if fov_recompute:
  353.         #recompute FOV if needed (the player moved or something)
  354.         fov_recompute = False
  355.         libtcod.map_compute_fov(fov_map, player.x, player.y, TORCH_RADIUS, FOV_LIGHT_WALLS, FOV_ALGO)
  356.  
  357.         #go through all tiles, and set their background color according to the FOV
  358.         for y in range(MAP_HEIGHT):
  359.             for x in range(MAP_WIDTH):
  360.                 visible = libtcod.map_is_in_fov(fov_map, x, y)
  361.                 wall = map[x][y].block_sight
  362.                 if not visible:
  363.                     #if it's not visible right now, the player can only see it if it's explored
  364.                     if map[x][y].explored:
  365.                         if wall:
  366.                             libtcod.console_set_back(con, x, y, color_dark_wall, libtcod.BKGND_SET)
  367.                         else:
  368.                             libtcod.console_set_back(con, x, y, color_dark_ground, libtcod.BKGND_SET)
  369.                 else:
  370.                     #it's visible
  371.                     if wall:
  372.                         libtcod.console_set_back(con, x, y, color_light_wall, libtcod.BKGND_SET )
  373.                     else:
  374.                         libtcod.console_set_back(con, x, y, color_light_ground, libtcod.BKGND_SET )
  375.                     #since it's visible, explore it
  376.                     map[x][y].explored = True
  377.  
  378.     #draw all objects in the list, except the player. we want it to
  379.     #always appear over all other objects! so it's drawn later.
  380.     for object in objects:
  381.         if object != player:
  382.             object.draw()
  383.     player.draw()
  384.  
  385.     #blit the contents of "con" to the root console
  386.     libtcod.console_blit(con, 0, 0, MAP_WIDTH, MAP_HEIGHT, 0, 0, 0)
  387.  
  388.  
  389.     #prepare to render the GUI panel
  390.     libtcod.console_set_background_color(panel, libtcod.black)
  391.     libtcod.console_clear(panel)
  392.  
  393.     #print the game messages, one line at a time
  394.     y = 0
  395.     for (line, color) in game_msgs:
  396.         libtcod.console_set_foreground_color(panel, color)
  397.         libtcod.console_print_left(panel, MSG_X, y, libtcod.BKGND_NONE, line)
  398.         y += 1
  399.  
  400.     #show the player's stats
  401.     render_bar(1, 0, BAR_WIDTH, 'HP', player.fighter.hp, player.fighter.max_hp,
  402.         libtcod.light_red, libtcod.darker_red)
  403.     #render_bar(2, 0, BAR_WIDTH, 'MP', player.fighter.mp, player.fighter.mp,
  404.     #    libtcod.light_blue, libtcod.darker_blue)
  405.  
  406.     #blit the contents of "panel" to the root console
  407.     libtcod.console_blit(panel, 0, 0, MAP_WIDTH, PANEL_HEIGHT, 0, 0, SCREEN_HEIGHT-PANEL_HEIGHT)
  408.  
  409.  
  410. def message(new_msg, color = libtcod.white):
  411.     #split the message if necessary, among multiple lines
  412.     new_msg_lines = textwrap.wrap(new_msg, MSG_WIDTH)
  413.  
  414.     for line in new_msg_lines:
  415.         #if the buffer is full, remove the first line to make room for the new one
  416.         if len(game_msgs) == PANEL_HEIGHT:
  417.             del game_msgs[0]
  418.  
  419.         #add the new line as a tuple, with the text and the color
  420.         game_msgs.append( (line, color) )
  421.  
  422.  
  423. def player_move_or_attack(dx, dy):
  424.     global fov_recompute
  425.    
  426.     #the coordinates the player is moving to/attacking
  427.     x = player.x + dx
  428.     y = player.y + dy
  429.  
  430.  
  431.    
  432.    
  433.     #try to find an attackable object there
  434.     target = None
  435.     for object in objects:
  436.         if object.fighter and object.x == x and object.y == y:
  437.             target = object
  438.             break
  439.     #stairs awareness check
  440.         if object.color is libtcod.grey and object.x == x and object.y == y:
  441.             message('you found stairs! Make a new map!')
  442.             libtcod.console_clear(con)
  443.             make_map()
  444.            
  445.                    
  446.             # put new map generation here!
  447.            
  448.  
  449.            
  450.     #attack if target found, move otherwise
  451.     if target is not None:
  452.         player.fighter.attack(target)
  453.     else:
  454.         player.move(dx, dy)
  455.         fov_recompute = True
  456.  
  457.  
  458. def handle_keys():
  459.     #key = libtcod.console_check_for_keypress()  #real-time
  460.     key = libtcod.console_wait_for_keypress(True)  #turn-based
  461.  
  462.     if key.vk == libtcod.KEY_ENTER and key.lalt:
  463.         #Alt+Enter: toggle fullscreen
  464.         libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
  465.  
  466.     elif key.vk == libtcod.KEY_ESCAPE:
  467.         return 'exit'  #exit game
  468.  
  469.     if key.vk == libtcod.KEY_SPACE:
  470.         if player.fighter.hp < 1:
  471.             message('You are dead. Stop trying. Really.')
  472.             return      
  473.         player.fighter.hp = player.fighter.hp + 1
  474.         #print 'You Rested to gain Health!'
  475.         message('You Rested to gain Health!', libtcod.white)
  476.                
  477.         if player.fighter.hp > player.fighter.max_hp:
  478.             player.fighter.hp = player.fighter.max_hp
  479.             #print 'But your HP was Full!'
  480.             message('But your HP was full!', libtcod.white)
  481.         return 'Rested'
  482.     if key.vk == libtcod.KEY_TAB:
  483.         message('Current XP '+str (player.fighter.xp), libtcod.white)
  484.         message('Power '+str (player.fighter.power), libtcod.white)
  485.         message('Defense '+str (player.fighter.defense), libtcod.white)
  486.                
  487.        
  488.     if game_state == 'playing':
  489.         #movement keys
  490.         if libtcod.console_is_key_pressed(libtcod.KEY_UP):
  491.             player_move_or_attack(0, -1)
  492.  
  493.         elif libtcod.console_is_key_pressed(libtcod.KEY_DOWN):
  494.             player_move_or_attack(0, 1)
  495.  
  496.         elif libtcod.console_is_key_pressed(libtcod.KEY_LEFT):
  497.             player_move_or_attack(-1, 0)
  498.  
  499.         elif libtcod.console_is_key_pressed(libtcod.KEY_RIGHT):
  500.             player_move_or_attack(1, 0)
  501.         else:
  502.             return 'didnt-take-turn'
  503.  
  504. def level_up(player):
  505.     if player.fighter.xp > 2:
  506.         player.fighter.max_hp=player.fighter.max_hp + 5
  507.         player.fighter.power=player.fighter.power + 1
  508.         #player.hp=player.hp + 1
  509.         player.fighter.xp = 0
  510.  
  511.  
  512. def player_death(player):
  513.     #the game ended!
  514.     global game_state
  515.     message('You died!', libtcod.red)
  516.     game_state = 'dead'
  517.  
  518.     #for added effect, transform the player into a corpse!
  519.     player.char = '%'
  520.     player.color = libtcod.dark_red
  521.  
  522. def monster_death(monster):
  523.     global xp
  524.     #transform it into a nasty corpse! it doesn't block, can't be
  525.     #attacked and doesn't move
  526.     player.fighter.xp=player.fighter.xp + 1
  527.     message(monster.name.capitalize() + ' is dead!')
  528.     monster.char = '%'
  529.     monster.color = libtcod.dark_red
  530.     monster.blocks = False
  531.     monster.fighter = None
  532.     monster.ai = None
  533.     monster.name = 'remains of ' + monster.name
  534.     monster.send_to_back()
  535.  
  536.  
  537. #############################################
  538. # Initialization & Main Loop
  539. #############################################
  540.  
  541. libtcod.console_set_custom_font('Ae.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_ASCII_INROW, 16,16)
  542. libtcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, 'Try not to Die!', False)
  543. libtcod.sys_set_fps(LIMIT_FPS)
  544. con = libtcod.console_new(MAP_WIDTH, MAP_HEIGHT)
  545. panel = libtcod.console_new(SCREEN_WIDTH, PANEL_HEIGHT)
  546.  
  547. #create object representing the player
  548. fighter_component = Fighter(hp=30, defense=2, power=5, xp= 0, death_function=player_death)
  549. player = Object(0, 0, '@', 'player', libtcod.white, blocks=True, fighter=fighter_component)
  550.  
  551. #the list of objects with just the player
  552. objects = [player]
  553.  
  554. #generate map (at this point it's not drawn to the screen)
  555. make_map()
  556.  
  557. #create the FOV map, according to the generated map
  558. fov_map = libtcod.map_new(MAP_WIDTH, MAP_HEIGHT)
  559. for y in range(MAP_HEIGHT):
  560.     for x in range(MAP_WIDTH):
  561.         libtcod.map_set_properties(fov_map, x, y, not map[x][y].blocked, not map[x][y].block_sight)
  562.  
  563. fov_recompute = True
  564. first_time = True  #for turn-based games
  565. game_state = 'playing'
  566. player_action = None
  567.  
  568. #create the list of game messages and their colors, starts empty
  569. game_msgs = []
  570.  
  571. #a warm welcoming message!
  572. message('Welcome stranger! Prepare to battle Glitches and Poor programming Skills!', libtcod.red)
  573.  
  574.  
  575. while not libtcod.console_is_window_closed():
  576.  
  577.     #erase all objects at their old locations, before they move
  578.     for object in objects:
  579.         object.clear()
  580.  
  581.     #handle keys and exit game if needed
  582.     if not first_time:  #for turn-based games
  583.         player_action = handle_keys()
  584.         xp=0
  585.         if player_action == 'exit':
  586.             break
  587.     first_time = False  #for turn-based games
  588.  
  589.     #let monsters take their turn
  590.     if game_state == 'playing' and player_action != 'didnt-take-turn':
  591.         for object in objects:
  592.             if object.ai:
  593.                 object.ai.take_turn()
  594.  
  595.     #render the screen
  596.     render_all()
  597.     #level up if possible
  598.     level_up(player)
  599.    
  600.     libtcod.console_flush()