Let’s find more small steps toward replacing the old Command idea with Imperative. I have in mind providing a Lexicon in the existing structure.

Please Observe
I suggest that it’s worthy of note that I’m well on the way of completely replacing the command parsing of our game with an entirely new mechanism, and that it has been happening in very small steps, each of which stands on its own, and each of which keeps everything running. I hope, and rather expect, that that will continue. We are, however, getting to the hard part, where we start plugging the new thing in, to replace the old one. Thus far, one could argue, it has been pretty easy to proceed without breaking anything, because nothing but our own tests has been relying on the Imperative.

One would not be wrong, but I suggest that the point is to look at the value of small steps. Mind you, I’m not saying that I’m particularly clever, or working at some genius level. On the contrary, I suggest that most of the moves I’ve made could have been devised and made by any competent programmer. Possibly better devised and better made.

The value, the lesson, is in the effect of small steps themselves. They are easy to test, the changes are easy to make, the code change is small and therefore it’s easier to get it right, and easier to spot what’s wrong when, inevitably, I mess up.

And yet, we accomplish large, important changes, in this easy step-by-step fashion. If I were to offer advice—but no, that’s not my job. My job is to show you what happens when I do things. Your job is to decide what to do. You can emulate what I do, ignore it entirely, build a better idea upon it, or abetter completely different idea. It’s up to you.

But darn it, I really do like these little steps.

Today, in Theory

I believe that the Imperative side of things is far enough along, with everything pretty nicely encapsulated. I do expect that things will need to change as we plug it in to the World/Player/Room, but it’s pretty decently built as it stands, and it’s rather well tested as well. So I’m thinking that today we begin working on the other side, the game itself, to prepare it to accept the Imperative stuff instead of the Command stuff that’s in there now.

I haven’t reviewed the code yet, so I am probably wrong, but I’m thinking just now that we will clearly need a game-oriented Lexicon, which contains all the Synonyms, Verbs, and Actions that the game understands. All we have now is a Lexicon for testing. The game’s Lexicon could be hand-crafted with programming, just as the testing one was. But I would like for the same DSL that defines the world to be able to define the Lexicon, so that a game designer tells the world what words to understand, what synonyms they have, and what to do when a given command is executed.

I don’t know that that’s possible, but I don’t see why it wouldn’t be straightforward. However, I don’t want to put DSL Lexicon building ahead of installing the new Imperatives in the game. If, for some reason, that turned out not to be desirable, the investment in the DSL might be largely wasted. I think the more immediate risk, low though I believe it to be, is the installation of the Imperative into the Game. And, as such, I think our first game Lexicon can be created “by hand”, by populating it with POKO, Plain Old Kotlin Objects.

So let’s see how to put a Lexicon into the game. We’ll start with a quick glance at the Lexicon itself.

Today, Reviewing the Imperative.

I like to start any session by reviewing the code that may need to be changed. We’ll start today with the Imperative side, specifically the Lexicon and its contained objects.

class Lexicon(val synonyms: Synonyms, val verbs: Verbs, val actions: Actions) {
    fun synonym(word:String):String = synonyms.synonym(word)
    fun translate(word: String): Imperative = verbs.translate(word)
    fun act(imperative: Imperative) = actions.act(imperative)

class Verbs(private val map:Map<String, Imperative>) {
    fun translate(verb:String): Imperative = map.getValue(verb)
}

class Synonyms(private val map: Map<String,String>) {
    fun synonym(word:String) = map.getValue(word)
}
typealias Action = (Imperative) -> Unit

class Actions(private val verbMap:Map<String, Action>) {
    fun act(imperative: Imperative) {
         verbMap.getValue((imperative.verb))(imperative)
    }
}

That’s really all there is to it. You can get a synonym for a word, translate a verb to an Imperative, and ask an Imperative to act. There’s a bit more work done in the ImperativeFactory, but that probably won’t concern us this morning.

We could imagine Lexicon as an interface, with alternate implementations. Since we only have one need, we’ll probably not need to build interface infrastructure. If we do need to, we’ll retrofit it readily, I should think. Let’s do review the tables that live under the Lexicon, because they include some of the logic, via their defaults:

Synonyms
A word can have synonyms, and the word on the right is the preferred one. If you say “e”, the game will see “east”. If a word has no synonym in the table, then it is its own synonym and is just returned. If you say “fred”, “fred” will come out.
private val TestSynonymTable = mapOf(
    "e" to "east",
    "n" to "north",
    "w" to "west",
    "s" to "south").withDefault { it }
Verbs
The verb table turns any verb (the first word in a two word command) into an instance of Imperative, and it provides a tentative noun to go with it. This means that “east” can be a verb and its Imperative will say verb:”go”, noun:”east”. The intention is that the combination of synonyms and the verb transformation here, any command will have its standard verb in the verb position. All moves turn to “go”, and so on.
private val TestVerbTable = mapOf(
    "go" to Imperative("go", "irrelevant"),
    "east" to Imperative("go", "east"),
    "west" to Imperative("go", "west"),
    "north" to Imperative("go", "north"),
    "south" to Imperative("go", "south"),
    "say" to Imperative("say", "irrelevant"),
    "xyzzy" to Imperative("say", "xyzzy"),
    ).withDefault { (Imperative(it, "none"))
}
Actions
The action table maps an Imperative to an action, expressed as a lambda. Presently the parameter to the lambda is the Imperative itself, which implies that everything an Action can do will have to be accessible via the Action. It is possible that we’ll need to change this parameter to some higher-up object, but we’ll see.
private val TestActionTable = mapOf(
    "go" to { imp: Imperative -> imp.say("went ${imp.noun}")},
    "say" to { imp: Imperative -> imp.say("said ${imp.noun}")},
    "inventory" to { imp: Imperative -> imp.say("You got nothing")}
).withDefault { { imp: Imperative -> imp.say("I can't ${imp.verb} a ${imp.noun}") }}

Today, Reviewing the Game

Now let’s look at how the game is played, with an eye to plugging in our new Imperative, and in particular, thinking about providing the game’s starting Lexicon.

Player
The Player object knows the current room, and creates the GameResponse object that is filled in with the results of a command. Then it (currently) creates a Command with the input string, and asks the world to do the command:
// Player
    fun command(commandString: String) {
        response = GameResponse()
        var command = Command(commandString)
        world.command(command, currentRoom, response)
        currentRoom = response.nextRoom
    }
World
The World receives the command, containing the command string and little else at this point, plus the current room and the Response to be filled in. It saves the input response in its member variable so that all the objects that need to produce response elements can see it. Then it asks the current room to perform the command, passing the world itself to the method. On return, it fills in the next room, as part of the response, which the Player already has, so it need not be returned.

This might be done another way, with the World creating the Response and returning it to the player. Much of this rigmarole is unnecessary, but was done to “support” the possibility of more than one player in the world at the same time. There’s no plan for that to happen, but having the Player exist and know its room seemed to make sense. Possibly it should just know its room name.

The player will also need to contain the Inventory, which is now in the world.

Looking ahead, we might consolidate some of these separate objects if we don’t need them. My feeling right now is that there’s a difference in my mind between the player and the world, and reflecting that separation can’t hurt and might help.

//World
    fun command(cmd: Command, currentRoom: Room, newResponse: GameResponse) {
        response = newResponse
        currentRoom.command(cmd, this)
        response.nextRoom = roomNamedOrDefault(response.nextRoomName, currentRoom)
    }
Room
The Room is given the command to execute, and relies on the World to have the response. We assume that you’ll end the command in the same room as you start in, though this is often not the case. This allows individual commands to change the room, but avoids all the commands having to specify an output room.

As configured now, the actions are methods on Room, inventory, take, move, and castSpell.

// Room
    fun command(command: Command, world: World) {
        world.response.nextRoomName = roomName
        command.validate()
        val action: (Command, World)->Unit = when(command.verb) {
            "inventory" -> inventory
            "take" -> take
            "go" -> move
            "say" -> castSpell
            else -> unknown
        }
        action(command, world)
    }
Action Methods
It seems prudent to look at at least one action method, since the Imperative works a bit differently. Below, the move command, for example “go east” looks in the room’s moves for example for “east”, finding a targetRoom name, the room to the east, and a lambda function which, when executed, returns true if the move is allowed, false otherwise. Most “go” commands are initialized to return true but some contain operational blocks.
    val move = {command: Command, world: World ->
        val (targetName, allowed) = moves.getValue(command.noun)
        if (allowed(world)) world.response.nextRoomName = targetName
    }

Reflection

Enough review, let’s think for a moment. Note that the command function in Room has done the validation of the input string, looked up the action, and executed it. With the Imperative plugged in, one way might be like this:

  1. Room extracts the input string from the Command (which could be radically simplified);
  2. Room passes string to ImperativeFactory.fromString, which returns an Imperative;
  3. Room asks Imperative to act;
  4. Actions call methods on Room, filling in the Response as needed

It seems to me that this could all be done behind the scenes, with no other object needing to know that it had happened. (Although we may find reasons to do otherwise.) The Imperative would be essentially ephemeral, coming into existence, figuring out what to do, doing it, and vanishing.

Some object should have an ImperativeFactory, but it has no memory other than its lexicon, so we could, in principle, create a fresh factory every time, right in Room.command. That seems good to me: I’m not afraid to create and destroy little objects as I go. Nor should anyone else be. Well, C++ programmers, maybe.

A Plan Emerges

OK, here’s my tentative plan. I’ll add a Lexicon instance to world, and initialize it there. For now, it won’t be dynamic, but we have no requirement for that. Once world has a lexicon, the room can get to it and can create the factory, get the imperative and do its thing.

Where I am uncertain, almost entirely, is just how we’ll hook up the action table’s expected form of lambdas with something that will work in the world and room. I’m sure that Kotlin and IDEA will help, mostly by rejecting anything I try until I guess the right thing.

Shall we TDD the world’s lexicon? Let’s do.

    @Test
    fun `world has lexicon`() {
        val world = world {}
        val lex = world.lexicon
    }

IDEA tells me that world does not know lexicon, no big surprise, so let’s be having one. Here’s the world DSL function:

fun world(details: World.()->Unit): World{
    val world = World()
    world.details()
    return world
}

Here’s World:

class World {
    val flags = GameStatusMap()
    val inventory = mutableSetOf<String>()
    val name = "world"
    val resultString: String get() = response.resultString
    private val rooms = Rooms()
    lateinit var response: GameResponse
    ...

OK, we need a Lexicon, which needs a Synonyms, a Verbs, an Actions, each of which requires a map. I’ll just start creating these, as simply as I can.

OK, I basically just let IDEA prompt me with method creation, and filled in the blanks with the old tables from the tests. That’s more than I need for my current new test but I didn’t see the point of driving this out bit by bit. Here’s what I have for creating the world Lexicon now:

class World {
    val lexicon = makeLexicon()

    private fun makeLexicon(): Lexicon {
        return Lexicon(makeSynonyms(), makeVerbs(), makeActions())
    }

    private fun makeSynonyms(): Synonyms {
        return Synonyms( mutableMapOf(
            "e" to "east",
            "n" to "north",
            "w" to "west",
            "s" to "south").withDefault { it }
        )
    }

    private fun makeVerbs(): Verbs {
        return Verbs(mutableMapOf(
            "go" to Imperative("go", "irrelevant"),
            "east" to Imperative("go", "east"),
            "west" to Imperative("go", "west"),
            "north" to Imperative("go", "north"),
            "south" to Imperative("go", "south"),
            "say" to Imperative("say", "irrelevant"),
            "xyzzy" to Imperative("say", "xyzzy"),
        ).withDefault { (Imperative(it, "none"))})
    }

    private fun makeActions(): Actions {
        return Actions(mutableMapOf(
            "go" to { imp: Imperative -> imp.say("went ${imp.noun}")},
            "say" to { imp: Imperative -> imp.say("said ${imp.noun}")},
            "inventory" to { imp: Imperative -> imp.say("You got nothing")}
        ).withDefault { { imp: Imperative -> imp.say("I can't ${imp.verb} a ${imp.noun}") }})
    }

This ought to make my minuscule test run. With the addition of a missing parenthesis, it does. Let’s do a save point commit, that was a lot of typing.

Now a quick few checks in the test, just for reassurance:

    @Test
    fun `world has lexicon`() {
        val world = world {}
        val lex = world.lexicon
        assertThat(lex.synonym("e")).isEqualTo("east")
        val imp = lex.translate("east")
        assertThat(imp.verb).isEqualTo("go")
        assertThat(imp.noun).isEqualTo("east")
    }

Test passes. Commit: World initializes a Lexicon, presently same as test version.

Summary

I’ve been at this for a bit less than two hours, I guess, and have accomplished what I set out to do, installing a Lexicon into World. Next step will be to get Room to create and use an Imperative, and then the hard part will be recasting the Actions so that they’ll work for us. I think the biggest issue will be to decide whether to keep the imperative as the argument for the commands, and therefore to enhance it to know the world, or … something else.

Either way, it’s time for a break. The morning has gone well so far, and we’re a step closer to our new Imperative.

I do feel that this is taking “a while”, and if this were a real product effort, I think we’d be better off to be adding some features. On the other hand, we’d have more than one ancient programmer on the team, so things might not take quite so long. Recall, though, that the game hasn’t been broken, and that we could add features to it independently of the Imperative effort, if we wanted to. We’re not stuck dead in the water by any means.

I could imagine having broken the game by now, by plugging in the Imperative prematurely, so that all the work of the past few days would have prevented us from releasing new features, because we’d have broken the command dispatch of the game. We didn’t do that … we didn’t need to, and we probably won’t break it for more than an hour or so, ever.

This is an example of why I believe that there are no “large refactorings” that have to stop progress. I believe that we can always find a way to edge up on a change, building up whatever largish thing we need, and then swapping it in with just a few quick moves. It’s not necessarily as easy as this, but it’s always possible, for large values of always.

Come along next time, I’m sure we’ll plug in the Imperative. We might even use it.