stages (a roguelike in Python # 14)

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,
8)

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() [/sourcecode] 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. [sourcecode language="Python"] --- - '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' - [/sourcecode] [sourcecode language="Python"] --- - 'Sexangulare' - 0 - 0 - normal - '' - 'Stage' --- - 'portal' - 38 - 15 - normal - 'to:Cyanothus' - [/sourcecode] The maps are 80x50, 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.

Advertisements

No comments yet

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: