updates in a minor key (a roguelike in Python #6)
The Python demo has seen a lot of small changes as I attempt to drag it kicking and screaming into something a little more usable for a real game.

It’s at about 500 LOC at the moment and not yet incomprehensible to me — a good thing I think since I’m not working on it every day — and posted in full below, but I’ll try to highlight the bigger changes first.
One big change is I’m now using a different structure to hold the level information. I still have a hardcoded level, but when I initalize the world object I run the level through this function:
def level_make(self):
self.level_state = []
for line in self.level:
line_data = []
for cell in line:
line_data.append(self.cell_types[cell])
self.level_state.append(line_data)
The function level_make() uses the self.cell_types dictionary, which at the moment looks like this:
self.cell_types = {
' ' : [' ', 'transparent', 'walkable'],
'#' : ['#'],
'=' : ['=', 'transparent']}
So instead of an array of strings as I had before with just the hardcoded map, now cell data is in an array of arrays. When I want to add a new cell type I’ll add it to the dictionary, and when I want to add new properties to the cells I can include it in the array. I don’t need to add a new property to each cell type — just the one where it counts. I hope this won’t get too confusing in the future.
I felt like I needed this structure to handle cases where the walkability or transparency of an individual cell was changed due to some area affect — it wouldn’t really do to just hope all ground cells always would be walkable.
As you would expect this level structure changes how you test for certain things — for example, in the field-of-view code, I now have code like this:
if 'walkable' in cell and 'transparent' in cell:
libtcod.map_set_properties(self.fov_map, j, i, True, True)
elif 'walkable' in cell:
libtcod.map_set_properties(self.fov_map, j, i, False, True)
elif 'transparent' in cell:
libtcod.map_set_properties(self.fov_map, j, i, True, False)
whereas before I tested the cell type directly.
In the screenshot you can see the colors have changed — it’s just temporary at the moment, but I’m trying to add better looking lights to the view. Currently I’m mocking it up with some ad hoc light code in the Fov class, with the goal of keeping track of a separate light map later down the road.
Of course, I can’t not point out that other little creature scampering around — the fearsome ‘?’.
Mr ‘?’ is running on this behaviour handler:
class AI(object):
def __init__(self, thing, brain):
self.choices = ['up', 'down', 'left', 'right']
self.thing = thing
def update(self):
choice = random.randint(0, 3)
command_name = self.choices[choice]
command_func = getattr(game.command, command_name)
command_func(self.thing)
and I created it with an instancing of the Actor class in the setting up stages of the game object. It’s pretty dumb, as it chooses a direction randomly on each update.
One last small thing is the first attempt to hold all the game’s things in a list — the Actor class adds each new instance of itself to this list, for example. I’m unsure at the moment if the player should be in this list, though at the moment it is — I might change that to keep things simpler.
My next goal is to get some rudimentary saving and loading going.
Here’s the complete demo:
#!/usr/bin/python
'''
libtcod OO python tutorial
This code modifies samples_py.py from libtcod 1.4.1. It shows a '@'
walking around a scrolling map with a source of light giving simple FOV.
It's in the public domain.
'''
#############################################
# imports
#############################################
import os, random
import libtcodpy as libtcod
#############################################
# be things
#############################################
class Thing(object):
def __init__(self, name, x=-1, y=-1):
self.name = name
self.x = x
self.y = y
def update(self):
pass
class World(Thing):
def __init__(self):
self.cell_types = {
' ' : [' ', 'transparent', 'walkable'],
'#' : ['#'],
'=' : ['=', 'transparent']}
super(World, self).__init__('world')
self.walkable = [' ']
self.things = []
self.level_width = 46
self.level_height = 31
self.level = [
'##############################################',
'####################### #################',
'##################### # ###############',
'###################### ### ###########',
'################## ##### ####',
'################ ######## ###### ####',
'############### #################### ####',
'################ ###### ##',
'######## ####### ###### # # # ##',
'######## ###### ### ##',
'######## ##',
'#### ###### ### # # # ##',
'#### ### ########## #### ##',
'#### ### ########## ###########=##########',
'#### ################## ##### #####',
'#### ### #### ##### #####',
'#### # #### #####',
'######## # #### ##### #####',
'######## ##### ####################',
'########## #################################',
'########## #################################',
'########### ################# #############',
'############ ############### ##############',
'############# ########### ##############',
'############### ######### ##############',
'################ #### #####',
'################ ### ########### #######',
'############### ## ############## #######',
'############# ### ################# #####',
'############# ################## #',
'##############################################',
]
self.level_make()
def level_make(self):
self.level_state = []
for line in self.level:
line_data = []
for cell in line:
line_data.append(self.cell_types[cell])
self.level_state.append(line_data)
class Actor(Thing):
def __init__(self, name, x=-1, y=-1, *handlers):
super(Actor, self).__init__(name, x, y)
self.brain = Brain()
self.brain.add_handlers(self, *handlers)
self.speed = self.brain.speed
game.world.things.append(self)
def update(self):
self.brain.update()
#############################################
# do things
#############################################
class Brain(object):
def __init__(self):
self.handlers = []
self.speed = 'normal'
def add_handlers(self, thing, *handlers):
for handler in handlers:
self.handlers.append(handler(thing, self))
def remove_handlers(self, *handlers):
for handler in handlers:
self.handlers.remove(handler)
def update(self):
for handler in self.handlers:
handler.update()
class Command(object):
def __init__(self):
pass
def cell_has_monster(self, x, y):
for thing in game.world.things:
if (thing.x, thing.y) == (x, y):
return True
def up(self, thing):
if not self.cell_has_monster(thing.x, thing.y-1) and 'walkable' in game.world.level_state[thing.y-1][thing.x]:
thing.y = thing.y - 1
def down(self, thing):
if not self.cell_has_monster(thing.x, thing.y+1) and 'walkable' in game.world.level_state[thing.y+1][thing.x]:
thing.y = thing.y + 1
def right(self, thing):
if not self.cell_has_monster(thing.x+1, thing.y) and 'walkable' in game.world.level_state[thing.y][thing.x+1]:
thing.x = thing.x + 1
def left(self, thing):
if not self.cell_has_monster(thing.x-1, thing.y) and 'walkable' in game.world.level_state[thing.y][thing.x-1]:
thing.x = thing.x - 1
class Fov(object):
def __init__(self, thing, brain):
self.brain = brain
self.fov_map = None
self.light_map = None
self.fov_radius = 8
def update(self):
player.view.cells_get()
self.fov_map = libtcod.map_new(player.view.width, player.view.height)
self.light_map = libtcod.map_new(game.world.level_width, game.world.level_height)
for cells in player.view.cells:
x, y, j, i = cells
cell = game.world.level_state[y][x]
if 'walkable' in cell and 'transparent' in cell:
libtcod.map_set_properties(self.fov_map, j, i, True, True)
elif 'walkable' in cell:
libtcod.map_set_properties(self.fov_map, j, i, False, True)
elif 'transparent' in cell:
libtcod.map_set_properties(self.fov_map, j, i, True, False)
if 'walkable' in cell and 'transparent' in cell:
libtcod.map_set_properties(self.light_map, j, i, True, True)
elif 'walkable' in cell:
libtcod.map_set_properties(self.light_map, j, i, False, True)
elif 'transparent' in cell:
libtcod.map_set_properties(self.light_map, j, i, True, False)
libtcod.map_compute_fov(self.fov_map, player.view.focus_x, player.view.focus_y, self.fov_radius, True)
libtcod.map_compute_fov(self.light_map, 15, 10, 2, True)
self.brain.fov_map = self.fov_map
self.brain.light_map = self.light_map
class AI(object):
def __init__(self, thing, brain):
self.choices = ['up', 'down', 'left', 'right']
self.thing = thing
def update(self):
choice = random.randint(0, 3)
command_name = self.choices[choice]
command_func = getattr(game.command, command_name)
command_func(self.thing)
#############################################
# player
#############################################
class Awesome(object):
def __init__(self):
self.thing = Actor('player', 26, 16, Fov)
self.ui = UI()
self.view = View()
self.input = Input()
game.view = self.view
game.input = self.input
class UI(object):
def __init__(self):
self.width = 40
self.height = 22
self.state = self.intro_draw
self.chars = {
'player' : '@',
'monster' : '?',
'ground' : ' ',
'wall' : '#',
'window' : '='}
self.cell_colors = {
'dark wall' : libtcod.Color(0, 0, 0),
'visible wall' : libtcod.Color(130, 110, 50),
'dark ground' : libtcod.Color(0, 0, 0),
'visible ground' : libtcod.Color(180, 180, 180),
'lit wall' : libtcod.Color(244, 199, 18),
'lit ground' : libtcod.Color(244, 231, 18)}
font = os.path.join('fonts', 'arial12x12.png')
libtcod.console_set_custom_font(
font,
libtcod.FONT_LAYOUT_TCOD |
libtcod.FONT_TYPE_GREYSCALE,
32,
libtcod.console_init_root(
self.width,
self.height,
'Python Demo',
False)
libtcod.console_credits()
def draw(self):
self.state()
def intro_draw(self):
libtcod.console_clear(0)
libtcod.console_set_foreground_color(0, libtcod.white)
libtcod.console_print_center(0, 20, 8, libtcod.BKGND_NONE, "A Python Demo")
libtcod.console_print_center(0, 20, 11, libtcod.BKGND_NONE, "press any key")
libtcod.console_flush()
def gameplay_draw(self):
libtcod.console_clear(0)
libtcod.console_set_foreground_color(0, libtcod.white)
libtcod.console_print_left(0, 1, 1, libtcod.BKGND_NONE, "HJKL move around")
libtcod.console_print_left(0, 20, 3, libtcod.BKGND_NONE, "speed: %s" % player.thing.brain.speed)
libtcod.console_print_left(0, 20, 4, libtcod.BKGND_NONE, "game turn: %d" % game.turns)
libtcod.console_print_left(0, 20, 5, libtcod.BKGND_NONE, "phase: %s" % game.phase)
libtcod.console_print_left(0, 20, 6, libtcod.BKGND_NONE, "phase count: %d" % game.phase_count)
libtcod.console_set_foreground_color(0, libtcod.black)
libtcod.console_blit(
player.view.screen,
0,
0,
player.view.width,
player.view.height,
0,
0,
self.height - player.view.height,
255)
libtcod.console_flush()
class View(object):
def __init__(self):
self.width = 19
self.height = 19
self.focus_x = None
self.focus_y = None
self.style = self.scroll
self.cells = []
self.screen = libtcod.console_new(self.width, self.height)
libtcod.console_set_foreground_color(self.screen, libtcod.black)
def update(self):
self.draw()
def cells_get(self):
self.cells = []
self.style()
for i in range(self.height):
y = self.top_view_frame + i
for j in range(self.width):
x = self.left_view_frame + j
self.cells.append((x, y, j, i))
def scroll(self):
self.left_view_frame = min(max(0, player.thing.x - self.width//2), game.world.level_width - self.width)
self.top_view_frame = min(max(0, player.thing.y - self.height//2), game.world.level_height - self.height)
self.x_left_offset = min(player.thing.x, self.width//2)
self.x_right_offset = max(0, (self.width//2 - (game.world.level_width - player.thing.x) + (self.width % 2)))
self.focus_x = self.x_left_offset + self.x_right_offset
self.y_top_offset = min(player.thing.y, self.height//2)
self.y_bottom_offset = max(0, (self.height//2 - (game.world.level_height - player.thing.y) + (self.height % 2)))
self.focus_y = self.y_top_offset + self.y_bottom_offset
def draw(self):
libtcod.console_clear(self.screen)
for cells in self.cells:
x, y, j, i = cells
affect, cell = 'dark', 'wall'
if libtcod.map_is_in_fov(player.thing.brain.fov_map, j, i): affect = 'visible'
if libtcod.map_is_walkable(player.thing.brain.fov_map, j, i): cell = 'ground'
if (libtcod.map_is_in_fov(player.thing.brain.light_map, x, y) and
libtcod.map_is_in_fov(player.thing.brain.fov_map, j, i)): affect = 'lit'
color = player.ui.cell_colors['%s %s' % (affect, cell)]
libtcod.console_set_back(self.screen, j, i, color, libtcod.BKGND_SET)
if player.ui.chars['window'] in game.world.level_state[y][x]:
libtcod.console_put_char(self.screen, j, i, libtcod.CHAR_DHLINE, libtcod.BKGND_NONE)
for thing in game.world.things:
if not thing.name == 'player' and (thing.x, thing.y) == (x, y):
libtcod.console_put_char(self.screen, j, i, player.ui.chars[thing.name], libtcod.BKGND_NONE)
libtcod.console_put_char(self.screen, self.focus_x, self.focus_y, player.ui.chars[player.thing.name], libtcod.BKGND_NONE)
class Input(object):
def __init__(self):
self.key = None
self.keyboard = Keyboard()
def intro_update(self):
self.key = libtcod.console_wait_for_keypress(True)
def update(self):
if player.input.key:
self.keyboard.update()
class Keyboard(object):
def __init__(self):
self.keycfg = {
'k' : 'up',
'j' : 'down',
'h' : 'left',
'l' : 'right'}
def get_key(self, key):
if key.vk == libtcod.KEY_CHAR:
return chr(key.c)
else:
return key.vk
def update(self):
key = self.get_key(player.input.key)
if key in self.keycfg:
command_name = self.keycfg[key]
command_func = getattr(game.command, command_name)
command_func(player.thing)
player.input.key = None
#############################################
# game
#############################################
class Game(object):
def __init__(self):
self.world = World()
self.command = Command()
self.view = None
self.input = None
self.state = self.intro_update
self.phases = ['fast', 'normal', 'slow', 'quick', 'normal']
self.phases_for = {
'fast' : ('fast', 'normal', 'slow'),
'normal' : ('normal', 'slow'),
'slow' : ('normal'),
'quick' : ('normal', 'slow', 'quick'),
'fast+quick' : ('fast', 'normal', 'slow', 'quick'),
'fast+slow' : ('fast', 'normal'),
'quick+slow' : ('quick', 'normal'),
'fast+quick+slow' : ('fast', 'normal', 'quick')}
self.phase = self.phases[0]
self.phase_count = 0
self.turns = 0
def update(self):
self.state()
def intro_update(self):
player.ui.draw()
self.input.intro_update()
game.state = game.gameplay_update
player.ui.state = player.ui.gameplay_draw
self.monster = Actor('monster', 30, 16, AI)
player.thing.brain.update()
player.view.update()
libtcod.console_clear(0)
def gameplay_update(self):
self.input.update()
for thing in game.world.things:
if game.phase in game.phases_for[thing.speed]:
thing.update()
self.view.update()
self.phase_count += 1
self.phase = self.phases[self.phase_count % 5]
if game.phase in game.phases_for['normal']: self.turns += 1
#############################################
# get it started & run
#############################################
if __name__ == '__main__':
game = Game()
player = Awesome()
while not libtcod.console_is_window_closed():
game.update()
player.ui.draw()
if game.phase in game.phases_for[player.thing.brain.speed]:
player.input.key = libtcod.console_wait_for_keypress(True)
if player.input.key.vk == libtcod.KEY_ESCAPE:
break
No comments yet
Leave a reply
Subscribe to Kooneiform



