Scourge is a complex game, and programming its model involves accomplishing several tasks. Starting out, I wanted to separate the game concepts that are specific to Scourge from the concepts that form the underlying structure of a strategy board game.
So! What makes a good model for a strategy board game? By “good” I mean, “offers a representation of the system it’s modeling that best facilitates reasoning about that system”. I want views, net protocols and computer opponents to be relatively easy to map to this thing. So here’s the list of criteria that I came up with:
- Each player decision must be explicitly represented : rather than leaving it up to players to discover what options are available to them, the model should create options from the state that players may then evaluate, compare and choose from. This limits the role of an AI to purely strategizing, and reduces the amount of code that a player relies on to reason about a particular rule. (I could technically write a functioning AI that performs no rule-specific reasoning about its decisions, relying solely upon a coarse estimate of the board’s value. I think it makes for a good starting point!)
- A traversable decision tree : we can couple the aforementioned player decision generation with a rewindable history object, so that players can climb up and down the decision tree like a rock climber on a climbing wall, moving from handhold to handhold, trying to reach a goal position.
- Dynamic rules : there are games with many, many rules, with each one applying to a particular context. If the game is being played in a context where a rule never applies, then the game model should not represent that rule computationally– after all, it will never contribute to the available options to a player.
That last condition relates to entity system frameworks, which allow the game devs who work with them to maintain complex changing systems. But the games I’m interested in generally don’t change much after they begin; the third condition only really applies to the generation of games, and is rigid afterwards.
My system needs to be built flexibly, but to run rigidly and deterministically, and thirdly, to limit the players to a set of decisions. I call this the Rule-Option Paradigm Entity System, or the ROPES, and it’s the most distinctive model I’ve ever written. Feast your eyes, visitor!
There’s a little bit more to it than this, but really, this is all most programmers will ever see of it. Let’s break it down.
ROPES games are comprised of
Rules, which are just
Option generators; to perform a player action, the game simply passes the player’s choice of
Option to the
Rule that generated it. That
Rule, in turn, updates the
State, and the game updates all the
Rules, and so they generate new
Options, and so on. There’s our explicit player decision representation.
On the bottom right there’s a
History object, which is just a buffer of data that can record and undo changes we make to it. The
StateHistorian maps the
State of the game to that
History. This gives us the ability to make changes and then rewind them.
The third major feature of the ROPES, and possibly the most prominent in the codebase, is mostly left out of this diagram– the ability to configure games quickly. Scourge’s prototype was built in a way that makes it harder and harder to add new features, and to configure existing ones, because the game’s complexity was related to the number of
Rules in it. In contrast, the ROPES makes no assumptions about the way a game is played; each
Rule is a separate, independent logical component. When you line up a bunch of
Rules, each one specifies what
Aspects it needs on the different entities within the
State. The ROPES then builds the
State and the
Rules get pointers to the
Aspects they need. In this way, the complexity of a ROPES game is related to the number of
Rules you play by, not the number of
Rules you have available.
Here’s an example of a Scourge
Rule built in the ROPES. Note that it has only one option. That’s because not all
Rules of a game are used to offer choice to a player– some are active right after a player makes a choice. The ROPES offers a convenient way to chain
Rules like this together, so that a player action triggers a sequence of
So! We have a model with initial configurability; it has a history, so that we can choose options and then undo them, which helps us reason about the future; and apart from its
State, it exposes
Options to the player, so that the decision tree is directly traversable. Job done.
I think this is a big enough blob of text for now. I’ll be producing a couple ROPES examples soon– one for Tic-Tac-Toe and one for Checkers– that will hopefully demonstrate by example what I may have failed to communicate here. The ROPES has helped Scourge grow quickly, and I think it’s a useful stepping stone for similar games.