Source: Kooneiform » roguelike
A few days ago I got to the point where I wanted to add switching levels and a get command. Adding the get command led me to consider how I would represent inventory in the code, and I thought simple, every thing has a list of things it holds.
The new code is after the jump, but first some explanation.
My thought was that a level is simply a thing that holds the things inside it. OK, I was thinking this is good, because I had wanted to have different kinds of levels in the game, like airships, giant woolly mammoths and so forth, and this would potentially make it straightforward to have a giant woolly mammoth walking around that the player could jump on and explore. So I decided a level would be a kind of handler that gets attached to a thing and adds the functionality of a map.
As you can tell I’m not too concerned with representing scale at this point — I don’t think I will be at any point really, this is a game after all. Regardless, I decided to call a level handler a Stage.
You can see in previous postings of the demo that I managed the map in a single World object. So I needed to do some heavy rewriting, and this led me to reconsider the handler/thing structure altogether. As a result I threw out the Brain class and now add handlers directly to a Thing. I think this is simpler overall.
At one point I got sidetracked by the magic of Python’s __getattr__. This is an attribute on every object, that’s executed when the interpreter doesn’t find the attribute it’s looking for as a result of a call on a property or function. So I was doing things like calling thing.foo where foo was actually an attribute on a handler, not the thing itself. However this proved to be more trouble than it was worth (since, as far as I could tell, __getattr__ expects you to return a value when called, and I decided this was too much hassle). The easier solution for me was to rewrite things so that wherever I call an attribute of a handler, I access it through a dictionary of handlers on the thing, like this:
cells = stage.handlers['Stage'].cells_get(self.thing, self.fov_radius)
A little verbose, but easier in the end, and maybe clearer overall.
Saving the game now means saving the stages in a game. I’ll need to revise this in the future to account for stages within stages though. Switching stages means removing the thing from its current stage and adding it to the new stage. By keeping track of the player’s current stage I know what to draw to the screen. Currently I only update things in the player’s stage, so this will need to be changed in the future so time doesn’t freeze on a stage when the player leaves it. And finally, getting and dropping things means removing and adding the thing to the things that are getting it or receiving it.
There’s a crude inventory display in the upper part of the UI at the moment, but there’s only one thing to get.
Things are now kept in a file by stage, which is named like stage_name.things, and the maps are in stage_name.map.
addendum: With all this talk of stages, it may seem as if something is being left out — namely procedural generation of levels! And for the moment this is true, but as I think I’ve mentioned before I’m not going to focus on generating levels until I have a better idea of what I want the game to be. So for now I’m sticking with hardcoded content to give me a base to test on.
#!/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
import random
import yaml
import cProfile
import libtcod.libtcodpy as libtcod
#############################################
# profile
#############################################
PROFILER = cProfile.Profile()
#############################################
# constants
#############################################
PROFILE = False
SAVING = True
#############################################
# thing
#############################################
class Thing(object):
def __init__(self, name, x, y, speed, properties, *handlers):
self.name = name
self.x = x
self.y = y
self.speed = speed
self.properties = properties
self.handlers = {}
if not None in handlers:
for string in handlers:
handler = eval(string)
self.handlers[string] = handler(self)
self.things = []
self.stage = None
def set_position(self, dx, dy):
self.x = self.x + dx
self.y = self.y + dy
def set_name(self, name):
self.name = name
#############################################
# handlers
#############################################
class Stage(object):
def __init__(self, thing):
self.thing = thing
self.cell_types = {
' ' : ['ground', 'transparent', 'walkable'],
'#' : ['wall']}
self.width = None
self.height = None
self.state = []
name = self.thing.name + '.map'
self.load(name)
def load(self, name):
file = open(name, 'r')
for line in file:
line = line.strip(' \n')
line_data = []
for cell in line:
line_data.append(self.cell_types[cell])
self.state.append(line_data)
self.width = len(self.state[0])
self.height = len(self.state)
file.close()
def update(self):
pass
def cells_get(self, thing=None, radius=None):
cells = []
r = 0
if radius:
r = radius
y_start = 0
x_start = 0
y_end = self.height
x_end = self.width
if thing:
y_start = max(thing.y - r, 0)
x_start = max(thing.x - r, 0)
y_end = min(self.height, thing.y + r + 1)
x_end = min(self.width, thing.x + r + 1)
for y in range(y_start, y_end):
for x in range(x_start, x_end):
cells.append((x, y))
return cells
def cell_has_monster(self, x, y):
for thing in self.thing.things:
if (thing.x, thing.y) == (x, y) and (thing.name == 'monster' or thing.name == 'player'):
return True
def cell_is_unwalkable(self, x, y):
if not 'walkable' in self.state[y][x]:
return True
def cell_has_closed_door(self, x, y):
for thing in self.thing.things:
if (thing.x, thing.y) == (x, y) and thing.name == 'closed door':
return True
def cell_type_get(self, x, y):
return self.state[y][x][0]
def cell_add_property(self, x, y, property):
self.state[y][x] = self.state[y][x] + [property]
#############################################
# do things
#############################################
class Fov(object):
def __init__(self, thing):
self.thing = thing
self.fov_map = None
self.fov_radius = 2
self.fov_map_to_world_coordinates = []
def update(self):
stage = self.thing.stage
if stage == player.thing.stage:
self.fov_map_to_world_coordinates = []
cells = stage.handlers['Stage'].cells_get(self.thing, self.fov_radius)
cell_first = cells[0]
cell_last = cells[len(cells)-1]
difference_right = cell_last[0] - self.thing.x
difference_bottom = cell_last[1] - self.thing.y
difference_left = self.thing.x - cell_first[0]
difference_top = self.thing.y - cell_first[1]
fov_map_size_x = difference_right + difference_left + 1
fov_map_size_y = difference_bottom + difference_top + 1
self.fov_map = libtcod.map_new(fov_map_size_x, fov_map_size_y)
for i in range(len(cells)):
x, y = cells[i]
fov_map_x = i % fov_map_size_x
fov_map_y = i // fov_map_size_x
cell = stage.handlers['Stage'].state[y][x]
if 'walkable' in cell and 'transparent' in cell:
libtcod.map_set_properties(self.fov_map, fov_map_x, fov_map_y, True, True)
elif 'walkable' in cell:
libtcod.map_set_properties(self.fov_map, fov_map_x, fov_map_y, False, True)
elif 'transparent' in cell:
libtcod.map_set_properties(self.fov_map, fov_map_x, fov_map_y, True, False)
for thing in self.thing.stage.things:
if thing.name == 'window' and (x, y) == (thing.x, thing.y):
libtcod.map_set_properties(self.fov_map, fov_map_x, fov_map_y, True, False)
if thing.name == 'closed door' and (x, y) == (thing.x, thing.y):
libtcod.map_set_properties(self.fov_map, fov_map_x, fov_map_y, False, False)
if (x, y) == (self.thing.x, self.thing.y):
fov_x_focus = fov_map_x
fov_y_focus = fov_map_y
self.fov_map_to_world_coordinates.append(((fov_map_x, fov_map_y), (x, y)))
libtcod.map_compute_fov(self.fov_map, fov_x_focus, fov_y_focus, self.fov_radius, True)
if self.thing.name == 'player':
for i in range(len(cells)):
x, y = cells[i]
fov_map_x = i % fov_map_size_x
fov_map_y = i // fov_map_size_x
cell = stage.handlers['Stage'].state[y][x]
if (libtcod.map_is_in_fov(self.fov_map, fov_map_x, fov_map_y) and
not 'discovered' in stage.handlers['Stage'].state[y][x]):
stage.handlers['Stage'].cell_add_property(x, y, 'discovered')
class AI(object):
def __init__(self, thing):
self.thing = thing
self.choices = ['north', 'south', 'west', 'east']
def update(self):
if self.thing.stage == player.thing.stage:
choice = random.randint(0, 3)
command_name = self.choices[choice]
game.command.do(command_name, self.thing)
#############################################
# player
#############################################
class Awesome(object):
def __init__(self):
self.ui = UI()
self.view = View()
self.input = Input()
self.name = ''
self.thing = None
def create(self, stage):
self.thing = Thing('player', 26, 15, 'normal', '', 'Fov')
stage.things.append(self.thing)
self.thing.stage = stage
self.thing.handlers['Fov'].fov_radius = 5
class UI(object):
def __init__(self):
self.width = 55
self.height = 30
self.state = self.splash
self.dialog = libtcod.console_new(30, 10)
libtcod.console_set_background_color(self.dialog, libtcod.light_grey)
libtcod.console_set_foreground_color(self.dialog, libtcod.black)
libtcod.console_clear(self.dialog)
self.top = libtcod.console_new(53, 3)
libtcod.console_set_background_color(self.top, libtcod.light_grey)
libtcod.console_set_foreground_color(self.top, libtcod.white)
libtcod.console_clear(self.top)
self.bottom = libtcod.console_new(53, 3)
libtcod.console_set_background_color(self.bottom, libtcod.light_grey)
libtcod.console_set_foreground_color(self.bottom, libtcod.white)
libtcod.console_clear(self.bottom)
self.right = libtcod.console_new(21, 20)
libtcod.console_set_background_color(self.right, libtcod.grey)
libtcod.console_set_foreground_color(self.right, libtcod.white)
libtcod.console_clear(self.right)
self.main_menu_options = ['new game', 'load saved game']
self.main_menu_cursor = 0
self.load_game_selected = ''
self.load_game_cursor = 0
self.save_games = []
self.chars = {
'player' : '@',
'monster' : '?',
'ground' : ' ',
'wall' : '#',
'window' : libtcod.CHAR_DHLINE,
'lamp' : 'l',
'knife' : 'k',
'open door' : '-',
'closed door' : '+',
'portal' : 'O'}
self.cell_colors = {
'wall' : libtcod.Color(67, 65, 52),
'ground' : libtcod.Color(145, 140, 140),
'dark': libtcod.Color(1, 1, 1),
'lit': libtcod.Color(255, 255, 33),
'visible': libtcod.Color(254, 254, 233),
'discovered': libtcod.Color(50, 50, 100)}
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_set_background_color(0, libtcod.dark_grey)
libtcod.mouse_show_cursor(False)
libtcod.console_credits()
self.states = {
'splash' : self.splash,
'main_menu' : self.main_menu,
'new_game' : self.new_game,
'load_game' : self.load_game,
'gameplay' : self.gameplay}
def change_state(self, state):
self.state = self.states[state]
def draw(self):
self.state()
def splash(self):
libtcod.console_clear(0)
libtcod.console_set_foreground_color(0, libtcod.white)
libtcod.console_print_center(0, self.width//2, self.height//2-4, libtcod.BKGND_NONE, "A Python Demo")
libtcod.console_flush()
def main_menu(self):
libtcod.console_clear(0)
libtcod.console_set_foreground_color(0, libtcod.white)
libtcod.console_print_center(0, self.width//2, self.height//2-4, libtcod.BKGND_NONE, "A Python Demo")
for i in range(len(self.main_menu_options)):
option = self.main_menu_options[i]
if i == self.main_menu_cursor:
cursor = '>'
else:
cursor = ' '
libtcod.console_print_left(0, 20, self.height//2+i, libtcod.BKGND_NONE, "%c%s %s%c" % (libtcod.COLCTRL_1, cursor, option, libtcod.COLCTRL_STOP))
libtcod.console_flush()
def new_game(self):
libtcod.console_clear(0)
libtcod.console_clear(self.dialog)
libtcod.console_print_left(self.dialog, 2, 5, libtcod.BKGND_NONE, "%cTell me your name, hunter. %c" % (libtcod.COLCTRL_1, libtcod.COLCTRL_STOP))
libtcod.console_print_center(self.dialog, 15, 8, libtcod.BKGND_NONE, "%c%s_%c" % (libtcod.COLCTRL_1, player.name, libtcod.COLCTRL_STOP))
libtcod.console_blit(
self.dialog,
0,
0,
30,
10,
0,
(self.width - 30)/2,
10,
200)
libtcod.console_flush()
def load_game(self):
libtcod.console_clear(0)
libtcod.console_clear(self.dialog)
libtcod.console_print_center(self.dialog, 15, 1, libtcod.BKGND_NONE, "%cTell me your name, hunter. %c" % (libtcod.COLCTRL_1, libtcod.COLCTRL_STOP))
i = self.load_game_cursor
game = self.save_games[i].split('.')[0].replace('_', ' ')
libtcod.console_print_center(self.dialog, 15, 4, libtcod.BKGND_NONE, "%c> %s%c" % (libtcod.COLCTRL_1, game, libtcod.COLCTRL_STOP))
libtcod.console_print_center(self.dialog, 15, 8, libtcod.BKGND_NONE, "%cup/down to select%c" % (libtcod.COLCTRL_1, libtcod.COLCTRL_STOP))
libtcod.console_blit(
self.dialog,
0,
0,
30,
10,
0,
(self.width - 30)/2,
10,
200)
libtcod.console_flush()
def gameplay(self):
libtcod.console_clear(0)
libtcod.console_clear(self.right)
libtcod.console_clear(self.top)
libtcod.console_print_left(self.right, 1, 1, libtcod.BKGND_NONE, "HJKL move around")
libtcod.console_print_left(self.right, 1, 4, libtcod.BKGND_NONE, "%s" % player.name)
libtcod.console_print_left(self.right, 1, 5, libtcod.BKGND_NONE, "in %s" % player.thing.stage.name)
libtcod.console_print_left(self.right, 1, 7, libtcod.BKGND_NONE, "game turn: %d" % game.time.turns)
libtcod.console_print_left(self.right, 1, 9, libtcod.BKGND_NONE, "%s" % game.message.text)
libtcod.console_print_left(self.top, 1, 1, libtcod.BKGND_NONE, "%s" % player.thing.things)
libtcod.console_blit(
player.view.screen,
0,
0,
player.view.width,
player.view.height,
0,
1,
5,
255)
libtcod.console_blit(
self.top,
0,
0,
53,
3,
0,
1,
1,
255)
libtcod.console_blit(
self.bottom,
0,
0,
53,
3,
0,
1,
26,
255)
libtcod.console_blit(
self.right,
0,
0,
21,
20,
0,
33,
5,
255)
libtcod.console_flush()
class View(object):
def __init__(self):
self.width = 30
self.height = 20
self.anchor_x = None
self.anchor_y = None
self.screen = libtcod.console_new(self.width, self.height)
libtcod.console_set_foreground_color(self.screen, libtcod.black)
def update(self):
stage = player.thing.stage.handlers['Stage']
libtcod.console_clear(self.screen)
self.anchor_x = min(max(0, player.thing.x - self.width//2), stage.width - self.width)
self.anchor_y = min(max(0, player.thing.y - self.height//2), stage.height - self.height)
viewport = []
for j in range(self.height):
line = []
for i in range(self.width):
x, y = self.viewport_ij_to_world_xy(i, j)
cell = {
'light' : 0,
'char' : ' ',
'fov' : [],
'cell_type' : stage.cell_type_get(x, y),
'discovered' : 0,
'scalar' : 1}
if 'discovered' in stage.state[y][x]:
cell['discovered'] = 1
line.append(cell)
viewport.append(line)
maps = []
for thing in player.thing.stage.things:
if 'Fov' in thing.handlers:
maps.append((thing, thing.handlers['Fov'].fov_map, thing.handlers['Fov'].fov_map_to_world_coordinates))
for tuple in maps:
thing, fov_map, fov_map_to_world_coordinates = tuple
for cell in fov_map_to_world_coordinates:
x, y = cell[1][0], cell[1][1]
if self.xy_in_viewport(x, y):
fov_map_x, fov_map_y = cell[0][0], cell[0][1]
i, j = self.world_xy_to_viewport_ij(x, y)
distance_squared = max(1, (x - thing.x) * (x - thing.x) + (y - thing.y) * (y - thing.y))
fov_radius_squared = thing.handlers['Fov'].fov_radius * thing.handlers['Fov'].fov_radius
scalar = (fov_radius_squared - distance_squared * 1.0)/fov_radius_squared
if libtcod.map_is_in_fov(fov_map, fov_map_x, fov_map_y):
viewport[j][i]['fov'].append(thing.name)
if thing.name == 'lamp':
viewport[j][i]['light'] = max(.5, scalar)
if thing.name == 'player':
viewport[j][i]['scalar'] = max(.5, scalar)
for j in range(self.height):
for i in range(self.width):
cell = viewport[j][i]
affect = 'dark'
cell_type = cell['cell_type']
if 'player' in cell['fov']:
if cell['light'] == 0:
affect = 'visible'
else:
affect = 'lit'
if cell_type == 'wall':
n = max(0, j-1)
e = min(self.width-1, i+1)
s = min(self.height-1, j+1)
w = max(0, i-1)
cells = [
viewport[n][w],
viewport[n][i],
viewport[n][e],
viewport[j][w],
viewport[j][e],
viewport[s][w],
viewport[s][i],
viewport[s][e]]
for adjacent_cell in cells:
if not 'player' in adjacent_cell['fov'] and adjacent_cell['cell_type'] == 'ground' and adjacent_cell['light'] > 0:
affect = 'visible'
break
else:
cell['scalar'] = cell['light']
elif cell['discovered'] > 0:
affect = 'discovered'
if 'player' in cell['fov']:
affect_color = player.ui.cell_colors[affect] * cell['scalar']
else:
affect_color = player.ui.cell_colors[affect]
cell_type_color = player.ui.cell_colors[cell_type]
color = affect_color * cell_type_color
char = cell['char']
libtcod.console_set_back(self.screen, i, j, color, libtcod.BKGND_SET)
libtcod.console_put_char(self.screen, i, j, char, libtcod.BKGND_NONE)
for thing in player.thing.stage.things:
i, j = self.world_xy_to_viewport_ij(thing.x, thing.y)
if self.xy_in_viewport(thing.x, thing.y) and 'player' in viewport[j][i]['fov']:
libtcod.console_put_char(self.screen, i, j, player.ui.chars[thing.name], libtcod.BKGND_NONE)
def world_xy_to_viewport_ij(self, x, y):
i = x - self.anchor_x
j = y - self.anchor_y
return (i, j)
def viewport_ij_to_world_xy(self, i, j):
x = i + self.anchor_x
y = j + self.anchor_y
return (x, y)
def xy_in_viewport(self, x, y):
if (self.anchor_x <= x < self.anchor_x+self.width and
self.anchor_y <= y < self.anchor_y+self.height):
return True
class Input(object):
def __init__(self):
self.state = self.splash
file = open('keys.cfg', 'r')
self.keycfg = yaml.load(file)
self._keys = {
'k' : 'k',
'j' : 'j',
'h' : 'h',
'l' : 'l',
'p' : 'p',
libtcod.KEY_UP : 'up arrow',
libtcod.KEY_RIGHT : 'right arrow',
libtcod.KEY_LEFT : 'left arrow',
libtcod.KEY_DOWN : 'down arrow',
libtcod.KEY_KP8 : 'keypad 8',
libtcod.KEY_KP6 : 'keypad 6',
libtcod.KEY_KP4 : 'keypad 4',
libtcod.KEY_KP2 : 'keypad 2',
'x' : 'x',
libtcod.KEY_SPACE : 'space bar'}
self.ctrl = ''
self.states = {
'splash' : self.splash,
'main_menu' : self.main_menu,
'new_game' : self.new_game,
'load_game' : self.load_game,
'gameplay' : self.gameplay}
def get_key(self, key):
if key.lctrl:
self.ctrl = 'ctrl '
else:
self.ctrl = ''
if not key.vk == 65:
return key.vk
else:
return chr(key.c)
def change_state(self, state):
self.state = self.states[state]
def update(self):
self.state()
def splash(self):
libtcod.console_wait_for_keypress(True)
def main_menu(self):
self.key = libtcod.console_wait_for_keypress(True)
if self.key.vk == libtcod.KEY_ESCAPE or libtcod.console_is_window_closed():
game.exit = True
elif self.key.vk == libtcod.KEY_ENTER:
pass
elif self.key.vk == libtcod.KEY_DOWN:
player.ui.main_menu_cursor = min(len(player.ui.main_menu_options)-1, player.ui.main_menu_cursor+1)
elif self.key.vk == libtcod.KEY_UP:
player.ui.main_menu_cursor = max(0, player.ui.main_menu_cursor-1)
def new_game(self):
self.key = libtcod.console_wait_for_keypress(True)
if self.key.vk == libtcod.KEY_ENTER:
pass
elif self.key.vk == libtcod.KEY_BACKSPACE:
name = player.name
player.name = name[:-1]
elif self.key.vk in (65, libtcod.KEY_SPACE):
player.name = player.name + chr(self.key.c)
def load_game(self):
self.key = libtcod.console_wait_for_keypress(True)
if self.key.vk == libtcod.KEY_ENTER or self.key.vk == libtcod.KEY_ESCAPE:
pass
elif self.key.vk == libtcod.KEY_DOWN:
player.ui.load_game_cursor = min(len(player.ui.save_games)-1, player.ui.load_game_cursor+1)
elif self.key.vk == libtcod.KEY_UP:
player.ui.load_game_cursor = max(0, player.ui.load_game_cursor-1)
def gameplay(self):
self.key = libtcod.console_wait_for_keypress(True)
key = self.get_key(self.key)
if key in self._keys:
key = self._keys[key]
while not key in self.keycfg:
if self.key.vk == libtcod.KEY_ESCAPE or libtcod.console_is_window_closed():
if SAVING: game.save()
game.exit = True
return
game.message.text = "Try again!"
player.ui.draw()
self.key = libtcod.console_wait_for_keypress(True)
key = self.get_key(self.key)
if key in self._keys:
key = self._keys[key]
command_name = self.ctrl + self.keycfg[key]
game.message.text = "Go!"
if game.command.do(command_name, player.thing) == 'free action':
player.view.update()
player.ui.draw()
self.gameplay()
#############################################
# game
#############################################
class Game(object):
def __init__(self):
self.message = Message()
self.time = Time()
self.command = Command()
self.state = self.splash
self.exit = False
self.stages = []
self.states = {
'splash' : self.splash,
'main_menu' : self.main_menu,
'new_game' : self.new_game,
'load_game' : self.load_game,
'gameplay' : self.gameplay}
def change_state(self, state):
self.state = self.states[state]
player.input.change_state(state)
player.ui.change_state(state)
def update(self):
self.state()
def splash(self):
player.ui.draw()
player.input.update()
self.change_state('main_menu')
def main_menu(self):
if not player.ui.save_games:
dir = os.listdir('.')
for file in dir:
if 'sav' in file:
player.ui.save_games.append(file)
player.ui.draw()
player.input.update()
if player.input.key.vk == libtcod.KEY_ENTER:
if player.ui.main_menu_options[player.ui.main_menu_cursor] == 'new game':
self.change_state('new_game')
elif player.ui.main_menu_options[player.ui.main_menu_cursor] == 'load saved game' and player.ui.save_games:
self.change_state('load_game')
def new_game(self):
player.ui.draw()
player.input.update()
if player.input.key.vk == libtcod.KEY_ENTER:
if player.ui.main_menu_options[player.ui.main_menu_cursor] == 'new game':
things_files = []
dir = os.listdir('.')
for file in dir:
if 'things' in file:
things_files.append(file)
for file in things_files:
new_things = []
file = open(file, 'r')
things = yaml.load_all(file)
for data in things:
thing = Thing(*data)
new_things.append(thing)
file.close()
_stage = None
for thing in new_things:
if 'Stage' in thing.handlers:
_stage = thing
self.stages.append(_stage)
for thing in new_things:
if not 'Stage' in thing.handlers:
thing.stage = _stage
_stage.things.append(thing)
current_stage = None
for stage in self.stages:
if stage.name == 'Cyanothus':
current_stage = stage
player.create(current_stage)
for handler in player.thing.handlers:
player.thing.handlers[handler].update()
player.view.update()
self.change_state('gameplay')
elif player.input.key.vk == libtcod.KEY_ESCAPE:
self.change_state('main_menu')
def load_game(self):
player.ui.draw()
player.input.update()
if player.input.key.vk == libtcod.KEY_ENTER:
file = player.ui.save_games[player.ui.load_game_cursor]
self.load(file)
player.name = file.split('.')[0].replace('_', ' ')
self.change_state('gameplay')
elif player.input.key.vk == libtcod.KEY_ESCAPE:
self.change_state('main_menu')
def gameplay(self):
if self.time.phase in self.time.phases_for[player.thing.speed]:
player.input.update()
if game.exit == True:
return
for thing in player.thing.stage.things:
if self.time.phase in self.time.phases_for[thing.speed]:
for handler in thing.handlers:
thing.handlers[handler].update()
self.time.tick()
if PROFILE:
PROFILER.runcall(player.view.update)
PROFILER.dump_stats('profile_view_draw')
else:
player.view.update()
def save(self):
for stage in self.stages:
for thing in stage.things:
if 'Fov' in thing.handlers:
thing.handlers['Fov'].fov_map_to_world_coordinates = []
if hasattr(thing, 'stage'):
thing.stage = None
name = player.name.replace(' ', '_') + '.sav'
file = open(name, 'w')
yaml.dump_all(self.stages, file)
file.close()
def load(self, name):
file = open(name, 'r')
self.stages = list(yaml.load_all(file))
file.close()
for stage in self.stages:
for thing in stage.things:
if thing.name == 'player':
player.thing = thing
for stage in self.stages:
for thing in stage.things:
thing.stage = stage
for handler in player.thing.handlers:
player.thing.handlers[handler].update()
player.view.update()
class Message(object):
def __init__(self):
self.text = "Go!"
class Time(object):
def __init__(self):
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[1]
self.phase_count = 0
self.turns = 0
def tick(self):
self.phase_count += 1
self.phase = self.phases[self.phase_count % 5]
if self.phase in self.phases_for['normal']: self.turns += 1
class Command(object):
def __init__(self):
self.playbook = {
'north' : ('takes turn', self.move, 0, -1),
'east' : ('takes turn', self.move, 1, 0),
'south' : ('takes turn', self.move, 0, 1),
'west' : ('takes turn', self.move, -1, 0),
'ctrl north' : ('takes turn', self.ctrl, 0, -1),
'ctrl east' : ('takes turn', self.ctrl, 1, 0),
'ctrl south' : ('takes turn', self.ctrl, 0, 1),
'ctrl west' : ('takes turn', self.ctrl, -1, 0),
'direct' : ('takes turn', self.direct, None),
'indirect' : ('free action', self.indirect, None),
'print history' : ('free action', self.print_history, None)}
self.rulebook = {
'move' : ('cell has monster', 'cell is unwalkable', 'cell has closed door', 'open door implicitly'),
'close_door' : ('cell has closed door',),
'get' : ('thing not gettable',),
'drop' : ()}
self.rules = {
# condition checks
'cell has monster' : "enactor.stage.handlers['Stage'].cell_has_monster(enactor.x+dx, enactor.y+dy)",
'cell is unwalkable' : "enactor.stage.handlers['Stage'].cell_is_unwalkable(enactor.x+dx, enactor.y+dy)",
'cell has closed door' : "enactor.stage.handlers['Stage'].cell_has_closed_door(enactor.x+dx, enactor.y+dy)",
'thing not gettable' : "not 'gettable' in target.properties",
# automatic procedures
'open door implicitly' : "self.open_door_implicitly(enactor.x+dx, enactor.y+dy)"}
self.history = []
def do(self, string, enactor):
command = self.playbook[string]
command_name, args = command[1], command[2:]
command_name(enactor, *args)
self.history.append((string, enactor.name))
return command[0]
def move(self, enactor, *args):
dx, dy = args
if True in [eval(self.rules[rule]) for rule in self.rulebook['move']]:
pass
else:
enactor.set_position(dx, dy)
target_stage_name = None
for thing in enactor.stage.things:
if thing.name == 'portal' and (enactor.x, enactor.y) == (thing.x, thing.y):
target_stage_name = thing.properties.split(':')[1]
target_stage = None
for stage in game.stages:
if stage.name == target_stage_name:
target_stage = stage
if target_stage:
for stage in game.stages:
if stage == target_stage:
stage.things.append(enactor)
elif stage == enactor.stage:
stage.things.remove(enactor)
enactor.stage = target_stage
def ctrl(self, enactor, *args):
dx, dy = args
self.close_door(enactor, dx, dy)
def direct(self, enactor, *args):
self.get(enactor)
def indirect(self, enactor, *args):
self.drop(enactor)
def get(self, enactor):
target = False
for thing in enactor.stage.things:
if not enactor == thing and (enactor.x, enactor.y) == (thing.x, thing.y):
target = thing
if target:
if True in [eval(self.rules[rule]) for rule in self.rulebook['get']]:
pass
else:
enactor.things.append(target)
enactor.stage.things.remove(target)
def drop(self, enactor):
to_remove = False
if True in [eval(self.rules[rule]) for rule in self.rulebook['drop']]:
pass
else:
for thing in player.thing.things:
to_remove = thing
thing.x, thing.y = player.thing.x, player.thing.y
if to_remove:
enactor.things.remove(to_remove)
enactor.stage.things.insert(0, to_remove)
def close_door(self, enactor, dx, dy):
if True in [eval(self.rules[rule]) for rule in self.rulebook['close_door']]:
pass
else:
for thing in player.thing.stage.things:
if (enactor.x+dx, enactor.y+dy) == (thing.x, thing.y) and thing.name == 'open door':
thing.set_name('closed door')
def open_door_implicitly(self, x, y):
if player.thing.stage.handlers['Stage'].cell_has_closed_door(x, y):
for thing in player.thing.stage.things:
if (thing.x, thing.y) == (x, y) and thing.name == 'closed door':
thing.set_name('open door')
def print_history(self, enactor, *args):
print self.history
print player.thing.things
#############################################
# get it started
#############################################
if __name__ == '__main__':
# try:
# import psyco
# psyco.full()
# except ImportError:
# pass
game = Game()
player = Awesome()
while not game.exit:
game.update()
if not game.exit:
player.ui.draw()
Here are the yaml files for things. Note that ‘Cyanothus’ currently is hardcoded as the starting stage. Much of the stage functionality is roughly coded at this point really.
---
- 'Cyanothus'
- 0
- 0
- normal
- ''
- 'Stage'
---
- 'window'
- 35
- 12
- normal
- ''
-
---
- 'closed door'
- 30
- 15
- normal
- ''
-
---
- 'monster'
- 20
- 9
- normal
- ''
- 'AI'
---
- 'lamp'
- 29
- 6
- normal
- ''
- 'Fov'
---
- 'lamp'
- 41
- 6
- normal
- ''
- 'Fov'
---
- 'lamp'
- 29
- 11
- normal
- ''
- 'Fov'
---
- 'lamp'
- 41
- 11
- normal
- ''
- 'Fov'
---
- 'knife'
- 35
- 15
- normal
- 'gettable'
-
---
- 'portal'
- 38
- 15
- normal
- 'to:Sexangulare'
-
---
- 'Sexangulare'
- 0
- 0
- normal
- ''
- 'Stage'
---
- 'portal'
- 38
- 15
- normal
- 'to:Cyanothus'
-
The maps are 80×50, and can be any combination of ‘#’ and ‘ ‘ in plain text. The location of things will have to work with the spaces on the map of course.