This morning, I want to try a different way of mating the new Imperative to the existing scheme. First, I’ll have to figure out what that scheme is, and whether there’s anything in it to like. I suspect there is.

I could have saved I don’t know how many minutes if I had set up Jekyll to generate index.html from untitled.md. I’ve spent literally moments editing untitled to index.

Aaaanyway, I think it’s time to start bringing my new Imperative stuff into play, replacing the parsing that is there now. Issues include:

  • I don’t remember perzackly1 how it works now;
  • We need to get the Imperative’s knowledge right;
  • We want to go in small steps;
  • No, smaller than that;
  • We want to keep as many tests working as we can;
  • We need to get the files moved over to the main side.

I’m sure there are more. Let me say a few words about small steps (supported by tests).

Small Steps
I would have said that I usually go i very small steps. I’ve often said that if I go 20 minutes without a green bar, I’m in the weeds and someone should pull me out. I’ve always granted that sometimes I know or think I know, a big chunk of code to write, and that I tend to go ahead and write it, even if it isn’t all demanded by my tests or the current situation. Still, I thought I was pretty darn good at taking small steps.

Then GeePaw Hill started talking about Many More Much Smaller Steps, on line and in our chats, and I started paying more attention to the size of the steps I was making. What I discovered was that it worked better to take steps even smaller than I was already doing. Something for everyone to think about? I suspect so.

Supported by Tests
Are we test-driven? Are we guided by tests? Supported by tests? Are they even tests, or are they examples? Or checks? Well, in this language they start with the word @Test so I’ll call them tests. But are they driving me, guiding me, or supporting me?

Well, yes, but I do write them myself, and I do rewrite or revise them if I don’t find them ideal, and sometimes I even remove them when they’re mo longer very useful. So, probably more support than guide, and more guide than drive. But whatever words we use, I find my tests to be very useful in helping me, first, work out how to use something I haven’t written yet, and then to see that it works. And, of course, I generally keep them because a I change things, sometimes they alert me to the fact that my changes have broken something.

In this language and IDE that is new to me, I’m finding my tests to be very helpful in keeping my changes small, and in being sure that things do what I think they do. This remains true in spite of the fact that Kotlin type checks my work like the truly irritating Sister Mary Ernestine used to do in high school English class. turns out that dotting every i and crossing every t is good training for programming without error. It’s just that there’s more: the code has to do, and continue to do, what I want it to do.

Summary of Digression
Improving my already pretty good habits is paying off for me. I’ve been doing this programming stuff for six decades, and this TDD / refactoring stuff for more than two decades (overlapping, I’m not that old), and if more, smaller steps, and more supporting tests help me … well, I’m not the boss of you. Let’s get to it.

I want to start the process of using the new Imperative and its related table objects, in the real program. The old way worked well enough. The new way works better, in my view, and is much nicer code, though both of them have aspects that I like. Either one of them would probably be more than we need for a game that only accepts two-word commands, but we’re here to learn Kotlin and IDEA, not just to write a silly game.

A central idea in all this is that I want the words and operations that the game does to be defined, to the greatest extent possible, in the simple DSL that defines the world. Closely related to that desire is that, pretty clearly, the commands that are done need access to the world and its contents in order to function. Saying “xyzzy”2 only works in certain rooms. “Take axe” only works in a room where there is an axe. And so on.

On the game side of the field, we have some objects that are dealing with all that. Perhaps the main one of these is the CommandContext, an interface with only one implementor3 so far, the TestCommandContext. Let’s review those. Maybe, if we’re lucky, we can build a new CommandContext implementor for Imperatives, and then smoothly slide it into place.

interface CommandContext {
    val directions: List<String>                // {e, east, w, west, up, dn, down ...}
    val verbsTakingNoNoun: List<String>         // {inventory, look }
    val magicWords: List<String>                // {xyzzy, plugh }
    val operations: Map<Verb,(Command)->String> // {take->(), ...}
}

class TestCommandContext : CommandContext {
    override val directions = listOf(
        "n","e","s","w","north","east","south","west",
        "nw","northwest", "sw","southwest", "ne", "northeast", "se", "southeast",
        "up","dn","down")
    override val verbsTakingNoNoun = listOf("inventory", "look")
    override val magicWords = listOf("xyzzy", "plugh", "wd40")
    override val operations: Map<Verb, (Command) -> String> = mutableMapOf(
        "commandError" to {command: Command -> "command error '${command.input}'." },
        "countError" to {command: Command ->
            "I understand only one- and two-word commands, not '${command.noun}'."},
        "go" to {command: Command -> "went ${command.noun}." },
        "inventory" to {command: Command ->  "Did inventory with '${command.noun}'."},
        "say" to {command:Command -> "said ${command.noun}." },
        "take" to {command:Command -> "${command.noun} taken."},
        "verbError" to {command: Command -> "I don't understand ${command.noun}."},
    )
}

I’m a bit disappointed with past me, in that I don’t see the world represented in the context, and it seems that it should be. But past me moved away from this idea to examine a potentially better one, and has come home with something we do prefer. So I forgive past me quickly, as is usually my preference.

Far more disappointing, and I’m talking about the code now, not YT, is that the context just has lists and maps in it, not behavior, which foretells code like this:

    fun findOperation(): Command {
        operation = context.operations.get(verb)!!
        return this
    }

Direct access to all the lists and sets. Not great. I recently vowed to be much more inclined to cover primitive objects like List and Map with small classes of my own, but that was after I wrote what’s here.

Therefore …
We’ll not feel obliged to be compatible with this old code. Let’s instead look at where it plugs in, and work toward plugging our new Imperative idea in neatly.

In our View, presently, we create the world member, and a player member, and we send commands to the player:

    val player = Player(world, "wellhouse")

    fun someoneTyped() {
        val cmd = myCommand.text
        player.command(cmd)
        myText.appendText("\n> " + cmd)
        myText.appendText("\n"+player.resultString)
        myCommand.text = ""
        myCommand.appendText("")
    }

Following that lead:

class Player(val world: World, startingName: String) {
    val resultString: String get() = response.resultString
    var currentRoom = world.unsafeRoomNamed(startingName)
    val currentRoomName get() = currentRoom.roomName
    val roomReferences: Set<String> get() = world.roomReferences
    var response: GameResponse = GameResponse()

    fun command(commandString: String) {
        response = GameResponse()
        var command = Command(commandString)
        world.command(command, currentRoom, response)
        currentRoom = response.nextRoom
    }
}

I think the order of those members should be improved, with the vanilla getters moved down. I think there may also be another way to express them in Kotlin, but I’m not sure.

class Player(val world: World, startingName: String) {
    var response: GameResponse = GameResponse()
    var currentRoom = world.unsafeRoomNamed(startingName)
    val roomReferences: Set<String> get() = world.roomReferences
    
    val currentRoomName get() = currentRoom.roomName
    val resultString: String get() = response.resultString

I also have a bit of a fear that the Response isn’t properly initialized. Let’s have a look at it:

data class GameResponse(val name:String="GameResponse") {
    val resultString: String get() = sayings + nextRoom.longDesc +"\n"+ nextRoom.itemString()
    var sayings = ""
    lateinit var nextRoomName: String
    var nextRoom: Room by singleAssign<Room>()

    fun say(s:String) {
        sayings += s+"\n"
    }
}

Yeah … that’s pretty weird. I don’t really know how to express it better. We really do have to assign the room and room name late, because that information comes during command execution, where the command is filling in the response as it runs. Here again, it seems to me that the members aren’t in a good order. Let’s change that:

data class GameResponse(val name:String="GameResponse") {
    var sayings = ""
    lateinit var nextRoomName: String
    var nextRoom: Room by singleAssign<Room>()
    val resultString: String get() = sayings + nextRoom.longDesc +"\n"+ nextRoom.itemString()

It seems odd to have both the nextRoomName and nextRoom. You’d think you could derive the name from the room, or that you wouldn’t need the room. The fact is (well, to be more accurate: I think) that the go has the room name, not the room, and that the room gets filled in or used later. Let’s explore that: we might be able to simplify. First, though, a test and commit: “tidying”.

The command proceeds like this:

// Player
    fun command(commandString: String) {
        response = GameResponse()
        var command = Command(commandString)
        world.command(command, currentRoom, response)
        currentRoom = response.nextRoom
    }
// World
    fun command(cmd: Command, currentRoom: Room, newResponse: GameResponse) {
        response = newResponse
        currentRoom.command(cmd, this)
        response.nextRoom = roomNamedOrDefault(response.nextRoomName, currentRoom)
    }
// 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)
    }

In room there, we are initializing the response to assume that we are going to stay in the same room, and if a command wants to change rooms, it tells the response:

    val move = {command: Command, world: World ->
        val (targetName, allowed) = moves.getValue(command.noun)
        if (allowed(world)) world.response.nextRoomName = targetName
    }
Assessing
It seems to me that the Response’s oddness is in part due to the fact that it is created way back in Player and passed down through world, and then filled with sayings and a nextRoomName by command execution in the room, where the nextRoom i finally filled in in world before returning to the player who already has a hold on that response. It seems to me that the Player should expect a Response back from world.command, and that world and its subordinate room should sort out the details of how it’s filled in among themselves. Ideally, perhaps, the Response that Player sees should be read-only. (In practice, I don’t pay a lot of attention to such things, because I trust myself not to tamper with what I shouldn’t. Usually, but not always, that trust is well placed.)

Let’s see if we can move creation of the Response down, and in particular if we can make player not care about what room is current.

Ah. I remember “why” I did that. There might be more than one person in the cave, even though there never will be, so I decided that the Player has the knowledge of what room they are in, and passed that info in via the response.

Furthermore … the Player should have the inventory. The world currently thinks there is only one inventory, so it’s not player-centric yet:

    private val take = { command: Command, world: World ->
        val done = contents.remove(command.noun)
        if ( done ) {
            world.addToInventory(command.noun)
            world.response.say("${command.noun} taken.")
        } else {
            world.response.say("I see no ${command.noun} here!")
        }
    }
Key Observation

We are totally not where I thought we’d be. In reviewing how to plug the new Imperative into the game, I’ve (re) discovered some aspects of the design that need improvement. There is no surprise to that, nor should there be any shame: we are, by intention and plan, evolving this program into being what we want. Well, maybe a little shame, in that the current design, here on Sunday 4 September, isn’t as good for what it does as it should be. That’s not “technical debt”, that’s really design lag or something. It’s also a nearly inevitable effect of incremental design … unless I’m really careful to clean things up.

Possibly—probably—I’ve been going too fast and skipping to a new idea before the previous idea was well installed into the system. I am subject to that, especially in the early days of a program, or even a sub-problem, because I enjoy the thrill of discovery. But now, there’s a bit of a mess over here amid the world and the room and the player, and since we’re about to deal with a new scheme for our commands, it may be that we need to deal with it.

Or do we? Not that we should just sweep this design issue under the carpet: there is no carpet in my house. But could we plug the imperative in, right around here, in 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)
    }

Until the call to validate, the Command hasn’t done anything, it’s just got the command input string, unchecked, and it self-initializes to have a TestCommandContext, which is that bunch of tables we looked at earlier.

I’ll bet we can splice our new Imperative right in there, and then deal with moving and adjusting the response, the player, and whatever all else, later.

This will be a good thing, if it works, because it will simplify the overall concerns by one big one. And, in doing it, we’re likely to at least bump up against and recognize, other issues in the design.

I’m glad we had this little chat. Let’s look at what it would take to do the command using Imperative. We’ll need to do at least two things:

  • Ready Imperative to accept an unchecked string input;
  • Provide suitable tables, probably in “test” form;
  • Give Imperative access to whatever it needs to execute a command;
  • Mate Imperative with Response and whatever is already in place.4

First, the unchecked string issue. I think that currently, ImperativeFactory, which creates our Imperatives, has two create commands:

class ImperativeFactory(private val verbs:Verbs, private val synonyms:Synonyms = Synonyms(TestSynonymTable)) {
    constructor(map: Map<String, Imperative>) : this(Verbs(map))

    fun create(verb:String): Imperative = imperative(verb)
    fun create(verb:String, noun:String) = imperative(verb).setNoun(synonym(noun))
    private fun imperative(verb: String) = verbs.translate(synonym(verb))
    private fun synonym(verb: String) = synonyms.synonym(verb)
}

We need to make a method for the factory that starts from an input string, and does the initial parsing into one or two words. I’m not as happy as I once was with the overloading of create here. It was fun to do, but all our uses know which one they want to call. Let’s rename those methods, fromOneWord and fromTwoWords to indicate what they are, and to free up the word create in case we need it. But we won’t.

    fun fromOneWord(verb:String): Imperative = imperative(verb)
    fun fromTwoWords(verb:String, noun:String) = imperative(verb).setNoun(synonym(noun))

IDEA does this for me quite nicely. Tests run. Commit: Rename methods.

Now let’s add to the Imperative tests to deal with an unclean string input.

    @Test
    fun `raw string input`() {
        val factory = ImperativeFactory(TestVerbTable)
        var imp = factory.fromString("east")
    }

That’ll drive, um, guide, um support my creation of a fromString method. I’ll “fake it till you make it”:

    fun fromString(input: String): Imperative {
        return fromOneWord(input)
    }

I expect green. Get it. Smallest step I could think of to get back to green. Now maybe we should check the result. I think it should say “go east”.

    @Test
    fun `raw string input`() {
        val factory = ImperativeFactory(TestVerbTable)
        var imp = factory.fromString("east")
        assertThat(imp.verb).isEqualTo("go")
        assertThat(imp.noun).isEqualTo("east")
    }

Green, of course. We should commit some save points here, shouldn’t we? “Raw input tests.”

Time to make it more difficult:

        imp = factory.fromString("go west")
        assertThat(imp.verb).isEqualTo("go")
        assertThat(imp.noun).isEqualTo("west")

Let’s see this fail:

Expecting:
 <"go west">
to be equal to:
 <"go">
but was not.

Right. Improve the code:

    fun fromString(input: String): Imperative {
        val words = input.split(" ")
        return when (words.size) {
            1-> fromOneWord(words[0])
            2-> fromTwoWords(words[0], words[1])
            else -> fromTwoWords(":oneWord", ":orTwoWords")
        }
    }

I just invented the convention where I return those two non words. if I don’t get 1 or 2 words. Kotlin required me to say something. We’ll deal with that later on. I expect green. I get green. Let’s extend the test to some error cases. Note that we’re just lexing here: we make no semantic interpretation of what comes in.

        imp = factory.fromString("too many words")
        assertThat(imp.verb).isEqualTo(":oneWord")
        assertThat(imp.noun).isEqualTo(":orTwoWords")

As soon as I type this in I don’t like it. First make sure it’s green.

Yes it is. Let’s change the rules to provide a different verb for 0 and for > 2, and to include the input, whatever it is, as the noun. We’ll make sure that flows through to a reasonable message in due time.

Change the test:

        val tooMany = "too many words"
        imp = factory.fromString(tooMany)
        assertThat(imp.verb).isEqualTo(":tooManyWords")
        assertThat(imp.noun).isEqualTo(tooMany)
        val noWords = "234563^%$#@"
        imp = factory.fromString(noWords)
        assertThat(imp.verb).isEqualTo(":tooFewWords")
        assertThat(imp.noun).isEqualTo(noWords)

Test to see the error.

Expecting:
 <":oneWord">
to be equal to:
 <":tooManyWords">
but was not.

Fix the code:

    fun fromString(input: String): Imperative {
        val words = input.split(" ")
        return when (words.size) {
            0-> fromTwoWords(":tooFewWords",input)
            1-> fromOneWord(words[0])
            2-> fromTwoWords(words[0], words[1])
            else -> fromTwoWords(":tooManyWords", input)
        }

I expect this to be green. It isn’t:

Expecting:
 <"234563^%$#@">
to be equal to:
 <":tooFewWords">
but was not.

Did I mess up the test? It looks OK:

        val noWords = "234563^%$#@"
        imp = factory.fromString(noWords)
        assertThat(imp.verb).isEqualTo(":tooFewWords")
        assertThat(imp.noun).isEqualTo(noWords)

Could that trivial code be wrong?

    fun fromString(input: String): Imperative {
        val words = input.split(" ")
        return when (words.size) {
            0-> fromTwoWords(":tooFewWords",input)
            1-> fromOneWord(words[0])
            2-> fromTwoWords(words[0], words[1])
            else -> fromTwoWords(":tooManyWords", input)
        }
    }

I don’t see how. I do notice something odd: IDEA displays the parameter names in the calls, and it is displaying verb for fromTwoWords but not noun. Recall that those two from methods used to have the same name. Has IDEA become confused?

I think I’ll close and reopen the project. It’s still doing it. (Or, not doing it.)

I tried “Repair IDE” and it’s still not showing that second parameter with its name. I don’t know, looking around, IDEA is sort of whimsical about when it shows the parameter names and when it doesn’t. I’ll proceed on the assumption that the problem with my test is in my code, usually a good bet.

I don’t see it, the code looks right to me. I could try a breakpoint, but I don’t want to know how to debug that way: it leads to bad habits, like stepping. I’ll do a print or two:

        return when (words.size) {
            0-> {println("doing too few words $input")
                    fromTwoWords(":tooFewWords",input)}
            1-> fromOneWord(words[0])
            2-> fromTwoWords(words[0], words[1])
            else -> fromTwoWords(":tooManyWords", input)
        }

The print does not print. What does split return?

Ah, my bad. It never returns zero. It just returns whatever is before the non-existent blank. So there really isn’t a zero case. OK, fine, be that way.

Wait, what about an empty string? I’ll try that. Right. Returns a one-element list with the empty string in it. OK, zero can’t happen. I decide this is my output:

        return when (words.size) {
            1-> fromOneWord(words[0])
            2-> fromTwoWords(words[0], words[1])
            else -> fromTwoWords(":tooManyWords", input)
        }

And fix the tests accordingly. I find myself wanting to drift from lexing to parsing. Aside from input case, which we’ll deal with in a moment, I resist messing with whatever comes in. My job is to get one or two words out of the deal.

The test, adjusted:

        val tooMany = "too many words"
        imp = factory.fromString(tooMany)
        assertThat(imp.verb).isEqualTo(":tooManyWords")
        assertThat(imp.noun).isEqualTo(tooMany)
        imp = factory.fromString("")
        assertThat(imp.verb).isEqualTo("")
        assertThat(imp.noun).isEqualTo("none")
        val noWords = "234563^%$#@"
        imp = factory.fromString(noWords)
        assertThat(imp.verb).isEqualTo(noWords)
        assertThat(imp.noun).isEqualTo("none")

Green. One more test, some upper case, which we don’t want.

        imp = factory.fromString("Go West")
        assertThat(imp.verb).isEqualTo("go")
        assertThat(imp.noun).isEqualTo("west")

This’ll fail:

Expecting:
 <"Go">
to be equal to:
 <"go">
but was not.

And we fix:

    fun fromString(input: String): Imperative {
        val words = input.lowercase().split(" ")
        return when (words.size) {
            1-> fromOneWord(words[0])
            2-> fromTwoWords(words[0], words[1])
            else -> fromTwoWords(":tooManyWords", input)
        }
    }

Right, OK, green. Commit: ImperativeSpike lexing improved.

I even remembered to test everything: I had taken advantage of IDEA’s willingness to just run one test.

Discovery
I’ve been working with the tests and code in the ImperativeSpike file, on the test side of the tree. I have 116 lines of tests in there, and several classes, Lexicon, Imperative, ImperativeFactory, Verbs, Synonyms, Actions, and the test versions of the underlying tables for those last three classes. My habit, in general, is to keep new things in the test file for a while, but I noticed during this exercise that I kept popping back and forth between my current test and the code in ImperativeFactory. I see that IDEA has bookmarks, which might have helped with the issue but the fact is, this stuff needs to be split up. We’re going to move all of it to the main side anyway.

I think I’ll put all the code in one file, at least for now, as the classes all work together and form a sort of package. There’s probably some structuring thing I “should” be doing in the project structure, but this program isn’t that big a deal. Let’s do move the classes, but all to the same file in Main. I’ll call it Imperative, and I’ll move that class first.

IDEA is willing, via F6, the obvious Move command, to move a raft of things at once, so I check them all off. Then it tells me that some things won’t be accessible. And it is willing to show me the conflicts. I honor its offer5.

I don’t see the issue. Is it that you generally can’t see from main to test? Would I be missing a require? And why does it object to TestActionTable and testSynonymTable, but not TestVerbTable?

I’ll proceed in smaller moves, maybe things will become more clear.

Huh. Can’t even move Imperative. Breakfast nearly on table. Fortunately, nothing is broken, I can pause any time. trying to move Imperative objects that it can’t see Lexicon. OK, will it let me move everything except my tests, even the test tables?

Yes, it’ll move it all. I can live with that. Test. Green. Rename ImperativeSpike to ImperativeTest. Test again. Commit: Moved Imperative guts to main. Test tables had to move as well.

I’ll sort that out anon. Breakfast is nearly on the table, time for the standard Sunday Morning ritual. I’ll sum up.

Summary

A review of the situation has led us to find a fairly decent place to plug in the new Imperative. We have it, and a bit of extra stuff, moved into main. We’ve enhanced the tests and code to deal with ill-formed input at the lexing level, and to make sure commands are lower case. We are better fixed for moving the class into play. I’m sure there will be patches to make and after it works, there are issues around the player, the response, the world, and so on. Inch by inch, y’know?

See you next time!



  1. Not really a word. Perhaps portmanteau (really a word) of “perfectly” and “exactly”? 

  2. Bless you! 

  3. Yes, “implementer” is more common. The “-or” suffix, to me, makes it more likely that the thing referred to is a program, not a person. But mostly I just prefer it that way. 

  4. Whenever I say “at least two” or “two”, I always get at least three things, and then I say “I lied about the two”. Truth is, once you start listing ideas, it can be hard to stop. I lied about the two. 

  5. Old joke, you can find it if you try.