Bossing Around

I finally got around to implementing the ‘Command’ design pattern for my little sokomand prototype. And it works quite beautifully. I am immensely grateful for Robert Nystroms explanation of the whole ordeal in his Game Programming Patterns book, which he even released for free on the web. I realize this coming from someone still wrangling with understanding many design patterns and their uses himself, this might not be the most reliable opinion on the matter but if you have any interest in writing up games and still want to get a deeper understanding of some of the common patterns arising there, this book is such an amazingly written trove of knowledge. It typically begins by presenting a problem, nudges you to say ‘hey, if this and that were different, we could make that way more flexible’ and then shows you how to do this and that. I love it. (though I am a filthy reader of the web version - whenever I next have some spare cash, this thing will be sitting in my bookshelf pronto.)

I carefully implemented the Pattern, following the book’s chapter step by step at first - thankfully so, because it exposed some errors of thinking on my part (who gets passed which information when?). It started very simply - one Command class, which was just an interface:

interface Command {
  fun execute(entity: Entity)
}

One actual Command:

class Move(val dir: Direction): Command {
    override fun execute(entity: Entity) {
        entity.move(dir)
    }
}

an input handler which would create the commands:

fun onKeyUp(keycode: Int) {
    when(keycode) {
        RIGHT -> level.executeCommand(Move(RIGHT))
        LEFT -> level.executeCommand(Move(LEFT))
        DOWN -> level.executeCommand(Move(DOWN))
        UP -> level.executeCommand(Move(UP))
    }
}

and finally, a function on the actual level itself, which would call the command on an entity (only the player for now)

fun executeCommand(command: Command) {
    command.execute(player)
}

Abstractions

The Command does not exactly work like the first example in the chapter because it carries state itself. See that small val: Direction at the top of its declaration? That is us passing in a state into the Command itself. In our case, it can only be one of 4 different variables (UP, DOWN, LEFT, RIGHT) since that are the only valid directions for moves in the game. One of the advantages of commands not carrying state, but ONLY wrapping around an execute function is that you can instantiate the Command class once and forever pass that around. This would kind of still work here, just instantiate it four times, once for each DIRECTION possible, but if we could move for example in 360° that would obviously go out the window. So think about whether you want your state to be abstract but reusable - no state - or flexible but specific. As an example in this case, we could have just created 4 different commands, one for each direction, and it would not have carried any state and could be reused as often as we wanted. Now, obviously this is a very simple example of the whole thing since we only carry 4 different states. But I hope it illustrates the difference between abstract and specific commands, and which you want to use. (Think for example, if we could move the player to any position, what would we have to pass in then? Would it still be a similar situation?)

And that is all the code I had to implement to get it to work exactly like before, when the input function itself found the player and said ‘Player: Move!’. Just, now it is exceedingly easy to remap a different command to the buttons we have or remap the button that controls a specific command. All we have to do is change our input handling function:

fun onKeyUp(keycode: Int) {
    when(keycode) {
        W -> level.executeCommand( TooHeavyToJump() )
        A -> level.executeCommand(Move(LEFT))
        S -> level.executeCommand(Move(DOWN))
        D -> level.executeCommand(Move(UP))
    }
}

Here, I changed both the keys we use to do stuff, and the stuff we can do. This is the exact level of abstraction Commands grant us. By creating an ‘envelope’ which has a standard form, and wrapping it around our actual function call, both sides now know how to handle not just specific function calls but any call we pass in, any we can think of really. As long as the envelope conforms to its dimensions (in our case: it has an execute function - which gets contracted with the Command interface), both sides will always know that it is one of our envelopes and pass it along accordingly.

To implement the actual Undo functionality, I had to change it slightly yet again. But I will get to that later.