GitHub Repo

A bit of world building. Will it point up any notable needs? Oh, yeah!

I pl.an to build up the little segment of the world that I drew a picture of the other day. Maybe a bit more, and I’ll leave what I already have if it fits in.

map, very messy

My first cut is this:

fun makeGameWorld(): World {
    val theWorld = world {
        room(R.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") {
                desc("water", "Perfectly ordinary sparkling spring water, " +
                        "with a tinge or iron that has leached from the surrounding rocks, " +
                        "and a minor tang of fluoride, which is good for your teeth.)")
            }
            go(D.East, R.Wellhouse)
            go(D.West, R.Woods6)
            go(D.South, R.WoodsS)
            go(D.Northwest, R.Woods12)
            action("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?") }
            }
        }
        room(R.Wellhouse ) {
            desc("well house", "You are in a small house near a spring. " +
                    "The house is sparsely decorated, in a rustic style. " +
                    "It appears to be well kept.")
//            item("bottle")
            item("keys")
            go(D.West, R.Spring)
            action("say", "xyzzy" ) {
                say("Swoosh!")
                response.nextRoomName = R.WoodsNearCave
            }
        }
        room(R.Woods6) {
            desc("woods", "You are in a dark and forbidding wooded area. " +
                    "It's not clear which way to go.")
            go(D.Northeast, R.Spring)
            go(D.West, R.Woods9)
        }
        room(R.Woods9) {
            desc("woods", "You are in a dark and forbidding wooded area. " +
                    "It's not clear which way to go.")
            go(D.Southeast, R.Woods6)
            go(D.North, R.Woods12)
        }
        room(R.Woods12) {
            desc("woods", "You are in a dark and forbidding wooded area. " +
                    "It's not clear which way to go.")
            go(D.West, R.Woods9)
            go(D.Southeast, R.Spring)
            item("bottle")
        }
        room(R.WoodsS) {
            desc("woods", "You are in a dark and forbidding wooded area. " +
                    "It's not clear which way to go.")
            go(D.South, R.WoodsNearCave)
            go(D.Northwest, R.Woods6)
        }
        room(R.WoodsNearCave) {
            desc("breezy woods", "You are in the woods. " +
                    "There is a cool breeze coming from the west.")
            go(D.West,R.CaveEntrance)
            go(D.North, R.WoodsS)
        }
        room(R.CaveEntrance) {
            desc("cave entrance",
                "You are at an entrance to a cave. " +
                        "There is a locked gate blocking your way west.")
            go(D.East,R.WoodsNearCave)
        }
    }
    return theWorld
}

Immediately I discover a bunch of tests using R.Woods. I convert them to R.WoodsS, since I removed the R.Woods name and added those ones with the clock numbers and the S one. Then I discover that “nw” is not a valid command. That’ll be in the synonyms, I think. No, it’s in the verbs. I add in the needed lines and remove some unneeded ones. The Synonyms and Verbs need to be consistent with each other. Details not worth displaying here.

I’ve created the room at the cave entrance:

room(R.CaveEntrance) {
    desc("You are at the cave entrance.",
        "You are at an entrance to a cave. " +
                "A cool breeze emanates from the cave." +
                "There is a locked gate blocking your way west.")
    go(D.East,R.WoodsNearCave)
    action("go", "west") {
        say("The gate is locked.")
    }
}

For now, when you try to go west from there, you get the message that the gate is locked. We’d like to implement “unlock gate”, and maybe “unlock” and “use keys”, and, of course, we’d like to unlock the gate.

I think that the gate status needs to be a fact in the world, because when you’re inside, the gate should be there, and still either locked or unlocked, whichever it was on the outside. (There might be another way into the cave, so it could be locked.)

World has this:

    val flags = GameStatusMap()

Which is:

class GameStatusMap {
    private val map = mutableMapOf<String, GameStatus>()

    fun get(name:String): GameStatus = map.getOrPut(name) { GameStatus() }
}

class GameStatus(var value: Int = 0) {
    val isTrue: Boolean get() = value != 0
    val isFalse: Boolean get() = value == 0
    fun increment() { value++ }
    fun not() { value = if (isTrue) 0 else 1 }
    fun set(truth: Boolean) { value = if (truth) 1 else 0 }
}

The GameStatus is a little object with an Int value that maps to true or false, so that it can be used as a simple flag, or could be used as a counter. In the game itself, I don’t think I use them at all. This is my big chance. Since they default to false, “gateOpen” is a good name.

I wind up with this:

        room(R.CaveEntrance) {
            desc("You are at the cave entrance.",
                "You are at an entrance to a cave. " +
                        "A cool breeze emanates from the cave." +
                        "There is a locked gate blocking your way west.")
            go(D.East,R.WoodsNearCave)
            action("unlock", "gate") {
                if (inventoryHas("keys")) {
                    say("You fumble through the keys and finally unlock the gate!")
                    flags.get("openGate").set(true)
                } else {
                    say("The gate is locked.")
                }
            }
            go(D.West, R.LowCave) {when(flags.get("openGate").isTrue) {
                true -> true
                false-> {
                    say("The gate is locked")
                    false }
                }
            }
        }

This is nearly good. Here’s a bit of game play:

> w
You are at an entrance to a cave. A cool breeze emanates from the cave.There is a locked gate blocking your way west.

> inventory
You have keys.

You are at the cave entrance.

> w
The gate is locked
You are at the cave entrance.

> unlock gate
You fumble through the keys and finally unlock the gate!
You are at the cave entrance.

> w
You are in a low cave room. The cave continues to the west.

Reflection

I can see that this needs work:

  1. The gate is not mentioned on the cave side.
  2. The gate is described as locked on the entrance side, and will be even if it is unlocked.
  3. We need additional phrases that can unlock the gate.

For that last bit, suppose that we wanted to say “use keys” as well as “unlock gate”. We could duplicate the whole action, but that’s kind of long and redundant. We could save the block as a val and then use it twice. Or we could rig up some way to use multiple phrases in an action.

What else have I learned?

  • The job of defining the world is pretty tedious: there’s just a lot to express. With a good map, with actual room names on each room node, we could tick through them with some accuracy, but it would still be rather long and boring.

  • There is no check to be sure that every R.Whatever has had a room() call associated with it. If we also didn’t reference it in a go, the enum item would show up as unused, but naming it in a go counts as a use. So we need some kind of flags in the room that can be used to indicate that the room has a definition, and so on. A quick check might be to see whether the room has a description other than empty string.

  • The actions and room permissions are a bit wordy, in that the move has to return true or false, as well as say anything that it needs to say. I don’t see a super way around that. Could I perhaps have two additional “say” commands, “no” and “yes”? That might be fun to try.

  • Items need descriptions. I’ve put a magazine in the well house. I’d like to say “Zork! Magazine”, but if I do, there’s no way to refer to it. So it needs to be named “magazine” and displayed as “Zork Magazine”. That’s even more tricky than “bottle of water”, but I’m sure we can figure something out.

I’ll work on some of these ideas next time. I’ve got a small but viable playable area including a fairly nasty puzzle right off the bat, because it’s not easy to get out of the woods. And we can make it a bit harder.

Right now, you can get the bottle by going northwest and returning southeast. But if we do this:

    room(R.Woods12) {
        desc("You are lost in the woods.", "You are in a dark and forbidding wooded area. " +
                "It's not clear which way to go.")
        go(D.West, R.Woods9)
        go(D.South, R.Spring)
        item("bottle")
    }

Now you get there going northeast, and have to go south to get back. Nasty, since all the woods rooms have the same description. We could make them worse by having the long description and the short be the same.

We need to add more “things” to be found. The trick to getting out of the woods, other than by chance, is to leave items in the rooms, which will enable you to draw a map. Forget that I told you that.

Lots to do. Enough for now. Things to think about and do tomorrow. See you then!