Complete Roguelike Tutorial, using python3+libtcod, part 2 code
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