Written by the same guy who did http://gameprogrammingpatterns.com/, it’s worth a read even if you aren’t developing a turn based game, you might pick up how various problems are thought about and solved regardless of genre.
Even has interactive demos!
Most of you know I’m a design-patterns-consider-harmful kinda person. Now this author’s design-patterns page is pretty good, but this is WTF.
Why on earth would one implement an action as a command-pattern? Oh yeah because all of your problems must be broken into into a fixed set of cookie-cutter solutions.
WTF:
abstract class Action {
Actor _actor;
Game _game;
Queue<Action> _actions;
GameResult _gameResult;
bool _consumesEnergy;
Every “action” not only stores the actor executing it, but also the game it’s running in? Because obviously an actor can be running in many game instances at the same time with shared state? WTF? And a queue? And it’s result? Why is does even store the actor? Oh yeah, because there are only two ways to manually dispatch a method call: command and visitor patterns.
Well, the author is nuts anyway… need scripting in your game? Write a language, parser, compiler, linker, VM, bridge to interface with native code, etc etc.
It seems to trigger an identical response from you, too
I don’t know the names of most of the patterns anyway I think I know what a visitor pattern and a flyweight are supposed to achieve.
Still, his design for a roguelike engine is a perfectly sound basis.
An action doesn’t need a reference to the acting entity if it’s passed in. Likewise an action doesn’t need a reference to the game as the entity knows that information.
Last time I wrote a roguelike, I found having actions as command-pattern hugely simplified the AI and GUI, since I could generate all valid actions, then just push that list to the UI (for humans) or AI (for NPCs) to make the actual decision of which to use.
Patterns tend to be overdone and overly formalised, but I think you’re throwing the baby out with the bathwater here.
Not from my viewpoint. The author’s claiming is he focusing on the architecture rather than getting things done. Fine. From an architecture standpoint why is an action coupled with a specific invocation of that action? This adds complexity and is counter to the “best practices” war cry of decoupling of state and behavior. There are situations were this is useful, but there’s no rationale provided. An example might be if you want to be able to undo action(s). Ignoring the roguelike part this, doing it this way means fatter data and spawning an instance of an action each time one is performed. Whereas a decoupled version requires exactly one instance per concrete action over the lifetime of the game (or zero in some cases if you’re using java 8 ).
A manual dispatch designed for the actions of the game is what I’m arguing…gives you all the good parts and avoids the bad.
On other things. What’s with this ‘alternate’ action thing? What purpose does it serve? None as presented. It might if there was some common preconditions code but there’s no hint of that. Even with preconditions I wouldn’t do it that way, but that another story. Again (not important in a roguelike) spawning useless instances. The invoked action can automatically invoke the (appropriate) alternate automatically when needed. Also the author is seemly promoting the invoke method choosing the alternate…in some cases that makes sense in others it reduces flexibility. Take the move example (probably the most important for a roguelike). I’d argue it vastly superior for move to query what it’s attempting to move into for what happens in that case. Otherwise all special cases of move are in move’s implementation and not with the thing in question. Here’s some snippets from move:
WTF: either the actor has already moved or it hasn’t. Otherwise seems reasonable to live in ‘move’. Probably the only other thing you might what is add if for non-hostile for talk/store action.
// See if there is an actor there.
final target = game.stage.actorAt(pos);
if (target != null && target != actor) {
return alternate(new AttackAction(target));
}
For everything else it breaks down into two independent things:
Either we can move to the tile this action or not.
Something is triggered by the (attempted) move or not.
The current mess looks like this (massive fail):
// See if it's a door.
var tile = game.stage[pos].type;
if (tile.opensTo != null) {
return alternate(new OpenDoorAction(pos));
}
// See if we can walk there.
if (!actor.canOccupy(pos)) {
return fail('{1} hit[s] the ${tile.name}.', actor);
}
actor.pos = pos;
// See if the hero stepped on anything interesting.
if (actor is Hero) {
for (var item in game.stage.itemsAt(pos)) {
log('{1} [are|is] standing on {2}.', actor, item);
}
}
return succeed();