Still working toward the View displaying our new ‘say’ text. Might make it this morning.

If I recall where I left off yesterday, I have some text lines being stored in the GameResponse, with the intention of displaying those in the View. We have this test:

    @Test
    fun `room go has optional block`() {
        val myWorld = world {
            room("first") {
                desc("first room", "the long first room")
                go("n", "second") { true }
                go("s","second") {
                    it.say("The grate is closed!")
                    false
                }
            }
        }
        val myRoom = myWorld.unsafeRoomNamed("first")
        val response = GameResponse()
        myRoom.command("s", response)
        assertThat(response.nextRoomName).isEqualTo("first")
        assertThat(response.sayings).isEqualTo("The grate is closed!\n")
        val r2 = GameResponse()
        myRoom.command("n", r2)
        assertThat(r2.nextRoomName).isEqualTo("second")
    }

That works: the response is getting the message when we try to go south, and we remain in the same room, as intended. The rub is that the View can’t see the Response.

Now in the recent past, I was on a path of returning the response up to the World … or something like that. And we still have the issue of two objects, Game and World, that seem to have overlapping responsibilities.

Now it turns out that World has a the current response as a member variable:

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

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

So can’t we have the View ask the Game, ask the World? Perhaps we can. I’m glad we had this little talk. Let’s try it. And let’s even try to test it, in GameTest.

    @Test
    fun `game can provide sayings`() {
        val myWorld = world {
            room("first") {
                desc("first room", "the long first room")
                go("n", "second") { true }
                go("s","second") {
                    it.say("The grate is closed!")
                    false
                }
            }
            room("second") {
                desc("second room", "the long second room")
            }
        }
        val game = Game(myWorld, "first")
        game.command("s")
        assertThat(game.resultString).isEqualTo("hello")
    }

I know it’s not going to say “hello”. I’m going to do this in the spirit of Approval Tests, where when the output lines are the way I want them, I’ll change the assertion to expect that. Roughly what I want is for resultString to return the sayings and then the long description. (We’ll deal with short descriptions and such later.)

Game has this:

class Game(val world: World, startingName: String) {
    val resultString: String get() = currentRoom.longDesc

Let’s just defer that getter down to world, rather than the room.

    val resultString: String get() = world.resultString

That property doesn’t exist, so we implement it in World:

    val resultString: String get() {
        return response.sayings + "\n" + response.nextRoom.longDesc
    }

Now my error message is this:

Expecting:
 <"The grate is closed!

the long first room">
to be equal to:
 <"hello">
but was not.

Nearly good. First let me improve the test a bit, to make the output more reasonable.

    @Test
    fun `game can provide sayings`() {
        val myWorld = world {
            room("first") {
                desc("You're in the first room.", "You find yourself in the fascinating first room.")
                go("n", "second") { true }
                go("s","second") {
                    it.say("The grate is closed!")
                    false
                }
            }
            room("second") {
                desc("second room", "the long second room")
            }
        }
        val game = Game(myWorld, "first")
        game.command("s")
        assertThat(game.resultString).isEqualTo("hello")
    }

That changes the test result, of course:

Expecting:
 <"The grate is closed!

You find yourself in the fascinating first room.">
to be equal to:
 <"hello">
but was not.

However, the new implementation of resultString has broken another test, not a big surprise:

Expecting:
 <"
long second">
to be equal to:
 <"long second">
but was not.

Let’s remove the newline. I was just guessing that we need it.

    val resultString: String get() {
        return response.sayings + response.nextRoom.longDesc
    }

That fixes the one and gives this for the other:

Expecting:
 <"The grate is closed!
You find yourself in the fascinating first room.">
to be equal to:
 <"hello">
but was not.

Perfect. Put that approved string into the test:

        assertThat(game.resultString).isEqualTo("The grate is closed!\n" +
                "You find yourself in the fascinating first room.")

IDEA pasted the new-line-bearing string as those two lines with the +. Very nice, thanks IntelliJ. Test runs.

The code is begging for improvement, despite being only one line:

class World {
    val resultString: String get() {
        return response.sayings + response.nextRoom.longDesc
    }

Something about the fact that this code only refers to response suggests, nay screams that we should defer to the response.

class World {
    val resultString: String get() = response.resultString
    ...

data class GameResponse(val name:String="GameResponse") {
    val resultString: String get() = sayings + nextRoom.longDesc

I expect this to work. It does. Commit: Game now provides resultString, deferred to World, deferred to GameResponse.

Now let’s change the starting game and see what happens. Here’s our playable world now:

    val world = world {
        room("wellhouse") {
            desc("You're at wellhouse", "You're in a charming wellhouse")
            go("n", "wellhouse")
            go("s", "clearing")
        }
        room("clearing") {
            desc("You're in a clearing", "You're in a charming clearing")
            go("n", "wellhouse")
            go("s","clearing")
        }
    }

I’ll elaborate this a bit.

    val world = world {
        room("wellhouse") {
            desc("You're at wellhouse", "You're in a charming wellhouse")
            go("n", "wellhouse")
            go("s", "clearing")
            go("e", "cows")
        }
        room("clearing") {
            desc("You're in a clearing", "You're in a charming clearing. There is a fence to the east.")
            go("n", "wellhouse")
            go("s","clearing")
            go("e", "cows") {
                it.say("You can't climb the fence!")
                false
            }
        }
        room("cows") {
            desc("You're in with the cows.", "You're in a pasture with some cows.")
            go("w", "wellhouse")
        }
    }

And here’s a game transcript.

Welcome to Tiny Adventure!
You're in a charming wellhouse
s
You're in a charming clearing. There is a fence to the east.
e
You can't climb the fence!
You're in a charming clearing. There is a fence to the east.
n
You're in a charming wellhouse
e
You're in a pasture with some cows.
w
You're in a charming wellhouse

That’s just about what I had in mind. I did notice two anomalies in my window.

First, when I first did the move to the east from the clearing, the window didn’t scroll upward to shoe the clearing line. Second, when I try to enlarge the window, the text field doesn’t go to the bottom, it stays in the middle.

Needs a little work, but that’s for another day. For now, we’ve completed a small slice that allows us to block paths, and to issue messages that will display in the output. We’ll elaborate that in future sessions.

For now … let’s sum up and have a snack.

Summary

We moved a bit more toward “Tell, don’t ask”, by moving the calculation of the result string down from game, to world, to response, where it belongs, since the response, by design, is the object with all the answers. So that’s good.

We have tests for pretty much everything, except for the way the window looks. And, interesting coincidence, the only known defects are in the way the window looks.

Our game needs lots more work to be even mildly interesting to play, but we have a lot of the necessary mechanisms in place, and have demonstrated how we can cause variable results depending on game state. We don’t actually do that, but clearly if we wrote our allowed function to have a conditional in it, it would do just fine.

We’ll work on things like inventory and puzzles in future days. I’ll be trying to pick things that will teach me about DSLs and Kotlin. The purpose is learning, not a real game, though it would be nice if something that shows the capability of a real game like ADVENTURE were to be our result.

We’ll see what we do. I hope you’ll join me.