Mostly I plan to solve the water problem this morning. But I’m wondering about concision. When is enough too much?

GitHub Repo

How Small is Too Small?

Since yesterday’s two, nay three examples of Kotlin’s concise code, I’ve spent a bit of time tidying1, q.v2. I’ve pushed in the direction of concise code, pushed pretty hard in fact. Not my using short names like i and q, but using the Kotlin idioms that I know, resulting in some pretty tight-looking code. Here are some examples:

    fun asFound(): String {
        return map.values.joinToString(separator = "") { it.asFound() }
    }

    fun contains(item: String): Boolean = map.containsKey(item)

    fun moveItemTo(name: String, target: Items): Boolean
        = remove(name)?.also {target.add(it)} != null

    private fun remove(key:String): Item? = map.remove(key)

Take that first one. We can rewrite it this way:

    fun asFound(): String
        = map.values.joinToString(separator = "") { it.asFound() }

Shorter. Basically a one-liner. I’ve been doing that whenever I notice an opportunity. Why? Two reasons:

  1. It’s fun. I enjoy learning Kotlin’s possibilities and style. It makes me feel clever and competent.
  2. I want to find out how far is too far. That moveItemTo up above: that one is pretty strange. I explain it in article 73, if you’re interested.

I think it’s valuable to try pushing ideas to their limits. How small a step is too small? What kind of test is too trivial to write? What makes code too concise? The best way to learn it, for me, is to practice until the ideas are there, reflexively, almost without trying.

The biggest issue with concise code, of course, is in the reading of it. There’s this thing that APL programmers do, where they write some patch of what looks like cartoon cursing and then challenge other APL programmers to figure what the code does. We don’t want to have to think hard when we read code: we read more than we write. (A proof of this is too large for the margin. Left to the reader.) So when we’re scanning through the code, we don’t want to be brought up Whoa! What’s THAT??, and we don’t want to misunderstand something just because we didn’t notice the difference between a dot and a colon.

Still, what we can grok is a function of how we practice. So we’ll practice concision for a while and see what we learn.

Share Water3

As I ran hot water on my head this morning, I realized that while we do want to be able to take water and put it in the bottle, the present partial scheme is probably mistaken. The room definition is this:

        room("spring") {
            desc("spring", "You are at a clear water spring. There is a well house to the east, and a wooded area to the west and south.")
            item("water") {}
            item("bottle") {}
            item("keys") {}
            go("e", "well house")
            go("w", "woods")
            go("s", "woods toward cave")
            action(Phrase("take", "water")) { imp
                ->  if (inventoryHas("bottle")) {
//                    addToInventory("bottle of water")
//                    removeInventory("empty bottle")
                    say("You have filled your bottle with water.")
            } else {
                imp.say("What would you keep it in?") }
            }
        }

The “take water” code is incomplete, and wrong in a few ways. First, it assumes that we’d have two different things named “bottle”, “empty bottle” and “bottle of water”. I think that for that, we’ll do better to have the bottle have a contents or a property, not two different objects. Another concern is that we we were actually to “take” the water, it would disappear from the spring. That probably won’t be a concern, because we won’t ever really take it, but we should keep an eye on the issue.

Let’s take simple cut at the bottle contents issue. Let’s give an Item a member variable. We’ll call it, oh, property, and it’ll just be a string, defaulted to empty.

Let’s express what happens with a test. How about this:

    @Test
    fun `taking water`() {
        val world = world {
            room("spring") {
                desc("spring", "fresh water spring")
                item("bottle") {
                    desc("a bottle")
                }
                item("water") {
                    desc("water", "cool, fresh water")
                }
            }
        }
        val player = Player(world, "spring")
        var resultString = player.command("take bottle")
        assertThat(resultString).contains("bottle taken")
        resultString = player.command("take water")
        assertThat(resultString).contains("You fill your bottle with water.")
    }

This is enough to fail. Test.

Expecting:
 <"water taken.
fresh water spring
">
to contain:
 <"You fill your bottle with water."> 

Now let’s make this happen. We want a special action for “take water”, in the spring room:

    action(Phrase("take", "water")) { imp
        ->  if (inventoryHas("bottle")) {
        say("You fill your bottle with water.")
    } else {
        imp.say("What would you keep it in?") }
    }

This should work so far. Let’s make sure the water is still there:

    val player = Player(world, "spring")
    var resultString = player.command("take bottle")
    assertThat(resultString).contains("bottle taken")
    assertThat(resultString).contains("You find water.")
    resultString = player.command("take water")
    assertThat(resultString).contains("You fill your bottle with water.")
    assertThat(resultString).contains("You find water")

This runs green. Now for the tricky part:

    resultString = player.command("inventory")
    assertThat(resultString).contains("bottle of water")

This’ll fail:

Expecting:
 <"You have a bottle.

fresh water spring
You find water.
">
to contain:
 <"bottle of water"> 

We would like to set the property of the bottle Item to “ of water”, and to have that displayed in the appropriate places.

Let’s get the inventory Items to help us.

    action(Phrase("take", "water")) { imp->
        if (inventoryHas("bottle")) {
            inventorySetProperty("bottle", " of water")    
            say("You fill your bottle with water.")
        } else {
            imp.say("What would you keep it in?") }
    }

We of course don’t have inventorySetProperty but that’s easily remedied.

class World ...
    fun inventorySetProperty(item: String, property: String) {
        inventory.setProperty(item,property)
    }

class Items
    fun setProperty(item: String, property: String) {
        map[item]?.setProperty(property)
    }

class Item
    var property = ""

    fun setProperty(propertyString: String) {
        property = propertyString
    }

I expect this code to compile and run but not to change the message.

I get a bizarre compiler message. Apparently “property” is some kind of magic word. I’ll change my stuff to “info” … I get the same message again. I’m starting to think one needs to avoid names starting with “set” … I use “setInformation”, and follow the chain to change them all.

Changing Property to Information throughout satisfies the compiler. The error message still comes out, no surprise. We need to change how the inventory display works.

class World ...
    private fun showInventory() {
        say( inventory.asCarried() )
    }

class Item
    fun asCarried(): String {
        return map.values.joinToString(
            prefix = "You have ",
            transform = { it.shortDesc },
            separator = ", ",
            postfix = ".\n"
        )
    }

Let’s have another property, carriedDescription:

class Items
    fun asCarried(): String {
        return map.values.joinToString(
            prefix = "You have ",
            transform = { it.carriedDescription },
            separator = ", ",
            postfix = ".\n"
        )
    }

class Item
    val carriedDescription: String get() = "$shortDesc$info"

The test runs green. Commit: Item has info used in display of inventory.

This doesn’t make water work correctly in the game, of course. Let’s enhance our test, which has the only working copy of the room action at the moment.

    val player = Player(world, "spring")
    var resultString = player.command("take water")
    assertThat(resultString).contains("What would you keep it in?")
    resultString = player.command("take bottle")
    assertThat(resultString).contains("bottle taken")

I think this succeeds. It does. Therefore, this room action can be used in the real game:

    action(Phrase("take", "water")) { imp->
        if (inventoryHas("bottle")) {
            inventorySetInformation("bottle", " of water")
            say("You fill your bottle with water.")
        } else {
            imp.say("What would you keep it in?") }
    }

I can’t resist trying this in the game:

Welcome to Tiny Adventure!
You are at a clear water spring. There is a well house to the east, and a wooded area to the west and south..
You find water.
You find bottle.
You find keys.

> take water
What would you keep it in?
You are at a clear water spring. There is a well house to the east, and a wooded area to the west and south.
You find water.
You find bottle.
You find keys.

> take bottle
bottle taken.
You are at a clear water spring. There is a well house to the east, and a wooded area to the west and south.
You find water.
You find keys.

> inventory
You have bottle.

You are at a clear water spring. There is a well house to the east, and a wooded area to the west and south.
You find water.
You find keys.

> take water
You fill your bottle with water.
You are at a clear water spring. There is a well house to the east, and a wooded area to the west and south.
You find water.
You find keys.

> inventory
You have bottle of water.

You are at a clear water spring. There is a well house to the east, and a wooded area to the west and south.
You find water.
You find keys.

So, woot! We have the water thing cracked. We have a very rudimentary bit of “info” associated with the Item, but it’s enough to do a lot. We could have a bottle of oil, or a box of kittens, or a glass containing poison.

Commit: Game updated with take water at spring.

There’s always a trade-off to be made in a program, as to how much sophistication we want to add in. The original Adventure / Colossal Cave game did everything with plain vanilla upper-case strings, no objects, nothing. FORTRAN, old school. We have more language power here, but we still do things simply to begin with and enhance them as needed.

We’ll wrap for today, but I wanted to point something out.

Brief Homily

A few days ago, we were talking about why we wrap native collections in objects of our own. (And, whether we mentioned it or not, the same reasons apply to why we might wrap a simple String.) Today shows why we do that: it makes the code simpler. Look at how we set information on an inventory item:

class World ...
    fun inventorySetInformation(item: String, property: String) {
        inventory.setInformation(item,property)
    }

class Items ...
    fun setInformation(item: String, property: String) {
        map[item]?.setInformation(property)
    }

class Item ...
    fun setInformation(infoString: String) {
        info = infoString
    }

Bing, bing, bing. One line in each place. Simple, direct, every only concerns itself with sending a single message to the right object. At the bottom, save the value.

Personally, I think that’s quite fine. YMMV, and if it does, you do you, and feel free to engage me in a conversation on Twitter or wherever else you may find me.

Nota Bene:4
I’m not totally in love with the particular names, especially inventorySetInformation. They could be improved, and when I get a better idea, I’ll improve them, with the help of IDEA. But good name or poor, a series of direct one-line calls just about can’t be beaten.

See you next time!!



  1. Kent Beck’s current work on tidying is well worth your time and money. 

  2. Latin: “quod vide”, “which see”. Means look there for more info. Something like that. My Latin days are far behind me. 

  3. Possibly dirtier than it sounds, cf5. Stranger in a Strange Land. 

  4. Nota Bene: Note well. Latin again. I just like adding footnotes. 

  5. “Confer”. “See” would be preferred here by most style guides, but my house my rules. And I wanted to footnote a footnote.