At a more civilized hour, I have an improvement in mind. And it’s really time to start doing something about inventory.

It’s just shy of 1100. The cat woke me a bit after 1000, starving, as they do. She had a legitimate concern today, as she usually gets breakfast about 0700 or 0800. I suppose at 1200 she’d have eaten my nose. Cats, gotta admire their focus on their own well-being. Anyway, I have an idea. Let me recast a test to demonstrate it.

Here’s the old form:

  go("e","treasure") {
      if (flags.get("unlocked").isTrue)
          true
      else {
          say("The room is locked by a glowing lock")
          false
      }
  }

I’ll rewrite it my newly proposed style. When I started this paragraph, I was going to propose this:

      if (flag("unlocked").isTrue)

But looking at it, why not go all the way:

      if (flag("unlocked"))

Sure, why not? Now I just have to implement flag. I think I can just put that method on World.

class World {
    fun flag(name: String) = flags.get(name).isTrue

So that’s nice. However, now that I’ve done it, how will I increment the thing or send it not? I think, for now, we’d better settle for returning the GameStatus and let the caller say isTrue.

    fun flag(name: String) = flags.get(name)


      if (flag("unlocked").isTrue)

Reflection

It seems that I’ve spent hours getting this one line of code just right. Not only “seems”, it’s true. Couldn’t we just have lived with

      if (flags.get("unlocked").isTrue)

And gotten on with our lives? Or we could have stopped even earlier, at:

      if (status.getOrDefault("unlocked", false))

YMMV. I’m trying to get to a DSL (Dungeon Specification Language?) that is as comfortable to write as is reasonable, and I’m trying to learn the ins and outs of Kotlin, so this kind of exploration is valuable to me. Besides, what I do at 4 AM is my business.

I might even change it, and will certainly extend it, as new needs arise. For me, this is how programs are best made, growing organically, with occasional trimming and small replanting sessions. But now …

Loot and Inventory

I think it’s time to begin putting loot in the world, things that can be found and used. We’ll start with keys. We’ll put keys somewhere, and in another place we’ll put a door that can only be opened if you have the keys.

This is going to be a rather large story, I expect, far too large to do in one go, if your goes are only the size of mine1. Let’s list some of the things we’ll need for this feature to be reasonably complete.

  1. The command language will need to understand verbs vs nouns: “take keys” vs “take rod”.
  2. We’ll need a repository for all the nouns that the player has collected, an “Inventory”.
  3. We’ll need a verb “inventory” to display the current inventory2.
  4. We’ll need a place in each room for all the nouns that are currently there.
  5. We’ll need to display the room’s contents when we enter the room.

We could start with #1, improving the command parsing.

We probably don’t want to start with #2 or #3, “inventory”, because we’d have to fake loading it. But we could do that.

Starting with #4 also seems reasonable and would make the game more interesting in play mode. We could even take a “take” command, as we did with “cast wd40”.

We probably can’t do #5 until we have something to display.

Let’s start with the 4:5 axis, things in the room and displaying them.

Room Contents

I reckon that the room can contain a collection, contents. The contents can just be strings for now, like “keys” and “axe”. I believe that all the logic of what these things do will be in the commands, so that strings may suffice. We’ll find out. I guess that the Room class can have a new method that puts an item into its contents.

I think it’s about time to create an extensive test that includes everything we can do, if only so I can remember all the syntax. But not yet. We’ll add a Room test for now.

    @Test
    fun `room has contents`() {
        val world = world {
            room("storage") {
                desc("storage room", "large storage room")
                item("broom")
            }
        }
        val room = world.unsafeRoomNamed("storage")
        assertThat(room.contents).has("broom")
    }

Seems sufficient for the first test. IDEA informs me that neither item nor contents are defined. I code this masterpiece:

    fun item(thing: String) {
        contents+=thing
    }

Now we have two lines clamoring for contents. I respond:

class Room(val roomName: String) {
    val contents = mutableListOf<String>()

I think this may placate the beast. If it does not, I’ll learn something. I learn something. Should have said:

        assertThat(room.contents).contains("broom")

Test is green. I am confident that we can have as many items as we like. However, it would be better if this were a set, so that there can’t be duplicates. Let’s enhance the test.

    @Test
    fun `room has contents`() {
        val world = world {
            room("storage") {
                desc("storage room", "large storage room")
                item("broom")
                item("broom")
            }
        }
        val room = world.unsafeRoomNamed("storage")
        assertThat(room.contents).contains("broom")
        assertThat(room.contents.size).isEqualTo(1)
    }

That should fail with 2 for 1.

Expecting:
 <2>
to be equal to:
 <1>
but was not.

And, of course:

    val contents = mutableSetOf<String>()

I expect green. Green is what I get. Commit: Rooms have contents, items are strings.

So that’s nice. Now, whenever we are in a room, we would like to display the contents. If I recall the ADVENTure game, it would say something like this:

There is an axe here.
There is a broom here.
There is water here.

Note the nice handling of the articles. I was thinking that we might just say that the rule is to provide the article as part of the string:

    item("an axe")

But that could cause problems for take axe.

I want to digress for a moment here, and talk about how ADVENT was implemented and how it’s influencing me here.

ADVENT
The original program, written in FORTRAN 77, was about 700 lines of code and 700 lines of tables defining the cave. To make the cave larger, you’d basically just have to add entries to the tables. The code was pretty tight and compact, not readable at first glance. I’m trying for a somewhat similar style, which is why I’m leaning so hard on the text items. I don’t think we should stick to that, but it’ll be interesting to see how this program turns out in size and style.

For now, we’ll do this:

You find: axe.
You find: broom.
You find: water.

That’ll keep the syntax wolves at bay for a while.

Let’s have a method on room for, oh, itemString.

    @Test
    fun `room has contents`() {
        val world = world {
            room("storage") {
                desc("storage room", "large storage room")
                item("broom")
                item("broom")
                item("water")
                item("axe")
            }
        }
        val room = world.unsafeRoomNamed("storage")
        assertThat(room.contents).contains("broom")
        assertThat(room.contents.size).isEqualTo(3)
        val itemString = room.itemString()
        assertThat(itemString).contains("axe")
        assertThat(itemString).contains("broom")
        assertThat(itemString).contains("water")
    }

And …

    fun itemString(): String {
        var report = ""
        contents.forEach { report += "You find $it.\n" }
        return report
    }

This runs green. Woot! Commit: Room knows itemString.

Now let’s make the game show this info. I’ll enhance the starter game while I’m at it.

class MainView: View() {
    val world = world {
        room("wellhouse") {
            desc("You're at wellhouse", "You're in a charming wellhouse")
            item("keys")
            item("bottle")
            item("water")
            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.")
            item("cows")
            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")
        }
    }

The view just asks for game.resultString, which asks for world.resultStrng, which asks for response.resultString, so we can enhance that from this:

    val resultString: String get() = sayings + nextRoom.longDesc

To this:

    val resultString: String get() = sayings + nextRoom.longDesc + nextRoom.itemString()

I notice that I’m not consistent in whether things are properties or functions. I always have this problem in languages like these. I’ll settle in on a pattern sooner or later. Anyway, let’s see how this works.

Welcome to Tiny Adventure!
You're in a charming wellhouse
> n
You're in a charming wellhouseYou find keys.
You find bottle.
You find water.

> s
You're in a charming clearing. There is a fence to the east.You find cows.

Right. We need some formatting. For now, let’s brute force it:

    val resultString: String get() = sayings + nextRoom.longDesc +".\n"+ nextRoom.itemString()

And try again:

Welcome to Tiny Adventure!
You're in a charming wellhouse
> n
You're in a charming wellhouse.
You find keys.
You find bottle.
You find water.

> s
You're in a charming clearing. There is a fence to the east..
You find cows.

> e
You can't climb the fence!
You're in a charming clearing. There is a fence to the east..
You find cows.

> w
You're in a charming clearing. There is a fence to the east..
You find cows.

> n
You're in a charming wellhouse.
You find keys.
You find bottle.
You find water.

I call that a win, but I’d like to see the full string coming out at the beginning. That’ll be tricky: we don’t really have a response object at this point. How does this work now?

        myText = textarea(textContents + "\n"+game.currentRoom.longDesc) {
            isEditable = false
            vgrow = Priority.ALWAYS
        }

We’ll brute force this again:

        myText = textarea(textContents + "\n"+game.currentRoom.longDesc + ".\n" + game.currentRoom.itemString()) {
            isEditable = false
            vgrow = Priority.ALWAYS
        }

This gives me a desirable result:

Welcome to Tiny Adventure!
You're in a charming wellhouse.
You find keys.
You find bottle.
You find water.

> s
You're in a charming clearing. There is a fence to the east..
You find cows.

I see that I am not consistent about my punctuation. We’ll deal with that anon. For now, we have a nice new feature to demonstrate to our colleagues. Let’s sum up.

Summary

I think the most interesting thing about the things we’ve wrought over the past several days have almost all been in very tiny methods, often no more than one line. I consider that a very good thing.

The game definition DSL seems fairly compact and easy to write so far. I’m pretty sure it’ll continue to grow nicely, and am hopeful that the new features will go in easily. I am confident that if they don’t initially want to go in easily, we’ll quickly find simple refactoring steps to make it easy enough.

We’ll find out, won’t we? See you next time!



  1. We can compare them later, but I assure you, my goes are as small as I can make them. 

  2. Yes, I know that inventory isn’t a verb. In our little language, it will be, and it might not even be the worst one.