Separation of Church and State


Actually, I mean to talk about a far more important topic: the separation of Game and World.

Spaghetti Code

A piece of software is, first, an organic whole. However, we've all had to unravel somebody else's spaghetti (or, better, our own) and know that it's useful to break that whole into organized pieces. You might use object oriented programming, for instance.

One useful way to break down a game is into a Game Engine part and a World Data part. Once you've got that, you just point the Game Engine to your World Data and press Play.

Separation of Sokoban and Nethack (*** Minor Spoilers for Nethack, ladies...)

If you're making Sokoban this maybe isn't so hard to achieve. The Game part governs movement rules, and maybe a other things like level selection, scoring, restarting, while the World part is the actual collection of Sokoban levels consisting of walls, pits, and stones.

But try to take apart something like NetHack and you soon find it hard to know where to draw the line. NetHack has notoriously many "special cases" to it's game rules; you don't have to look very hard to find code like:

/* penalty for breaking eggs laid by you */

if (otmp->otyp == EGG && otmp->spe && otmp->corpsenm >= LOW_PM) change_luck((schar) -min(otmp->quan, 5L));

result = "splat";

This is a pretty specific effect; there's a special luck penalty (proportional to how many eggs, I think, or how big, but not less than 5) if you kick an egg laid by yourself.

Should this really be a core behaviour of the Game Engine, or somehow encoded in the World Data?

Silly picture of anthropomorphic rabbit from the game Lugaru kicking a gigantic egg, with black yolk flying everywhere

Plugin System

We might attack the problem of specificity in object behaviour with a plugin system.

For instance, all objects might have a method onTouched () that is called automatically whenever an object is touched. In this way, we start to separate the core Game Engine object behaviour (detecting a touch and then calling onTouched ()) and the World Data which has a specific type of object.

For example, it's not too hard to imagine implemeting in this system a Stove Burner which when touched, burns you. There is an Object class (part of our Game Engine) and a Stove Burner (part of our World Data) which includes an onTouch () method.

If You Play With Fire...

But something still seems amiss. Even given callbacks, the Stove Burner's onTouched () method is clearly code, not data, so it belongs in the Engine, doesn't it? Perhaps only every instance of a Stove Burner is part of World? But then, back to square one, it seems awfully specific functionality to have a Stove Burner as part of the Game Engine.

Worse, suppose we decide it's part of our Game Engine. Then, what about a very specific and special Blue Stove Burner, that appears only once, and is the only one that can reach a high enough temperature to melt Titanium Oxide, a key part of your cleverly crafted "Paint a Snowstorm" puzzle?

Well, maybe the truth is that a piece of software is, after all, an organic whole. Since, as with science, it's really up to us and our patterns of thinking to draw the lines where they are useful to us, we shouldn't be surprised when we aren't certain where to draw them. Happens all the time.

Separation of Data and Code

Are we out of luck? Do we live game designers forever live with spaghetti code?* Or is there a principle for drawing this line that makes sense? In this case I think there is.

All data is code. Whether in practice we call it one, or the other, has mostly to do with complexity. I'm going to call it Interface Complexity. A database with a trillion records is likely more information dense and so in some sense (entropically) more complex than the nethack source code, but without being mathematically rigorous, at least, we can say the database has a lower Interface Complexity in that it's easier for us to understand it.**

So in order to define what is World (vs. what is Game) we need to define some interfaces with a low-IC, where "low" is, well, low enough that we aren't making too many mistakes.

Some Examples

In the above stove top example, I think we would do well to work with a property and effect system. It should be obvious enough that the particular instance of a Stove Top, given that the game engine otherwise can determine what a "Stove Top" is, is likely representable by a simple interface. For example an array like { Area Name, Position, Rotation, Object Type } would define an instance given enough context for Object Type.

But we already have a hunch that we don't want Object Types to be part of Game, i.e., part of code; we want them to be if possible part of World (data), as we're going to define a lot of them.

We could store our Object Types in a simple database table, but then how do we handle special effects (such as Burn When Touched) that will likely have to be handled in custom code? If we store the code for the events inside the database table itself, we've just increased the IC of our Object Types tremendously (though perhaps not unmanagably.)

Draw the Line at Interface Complexity

Instead, what we need to do is have the Effects, capital E, part of the Game. Since it's only the effects that are high-IC, we'll just write a plugin architecture for effects. What we'll then do is just as part of our Object Types, supply a list of Effects (let's say, by name.) A list of strings is certaily low-IC, we might still make a mistake (e.g., putting in "burn_when_touch" instead of "burn_when_touched") but as it's low-IC we can likewise imagine putting some support in for ourselves to catch such mistakes.

Certainly that's an easier problem than a foolproof programming language.

* Yes, but for other reasons entirely... =)

** There's an interesting question: what's it mean to understand some information?

2010-07-20


◀ Back