text: # map_utils.py
  from tdl.map import Map
  from random import randint
  from entity import Entity
  from game_messages import Message
  from item_functions import heal, cast_lightning, cast_fireball, cast_confuse
  from random_utils import from_dungeon_level, random_choice_from_dict
  from render_functions import RenderOrder
  from components.fighter import Fighter
  from components.ai import BasicMonster
  from components.equipment import EquipmentSlots
  from components.equippable import Equippable
  from components.item import Item
  from components.stairs import Stairs
  
  class GameMap(Map):
      def __init__(self, width, height, dungeon_level=1):
          super().__init__(width, height)
          self.explored = [[False for y in range(height)] for x in range(width)]
  
          self.dungeon_level = dungeon_level
  
  class Rect():
      def __init__(self, x, y, w, h):
          self.x1 = x
          self.y1 = y
          self.x2 = x + w
          self.y2 = y + h
  
      def center(self):
          center_x = int((self.x1 + self.x2)/2)
          center_y = int((self.y1 + self.y2)/2)
          return (center_x, center_y)
  
      def intersect(self, other):
          # returns true if this rectangle intersects with another one
          return (self.x1 <= other.x2 and self.x2 >= other.x1 and
                  self.y1 <= other.y2 and self.y2 >= other.y1)
  
  def create_room(game_map, room):
          for x in range(room.x1 + 1, room.x2):
              for y in range(room.y1 + 1, room.y2):
                  game_map.walkable[x, y] = True
                  game_map.transparent[x, y] = True
  
  def create_h_tunnel(game_map, x1, x2, y):
      for x in range(min(x1, x2), max(x1, x2) + 1):
          game_map.walkable[x, y] = True
          game_map.transparent[x, y] = True
  
  def create_v_tunnel(game_map, y1, y2, x):
      for y in range(min(y1, y2), max(y1, y2) +1):
          game_map.walkable[x, y] = True
          game_map.transparent[x, y] = True
  
  def place_entities(room, entities, dungeon_level, colors):
      # get random number of monsters
      max_monsters_per_room = from_dungeon_level([[1, 1], [2, 2], [3, 4], [5, 6]], dungeon_level)
      max_items_per_room = from_dungeon_level([[1, 1], [2, 4]], dungeon_level)
  
      number_of_monsters = randint(0, max_monsters_per_room)
      number_of_items = randint(0, max_items_per_room)
  
      monster_chances = {
          'orc': 80,
           'troll': from_dungeon_level([[5, 3], [30, 5], [60, 7]], dungeon_level)
      }
      item_chances = {
          'healing_potion': 35,
          'sword': from_dungeon_level([[5, 4]], dungeon_level),
          'shield': from_dungeon_level([[15, 8]], dungeon_level),
          'lightning_scroll': from_dungeon_level([[25, 4]], dungeon_level),
          'fireball_scroll': from_dungeon_level([[25, 6]], dungeon_level),
          'confusion_scroll': from_dungeon_level([[10, 2]], dungeon_level)
      }
  
      for i in range(number_of_monsters):
          x = randint(room.x1 + 1, room.x2 - 1)
          y = randint(room.y1 + 1, room.y2 -1)
  
          if not any ([entity for entity in entities if entity.x ==x and entity.y == y]):
              monster_choice = random_choice_from_dict(monster_chances)
              if monster_choice == 'orc':
                  fighter_component = Fighter(hp=20, defense=0, power=4, xp=35)
                  ai_component = BasicMonster()
                  monster = Entity(x, y, 'o', colors.get('desaturated_green'), 'Orc',
                   blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component)
              else:
                  fighter_component = Fighter(hp=30, defense=2, power=8, xp=100)
                  ai_component = BasicMonster()
                  monster = Entity(x, y, 'T', colors.get('darker_green'), 'Troll',
                   blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component)
              entities.append(monster)
      
      for i in range(number_of_items):
          x = randint(room.x1 + 1, room.x2 - 1)
          y = randint(room.y1 + 1, room.y2 - 1)
  
          if not any([entity for entity in entities if entity.x == x and entity.y == y]):
              item_chance = random_choice_from_dict(item_chances)
  
              if item_chance == 'healing_potion':
                  item_component = Item(use_function=heal, amount=40)
                  item = Entity(x, y, '!', colors.get('violet'), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component)
              elif item_chance == 'sword':
                  equippable_component = Equippable(EquipmentSlots.MAIN_HAND, power_bonus=3)
                  item = Entity(x, y, '/', colors.get('sky'), 'Sword', equippable=equippable_component)
              elif item_chance == 'shield':
                  equippable_component = Equippable(EquipmentSlots.OFF_HAND, defense_bonus=1)
                  item = Entity(x, y, '[', colors.get('darker_orange'), 'Shield', equippable=equippable_component)
              elif item_chance == 'fireball_scroll':
                  item_component = Item(use_function=cast_fireball, targeting=True, targeting_message=Message('Left-click a target, or right click to cancel.', colors.get('light_cyan')), damage=25, radius=3)
                  item = Entity(x, y, '#', colors.get('red'), 'Fireball Scroll', render_order=RenderOrder.ITEM, item=item_component)
              elif item_chance == 'confusion_scroll':
                  item_component = Item(use_function=cast_confuse, targeting=True, targeting_message=Message('Left-click a target, or right click to cancel.', colors.get('light_cyan')))
                  item = Entity(x, y, '#', colors.get('light_pink'), 'Confusion Scroll', render_order=RenderOrder.ITEM, item=item_component)
              else:
                  item_component = Item(use_function=cast_lightning, damage=40, maximum_range=5)
                  item = Entity(x, y, '#', colors.get('yellow'), 'Lightning Scroll', render_order=RenderOrder.ITEM, item=item_component)
              entities.append(item)
  
  def make_map(game_map, max_rooms, room_min_size, room_max_size, map_width,
   map_height, screen_width, screen_height, player, entities, colors):
      print("map width: {0}, map height: {1}".format(map_width, map_height))
  
      rooms = []
      num_rooms = 0
  
      center_of_last_room_x = None
      center_of_last_room_y = None
  
      for r in range(max_rooms):
          w = randint(room_min_size, room_max_size)
          h = randint(room_min_size, room_max_size)
          x = randint(screen_width/2, map_width - w - 1 - screen_width/2)
          y = randint(screen_height/2, map_height - h - 1 - screen_height/2)
  
          # use "Rect" class to make new rooms
          new_room = Rect(x, y, w, h)
  
          # run through the other rooms and see if they intersect
          for other_room in rooms:
              if new_room.intersect(other_room):
                  break
          else:
              create_room(game_map, new_room)
  
              (new_x,new_y) = new_room.center()
  
              center_of_last_room_x = new_x
              center_of_last_room_y = new_y
  
              if num_rooms == 0:
                  # if first room, place player in the center
                  player.x = new_x
                  player.y = new_y
              else:
                  # all other rooms need to connect to previous room
                  (prev_x, prev_y) = rooms[num_rooms - 1].center()
  
                  if randint(0,1) == 1:
                      create_h_tunnel(game_map, prev_x, new_x, prev_y)
                      create_v_tunnel(game_map, prev_y, new_y, new_x)
                  else:
                      create_v_tunnel(game_map, prev_y, new_y, prev_x)
                      create_h_tunnel(game_map, prev_x, new_x, new_y)
              
              place_entities(new_room, entities, game_map.dungeon_level, colors)
  
              rooms.append(new_room)
              num_rooms += 1
  
      stairs_component = Stairs(game_map.dungeon_level + 1)
      down_stairs = Entity(center_of_last_room_x, center_of_last_room_y, '>', (255, 255, 255), 'Stairs', render_order=RenderOrder.STAIRS, stairs=stairs_component)
      entities.append(down_stairs)
  
  def next_floor(player, message_log, dungeon_level, constants):
      game_map = GameMap(constants['map_width'], constants['map_height'], dungeon_level)
      entities = [player]
  
      make_map(game_map, constants['max_rooms'], constants['room_min_size'],
               constants['room_max_size'], constants['map_width'], constants['map_height'], constants['screen_width'], constants['screen_height'], player,
               entities, constants['colors'])
  
      player.fighter.heal(player.fighter.max_hp // 2)
  
      message_log.add_message(Message('You take a short rest to recover your strength.', constants['colors'].get('light_violet)')))
  
      return game_map, entities