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