Difference between revisions of "Complete Roguelike Tutorial, using python3+libtcod, part 2 code"

From RogueBasin
Jump to navigation Jump to search
Line 6: Line 6:


<div style="background-color: #EEEEEE; border-style: dotted"><syntaxhighlight lang="python">
<div style="background-color: #EEEEEE; border-style: dotted"><syntaxhighlight lang="python">
import libtcodpy as libtcod
#!/usr/bin/env python
import os


#actual size of the window
import libtcodpy as tcod
SCREEN_WIDTH = 80
SCREEN_HEIGHT = 50


LIMIT_FPS = 20  #20 frames-per-second maximum


# ######################################################################
# Game Constants
# ######################################################################
# Size of the terminal window in characters
SCREEN_WIDTH = 80  # characters wide
SCREEN_HEIGHT = 50  # characters tall
LIMIT_FPS = 20  # 20 frames-per-second maximum
REALTIME = False  # set True for real-time, False for turn-based


class Object:
 
     #this is a generic object: the player, a monster, an item, the stairs...
# ######################################################################
     #it's always represented by a character on screen.
# Exceptions
     def __init__(self, x, y, char, color):
# ######################################################################
class GameError(Exception):
    """Base Exception for all game errors"""
 
 
class FontError(GameError):
    """Font could not be loaded"""
 
 
# ######################################################################
# Classes
# ######################################################################
class Direction(object):
    """Defines direction of movement
 
    Matrix:
 
      -1, -1 | 0, -1 |  1, -1
      -1, 0  | 0, 0  |  1, 0
      -1, 1  | 0, 1  |  1, 1
 
          |  UP  |
      LEFT | NONE | RIGHT
          | DOWN |
 
        NW | NORTH | NE
      WEST | NONE  | EAST
        SW | SOUTH | SE
 
    """
    NONE = (0, 0)
 
    UP = (0, -1)
    DOWN = (0, 1)
    LEFT = (-1, 0)
    RIGHT = (1, 0)
 
    NORTH = UP
    NE = (1, -1)
    EAST = RIGHT
    SE = (1, 1)
    SOUTH = DOWN
    SW = (-1, 1)
    WEST = LEFT
    NW = (-1, -1)
 
 
class Object(object):
     """Game object.  This is used for any displayable game object such
    as the player, a mob, an item, a staircase, etc.
 
    Args:
        character (str): the character to display
        position (Position): (x, y) the position of the object on the map
        color (tcod.Color, optional): (R, G, B) color to use when drawing [default: tcod.white]
    """
 
    def __init__(self, character, position, color=tcod.white):
        self.character = character
        self.position = position
        self.color = color
 
     def clear(self):
        """Erase the character from the console"""
        self.draw(' ')
 
    def draw(self, character=None):
        """Controls how the object is displayed on the screen.
 
        Args:
            character (str, optional): the character to display [default: self.character]
        """
        global con
 
        character = character or self.character
        tcod.console_set_default_foreground(con, self.color)
        x, y = self.position
        tcod.console_put_char(con, x, y, character, tcod.BKGND_NONE)
 
    def move(self, direction):
        """Moves the object in a specific direction.
 
        Modifies the object position.
 
        Args:
            direction (Direction, tuple): UP, DOWN, LEFT, RIGHT
        """
        self.position += direction
 
 
class Position(object):
    """A class to help with position-related math in 2d space
 
    Args:
        x (int): the width coordinate value
        y (int): the height coordinate value
    """
     def __init__(self, x, y):
         self.x = x
         self.x = x
         self.y = y
         self.y = y
         self.char = char
 
         self.color = color
    def __eq__(self, other):
   
        equal = False
     def move(self, dx, dy):
         if isinstance(other, Position):
         #move by the given amount
            if self.x == other.x and self.y == other.y:
         self.x += dx
                equal = True
         self.y += dy
         elif isinstance(other, (tuple, list)):
   
            if len(self) == len(other):
     def draw(self):
                if self.x == other[0] and self.y == other[1]:
         #set the color and then draw the character that represents this object at its position
                    equal = True
        libtcod.console_set_default_foreground(con, self.color)
        return equal
         libtcod.console_put_char(con, self.x, self.y, self.char, libtcod.BKGND_NONE)
 
   
     def __hash__(self):
     def clear(self):
         return hash((self.x, self.y))
         #erase the character that represents this object
 
        libtcod.console_put_char(con, self.x, self.y, ' ', libtcod.BKGND_NONE)
    def __iter__(self):
         yield self.x
         yield self.y
 
     def __len__(self):
         return len((self.x, self.y))
 
    def __add__(self, other):
         if isinstance(other, Direction):
            dx, dy = other
            self.x += dx
            self.y += dy
        elif isinstance(other, (list, tuple)):
            dx, dy = other
            if len(other) == len(self):
                self.x += dx
                self.y += dy
        return self
 
     def __repr__(self):
         return f'<Position ({self.x}, {self.y})>'
 
    def __str__(self):
        return f'({self.x}, {self.y})'




# ######################################################################
# User Interface Control
# ######################################################################
def handle_keys():
def handle_keys():
     #key = libtcod.console_check_for_keypress() #real-time
    """Handles keyboard input
     key = libtcod.console_wait_for_keypress(True) #turn-based
 
   
    Updates:
     if key.vk == libtcod.KEY_ENTER and key.lalt:
        player_x: x coordinate of player position
         #Alt+Enter: toggle fullscreen
        player_y: y coordinate of player position
         libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
 
       
    Returns:
     elif key.vk == libtcod.KEY_ESCAPE:
        bool: True if exit the game is requested else False
         return True  #exit game
    """
   
    global player
     #movement keys
 
     if libtcod.console_is_key_pressed(libtcod.KEY_UP):
    exit_game = False
         player.move(0, -1)
 
       
     # Run with REALTIME or turn-based
     elif libtcod.console_is_key_pressed(libtcod.KEY_DOWN):
    if REALTIME:
         player.move(0, 1)
        key = tcod.console_check_for_keypress()
       
     else:
     elif libtcod.console_is_key_pressed(libtcod.KEY_LEFT):
        key = tcod.console_wait_for_keypress(True)
         player.move(-1, 0)
 
       
     if key.vk == tcod.KEY_ENTER and key.lalt:
     elif libtcod.console_is_key_pressed(libtcod.KEY_RIGHT):
         # Alt+Enter: toggle fullscreen
         player.move(1, 0)
         tcod.console_set_fullscreen(not tcod.console_is_fullscreen())
     elif key.vk == tcod.KEY_ESCAPE:
         exit_game = True  # exit game
 
     # movement keys
     if tcod.console_is_key_pressed(tcod.KEY_UP):
         player.move(Direction.UP)
     elif tcod.console_is_key_pressed(tcod.KEY_DOWN):
         player.move(Direction.DOWN)
     elif tcod.console_is_key_pressed(tcod.KEY_LEFT):
         player.move(Direction.LEFT)
     elif tcod.console_is_key_pressed(tcod.KEY_RIGHT):
         player.move(Direction.RIGHT)
 
    return exit_game
 
 
# ######################################################################
# Game
# ######################################################################
def initialize_game(font_filepath=None, window_title=None, fullscreen=False):
    """Sets up libtcod and creates a window
 
    Updates:
        player_x: x coordinate of player position
        player_y: y coordinate of player position
 
    Args:
        font_filepath (str): the path to the font file [default: terminal.png]
        window_title (str): the title to display for the game [default: Python3 Tutorial]
    """
    global player, con, npc
 
    # Setup displayed font
    font_filepath = os.path.abspath(font_filepath or 'terminal.png')
    font_flags = tcod.FONT_TYPE_GREYSCALE | tcod.FONT_LAYOUT_ASCII_INCOL
    if not os.path.exists(font_filepath):
        raise FontError("Could not open font file: {}".format(font_filepath))
    tcod.console_set_custom_font(font_filepath, font_flags)
 
    # Setup window
    window_title = window_title or 'Python3 Tutorial'
    fullscreen = fullscreen or False
    tcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, window_title, fullscreen)
 
    # Limit frames per second
    tcod.sys_set_fps(LIMIT_FPS)
 
    # Create the console
    con = tcod.console_new(SCREEN_WIDTH, SCREEN_HEIGHT)
 
    # Setup player's initial position
    player_starting_position = Position(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)
    player = Object('@', player_starting_position)


    npc_starting_position = Position(SCREEN_WIDTH // 2 - 5, SCREEN_HEIGHT // 2)
    npc = Object('@', npc_starting_position, color=tcod.yellow)


#############################################
# Initialization & Main Loop
#############################################


libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
def main():
libtcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, 'python/libtcod tutorial', False)
    global player, npc
libtcod.sys_set_fps(LIMIT_FPS)
con = libtcod.console_new(SCREEN_WIDTH, SCREEN_HEIGHT)


#create object representing the player
    initialize_game()
player = Object(SCREEN_WIDTH/2, SCREEN_HEIGHT/2, '@', libtcod.white)
    objects = [npc, player]


#create an NPC
    # Game loop
npc = Object(SCREEN_WIDTH/2 - 5, SCREEN_HEIGHT/2, '@', libtcod.yellow)
    exit_game = False
    while not tcod.console_is_window_closed() and exit_game is not True:
        tcod.console_set_default_foreground(0, tcod.white)


#the list of objects with those two
        for game_object in objects:
objects = [npc, player]
            game_object.draw()


        tcod.console_blit(con, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0)
        tcod.console_flush()


while not libtcod.console_is_window_closed():
        for game_object in objects:
   
            game_object.clear()
    #draw all objects in the list
 
    for object in objects:
        # handle keys
        object.draw()
        exit_game = handle_keys()
   
 
    #blit the contents of "con" to the root console and present it
 
    libtcod.console_blit(con, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0)
if __name__ == '__main__':
     libtcod.console_flush()
     main()


    #erase all objects at their old locations, before they move
    for object in objects:
        object.clear()
   
    #handle keys and exit game if needed
    exit = handle_keys()
    if exit:
        break
</syntaxhighlight></div>
</syntaxhighlight></div>


== The Map ==
== The Map ==

Revision as of 22:14, 15 September 2017

This is part of a series of tutorials; the main page can be found here.

Generalizing

#!/usr/bin/env python
import os

import libtcodpy as tcod


# ######################################################################
# Game Constants
# ######################################################################
# Size of the terminal window in characters
SCREEN_WIDTH = 80  # characters wide
SCREEN_HEIGHT = 50  # characters tall
LIMIT_FPS = 20  # 20 frames-per-second maximum
REALTIME = False  # set True for real-time, False for turn-based


# ######################################################################
# Exceptions
# ######################################################################
class GameError(Exception):
    """Base Exception for all game errors"""


class FontError(GameError):
    """Font could not be loaded"""


# ######################################################################
# Classes
# ######################################################################
class Direction(object):
    """Defines direction of movement

    Matrix:

      -1, -1 | 0, -1 |  1, -1
      -1, 0  | 0, 0  |  1, 0
      -1, 1  | 0, 1  |  1, 1

           |  UP  |
      LEFT | NONE | RIGHT
           | DOWN |

        NW | NORTH | NE
      WEST | NONE  | EAST
        SW | SOUTH | SE

    """
    NONE = (0, 0)

    UP = (0, -1)
    DOWN = (0, 1)
    LEFT = (-1, 0)
    RIGHT = (1, 0)

    NORTH = UP
    NE = (1, -1)
    EAST = RIGHT
    SE = (1, 1)
    SOUTH = DOWN
    SW = (-1, 1)
    WEST = LEFT
    NW = (-1, -1)


class Object(object):
    """Game object.  This is used for any displayable game object such
    as the player, a mob, an item, a staircase, etc.

    Args:
        character (str): the character to display
        position (Position): (x, y) the position of the object on the map
        color (tcod.Color, optional): (R, G, B) color to use when drawing [default: tcod.white]
    """

    def __init__(self, character, position, color=tcod.white):
        self.character = character
        self.position = position
        self.color = color

    def clear(self):
        """Erase the character from the console"""
        self.draw(' ')

    def draw(self, character=None):
        """Controls how the object is displayed on the screen.

        Args:
            character (str, optional): the character to display [default: self.character]
        """
        global con

        character = character or self.character
        tcod.console_set_default_foreground(con, self.color)
        x, y = self.position
        tcod.console_put_char(con, x, y, character, tcod.BKGND_NONE)

    def move(self, direction):
        """Moves the object in a specific direction.

        Modifies the object position.

        Args:
            direction (Direction, tuple): UP, DOWN, LEFT, RIGHT
        """
        self.position += direction


class Position(object):
    """A class to help with position-related math in 2d space

    Args:
        x (int): the width coordinate value
        y (int): the height coordinate value
    """
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        equal = False
        if isinstance(other, Position):
            if self.x == other.x and self.y == other.y:
                equal = True
        elif isinstance(other, (tuple, list)):
            if len(self) == len(other):
                if self.x == other[0] and self.y == other[1]:
                    equal = True
        return equal

    def __hash__(self):
        return hash((self.x, self.y))

    def __iter__(self):
        yield self.x
        yield self.y

    def __len__(self):
        return len((self.x, self.y))

    def __add__(self, other):
        if isinstance(other, Direction):
            dx, dy = other
            self.x += dx
            self.y += dy
        elif isinstance(other, (list, tuple)):
            dx, dy = other
            if len(other) == len(self):
                self.x += dx
                self.y += dy
        return self

    def __repr__(self):
        return f'<Position ({self.x}, {self.y})>'

    def __str__(self):
        return f'({self.x}, {self.y})'


# ######################################################################
# User Interface Control
# ######################################################################
def handle_keys():
    """Handles keyboard input

    Updates:
        player_x: x coordinate of player position
        player_y: y coordinate of player position

    Returns:
        bool: True if exit the game is requested else False
    """
    global player

    exit_game = False

    # Run with REALTIME or turn-based
    if REALTIME:
        key = tcod.console_check_for_keypress()
    else:
        key = tcod.console_wait_for_keypress(True)

    if key.vk == tcod.KEY_ENTER and key.lalt:
        # Alt+Enter: toggle fullscreen
        tcod.console_set_fullscreen(not tcod.console_is_fullscreen())
    elif key.vk == tcod.KEY_ESCAPE:
        exit_game = True  # exit game

    # movement keys
    if tcod.console_is_key_pressed(tcod.KEY_UP):
        player.move(Direction.UP)
    elif tcod.console_is_key_pressed(tcod.KEY_DOWN):
        player.move(Direction.DOWN)
    elif tcod.console_is_key_pressed(tcod.KEY_LEFT):
        player.move(Direction.LEFT)
    elif tcod.console_is_key_pressed(tcod.KEY_RIGHT):
        player.move(Direction.RIGHT)

    return exit_game


# ######################################################################
# Game
# ######################################################################
def initialize_game(font_filepath=None, window_title=None, fullscreen=False):
    """Sets up libtcod and creates a window

    Updates:
        player_x: x coordinate of player position
        player_y: y coordinate of player position

    Args:
        font_filepath (str): the path to the font file [default: terminal.png]
        window_title (str): the title to display for the game [default: Python3 Tutorial]
    """
    global player, con, npc

    # Setup displayed font
    font_filepath = os.path.abspath(font_filepath or 'terminal.png')
    font_flags = tcod.FONT_TYPE_GREYSCALE | tcod.FONT_LAYOUT_ASCII_INCOL
    if not os.path.exists(font_filepath):
        raise FontError("Could not open font file: {}".format(font_filepath))
    tcod.console_set_custom_font(font_filepath, font_flags)

    # Setup window
    window_title = window_title or 'Python3 Tutorial'
    fullscreen = fullscreen or False
    tcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, window_title, fullscreen)

    # Limit frames per second
    tcod.sys_set_fps(LIMIT_FPS)

    # Create the console
    con = tcod.console_new(SCREEN_WIDTH, SCREEN_HEIGHT)

    # Setup player's initial position
    player_starting_position = Position(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)
    player = Object('@', player_starting_position)

    npc_starting_position = Position(SCREEN_WIDTH // 2 - 5, SCREEN_HEIGHT // 2)
    npc = Object('@', npc_starting_position, color=tcod.yellow)


def main():
    global player, npc

    initialize_game()
    objects = [npc, player]

    # Game loop
    exit_game = False
    while not tcod.console_is_window_closed() and exit_game is not True:
        tcod.console_set_default_foreground(0, tcod.white)

        for game_object in objects:
            game_object.draw()

        tcod.console_blit(con, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0)
        tcod.console_flush()

        for game_object in objects:
            game_object.clear()

        # handle keys
        exit_game = handle_keys()


if __name__ == '__main__':
    main()

The Map

import libtcodpy as libtcod

#actual size of the window
SCREEN_WIDTH = 80
SCREEN_HEIGHT = 50

#size of the map
MAP_WIDTH = 80
MAP_HEIGHT = 45

LIMIT_FPS = 20  #20 frames-per-second maximum


color_dark_wall = libtcod.Color(0, 0, 100)
color_dark_ground = libtcod.Color(50, 50, 150)


class Tile:
    #a tile of the map and its properties
    def __init__(self, blocked, block_sight = None):
        self.blocked = blocked
        
        #by default, if a tile is blocked, it also blocks sight
        if block_sight is None: block_sight = blocked
        self.block_sight = block_sight

class Object:
    #this is a generic object: the player, a monster, an item, the stairs...
    #it's always represented by a character on screen.
    def __init__(self, x, y, char, color):
        self.x = x
        self.y = y
        self.char = char
        self.color = color
    
    def move(self, dx, dy):
        #move by the given amount, if the destination is not blocked
        if not map[self.x + dx][self.y + dy].blocked:
            self.x += dx
            self.y += dy
    
    def draw(self):
        #set the color and then draw the character that represents this object at its position
        libtcod.console_set_default_foreground(con, self.color)
        libtcod.console_put_char(con, self.x, self.y, self.char, libtcod.BKGND_NONE)
    
    def clear(self):
        #erase the character that represents this object
        libtcod.console_put_char(con, self.x, self.y, ' ', libtcod.BKGND_NONE)



def make_map():
    global map
    
    #fill map with "unblocked" tiles
    map = [[ Tile(False)
        for y in range(MAP_HEIGHT) ]
            for x in range(MAP_WIDTH) ]
    
    #place two pillars to test the map
    map[30][22].blocked = True
    map[30][22].block_sight = True
    map[50][22].blocked = True
    map[50][22].block_sight = True


def render_all():
    global color_light_wall
    global color_light_ground
    
    #go through all tiles, and set their background color
    for y in range(MAP_HEIGHT):
        for x in range(MAP_WIDTH):
            wall = map[x][y].block_sight
            if wall:
                libtcod.console_set_char_background(con, x, y, color_dark_wall, libtcod.BKGND_SET )
            else:
                libtcod.console_set_char_background(con, x, y, color_dark_ground, libtcod.BKGND_SET )

    #draw all objects in the list
    for object in objects:
        object.draw()
    
    #blit the contents of "con" to the root console
    libtcod.console_blit(con, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0)
    
def handle_keys():
    #key = libtcod.console_check_for_keypress()  #real-time
    key = libtcod.console_wait_for_keypress(True)  #turn-based
    
    if key.vk == libtcod.KEY_ENTER and key.lalt:
        #Alt+Enter: toggle fullscreen
        libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
        
    elif key.vk == libtcod.KEY_ESCAPE:
        return True  #exit game
    
    #movement keys
    if libtcod.console_is_key_pressed(libtcod.KEY_UP):
        player.move(0, -1)
        
    elif libtcod.console_is_key_pressed(libtcod.KEY_DOWN):
        player.move(0, 1)
        
    elif libtcod.console_is_key_pressed(libtcod.KEY_LEFT):
        player.move(-1, 0)
        
    elif libtcod.console_is_key_pressed(libtcod.KEY_RIGHT):
        player.move(1, 0)


#############################################
# Initialization & Main Loop
#############################################

libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
libtcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, 'python/libtcod tutorial', False)
libtcod.sys_set_fps(LIMIT_FPS)
con = libtcod.console_new(SCREEN_WIDTH, SCREEN_HEIGHT)

#create object representing the player
player = Object(SCREEN_WIDTH/2, SCREEN_HEIGHT/2, '@', libtcod.white)

#create an NPC
npc = Object(SCREEN_WIDTH/2 - 5, SCREEN_HEIGHT/2, '@', libtcod.yellow)

#the list of objects with those two
objects = [npc, player]

#generate map (at this point it's not drawn to the screen)
make_map()


while not libtcod.console_is_window_closed():
    
    #render the screen
    render_all()
    
    libtcod.console_flush()

    #erase all objects at their old locations, before they move
    for object in objects:
        object.clear()
    
    #handle keys and exit game if needed
    exit = handle_keys()
    if exit:
        break