Difference between revisions of "Complete Roguelike Tutorial, using python3+libtcod, part 2 code"
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 | #!/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 | |||
class Object: | |||
# ###################################################################### | |||
# Exceptions | |||
def __init__(self, x, y | # ###################################################################### | ||
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. | |||
self. | def __eq__(self, other): | ||
equal = False | |||
def | if isinstance(other, Position): | ||
if self.x == other.x and self.y == other.y: | |||
self.x | equal = True | ||
self.y | elif isinstance(other, (tuple, list)): | ||
if len(self) == len(other): | |||
def | if self.x == other[0] and self.y == other[1]: | ||
equal = True | |||
return equal | |||
def __hash__(self): | |||
def | 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(): | def handle_keys(): | ||
#key = | """Handles keyboard input | ||
key = | |||
Updates: | |||
if key.vk == | player_x: x coordinate of player position | ||
#Alt+Enter: toggle fullscreen | player_y: y coordinate of player position | ||
Returns: | |||
elif key.vk == | bool: True if exit the game is requested else False | ||
""" | |||
global player | |||
#movement keys | |||
if | exit_game = False | ||
player.move( | |||
# Run with REALTIME or turn-based | |||
elif | if REALTIME: | ||
player.move( | key = tcod.console_check_for_keypress() | ||
else: | |||
elif | key = tcod.console_wait_for_keypress(True) | ||
player.move( | |||
if key.vk == tcod.KEY_ENTER and key.lalt: | |||
elif | # Alt+Enter: toggle fullscreen | ||
player.move( | 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() | |||
</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