Kotlin 137: A Beautiful Bug
Info from a viewer leads to a question. The answer leads me to a bigger question: Why didn’t I think of that? THEN THIS ARTICLE GOES SIDEWAYS.
Yesterday my article on hyperspace and the odds of surviving caught the eye of Jeff Grigg who sent me some key info … about that Hyperspace function in Asteroids. The key quote, reportedly concerning the original game:
“choose a random even number from 0-62, explode if random_number >= number_of_asteroids + 44. So, looks like hyperspace should never randomly explode if there are at least 19 asteroids on the screen. Anything less, and there’s a chance.”
I have not yet verified that in the original source code, but today’s plan is to implement it. I was thinking about it yesterday and the first issue that came to mind was that my version of the game doesn’t even know how many asteroids there are. It doesn’t even know whether the ship is alive, much less how many asteroids are buzzing about. My game just has a collection of objects, doesn’t know what they are, just gives them a chance to update every clock tick, to interact in pairs, and lets them know when they are about to be pushing up daisies.
I had a couple of ideas, and asked on a Slack I belong to. I asked:
Riddle me this. Game’s objects that fly around are all same class: SolidObject, with strategy objects for drawing and finalizing. Probability of death upon emergence involves number of asteroids on screen. Game has no god object (or many, depending how you look at it). Could write a little object that counted asteroids … except … how can it tell the asteroids from all the other objects?
Objects could have a type field. Could have an asteroid count value (1 or 0) in a member. Could have different sub-types for different kinds of objects, have them call back. To me, having them have a count 0 or 1 seems good enough. But not what you’d call prime OO design.
Ideas
Shortly thereafter, Hill suggested:
Who calls the Asteroid’s factory method? That method is a static. You could count them there, and anyone who makes or destroys an asteroid can see it?
We spoke, well slacked another sentence or two and I came up with this:
class SolidObject( ...
companion object {
var asteroidCount: Int = 0 // added -------------
fun asteroid(pos:Point, vel: Velocity, killRad: Double = 500.0, splitCount: Int = 2): SolidObject {
asteroidCount += 1 // added ----------------
return SolidObject(
position = pos,
velocity = vel,
killRadius = killRad,
mutuallyInvulnerable = true,
view = AsteroidView(),
finalizer = AsteroidFinalizer(splitCount)
)
}
class AsteroidFinalizer ...
override fun finalize(solidObject: SolidObject): List<ISpaceObject> {
asteroidCount -= 1 // added --------------------
val objectsToAdd: MutableList<ISpaceObject> = mutableListOf()
val score = getScore()
objectsToAdd.add(score)
if (splitCount >= 1) {
objectsToAdd.add(asSplit(solidObject))
objectsToAdd.add(asSplit(solidObject))
}
return objectsToAdd
}
Three lines and the game knows how many asteroids there are. I was just experimenting, so I tested this manually, by displaying the asteroid count as part of the score, and it works perfectly.
Our mission today will be to implement the same odds in our game as reported in Jeff’s helpful tweet1 from yesterday. But first I want to think, seriously, about why I didn’t think of this simple way of knowing how many asteroids there were. OK, sometimes you just don’t think of something, but this is so simple and so obviously right that I want to see if there is a clog in my mind that needs busting. So why didn’t I think of this? I listed a few possible causes in my “chat” with Hill:
Age, the drugs, distracted by cat, wife on my case about leak on the porch, use of companion
quite new to me, general dislike of static
, fact that most objects in my life didn’t have finalize.
Another factor is that by choice, this design is centered on the notion of there being no god object, no object that knows everything. It’s built as if each object has its own identity and they interact one to one, and act on their own, without a coordinating all-knowing omnipotent controlling object. So in an important sense, I’m looking away from the center, where something like this variable might reside.
Another factor is that all the objects are in one pool. A reasonable design would have the asteroids in a pool, the missiles in a pool, the ship and saucer in one-element pools of their own. In that design the question “how many asteroids are there” has an immediate answer, the number of objects in the asteroid pool. (The original game had, if I recall, a fixed array of some number of possible asteroids, and an index showing how many were in use. A more modern game might have a collection and we’d ask about size.)
Summing it all up, I think that this decentralized design tends to make one not think of centralized answers; that I do tend strongly away from class variables and explicit how_many_asteroids variables, and all those other factors, but I’m not sure how to reset my mind so that I’ll more quickly go to this kind of idea. I’m not even sure that I should.
I’m sure it’ll stick in my mind for now, and that I’ll be tempted to use the companion similarly if opportunities arise.
One more thing: I don’t fully like this solution. Counting the asteroids that exist, it seems to me, is inherently better than having the asteroids check in and check out, while we count them. What if an asteroid slips in the back door or climbs out a window? The explicit count will still tell us how many are in the room. The counter at the door will not. The door counter could be wrong in a way that an explicit count could not.
That said, this design is inherently “distributed” and it should not surprise us that we’re cornered into designs like this one. Still … it troubles me just a bit. I know it’s right … and I don’t quite trust it.
I’m sure it’ll be OK. And if not, we might find out. More likely, if it isn’t right, we may never know.
Let’s Get To It
I suppose that I’d best test this new odds feature, and the basic counting feature. Now that I have the ability to look at the game, I am getting more careless about TDD-style development. So far I’m sure that I’ve wasted time, but I don’t think I’ve injected any defects that tests would have caught. I’m probably just forgetting them.
I start with this:
@Test
fun `asteroids are tallied`() {
assertThat(asteroidCount).isEqualTo(0)
}
And I decide to run the test, because I know that the other tests create asteroids, and that they do not finalize them, and I suspect this test can fail.
It does fail!
expected: 0
but was: 2
Now this is just what I was talking about. Some asteroids have been created, then slipped out through a window, and our count is wrong.
A Beautiful Bug!
Oh this is beautiful! Do you see how beautiful this error is? Our game has no centralized controller, no “first thing” that happens. But we have just created, with our three little lines of code, an important global rule:
asteroidCount
is the count of asteroids created that have not yet been finalized.
That’s really the “invariant”. It’s not “asteroidCount
is the number of asteroids on the screen”, or anything like that. We are fortunate that when we run the game on screen, Kotlin restarts the program, so the member is zeroed. And we’re fortunate that I wrote that test, because this could produce a real defect in the program.
In the fullness of time, the game will give you some number of ships. When they’re used up, there will almost certainly be asteroids left on the screen. The game will go into attract mode, probably clearing out the SpaceObjectCollection. Sooner or later, you come along with your quarter, and start the game. We generate 8 new asteroids and off we go. And the count is wrong. You don’t get your fair shot at dying in hyperspace. In fact, you might not even get your second wave of asteroids, because when the first batch are gone, you are supposed to get new ones. But our counter is wrong. Nothing left on the screen … but the counter thinks there are still some asteroids left.
Wow.
This is why I don’t trust this kind of answer, and writing that one-line test has brought it home to me.
Wow. I am dead in the water. Might have to feed the cat or make a chai. This way of knowing how many asteroids there are is simply not OK: it already required code in three separate places, or 2 1/2, the companion object, the asteroid function thereof, and the finalize function of the asteroid finalizer. Now we see that we need to deal with it in at least one new place, a place sufficiently high-level that we can be sure this variable is reset at the right times.
This will not endure. I’m sorry, I thought this was a simple idea but at least in this architecture it’s too dangerous to live.
OK, roll back that little spike. Let’s sum this disaster up.
Summary
One of the glories of the way I write these articles is that I can publish the ones like this. In a regular book or blog, you’d have to throw this away, skip today’s output, and tomorrow write up whatever “correct” way you had devised for counting asteroids. You might even allude to this solution and wisely bloviate about why it’s a bad idea.
Me, when I auger in, I get to just write it up. A horrible result is a perfect result when you’re writing about what really happens when we program.
Coupling
To my credit, I did say that there was something I didn’t like about this idea and now that we’re here, I can put my finger on it: it’s too much coupling. It couples together the asteroid factory method, a variable in the companion, and an entirely different class, the asteroid finalizer. Three lines sitting in the middle of code that does perfectly useful stuff, tying those three places together, requiring us to manage them together despite their being in separate files, and finally, the laugh of all laughs, it won’t even work without some kind of occasional initialization, also oddly coupled, that will need to be in a place we don’t even have.
Also to my credit, but I admit it was just because you were watching, I wrote the test, and as soon as I wrote it, even before running it, it rubbed my nose in the need for an initialization that we don’t have, and probably can’t have, because we don’t have a place2 for it to be.
I guess I do wish that I had thought of it, and I do think that keeping something in the companion may turn out to be useful someday, I wish more that I had seen more clearly why I didn’t like it. I just felt “this isn’t quite right somehow”, one of those feelings that you get when your car breaks down at night out in the woods, and you walk up to that dark house, and a wolf howls in the distance … you know … just a bit uneasy. It’s probably OK, let’s just knock …
For now, I’m declaring this simple idea to be inappropriate to our situation, and in order to get a count of the asteroids, I’m going to have to do something else. That something else will very likely have to do with actually counting them. I think maybe everyone is going to know how to answer isAsteroid
real soon now.
And this article can go to bed. We’ve learned something. A perfect time to stop!
-
This at least shows that Twitter is not yet entirely corrupt. Not yet. ↩
-
Yeah, OK, we could put it in the game init, but the tests are still getting it wrong. We could make the tests work by initializing the counter in the tests but that’s just more coupling, making things just a little bit worse. ↩