Complete Roguelike Tutorial, using python3+libtcod, part 7 code

From RogueBasin
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

GUI

Status Bars

import libtcodpy as tcod
import math

# Window Size
SCREEN_WIDTH = 80
SCREEN_HEIGHT = 50

# Map Size
MAP_WIDTH = 80
MAP_HEIGHT = 43

# 20 FPS Max
LIMIT_FPS = 20

# Dungeon generator parameters
ROOM_MAX_SIZE = 10
ROOM_MIN_SIZE = 6
MAX_ROOMS = 30
MAX_ROOM_MONSTERS = 2

# FOV parameters
FOV_ALGO = 0
FOV_LIGHT_WALLS = True
TORCH_RADIUS = 10

# GUI params
BAR_WIDTH = 20
PANEL_HEIGHT = 7
PANEL_Y = SCREEN_HEIGHT - PANEL_HEIGHT

# Tile colors
color_dark_wall = tcod.Color(0, 0, 100)
color_light_wall = tcod.Color(130, 110, 50)
color_dark_ground = tcod.Color(50, 50, 150)
color_light_ground = tcod.Color(200, 180, 50)

FULLSCREEN = False
TURN_BASED = True


class Tile:
    # Defines tiles on the map

    def __init__(self, blocked, block_sight=None):
        self.blocked = blocked

        # Tiles start unexplored
        self.explored = False

        # default if tile blocked, then also blocks sight
        if block_sight is None: 
            block_sight = blocked

        self.block_sight = block_sight


class Rect:
    # Define rooms on the map

    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 = (self.x1 + self.x2) // 2
        center_y = (self.y1 + self.y2) // 2
        return (center_x, center_y)

    def intersect(self, other):
        # True if this rectange intersect another rectangle
        return (self.x1 <= other.x2 and self.x2 >= other.x1 and self.y1 <= other.y2 and self.y2 >= other.y1)
               

class Object:
    # Base Class for objects, player, npc, item, stairs

    def __init__(self, x, y, char, name, color, blocks=False, fighter=None, ai=None):
        self.x = x
        self.y = y
        self.char = char
        self.name = name
        self.color = color
        self.blocks = blocks

        self.fighter = fighter
        if self.fighter:
            self.fighter.owner = self

        self.ai = ai
        if self.ai:
            self.ai.owner = self

    def move(self, dx, dy):
        # move given amount if destination not blocked
        if not is_blocked(self.x + dx, self.y + dy):
            self.x += dx
            self.y += dy

    def move_toward(self, target_x, target_y):
        # Vector and distance
        dx = target_x - self.x
        dy = target_y - self.y
        distance = math.sqrt(dx ** 2 + dy ** 2)

        # Nomalize
        dx = int(round(dx / distance))
        dy = int(round(dy / distance))
        self.move(dx, dy)

    def distance_to(self, other):
        # Distance
        dx = other.x - self.x
        dy = other.y - self.y
        return math.sqrt(dx ** 2 + dy ** 2)

    def send_to_back(self):
        # Draw first so others appear above it if on same tile.
        global objects
        objects.remove(self)
        objects.insert(0, self)

    def draw(self):
        # Set color and draw char at its position
        tcod.console_set_default_foreground(con, self.color)
        tcod.console_put_char(con, self.x, self.y, self.char, tcod.BKGND_NONE)

    def clear(self):
        # Erase char representing object
        tcod.console_put_char(con, self.x, self.y, ' ', tcod.BKGND_NONE)


class Fighter:
    # Combat related properties and methods
    def __init__(self, hp, defense, power, death_function=None):
        self.max_hp = hp
        self.hp = hp
        self.defense = defense
        self.power = power
        self.death_function = death_function

    def attack(self, target):
        # Formulate attack damage
        damage = self.power - target.fighter.defense

        if damage > 0:
            # Make target take damage
            print(self.owner.name.capitalize() + ' attacks ' + target.name + ' for ' + str(damage) + ' hit points.')
            target.fighter.take_damage(damage)
        else:
            print(self.owner.name.capitalize() + ' attacks ' + target.name + ' but it has no effect!')

    def take_damage(self, damage):
        # Apply damage
        if damage > 0:
            self.hp -= damage

            # Check for death
            if self.hp <= 0:
                function = self.death_function
                if function is not None:
                    function(self.owner)


class BasicMonster:
    # Basic AI
    def take_turn(self):

        # If you can see it, it can see you
        monster = self.owner
        if tcod.map_is_in_fov(fov_map, monster.x, monster.y):

            # Move toward player
            if monster.distance_to(player) >= 2:
                monster.move_toward(player.x, player.y)

            # Close enough to attack
            elif player.fighter.hp > 0:
                monster.fighter.attack(player)


def is_blocked(x, y):
    # Check tile
    if map[x][y].blocked:
        return True

    # Check for blocking objects
    for object in objects:
        if object.blocks and object.x == x and object.y == y:
            return True
    
    # Clear
    return False


def create_room(room):
    global map
    # Make room tiles passable
    for x in range(room.x1 + 1, room.x2):
        for y in range(room.y1 + 1, room.y2):
            map[x][y].blocked = False
            map[x][y].block_sight = False


def create_h_tunnel(x1, x2, y):
    global map
    # Make a horizontal tunnel
    for x in range(min(x1, x2), max(x1, x2) + 1):
        map[x][y].blocked = False
        map[x][y].block_sight = False


def create_v_tunnel(y1, y2, x):
    global map
    # Make a vertical tunnel
    for y in range(min(y1, y2), max(y1, y2) + 1):
        map[x][y].blocked = False
        map[x][y].block_sight = False


def make_map():
    global map, player

    # Fill map with blocked tiles
    map = [[Tile(True) 
           for y in range(MAP_HEIGHT)] 
           for x in range(MAP_WIDTH)]
    
    rooms = []
    num_rooms = 0

    for r in range(MAX_ROOMS):        
        
        # Random width and height
        w = tcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)
        h = tcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)

        # Random position in map
        x = tcod.random_get_int(0, 0, MAP_WIDTH - w - 1)
        y = tcod.random_get_int(0, 0, MAP_HEIGHT - h - 1)

        # Rect class = Easy rooms
        new_room = Rect(x, y, w, h)

        # Check for intersect
        failed = False
        for other_room in rooms:
            if new_room.intersect(other_room):
                failed = True
                break

        if not failed:
            # Valid room
            
            # Create it
            create_room(new_room)

            # New room's center
            (new_x, new_y) = new_room.center()

            if num_rooms == 0:
                # player starts at cent of new room
                player.x = new_x
                player.y = new_y

            else:
                # Connect to previous room

                # Previous room's center
                (prev_x, prev_y) = rooms[num_rooms - 1].center()

                # 50% chance
                if tcod.random_get_int(0, 0, 1) == 1:
                    
                    # first horizontal
                    create_h_tunnel(prev_x, new_x, prev_y)
                    create_v_tunnel(prev_y, new_y, new_x)

                else:
                    # first vertical
                    create_v_tunnel(prev_y, new_y, prev_x)
                    create_h_tunnel(prev_x, new_x, new_y)

            # add contents to room
            place_objects(new_room)

            # append room to room list
            rooms.append(new_room)
            num_rooms += 1


def place_objects(room):
    # Get random number of monsters
    num_monsters = tcod.random_get_int(0, 0, MAX_ROOM_MONSTERS)

    for i in range(num_monsters):
        # Get random spot for monster
        x = tcod.random_get_int(0, room.x1, room.x2)
        y = tcod.random_get_int(0, room.y1, room.y2)

        if not is_blocked(x,y):
            if tcod.random_get_int(0, 0, 100) < 80:
                # Create an Orc
                fighter_component = Fighter(hp=10, defense=0, power=3, death_function=monster_death)
                ai_component = BasicMonster()
                monster = Object(x, y, 'o', 'orc', tcod.desaturated_green, 
                                 blocks=True, fighter=fighter_component, ai=ai_component)

            else:
                # Create a Troll
                fighter_component = Fighter(hp=16, defense=1, power=4, death_function=monster_death)
                ai_component = BasicMonster()
                monster = Object(x, y, 'T', 'troll', tcod.dark_green, 
                                 blocks=True, fighter=fighter_component, ai=ai_component)
        
            objects.append(monster)


def render_all():
    global fov_map, fov_recompute
    global color_dark_wall, color_light_wall
    global color_dark_ground, color_light_ground

    if fov_recompute:
        # Recompute fov when asked
        fov_recompute = False
        tcod.map_compute_fov(fov_map, player.x, player.y, TORCH_RADIUS, FOV_LIGHT_WALLS, FOV_ALGO)

    # Set background color for all tiles
    for y in range(MAP_HEIGHT):
        for x in range(MAP_WIDTH):
            visible = tcod.map_is_in_fov(fov_map, x, y)
            wall = map[x][y].block_sight
            if not visible:
                # Player can only see explored tiles
                if map[x][y].explored:
                    if wall:
                        tcod.console_set_char_background(con, x, y, color_dark_wall, tcod.BKGND_SET)
                    else:
                        tcod.console_set_char_background(con, x, y, color_dark_ground, tcod.BKGND_SET)
            else:
                # It is visible
                if wall:
                    tcod.console_set_char_background(con, x, y, color_light_wall, tcod.BKGND_SET)
                else:
                    tcod.console_set_char_background(con, x, y, color_light_ground, tcod.BKGND_SET)

                # Visible tiles are explored
                map[x][y].explored = True

    # Draw all objects in list
    for object in objects:
        if object != player:
            object.draw()
        player.draw()

    # Blit con to root console
    tcod.console_blit(con, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0)

    # GUI section
    tcod.console_set_default_background(panel, tcod.black)
    tcod.console_clear(panel)

    # Player stats
    render_bar(1, 1, BAR_WIDTH, 'HP', player.fighter.hp, player.fighter.max_hp, tcod.light_red, tcod.darker_red)
        
    # Blit con to root console
    tcod.console_blit(panel, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, PANEL_Y)

    
def player_move_or_attack(dx, dy):
    global fov_recompute

    # Where the player is going
    x = player.x + dx
    y = player.y + dy

    # Look for attackable Object
    target = None
    for object in objects:
        if object.fighter and object.x == x and object.y == y:
            target = object
            break

    #attack if target is found
    if target is not None:
        player.fighter.attack(target)
    else:
        player.move(dx, dy)
        fov_recompute = True
        

def get_key_event(turn_based=None):
    if turn_based:
        key = tcod.console_wait_for_keypress(True)
    else:
        key = tcod.console_check_for_keypress()
    return key


def handle_keys():
    global fov_recompute
    
    key = get_key_event(TURN_BASED)

    if key.vk == tcod.KEY_ENTER and key.lalt:
        # Alt + Enter toggles fullscreen
        tcod.console_set_fullscreen(not tcod.console_is_fullscreen())

    elif key.vk == tcod.KEY_ESCAPE:
        # Escape exis game
        return 'exit'

    if game_state == 'playing':
        # movement
        if tcod.console_is_key_pressed(tcod.KEY_UP):
            player_move_or_attack(0,-1)
            fov_recompute = True

        elif tcod.console_is_key_pressed(tcod.KEY_DOWN):
            player_move_or_attack(0,1)
            fov_recompute = True

        elif tcod.console_is_key_pressed(tcod.KEY_LEFT):
            player_move_or_attack(-1,0)
            fov_recompute = True

        elif tcod.console_is_key_pressed(tcod.KEY_RIGHT):
            player_move_or_attack(1,0)
            fov_recompute = True
        else:
            return 'didnt-take-turn'


def player_death(player):
    # Game over
    global game_state
    print('You died!')
    game_state = 'dead'


def monster_death(monster):
    # Turn to corpse
    print(monster.name.capitalize() + ' is dead!')
    monster.char = '%'
    monster.color = tcod.dark_red
    monster.blocks = False
    monster.fighter = None
    monster.ai = None
    monster.name = 'remains of ' + monster.name
    monster.send_to_back()


def render_bar(x, y, total_width, name, value, maximum, bar_color, back_color):
    bar_width = int(float(value) / maximum * total_width)

    # Render background
    tcod.console_set_default_background(panel, back_color)
    tcod.console_rect(panel, x, y, total_width, 1, False, tcod.BKGND_SCREEN)

    # Render bar
    tcod.console_set_default_background(panel, bar_color)
    if bar_width > 0:
        tcod.console_rect(panel, x, y, bar_width, 1, False, tcod.BKGND_SCREEN)

    # Render center text with values
    tcod.console_set_default_foreground(panel, tcod.white)
    stats = name + ': ' + str(value) + '/' + str(maximum)
    tcod.console_print_ex(panel, int(x + total_width / 2), y, tcod.BKGND_NONE, tcod.CENTER, stats)

# Initialization & Main Loop

# Font
font_path = 'arial10x10.png'
font_flags = tcod.FONT_TYPE_GREYSCALE | tcod.FONT_LAYOUT_TCOD
tcod.console_set_custom_font(font_path, font_flags)

# Screen
window_title = 'Python 3 libtcod tutorial'
tcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, window_title, FULLSCREEN)

# Fps
tcod.sys_set_fps(LIMIT_FPS)

# New Console
con = tcod.console_new(SCREEN_WIDTH,SCREEN_HEIGHT)

# Player
fighter_component = Fighter(hp=30, defense=2, power=5, death_function=player_death)
player = Object(0, 0, '@', 'player', tcod.white, blocks=True, fighter=fighter_component)


# List of objects
objects = [player]

make_map()

# Fov map
fov_map = tcod.map_new(MAP_WIDTH, MAP_HEIGHT)
for y in range(MAP_HEIGHT):
    for x in range(MAP_WIDTH):
        tcod.map_set_properties(fov_map, x, y, not map[x][y].block_sight, not map[x][y].blocked)

fov_recompute = True
game_state = 'playing'
player_action = None

# Status bars
panel = tcod.console_new(SCREEN_WIDTH, PANEL_HEIGHT)

# Loop
while not tcod.console_is_window_closed():

    render_all()

    tcod.console_flush()
    
    # Erase old locations
    for object in objects:
        object.clear()

    # Handle keys and exit id needed
    player_action = handle_keys()

    # Exit if needed
    if player_action == 'exit':
        break


    # Let monsters take turn
    if game_state == 'playing' and player_action != 'didnt-take-turn':
        for object in objects:
            if object.ai:
                object.ai.take_turn()

The message box

import libtcodpy as tcod
import math
import textwrap

# Window Size
SCREEN_WIDTH = 80
SCREEN_HEIGHT = 50

# Map Size
MAP_WIDTH = 80
MAP_HEIGHT = 43

# 20 FPS Max
LIMIT_FPS = 20

# Dungeon generator parameters
ROOM_MAX_SIZE = 10
ROOM_MIN_SIZE = 6
MAX_ROOMS = 30
MAX_ROOM_MONSTERS = 2

# FOV parameters
FOV_ALGO = 0
FOV_LIGHT_WALLS = True
TORCH_RADIUS = 10

# GUI params
BAR_WIDTH = 20
PANEL_HEIGHT = 7
PANEL_Y = SCREEN_HEIGHT - PANEL_HEIGHT

# Message bar params
MSG_X = BAR_WIDTH + 2
MSG_WIDTH = SCREEN_WIDTH - BAR_WIDTH - 2
MSG_HEIGHT = PANEL_HEIGHT - 1

# Tile colors
color_dark_wall = tcod.Color(0, 0, 100)
color_light_wall = tcod.Color(130, 110, 50)
color_dark_ground = tcod.Color(50, 50, 150)
color_light_ground = tcod.Color(200, 180, 50)

FULLSCREEN = False
TURN_BASED = True


class Tile:
    # Defines tiles on the map

    def __init__(self, blocked, block_sight=None):
        self.blocked = blocked

        # Tiles start unexplored
        self.explored = False

        # default if tile blocked, then also blocks sight
        if block_sight is None: 
            block_sight = blocked

        self.block_sight = block_sight


class Rect:
    # Define rooms on the map

    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 = (self.x1 + self.x2) // 2
        center_y = (self.y1 + self.y2) // 2
        return (center_x, center_y)

    def intersect(self, other):
        # True if this rectange intersect another rectangle
        return (self.x1 <= other.x2 and self.x2 >= other.x1 and self.y1 <= other.y2 and self.y2 >= other.y1)
               

class Object:
    # Base Class for objects, player, npc, item, stairs

    def __init__(self, x, y, char, name, color, blocks=False, fighter=None, ai=None):
        self.x = x
        self.y = y
        self.char = char
        self.name = name
        self.color = color
        self.blocks = blocks

        self.fighter = fighter
        if self.fighter:
            self.fighter.owner = self

        self.ai = ai
        if self.ai:
            self.ai.owner = self

    def move(self, dx, dy):
        # move given amount if destination not blocked
        if not is_blocked(self.x + dx, self.y + dy):
            self.x += dx
            self.y += dy

    def move_toward(self, target_x, target_y):
        # Vector and distance
        dx = target_x - self.x
        dy = target_y - self.y
        distance = math.sqrt(dx ** 2 + dy ** 2)

        # Nomalize
        dx = int(round(dx / distance))
        dy = int(round(dy / distance))
        self.move(dx, dy)

    def distance_to(self, other):
        # Distance
        dx = other.x - self.x
        dy = other.y - self.y
        return math.sqrt(dx ** 2 + dy ** 2)

    def send_to_back(self):
        # Draw first so others appear above it if on same tile.
        global objects
        objects.remove(self)
        objects.insert(0, self)

    def draw(self):
        # Set color and draw char at its position
        tcod.console_set_default_foreground(con, self.color)
        tcod.console_put_char(con, self.x, self.y, self.char, tcod.BKGND_NONE)

    def clear(self):
        # Erase char representing object
        tcod.console_put_char(con, self.x, self.y, ' ', tcod.BKGND_NONE)


class Fighter:
    # Combat related properties and methods
    def __init__(self, hp, defense, power, death_function=None):
        self.max_hp = hp
        self.hp = hp
        self.defense = defense
        self.power = power
        self.death_function = death_function

    def attack(self, target):
        # Formulate attack damage
        damage = self.power - target.fighter.defense

        if damage > 0:
            # Make target take damage
            message(self.owner.name.capitalize() + ' attacks ' + target.name + ' for ' + str(damage) + ' hit points.')
            target.fighter.take_damage(damage)
        else:
            message(self.owner.name.capitalize() + ' attacks ' + target.name + ' but it has no effect!')

    def take_damage(self, damage):
        # Apply damage
        if damage > 0:
            self.hp -= damage

            # Check for death
            if self.hp <= 0:
                function = self.death_function
                if function is not None:
                    function(self.owner)


class BasicMonster:
    # Basic AI
    def take_turn(self):

        # If you can see it, it can see you
        monster = self.owner
        if tcod.map_is_in_fov(fov_map, monster.x, monster.y):

            # Move toward player
            if monster.distance_to(player) >= 2:
                monster.move_toward(player.x, player.y)

            # Close enough to attack
            elif player.fighter.hp > 0:
                monster.fighter.attack(player)


def is_blocked(x, y):
    # Check tile
    if map[x][y].blocked:
        return True

    # Check for blocking objects
    for object in objects:
        if object.blocks and object.x == x and object.y == y:
            return True
    
    # Clear
    return False


def create_room(room):
    global map
    # Make room tiles passable
    for x in range(room.x1 + 1, room.x2):
        for y in range(room.y1 + 1, room.y2):
            map[x][y].blocked = False
            map[x][y].block_sight = False


def create_h_tunnel(x1, x2, y):
    global map
    # Make a horizontal tunnel
    for x in range(min(x1, x2), max(x1, x2) + 1):
        map[x][y].blocked = False
        map[x][y].block_sight = False


def create_v_tunnel(y1, y2, x):
    global map
    # Make a vertical tunnel
    for y in range(min(y1, y2), max(y1, y2) + 1):
        map[x][y].blocked = False
        map[x][y].block_sight = False


def make_map():
    global map, player

    # Fill map with blocked tiles
    map = [[Tile(True) 
           for y in range(MAP_HEIGHT)] 
           for x in range(MAP_WIDTH)]
    
    rooms = []
    num_rooms = 0

    for r in range(MAX_ROOMS):        
        
        # Random width and height
        w = tcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)
        h = tcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)

        # Random position in map
        x = tcod.random_get_int(0, 0, MAP_WIDTH - w - 1)
        y = tcod.random_get_int(0, 0, MAP_HEIGHT - h - 1)

        # Rect class = Easy rooms
        new_room = Rect(x, y, w, h)

        # Check for intersect
        failed = False
        for other_room in rooms:
            if new_room.intersect(other_room):
                failed = True
                break

        if not failed:
            # Valid room
            
            # Create it
            create_room(new_room)

            # New room's center
            (new_x, new_y) = new_room.center()

            if num_rooms == 0:
                # player starts at cent of new room
                player.x = new_x
                player.y = new_y

            else:
                # Connect to previous room

                # Previous room's center
                (prev_x, prev_y) = rooms[num_rooms - 1].center()

                # 50% chance
                if tcod.random_get_int(0, 0, 1) == 1:
                    
                    # first horizontal
                    create_h_tunnel(prev_x, new_x, prev_y)
                    create_v_tunnel(prev_y, new_y, new_x)

                else:
                    # first vertical
                    create_v_tunnel(prev_y, new_y, prev_x)
                    create_h_tunnel(prev_x, new_x, new_y)

            # add contents to room
            place_objects(new_room)

            # append room to room list
            rooms.append(new_room)
            num_rooms += 1


def place_objects(room):
    # Get random number of monsters
    num_monsters = tcod.random_get_int(0, 0, MAX_ROOM_MONSTERS)

    for i in range(num_monsters):
        # Get random spot for monster
        x = tcod.random_get_int(0, room.x1, room.x2)
        y = tcod.random_get_int(0, room.y1, room.y2)

        if not is_blocked(x,y):
            if tcod.random_get_int(0, 0, 100) < 80:
                # Create an Orc
                fighter_component = Fighter(hp=10, defense=0, power=3, death_function=monster_death)
                ai_component = BasicMonster()
                monster = Object(x, y, 'o', 'orc', tcod.desaturated_green, 
                                 blocks=True, fighter=fighter_component, ai=ai_component)

            else:
                # Create a Troll
                fighter_component = Fighter(hp=16, defense=1, power=4, death_function=monster_death)
                ai_component = BasicMonster()
                monster = Object(x, y, 'T', 'troll', tcod.dark_green, 
                                 blocks=True, fighter=fighter_component, ai=ai_component)
        
            objects.append(monster)


def render_all():
    global fov_map, fov_recompute
    global color_dark_wall, color_light_wall
    global color_dark_ground, color_light_ground

    if fov_recompute:
        # Recompute fov when asked
        fov_recompute = False
        tcod.map_compute_fov(fov_map, player.x, player.y, TORCH_RADIUS, FOV_LIGHT_WALLS, FOV_ALGO)

    # Set background color for all tiles
    for y in range(MAP_HEIGHT):
        for x in range(MAP_WIDTH):
            visible = tcod.map_is_in_fov(fov_map, x, y)
            wall = map[x][y].block_sight
            if not visible:
                # Player can only see explored tiles
                if map[x][y].explored:
                    if wall:
                        tcod.console_set_char_background(con, x, y, color_dark_wall, tcod.BKGND_SET)
                    else:
                        tcod.console_set_char_background(con, x, y, color_dark_ground, tcod.BKGND_SET)
            else:
                # It is visible
                if wall:
                    tcod.console_set_char_background(con, x, y, color_light_wall, tcod.BKGND_SET)
                else:
                    tcod.console_set_char_background(con, x, y, color_light_ground, tcod.BKGND_SET)

                # Visible tiles are explored
                map[x][y].explored = True

    # Draw all objects in list
    for object in objects:
        if object != player:
            object.draw()
        player.draw()

    # Blit con to root console
    tcod.console_blit(con, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0)

    # GUI section
    tcod.console_set_default_background(panel, tcod.black)
    tcod.console_clear(panel)
        
    # Game messages
    y = 1
    for (line, color) in game_msgs:
        tcod.console_set_default_foreground(panel, color)
        tcod.console_print_ex(panel, MSG_X, y, tcod.BKGND_NONE, tcod.LEFT, line)
        y += 1

    # Player stats
    render_bar(1, 1, BAR_WIDTH, 'HP', player.fighter.hp, player.fighter.max_hp, tcod.light_red, tcod.darker_red)
    
    # Blit con to root console
    tcod.console_blit(panel, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, PANEL_Y)

    
def player_move_or_attack(dx, dy):
    global fov_recompute

    # Where the player is going
    x = player.x + dx
    y = player.y + dy

    # Look for attackable Object
    target = None
    for object in objects:
        if object.fighter and object.x == x and object.y == y:
            target = object
            break

    #attack if target is found
    if target is not None:
        player.fighter.attack(target)
    else:
        player.move(dx, dy)
        fov_recompute = True
        

def get_key_event(turn_based=None):
    if turn_based:
        key = tcod.console_wait_for_keypress(True)
    else:
        key = tcod.console_check_for_keypress()
    return key


def handle_keys():
    global fov_recompute
    
    key = get_key_event(TURN_BASED)

    if key.vk == tcod.KEY_ENTER and key.lalt:
        # Alt + Enter toggles fullscreen
        tcod.console_set_fullscreen(not tcod.console_is_fullscreen())

    elif key.vk == tcod.KEY_ESCAPE:
        # Escape exis game
        return 'exit'

    if game_state == 'playing':
        # movement
        if tcod.console_is_key_pressed(tcod.KEY_UP):
            player_move_or_attack(0,-1)
            fov_recompute = True

        elif tcod.console_is_key_pressed(tcod.KEY_DOWN):
            player_move_or_attack(0,1)
            fov_recompute = True

        elif tcod.console_is_key_pressed(tcod.KEY_LEFT):
            player_move_or_attack(-1,0)
            fov_recompute = True

        elif tcod.console_is_key_pressed(tcod.KEY_RIGHT):
            player_move_or_attack(1,0)
            fov_recompute = True
        else:
            return 'didnt-take-turn'


def player_death(player):
    # Game over
    global game_state
    message('You died!',tcod.red)
    game_state = 'dead'


def monster_death(monster):
    # Turn to corpse
    message(monster.name.capitalize() + ' is dead!', tcod.orange)
    monster.char = '%'
    monster.color = tcod.dark_red
    monster.blocks = False
    monster.fighter = None
    monster.ai = None
    monster.name = 'remains of ' + monster.name
    monster.send_to_back()


def render_bar(x, y, total_width, name, value, maximum, bar_color, back_color):
    bar_width = int(float(value) / maximum * total_width)

    # Render background
    tcod.console_set_default_background(panel, back_color)
    tcod.console_rect(panel, x, y, total_width, 1, False, tcod.BKGND_SCREEN)

    # Render bar
    tcod.console_set_default_background(panel, bar_color)
    if bar_width > 0:
        tcod.console_rect(panel, x, y, bar_width, 1, False, tcod.BKGND_SCREEN)

    # Render center text with values
    tcod.console_set_default_foreground(panel, tcod.white)
    stats = name + ': ' + str(value) + '/' + str(maximum)
    tcod.console_print_ex(panel, int(x + total_width / 2), y, tcod.BKGND_NONE, tcod.CENTER, stats)


def message(new_msg, color = tcod.white):
    # If long, split message
    new_msg_lines = textwrap.wrap(new_msg, MSG_WIDTH)

    for line in new_msg_lines:
        # If buffer full, remove old to make room for new
        if len(game_msgs) == MSG_HEIGHT:
            del game_msgs[0]

        # Add line as tuple with text and color
        game_msgs.append((line,color))


# Initialization & Main Loop

# Font
font_path = 'arial10x10.png'
font_flags = tcod.FONT_TYPE_GREYSCALE | tcod.FONT_LAYOUT_TCOD
tcod.console_set_custom_font(font_path, font_flags)

# Screen
window_title = 'Python 3 libtcod tutorial'
tcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, window_title, FULLSCREEN)

# Fps
tcod.sys_set_fps(LIMIT_FPS)

# New Console
con = tcod.console_new(SCREEN_WIDTH,SCREEN_HEIGHT)

# Player
fighter_component = Fighter(hp=30, defense=2, power=5, death_function=player_death)
player = Object(0, 0, '@', 'player', tcod.white, blocks=True, fighter=fighter_component)


# List of objects
objects = [player]

make_map()

# Fov map
fov_map = tcod.map_new(MAP_WIDTH, MAP_HEIGHT)
for y in range(MAP_HEIGHT):
    for x in range(MAP_WIDTH):
        tcod.map_set_properties(fov_map, x, y, not map[x][y].block_sight, not map[x][y].blocked)

fov_recompute = True
game_state = 'playing'
player_action = None

# Status bars
panel = tcod.console_new(SCREEN_WIDTH, PANEL_HEIGHT)

# List of game messaes and colors
game_msgs = []

# Welcome message
message('Welcome stranger! Prepare to perish in the Tombs of Ancient Kings!', tcod.red)

# Loop
while not tcod.console_is_window_closed():

    render_all()

    tcod.console_flush()
    
    # Erase old locations
    for object in objects:
        object.clear()

    # Handle keys and exit id needed
    player_action = handle_keys()

    # Exit if needed
    if player_action == 'exit':
        break


    # Let monsters take turn
    if game_state == 'playing' and player_action != 'didnt-take-turn':
        for object in objects:
            if object.ai:
                object.ai.take_turn()


Mouse Look

import libtcodpy as tcod
import math
import textwrap

# Window Size
SCREEN_WIDTH = 80
SCREEN_HEIGHT = 50

# Map Size
MAP_WIDTH = 80
MAP_HEIGHT = 43

# 20 FPS Max
LIMIT_FPS = 20

# Dungeon generator parameters
ROOM_MAX_SIZE = 10
ROOM_MIN_SIZE = 6
MAX_ROOMS = 30
MAX_ROOM_MONSTERS = 2

# FOV parameters
FOV_ALGO = 0
FOV_LIGHT_WALLS = True
TORCH_RADIUS = 10

# GUI params
BAR_WIDTH = 20
PANEL_HEIGHT = 7
PANEL_Y = SCREEN_HEIGHT - PANEL_HEIGHT

# Message bar params
MSG_X = BAR_WIDTH + 2
MSG_WIDTH = SCREEN_WIDTH - BAR_WIDTH - 2
MSG_HEIGHT = PANEL_HEIGHT - 1

# Tile colors
color_dark_wall = tcod.Color(0, 0, 100)
color_light_wall = tcod.Color(130, 110, 50)
color_dark_ground = tcod.Color(50, 50, 150)
color_light_ground = tcod.Color(200, 180, 50)

FULLSCREEN = False
TURN_BASED = True


class Tile:
    # Defines tiles on the map

    def __init__(self, blocked, block_sight=None):
        self.blocked = blocked

        # Tiles start unexplored
        self.explored = False

        # default if tile blocked, then also blocks sight
        if block_sight is None: 
            block_sight = blocked

        self.block_sight = block_sight


class Rect:
    # Define rooms on the map

    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 = (self.x1 + self.x2) // 2
        center_y = (self.y1 + self.y2) // 2
        return (center_x, center_y)

    def intersect(self, other):
        # True if this rectange intersect another rectangle
        return (self.x1 <= other.x2 and self.x2 >= other.x1 and self.y1 <= other.y2 and self.y2 >= other.y1)
               

class Object:
    # Base Class for objects, player, npc, item, stairs

    def __init__(self, x, y, char, name, color, blocks=False, fighter=None, ai=None):
        self.x = x
        self.y = y
        self.char = char
        self.name = name
        self.color = color
        self.blocks = blocks

        self.fighter = fighter
        if self.fighter:
            self.fighter.owner = self

        self.ai = ai
        if self.ai:
            self.ai.owner = self

    def move(self, dx, dy):
        # move given amount if destination not blocked
        if not is_blocked(self.x + dx, self.y + dy):
            self.x += dx
            self.y += dy

    def move_toward(self, target_x, target_y):
        # Vector and distance
        dx = target_x - self.x
        dy = target_y - self.y
        distance = math.sqrt(dx ** 2 + dy ** 2)

        # Nomalize
        dx = int(round(dx / distance))
        dy = int(round(dy / distance))
        self.move(dx, dy)

    def distance_to(self, other):
        # Distance
        dx = other.x - self.x
        dy = other.y - self.y
        return math.sqrt(dx ** 2 + dy ** 2)

    def send_to_back(self):
        # Draw first so others appear above it if on same tile.
        global objects
        objects.remove(self)
        objects.insert(0, self)

    def draw(self):
        # Set color and draw char at its position
        tcod.console_set_default_foreground(con, self.color)
        tcod.console_put_char(con, self.x, self.y, self.char, tcod.BKGND_NONE)

    def clear(self):
        # Erase char representing object
        tcod.console_put_char(con, self.x, self.y, ' ', tcod.BKGND_NONE)


class Fighter:
    # Combat related properties and methods
    def __init__(self, hp, defense, power, death_function=None):
        self.max_hp = hp
        self.hp = hp
        self.defense = defense
        self.power = power
        self.death_function = death_function

    def attack(self, target):
        # Formulate attack damage
        damage = self.power - target.fighter.defense

        if damage > 0:
            # Make target take damage
            message(self.owner.name.capitalize() + ' attacks ' + target.name + ' for ' + str(damage) + ' hit points.')
            target.fighter.take_damage(damage)
        else:
            message(self.owner.name.capitalize() + ' attacks ' + target.name + ' but it has no effect!')

    def take_damage(self, damage):
        # Apply damage
        if damage > 0:
            self.hp -= damage

            # Check for death
            if self.hp <= 0:
                function = self.death_function
                if function is not None:
                    function(self.owner)


class BasicMonster:
    # Basic AI
    def take_turn(self):

        # If you can see it, it can see you
        monster = self.owner
        if tcod.map_is_in_fov(fov_map, monster.x, monster.y):

            # Move toward player
            if monster.distance_to(player) >= 2:
                monster.move_toward(player.x, player.y)

            # Close enough to attack
            elif player.fighter.hp > 0:
                monster.fighter.attack(player)


def is_blocked(x, y):
    # Check tile
    if map[x][y].blocked:
        return True

    # Check for blocking objects
    for object in objects:
        if object.blocks and object.x == x and object.y == y:
            return True
    
    # Clear
    return False


def create_room(room):
    global map
    # Make room tiles passable
    for x in range(room.x1 + 1, room.x2):
        for y in range(room.y1 + 1, room.y2):
            map[x][y].blocked = False
            map[x][y].block_sight = False


def create_h_tunnel(x1, x2, y):
    global map
    # Make a horizontal tunnel
    for x in range(min(x1, x2), max(x1, x2) + 1):
        map[x][y].blocked = False
        map[x][y].block_sight = False


def create_v_tunnel(y1, y2, x):
    global map
    # Make a vertical tunnel
    for y in range(min(y1, y2), max(y1, y2) + 1):
        map[x][y].blocked = False
        map[x][y].block_sight = False


def make_map():
    global map, player

    # Fill map with blocked tiles
    map = [[Tile(True) 
           for y in range(MAP_HEIGHT)] 
           for x in range(MAP_WIDTH)]
    
    rooms = []
    num_rooms = 0

    for r in range(MAX_ROOMS):        
        
        # Random width and height
        w = tcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)
        h = tcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)

        # Random position in map
        x = tcod.random_get_int(0, 0, MAP_WIDTH - w - 1)
        y = tcod.random_get_int(0, 0, MAP_HEIGHT - h - 1)

        # Rect class = Easy rooms
        new_room = Rect(x, y, w, h)

        # Check for intersect
        failed = False
        for other_room in rooms:
            if new_room.intersect(other_room):
                failed = True
                break

        if not failed:
            # Valid room
            
            # Create it
            create_room(new_room)

            # New room's center
            (new_x, new_y) = new_room.center()

            if num_rooms == 0:
                # player starts at cent of new room
                player.x = new_x
                player.y = new_y

            else:
                # Connect to previous room

                # Previous room's center
                (prev_x, prev_y) = rooms[num_rooms - 1].center()

                # 50% chance
                if tcod.random_get_int(0, 0, 1) == 1:
                    
                    # first horizontal
                    create_h_tunnel(prev_x, new_x, prev_y)
                    create_v_tunnel(prev_y, new_y, new_x)

                else:
                    # first vertical
                    create_v_tunnel(prev_y, new_y, prev_x)
                    create_h_tunnel(prev_x, new_x, new_y)

            # add contents to room
            place_objects(new_room)

            # append room to room list
            rooms.append(new_room)
            num_rooms += 1


def place_objects(room):
    # Get random number of monsters
    num_monsters = tcod.random_get_int(0, 0, MAX_ROOM_MONSTERS)

    for i in range(num_monsters):
        # Get random spot for monster
        x = tcod.random_get_int(0, room.x1, room.x2)
        y = tcod.random_get_int(0, room.y1, room.y2)

        if not is_blocked(x,y):
            if tcod.random_get_int(0, 0, 100) < 80:
                # Create an Orc
                fighter_component = Fighter(hp=10, defense=0, power=3, death_function=monster_death)
                ai_component = BasicMonster()
                monster = Object(x, y, 'o', 'orc', tcod.desaturated_green, 
                                 blocks=True, fighter=fighter_component, ai=ai_component)

            else:
                # Create a Troll
                fighter_component = Fighter(hp=16, defense=1, power=4, death_function=monster_death)
                ai_component = BasicMonster()
                monster = Object(x, y, 'T', 'troll', tcod.dark_green, 
                                 blocks=True, fighter=fighter_component, ai=ai_component)
        
            objects.append(monster)


def render_all():
    global fov_map, fov_recompute
    global color_dark_wall, color_light_wall
    global color_dark_ground, color_light_ground

    if fov_recompute:
        # Recompute fov when asked
        fov_recompute = False
        tcod.map_compute_fov(fov_map, player.x, player.y, TORCH_RADIUS, FOV_LIGHT_WALLS, FOV_ALGO)

    # Set background color for all tiles
    for y in range(MAP_HEIGHT):
        for x in range(MAP_WIDTH):
            visible = tcod.map_is_in_fov(fov_map, x, y)
            wall = map[x][y].block_sight
            if not visible:
                # Player can only see explored tiles
                if map[x][y].explored:
                    if wall:
                        tcod.console_set_char_background(con, x, y, color_dark_wall, tcod.BKGND_SET)
                    else:
                        tcod.console_set_char_background(con, x, y, color_dark_ground, tcod.BKGND_SET)
            else:
                # It is visible
                if wall:
                    tcod.console_set_char_background(con, x, y, color_light_wall, tcod.BKGND_SET)
                else:
                    tcod.console_set_char_background(con, x, y, color_light_ground, tcod.BKGND_SET)

                # Visible tiles are explored
                map[x][y].explored = True

    # Draw all objects in list
    for object in objects:
        if object != player:
            object.draw()
        player.draw()

    # Blit con to root console
    tcod.console_blit(con, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0)

    # GUI section
    tcod.console_set_default_background(panel, tcod.black)
    tcod.console_clear(panel)

    # Names of objects under mouse
    tcod.console_set_default_foreground(panel, tcod.light_gray)
    tcod.console_print_ex(panel, 1, 0, tcod.BKGND_NONE, tcod.LEFT, get_names_under_mouse())
        
    # Game messages
    y = 1
    for (line, color) in game_msgs:
        tcod.console_set_default_foreground(panel, color)
        tcod.console_print_ex(panel, MSG_X, y, tcod.BKGND_NONE, tcod.LEFT, line)
        y += 1

    # Player stats
    render_bar(1, 1, BAR_WIDTH, 'HP', player.fighter.hp, player.fighter.max_hp, tcod.light_red, tcod.darker_red)
    
    # Blit con to root console
    tcod.console_blit(panel, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, PANEL_Y)

    
def player_move_or_attack(dx, dy):
    global fov_recompute

    # Where the player is going
    x = player.x + dx
    y = player.y + dy

    # Look for attackable Object
    target = None
    for object in objects:
        if object.fighter and object.x == x and object.y == y:
            target = object
            break

    #attack if target is found
    if target is not None:
        player.fighter.attack(target)
    else:
        player.move(dx, dy)
        fov_recompute = True
        

def get_key_event(turn_based=None):
    if turn_based:
        key = tcod.console_wait_for_keypress(True)
    else:
        key = tcod.console_check_for_keypress()
    return key


def handle_keys():
    global fov_recompute
    global key
    
    #key = get_key_event(TURN_BASED)

    if key.vk == tcod.KEY_ENTER and key.lalt:
        # Alt + Enter toggles fullscreen
        tcod.console_set_fullscreen(not tcod.console_is_fullscreen())

    elif key.vk == tcod.KEY_ESCAPE:
        # Escape exis game
        return 'exit'

    if game_state == 'playing':
        # movement
        if key.vk == tcod.KEY_UP:
            player_move_or_attack(0,-1)
            fov_recompute = True

        elif key.vk == tcod.KEY_DOWN:
            player_move_or_attack(0,1)
            fov_recompute = True

        elif key.vk == tcod.KEY_LEFT:
            player_move_or_attack(-1,0)
            fov_recompute = True

        elif key.vk == tcod.KEY_RIGHT:
            player_move_or_attack(1,0)
            fov_recompute = True
        
        else:
            return 'didnt-take-turn'

def get_names_under_mouse():
    global mouse

    #Return string with names of objects nuder moused tile
    (x, y) = (mouse.cx, mouse.cy)

    # Create list of names of objects at moused tile and in FOV
    names = [obj.name for obj in objects
             if obj.x == x and obj.y == y and tcod.map_is_in_fov(fov_map, obj.x, obj.y)]

    names = ', '.join(names)
    return names.capitalize()

def player_death(player):
    # Game over
    global game_state
    message('You died!',tcod.red)
    game_state = 'dead'


def monster_death(monster):
    # Turn to corpse
    message(monster.name.capitalize() + ' is dead!', tcod.orange)
    monster.char = '%'
    monster.color = tcod.dark_red
    monster.blocks = False
    monster.fighter = None
    monster.ai = None
    monster.name = 'remains of ' + monster.name
    monster.send_to_back()


def render_bar(x, y, total_width, name, value, maximum, bar_color, back_color):
    bar_width = int(float(value) / maximum * total_width)

    # Render background
    tcod.console_set_default_background(panel, back_color)
    tcod.console_rect(panel, x, y, total_width, 1, False, tcod.BKGND_SCREEN)

    # Render bar
    tcod.console_set_default_background(panel, bar_color)
    if bar_width > 0:
        tcod.console_rect(panel, x, y, bar_width, 1, False, tcod.BKGND_SCREEN)

    # Render center text with values
    tcod.console_set_default_foreground(panel, tcod.white)
    stats = name + ': ' + str(value) + '/' + str(maximum)
    tcod.console_print_ex(panel, int(x + total_width / 2), y, tcod.BKGND_NONE, tcod.CENTER, stats)


def message(new_msg, color = tcod.white):
    # If long, split message
    new_msg_lines = textwrap.wrap(new_msg, MSG_WIDTH)

    for line in new_msg_lines:
        # If buffer full, remove old to make room for new
        if len(game_msgs) == MSG_HEIGHT:
            del game_msgs[0]

        # Add line as tuple with text and color
        game_msgs.append((line,color))


# Initialization & Main Loop

# Font
font_path = 'arial10x10.png'
font_flags = tcod.FONT_TYPE_GREYSCALE | tcod.FONT_LAYOUT_TCOD
tcod.console_set_custom_font(font_path, font_flags)

# Screen
window_title = 'Python 3 libtcod tutorial'
tcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, window_title, FULLSCREEN)

# Fps
tcod.sys_set_fps(LIMIT_FPS)

# New Console
con = tcod.console_new(SCREEN_WIDTH,SCREEN_HEIGHT)

# Player
fighter_component = Fighter(hp=30, defense=2, power=5, death_function=player_death)
player = Object(0, 0, '@', 'player', tcod.white, blocks=True, fighter=fighter_component)


# List of objects
objects = [player]

make_map()

# Fov map
fov_map = tcod.map_new(MAP_WIDTH, MAP_HEIGHT)
for y in range(MAP_HEIGHT):
    for x in range(MAP_WIDTH):
        tcod.map_set_properties(fov_map, x, y, not map[x][y].block_sight, not map[x][y].blocked)

fov_recompute = True
game_state = 'playing'
player_action = None

# Status bars
panel = tcod.console_new(SCREEN_WIDTH, PANEL_HEIGHT)

# List of game messaes and colors
game_msgs = []

# Welcome message
message('Welcome stranger! Prepare to perish in the Tombs of Ancient Kings!', tcod.red)

mouse = tcod.Mouse()
key = tcod.Key()

# Loop
while not tcod.console_is_window_closed():

    tcod.sys_check_for_event(tcod.EVENT_KEY_PRESS|tcod.EVENT_MOUSE, key, mouse)

    render_all()

    tcod.console_flush()
    
    # Erase old locations
    for object in objects:
        object.clear()

    # Handle keys and exit id needed
    player_action = handle_keys()

    # Exit if needed
    if player_action == 'exit':
        break


    # Let monsters take turn
    if game_state == 'playing' and player_action != 'didnt-take-turn':
        for object in objects:
            if object.ai:
                object.ai.take_turn()