Kotlin 30
It’s 0415 and I’m awake, with what seems to be a good idea. Is it a good idea to program at 0415? That remains to be seen.
The idea is simple and I actually tweeted it last night:
What if my status things are Int underneath, and respond to isTrue and isFalse in the obvious way. Then you can treat them as flag if you wish, or increment/decrement, and get value freely.
It seems to me that almost invariably, the variables that a game writer will need will be simple flags—is the crystal bridge there or not—or simple counters—how many times have we seen a dwarf—and the latter are probably quite uncommon. So if I were to encode my status objects as Int, with 0 and 1 interpreting as false and true (remains to be seen about greater than 1, but I imagine that’d be true as well), we might have all we need. And it’s certainly simple enough.
I did have another idea for the morphing ones that change type, having to do with creating them in the right form at the last minute, but that would involve two classes and this scheme will work with just one trivial class.
Let’s do it. I’ll start with the StatusTest and StatusObjects.kt. I’ll save renaming the classes until the new tests all run, then let IDEA help me with the rename.
class StatusTest {
@Test
fun `status isTrue`() {
val status = BooleanStatus()
assertThat(status.isTrue).isEqualTo(false)
status.set(true)
assertThat(status.isTrue).isEqualTo(true)
assertThat(status.isFalse)
}
@Test
fun `Status map`() {
BooleanStatusMap().apply {
assertThat(get("unlocked").isFalse).isEqualTo(true)
get("unlocked").not()
assertThat(get("unlocked").isTrue).isEqualTo(true)
}
}
}
class BooleanStatus(var value: Boolean = false) {
val isTrue: Boolean get() = value
val isFalse: Boolean get() = !value
fun not() {
value = !value
}
fun set(truth: Boolean) {
value = truth
}
}
We want the value
to be Int. We want zero to be false and non-zero to be true. I enhance the test:
@Test
fun `status isTrue`() {
val status = BooleanStatus()
assertThat(status.isTrue).isEqualTo(false)
status.set(true)
assertThat(status.isTrue).isEqualTo(true)
assertThat(status.isFalse).isEqualTo(false)
status.increment()
assertThat(status.value).isEqualTo(2)
status.increment()
assertThat(status.value).isEqualTo(3)
assertThat(status.isTrue).describedAs("three is true").isEqualTo(true)
status.not()
assertThat(status.isTrue).isEqualTo(false)
assertThat(status.value).isEqualTo(0)
}
I had some difficulty convincing myself that non-zero is true. Somehow it confused me. 4 AM perhaps. Anyway, here’s the new code:
class BooleanStatus(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 }
fun set(number: Int) { value = number }
}
Everything’s green. Commit, then rename the classes. Commit: BooleanStatus implemented as zero==false, non-zero==true, supports value
and set
either boolean or Int.
Now the names. Since they’re not Boolean and not Int, I think the new names should be GameStatus and GameStatusMap. IDEA does that for me with two applications of the intuitively obvious keystroke: Shift-F6. Rename. Check. √.
Green. Commit: Rename BooleanStatus[Map] to GameStatus[Map].
Reflection
So, that went well. I should probably make value read-only but we’ll let that ride for now. Easily done if needed and frankly I trust myself not to abuse features like that. (Fool that I am!)
Oh. Probably we shouldn’t call that World variable flags
any more. It’s used like this:
go("e","treasure") {
if (flags.get("unlocked").isTrue)
true
else {
say("The room is locked by a glowing lock")
false
}
}
I don’t know if any of what follows is even possible, but wouldn’t it be great if instead of this:
0 if (flags.get("unlocked").isTrue) ...
we could say something like one of these:
1 if (Unlocked.isTrue) ...
2 if (flags.unlocked.isTrue) ...
3 if (flags["unlocked"].isTrue) ...
4 if (flag("unlocked").isTrue) ...
5 if (isTrue("unlocked")) ...
I can see that numbers 4 and 5 might be practical. I don’t see how to do any of the others. Number 3 is possible but Kotlin would return null
if the variable wasn’t present, instead of auto-initializing it.
I can imagine still more possibilities. Predeclaring them could even work:
val flags = object {
var unknown = false
var dwarves = 5
}
if (flags.unknown) ...
flags.dwarves++
I think the above could work. I’ve not tried the object
capability yet. I’ll gin up a test. This runs:
class ObjectTest {
@Test
fun `object`() {
val flags = object {
var b = false
var i = 0
}
assertThat(flags.b).isEqualTo(false)
flags.b = true
assertThat(flags.b).isEqualTo(true)
flags.i++
assertThat(flags.i).isEqualTo(1)
}
}
Come to think of it, I suppose we could declare variables where we define our world. Ah, but we need things like cast wd40
to reference those vars. I’ll have to think more about this. For now, I think I’d like to go back to bed and sleep, having tried out my 0,1 idea.
Summary
Testing an idea quickly almost always pays off in learning, in my view, so I’m glad that I tried this, and I’m glad that I then speculated about alternatives. If we were under too much pressure to code code code, we might not feel that we can take time to think, and what we have in this article is an example of thinking not just programming. It’s design thinking, with code.
In these early days of learning Kotlin, I find it particularly valuable to try different things, even the ones that don’t work (see yesterday), because I’m learning where the walls are in the Kotlin house. Always good to know.
For now, I’m going to try to get back to sleep for a bit.
See you later today!