I need smaller steps. The Game sends the command to the current rRoom. But the World knows the rooms, and neither the Game nor the Room do. Let’s try getting the World involved.

In Game:

    fun command(cmd: String) {
        val name = currentRoom.command(cmd)
        currentRoom = world.roomNamedOrDefault(name,currentRoom)
    }

I was thinking of passing the world to the room, but instead, let’s pass the room to the world:

    fun command(cmd: String) {
        val name = world.command(cmd, currentRoom)
        currentRoom = world.roomNamedOrDefault(name,currentRoom)
    }

This demands, in World:

    fun command(cmd: String, currentRoom: Room): String {
        return currentRoom.command(cmd, this)
    }

This demands a change to Room.command:

    fun command(cmd: String, world: World): String {
        return when(cmd) {
            "s" -> move("s")
            "n" -> move("n")
            "xyzzy" -> move("xyzzy")
            else -> "unknown cmd $cmd"
        }
    }

Tests should run. They do. Commit: game.command goes to world.command goes to room.command, passing world.

Quick Reflection

Now that was a tiny step, just passing the call through World. And I didn’t really need to pass World on down, since we don’t use it, so I over-built even so.

I was thinking that I’d use the World to look up the room and return it. But we don’t need to do that. Let’s remove that parameter and see what we can do instead.

IDEA does it for me and of course the tests still run. Commit: world no longer passed to Room.command.

Nice. Now, n World, let’s use the name that comes back from Room to return the room to Game rather than the name, which it then decodes:

    fun command(cmd: String, currentRoom: Room): Room {
        val name = currentRoom.command(cmd)
        return roomNamedOrDefault(name, currentRoom)
    }

And in Game, this:

    fun command(cmd: String) {
        val name = world.command(cmd, currentRoom)
        currentRoom = world.roomNamedOrDefault(name,currentRoom)
    }

Becomes this:

    fun command(cmd: String) {
        currentRoom = world.command(cmd, currentRoom)
    }

And we’re green. Commit: World returns currentRoom to Game, not currentRoomName.

Mini-Reflection

Our mission, besides just making things make more sense, is to move to a Response object as the return from command execution. We anticipate that there may be many messages and perhaps other information in that Response. Last time I tried this, I lost the thread because my steps were too large. This time they are incredibly small, and so far things seem to be going well.

Let’s now review what Game does, and what the View does. View first:

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

The View just displays text, so it makes sense, I think, for it to just request a string from the Game. The Game, and the World, may need more information.

Do we really need both a Game and a World? I’m still not sure. It feels like about half an object more than I need. We’ll let it be. Consolidating should be straightforward if we decide to do it.

Let’s think about the flow. It seems to me that most of the action will take place in the Room (or in subordinate objects if there come to be any), and that some action may take place at the World level. The World, for example, probably contains counters and indicators about what’s up with the dwarves, or whether the pirate should strike, things like that. (We could also imagine the Game doing that, but again, why do we even have it?)

So I think that the World should create the Response object and give it to the Room to fill in what it wants to fill in. They’ll have to have an agreement on what can be in there, of course.

Let’s create a simple response object and pass it down.

    fun command(cmd: String, currentRoom: Room): Room {
        response = GameResponse()
        val name = currentRoom.command(cmd, response)
        return roomNamedOrDefault(name, currentRoom)
    }

I’m planning to make response a member variable in world, like this:

class World {
    val name = "world"
    private val rooms = Rooms()
    private var response: GameResponse = GameResponse()

I create a local data class for now:

data class GameResponse() {
    
}

I am assuming that the GameResponse members are likely var, but we’ll try to make it as immutable as makes sense. I think the program should work now. Darn. A data class needs something in its constructor. Make it not a data class, OK?

Too many arguments for public final fun command(cmd: String): String defined in com.ronjeffries.adventureFour.Room

Right, we need to accept the parameter.

    fun command(cmd: String, response: GameResponse): String {
        return when(cmd) {
            "s" -> move("s")
            "n" -> move("n")
            "xyzzy" -> move("xyzzy")
            else -> "unknown cmd $cmd"
        }
    }

Oh! I had a GameResponse class already. I forgot. Let’s remove mine and use the one we have, except that it has a required field, name.

data class GameResponse(val name:String) {
    var responseString: String = ""
    var nextRoom: String = name
}

And creating it:

class World {
    val name = "world"
    private val rooms = Rooms()
    private var response: GameResponse = GameResponse("useless name")

I’m moving a bit too fast and loose, but I think this will work in a moment or two. Another usage:

    fun command(cmd: String, currentRoom: Room): Room {
        response = GameResponse("useless name")
        val name = currentRoom.command(cmd, response)
        return roomNamedOrDefault(name, currentRoom)
    }

Ha! We are green. I told you we were going to be OK. Commit: World and Game now share a GameResponse instance across command calls.

Now. Calm down, take a break. and reflect.

Reflecto-Break

The response looks like this:

data class GameResponse(val name:String) {
    var responseString: String = ""
    var nextRoom: String = name
}

It’s pretty clearly a placeholder for a good idea. I don’t know that I have a good idea yet, but let’s at least change the nextRoom member to be a Room. I think it’ll have to be a Room. I think this calls for it to be val, and single assign. Let’s try that.

data class GameResponse(val name:String) {
    var responseString: String = ""
    var nextRoom: Room by singleAssign<Room>()
}

Now let’s see how we could get the new room into the response. Room can’t do it, it just knows the name. Hmm, let’s see. For now, we’ll do it in World. We’ll change this:

    fun command(cmd: String, currentRoom: Room): Room {
        response = GameResponse("useless name")
        val name = currentRoom.command(cmd, response)
        return roomNamedOrDefault(name, currentRoom)
    }

To this:

    fun command(cmd: String, currentRoom: Room): Room {
        response = GameResponse("useless name")
        val name = currentRoom.command(cmd, response)
        response.nextRoom = roomNamedOrDefault(name, currentRoom)
        return response.nextRoom
    }

This should run. Green. Commit: World fills nextRoom in Response.

Now instead of returning the name from currentRoom.command, let’s fully embrace the fact that we’re filling in a response object and use it. I’ll have to put the nextRoomName in there:

data class GameResponse(val name:String) {
    var responseString: String = ""
    var nextRoomName: String by singleAssign<String>()
    var nextRoom: Room by singleAssign<Room>()
}

Change command to return Unit but to update the response:

    fun command(cmd: String, response: GameResponse): Unit {
        val name = when(cmd) {
            "s" -> move("s")
            "n" -> move("n")
            "xyzzy" -> move("xyzzy")
            else -> "unknown cmd $cmd"
        }
        response.nextRoomName = name
    }

And, finally, change world.command to look in the response for the next room name.

    fun command(cmd: String, response: GameResponse): Unit {
        val name = when(cmd) {
            "s" -> move("s")
            "n" -> move("n")
            "xyzzy" -> move("xyzzy")
            else -> "unknown cmd $cmd"
        }
        response.nextRoomName = name
    }

We are green. Commit: world and room command now communicate results via GameResponse object, not call-return.

OK, let’s review how this is working at the top, in Game.

    fun command(cmd: String) {
        currentRoom = world.command(cmd, currentRoom)
    }

Not much going on there, but we should really expect a response back, not a room. That’s where we are headed, I think.

I think the Game shouldn’t know the room at all, but that’s going to take a little work so that the world will know, probably via the response, and won’t need to be told.

We’ll save that for next time. Right now, we’ve done good stuff and should stop while we’re winning.

Summary

This morning we prepared the ground just a bit but ultimately lost the thread. We left the program working fine, just hadn’t gotten all the way to a response object.

This afternoon, we took many tiny steps. Each one was only a line or two changed. In one hour, we did five commits.

world and room command now communicate results via GameResponse object, not call-return.   
7 minutes ago
World and Game now share a GameResponse instance across command calls.   
29 minutes ago
World returns currentRoom to Game, not currentRoomName.   
51 minutes ago
world no longer passed to Room.command.   
59 minutes ago
game.command goes to world.command goes to room.command, passing world.   
Today 5:02 PM

Nice. Small steps for the win. And this time, I hope I didn’t use any new words!

See you next time!