Archive for the ‘7DRL 2010’ Tag

Bidden 7DRL code review

While things are still fresh in my mind I want to review the somewhat bizarre code design I came up with during the 7DRL, if only to mark a way point to look back on as things change.

Here’s the code (Leo makes exporting this outline easy):

+ Code
	- TODO
	+ @thin bidden.py
		- << docstring >>
		- << import >>
		- << constants >>
		+ class Game
			- __init__
			- load
			- save
			- create_gid
			- register_gid
		+ class Event
			- __init__
			- broadcast
			- register_component
		+ class Control
			- __init__
			- action
			- switch_view
			- quit
		+ Controllers
			+ class Menu
				- __init__
				+ new_game
					- << make player >>
					- << make binding >>
					+ << load from .bid >>
						- << test >>
					- << put things in stage >>
			+ class Intro
				- __init__
			+ class Play
				- __init__
				- move
				- use_boon
				- wait
				- help
			+ class Coda
				- __init__
		+ class Component
			- __init__
			- __lt__
			- __gt__
		+ Components
			+ class Hunter
				- __init__
			+ class Container
				- __init__
			+ class Model
				- __init__
				- on_death
			+ class XY
				- __init__
				- on_move
			+ class Stage
				- __init__
				- create
			+ class Collision
				- __init__
				- on_move
			+ class Bump
				- __init__
				- on_move
			+ class Title
				- __init__
				- create
				- make
			+ class Hunted
				- __init__
				- on_death
			+ class Wounds
				- __init__
			+ Boons
				+ class Boon
					- __init__
				+ class Strike
					- __init__
					- on_boon
			+ AIs
				+ class AI
					- __init__
				+ class Zombie
					- __init__
					- decide
		+ class Prototype
			- __init__
		+ class UI
			- init
			+ class Pane
				- __init__
				- fade_in
			+ class Animation
				- __init__
			- input
			- view
			- menu_draw
			- intro_draw
			- play_draw
			- coda_draw
			- quit_draw
			- play_help_draw
		+ class Keyboard
			- __init__
			- check
		- << run game >>

Let’s look at the concise version:

+ @thin bidden.py
	- << docstring >>
	- << import >>
	- << constants >>
	+ class Game
	+ class Event
	+ class Control
	+ Controllers
	+ class Component
	+ Components
	+ class Prototype
	+ class UI
	+ class Keyboard
	- << run game >>

Going over things from the main loop:

    while not libtcod.console_is_window_closed() and not game.control == 'quit':
        keys.check()

        if game.control == 'play':
            for item in game.gids:
                thing = game.gids[item]
                for k in thing.__dict__:
                    c = thing.__dict__[k]
                    if isinstance(c, AI):
                        c.decide()
        ui.view()

The keys instance of Keyboard waits for a key press. If it gets an ‘x’ or the control key it waits for another key press. This is my somewhat hacked way to get key + direction key combinations. It checks if the key is in the configuration for the game, and sends a message (such as ‘up’ or ‘enter’) to the method UI.input.

That method passes along the input to the current game.control, including it in a dict and adding the enactor game ID (in the case of the player, 0). There’s a control basically for each state of the game (main menu, in-play, etcetera). I tried to make all arguments passed around in the form of a dictionary, using the same key names for like types of data.

Each control inherits an action method from the Control parent. This method checks the input message against an actions dictionary, and if the message is a key, it runs the corresponding value as a method on the control instance (for example, I had the ‘enter’ key mapped to a method to advance the state of the game arbitrarily, so I could restart the game without exiting/restarting the Python file). Individual control instances may map the same message to different methods of course.

This method gets the data in the dict described above. A good example is the move method (in the ‘play’ control instance, mapped to the ‘up/down/right/left’ message which is mapped to the arrow keys in the keys instance):

def move(self, data):
    self.directions = {
                        'up' : {'x':0, 'y':-1},
                        'down' : {'x':0, 'y':1},
                        'right' : {'x':1, 'y':0},
                        'left' : {'x':-1, 'y':0},
                        }

    data['event'] = 'on_move'
    data['outcome'] = 'continue'
    data.update(self.directions[data['input']])


    event.broadcast(data)

So this method basically adds data to the dictionary, and then sends it to the instance of Event.

Event , in the context of the 7DRL, probably was a big mistake…going into the week I had vague thoughts of trying out a message passing/broadcasting scheme for the first time, and this was the result. I spent a lot of time figuring this out.

The Event.broadcast method takes the data and sends it along to anything registered to listen for the event included, in this case ‘on_move’. So, when the player presses the up key, it’s not directly moving the player-character, but telling the game to announce that the player-character has the intention of moving…I know, I know.

To digress for a moment, each thing in the game is an instance of Prototype, which basically is a collection of Components. A component is a narrowly-defined piece of functionality, that registers itself with the event instance upon creation. For example, if the component has a method called on_move, the event instance will tell the component when an on_move event occurs.

There is a component called XY that holds the in-game position of a thing, and its on_move looks like this:

def on_move(self, data):
    if (data['enactor'] == self.gid) and (data['outcome'] == 'continue'):
        self.x += data['x']
        self.y += data['y'] 
    return data

When the player presses the arrow key and the event instance broadcasts ‘on_move’, the XY component of the player-character will take that data — if the ‘outcome’ is ‘continue’ — modify its x and y position, then pass the data along back to the broadcaster.

The reason for ‘outcome’, and passing the data back to the broadcaster, is that other components handle things like collision with walls and bumping into other things. They’ll have an on_move method, and it receives the data broadcast. For example, here is collision:

def on_move(self, data):
    enactor = game.gids[data['enactor']]
    dx = data['x']
    dy = data['y']
    xx = enactor.XY.x + dx
    yy = enactor.XY.y + dy 

    stage = game.gids[game.stage].Stage 

    if not stage.cells[yy][xx]['kind'] == 'floor':
        data['outcome'] = 'failure'
        return data

    return data

Now obviously the question came up — in which order is the event broadcast? In other words, if the XY of the player happened to get the event before collision, the player would move before the collision check even happened.

To make this work I gave each component a priority from 1 to 1000, with a default of 1000. The collision component’s priority is 100, and the XY priority is 1000. Then before event broadcasts, it sorts the list of components registered for the event. This is easy to do with Python by writing a method like so on the Component parent.

def __lt__(self, other):
    if self.priority < other.priority:
        return True
    else:
        return False

If the class defines __lt__, then calling sort() on a list of instances of that class will sort it appropriately.

That’s all there is to the event broadcasting, basically a chain of event filters that modify both the event data and game world data as the event propagates through the listening components. I suppose I’ll find out what problems this causes down the road!

NPC actions are basically the same, the only difference being in how they’re initiated. Going back to the game loop:

        if game.control == 'play':
            for item in game.gids:
                thing = game.gids[item]
                for k in thing.__dict__:
                    c = thing.__dict__[k]
                    if isinstance(c, AI):
                        c.decide()

This runs through each component, and if it’s an AI, it runs the decide method. This is all I have for decide at the moment (in the Zombie component):

def decide(self):
    choice = random.choice(self.actions)

    data = {'enactor' : self.gid, 'input' : choice}
    control = game.controllers[game.control]
    control.action(data)

Where its self.actions is just this list — [‘up’, ‘down’, ‘right’, ‘left’, ‘wait’].

This just gave me an idea for attaching AI components to the player…anyway.

After the player does their thing and the AI runs, the UI instance runs its view method, which looks like this:

def view(self):
    libtcod.console_clear(0)

    for item in self.views:
            func = getattr(self, game.control + item)
            func()

    libtcod.console_flush()

So, clear the console, and go through its list of views and run the function — for example, there’s one called ‘play_draw’, where ‘play’ is the game.control and the item is ‘_draw’.

Other conceivable ones might be ‘_log’ to capture the game state, or perhaps ‘_write’ to turn this into IF ;D. In any case, yet another example of severe over-engineering…

These methods are fairly straightforward…I did one weird thing where certain events could create ‘animations’, components that last for just one game loop and get added to the list of game objects to be drawn. That’s something I need to redo (I want to have multi-frame animations possible between each player turn). My current idea here is to make drawing a series of frames, where normally there’s just one frame as I have now in play_draw, but with the potential for adding frames to make the animations.

So that’s it. If I expand this I can see the component system getting really massive…on the other hand I don’t know that any game wouldn’t have a similar situation once you add a lot of stuff. I can derive children from component classes in some cases (for example with AI) so that might keep things more organized. I like the separation of input/views/logic. Right now the world state is woven into the game logic quite a bit, but I’m of mixed feelings of trying to make a hard separation between the two. In this application it may not be worth it.

Any comments or criticism is particularly welcome at this point, so feel free.

Bidden day #7

total time: 54 hours

Well it’s 10 AM, up all night, and it’s rather shocking given the time spent, but I didn’t finish. The still warm entrails are here:

http://sites.google.com/site/biddenrl

You’ll need Python 2.6 and Windows to play (I use the word play very loosely). Linux shouldn’t be that hard but I’m not sure if I have the correct DLLs in the zip — you’re welcome to check it out though to see (I did include some .so DLLs, so maybe that will work).

I got to the point where the player had a goal, but really no gameplay. I spent a lot of time messing around with programming and obscure code structures, where I should have ruthlessly simplified/hacked things to get the gameplay vision in place, since I had a very clear idea of where I wanted to go. On the other hand I’m definitely pleased to have spent so much focused time this week (more than twice what I’ve ever done for Pyweek or Ludum Dare). A lot of the time went to basic programming tasks, which will speed up with practice I think. In general I feel like I’ve crossed a hurdle of some kind in making stuff.

In any case, I’m still very into the Bidden idea, so I’ll continue working on it until it’s fully realized.

Bidden day #7 in progress

time so far: 44 hours

The RNG smiled on me this afternoon and I finished early at work, so got an extra few hours in this afternoon. About 12 hours left, and I’m still very far from a success I think. But the structure is almost completely there. Right now it’s 9 PM and:

* I have the house to myself
* I have a full can of tea
* soul on the radio
* nothing to do tomorrow

We’ll see what happens!

Bidden day #6

time so far: 38 hours

* the creature is corpse’d when you bump into it (decorpsed? corpsefied? ‘killed’ is a little strong given the state of the creature model at the moment).

* world creation occurs properly at game start.

* beginning of a goal.

* but I did spend 2/3 of the night on message passing and event filtering! I’m sure this will come in handy.

tomorrow:

* add more creatures

* add at least one character boon

* add everything else

Bidden day #5

time so far: 35 hours

For some reason I thought it was Thursday today. What luck!

Still avoiding gameplay by making intro and help screens. I’ve also been messing around too much with stuff like UI and game input structures. However tonight I’ve been getting more messy and loose, and progress is accelerating. The screens above are the basic outline of a single play through. All I have to do now is add everything else!

Bidden day #4

time so far: 30 hours

Yes it’s true…only three more days to go…still no goals, conflict, character advancement or content. But I made a lot of title screens!

Really I couldn’t resist. The above example is a kind of mash-up from messing around with ascii-paint, the wonderful tool by priomsrb. I did get some other things done — I’m on my way to a structure for a menu, state switching and so on. I have a feeling though that I’m avoiding some of the harder things by doing these things that are, really, not totally necessary to making a functional game. I think it’s OK in the end though, it’s kind of like a mid-term break, and it should add to the overall experience (assuming I have something playable to experience!).

Still with the goal of a playable run-through from start to finish — would like to get that done by tomorrow night. Then Friday is work all night to add things and release.

Bidden day #3

time so far: 26 hours

The mockup slowly wrenches its way toward a real game. Believe it or not, up to this point in my programming self-education I had never touched procedural map generation — always working with hardcoded maps while doing other things. Luckily libtcod makes all of this fairly painless. I can’t imagine doing all of this from the ground up as some entrants are doing!

The above is a simple binary-space partition, with the borders of the rooms walled in, and then tunnels dug from room to room (somewhat randomly). I’m quite relieved to have made this milestone actually, as I was worried it was going to be a major stumbling block. I think I’ll be able to produce a rougher ruin map, and a twisting labyrinth map, in similar fashion. Of course it’s an open question if I’ll have any content to fill those maps with!

Basic movement and collision is in of course, and along with that and the level generation a proper code structure for the map as well.

I spent a bit of time with generating a template for title creation — if you look in the header you’ll see a partial example of that. In the story of this world there are many groups and factions, forming in every which way, and I hope with the titles to add some of that flavor.

Wrangled more with the json saving and loading — found since everything is unicode in json I need to convert symbols to str before printing with libtcod. It’s turning into more of a cost than a benefit, but one of my goals with this project was to give json a go, and I’m certainly doing that!

My long weekend is over, and it’s back to the dayjob Tuesday-Friday. Hopefully I’ll have some extra free time during the day this week, otherwise I see some long evenings ahead. My goal now that I have the basic structures is to get a playable run from start to finish, even if this just means the bare minimum of content required.

Almost double the number of entries this year compared to last year by the way — incredible!

Bidden day #2

time so far: 16 hours

Not much more than another mockup, but I like this one better than having the big sidebar not really doing much. The descriptive style of the header in this one is a bit of an experiment.

Spent a lot of the day working on saving and loading, choosing json instead of the tried and true YAML. Went for a walk in the afternoon and realized that was probably going overboard. Do I even need save games? Probably not, though I want them. In any case it’s worked out now, so I’ll go with it.

Tightened up the design, eliminating the mountain and slum map generation parameters. Also realized I should have a power limiter of some kind for boons, so put in limited uses. As you complete tasks you get more uses; at the same time the chance of more dangerous enemies and traps increases. I don’t want this to be a difficulty scale per se, though — there still should be low power enemies you can just blow away later in the game.

addenda: I couldn’t resist a couple of more hours post-post. For some reason I’ve decided to try a publisher/subscriber for the first time with this game. I got the game components to register handlers with an event broadcaster, and now keypresses send an event to the broadcaster, which propagates them to the appropriate handlers.

Bidden day #1

time so far: 8 hours

Nothing playable yet; just created a mockup in libtcod after making some notes and sketching out the code structure with Leo. The extent of my design:

I had the basic idea a couple of weeks ago and I figured the rest out in the notebook this morning. You play a bidden, a creature brought into a fantasy world to fulfill the wishes of the summoner.

In trying to keep this simple, yet still interesting and a roguelike, I’m planning the following:

* summoners bind you to a task, either to hunt (kill something), to seek (get or activate something), or to bear (deliver something).

* these summoners represent groups of different interests in the game world, but this aspect will be largely cosmetic.

* tasks take place in a terrain (valley, mountain, desert, cave — mostly cosmetic), and a place (tower, ruin, temple, labyrinth, slum) that influences what creatures are encountered (elevated, ancient, divine, beastly, mutated).

* Your bidden can wield one weapon and wear one piece of armor; these will be somewhat unlike conventional weaponry (in a cosmetic sense), and generated.

* Your bidden can have two boons (powers), with one active at a time. I have six boons planned, three each for combat and movement.

* Your bidden also can be warped. Each warp changes the active boon. I’ll have two warps, so counting the unwarped state, there are 18 possible powers. Particular victories and items/locations create warps.

* all attacks hit automatically, with a chance to destroy weaponry (based on the weapon/armor involved), or inflict a wound. The bidden can take five wounds before death. Particular items and victories heal wounds, and there’s a heal up when the bidden completes its task.

* the command interface:

* cardinal movement only
* use boon, ‘x [direction]’
* pick up or manipulate, ‘ [ctrl + direction]’
* cancel or main menu, ‘[escape]’
* confirm, ‘[space or return] ‘
* switch active boon, ‘[shift]’
* wait, ‘.’
* scroll message history, ‘ [pageup or pagedown] ‘
* command help, ‘?’
* bump to attack or open doors (maybe open doors is configurable)

* a list of achievements (tasks completed)

* a victory file

* a death file

So….probably way too ambitious! We’ll see.

Bidden 7DRL

It’s the inaugural post for my 2010 7DRL. My first 7DRL attempt, and my first attempt at a complete roguelike. Here we go!

In typical fashion, I’ve just created the home site for Bidden before there is an actual game!