GitHub Repo

I have what seemed, in the early morning hours, to be a good idea. Is it? Let’s find out. ARRGH: What we may have found out it that it’s too cute to live. HOWEVER: There may be a clean way out.

Arrgh:
I’m reporting this out because I report everything even if I burn hours on a mistake. Part of my message here is that things like this happen to all of us. Are we doomed? I’ll decide in the next article.

Yesterday, just to watch the asteroids break up, I hacked the game so that on every collision, it created a new ship. That worked because, right now, the only possible collision is between a ship and an asteroid, there being no missiles, saucers, sausages, nor saucer missiles. But it’s a hack, right here:

    fun processCollisions() {
        val colliding = colliders()
        flyers.removeAll(colliding)
        for (collider in colliding) {
            val splitOnes = collider.split()
            flyers.addAll(splitOnes)
        }
        newShip() // breaks a test. How should we do this?
    }

Now at first glance, we might think that we need something like this:

if (there is no ship) {
	if (there are ships left) {
		if (enough time has elapsed) {
			newShip()
		}
	}
}

But looking at the Game so far, it is so simple. It just cycles, saying update, processCollisions, draw. And the collision processing is very simple as well. We get all colliding objects and remove them from the flyers. We then ask each one to split, in which case it returns either two new asteroids, or nothing. There’s basically no “if this then that” anywhere in there. So I was thinking about how we could neatly arrange the game so that it could go through its phases, something like this:

  1. Attract mode: asteroids sailing about, no ship;
  2. Starting a game.
  3. Placing the ship (we’ll come back to that).
  4. Allow ship to escape to “hyperspace”, placing it back after a delay.
  5. When ship is destroyed, if another is available, create it after a delay.
  6. Display “GAME OVER”, and after a while, go to 1.

And I got to thinking: what if there was an invisible object in the flyers, and what if it was in charge of creating new ships (and perhaps other things like “GAME OVER”).

This object could encapsulate all the ship creation, timing, detecting when someone puts a quarter in the machine, and so on. It seemed at the time like a good idea. To find out if it is, I think we need to explore it a bit.

Why is having it in the flyers perhaps a good idea? Because the Game object riffles through the flyers repeatedly, so that if there was a special flyer in the list, it would have plenty of opportunities to do special things.

Some things seem p-clear about this special object1:

  • It can understand time, at least well enough to delay between one ship being destroyed and the next appearing.
  • It must have the same interface as other flyers.
  • It needs to be able to see whether the ship exists.
  • It probably needs to know how many ships your quarter has bought.
  • Are there bonus ships? If so, it’ll need to know about them.
When should I start testing and coding this thing?
I would like to be confident enough about the idea to feel that it has a good chance of working, and a very good chance that I’ll learn something useful even if it doesn’t work. It’s really tragic to try something, have it not work out, and to walk away having learned nothing. Could that even happen? I hope not. I want to think about a few other concerns before trying this thing.
  • We’ll need to create an interface and use it on the new thing, so that it can be in our Flyers collection.
  • It can’t collide with anyone. It’d be nice not to have to compare it with everyone to figure that out. (This is almost certainly not a good thing to think about. Why?2)
  • My intuition is telling me that it might become a tar baby, gathering up random bits of game behavior, like hyperspace, game over, and so on. We should keep an eye on that, but if it fits together, we want it together.

I say let’s go for it. Let’s get started and see what falls out. We’ll start with the interface, so that another kind of FlyingObject can exist.

Except, no, let’s start by renaming FlyingObject as Flyer. That’s what I call them, therefore that’s what they should be called.

A simple rename (shift function 6) renames the class and its test. It even renames the test file. I like this IDEA thing.

Now we want to extract the Flyer interface.

I take a guess at what to include in the interface:

interface IFlyer {
    fun collides(other: Flyer): Boolean
    fun draw(drawer: Drawer)
    fun move(deltaTime: Double)
    fun update(deltaTime: Double): List<Flyer>
}

I figure we’re going to be treating our new object to those functions, so that it can … oh … I think it probably needs to know split as well. Undo, do again.

interface IFlyer {
    fun collides(other: Flyer): Boolean
    fun draw(drawer: Drawer)
    fun move(deltaTime: Double)
    fun split(): List<Flyer>
    fun update(deltaTime: Double): List<Flyer>
}

If these are wrong we can change them, but might as well keep the ones we’re pretty sure about and ignore the rest.

Now let’s write some tests for our new object. What shall we call it? I have no good idea, so I choose ShipMonitor. Here’s my first cut:

class ShipMonitorTest {
    @Test
    fun `initial monitor`() {
        val game = Game()
        val asteroid = Flyer.asteroid(Vector2(100.0, 100.0), Vector2(50.0, 50.0))
        val ship = Flyer.ship(Vector2(1000.0, 1000.0))
        game.add(asteroid)
        game.add(ship)
        val monitor: IFlyer = ShipMonitor(ship)
        game.add(monitor)
    }
}

This has required me to create a class, not yet complete:

class ShipMonitor(ship: Flyer) : IFlyer {
}

And I had to change Flyers.add to expect an IFlyer. We’ll have some other issues like that. So far neither I nor Kotlin nor IDEA have found them. A search for Flyer would probably help me.

There may be a better way, but I just repeatedly compile and fix errors, all of which amount to setting Flyer to IFlyer in some type declaration. Surely there was a way to have automated that? Along the way I update the Interface:

interface IFlyer {
    fun collides(other: IFlyer): Boolean
    fun draw(drawer: Drawer)
    fun move(deltaTime: Double)
    fun split(): List<IFlyer>
    fun update(deltaTime: Double): List<IFlyer>
}

Continuing merrily along …

Here’s a nasty one:

    override fun collides(other: IFlyer):Boolean {
        if ( this === other) return false
        if ( this.ignoreCollisions && other.ignoreCollisions) return false
        val dist = position.distanceTo(other.position)
        val allowed = killRadius + other.killRadius
        return dist < allowed
    }

Because Flyer.collides needs to receive an IFlyer, Kotlin thinks that IFlyer needs to implement position, killRadius, and ignoreCollisions. I’d really rather not.

Kotlin thinks that collides has to deal with IFlyer because … let’s see … because I changed the parameter in IFlyer.

Hm. I really don’t want to do anything special about the types. I think I have to push IFlyer down everywhere.

What if … I’m not sure I like this … what if we add a member property to IFlyer, canCollide. True in Flyer, not so true in ShipMonitor.

interface IFlyer {
    val canCollide: Boolean
    fun collides(other: IFlyer): Boolean
    fun draw(drawer: Drawer)
    fun move(deltaTime: Double)
    fun split(): List<IFlyer>
    fun update(deltaTime: Double): List<IFlyer>
}

Then … create them in Flyer and ShipMonitor, true and false respectively.

This is too tedious to bore you with. I’ll keep drilling until either it’s interesting to look at or I give up.

I discover that, on ShipMonitor, IDEA offers to “create members” and gives me this:

class ShipMonitor(ship: Flyer) : IFlyer {
    override val canCollide = false
    override fun collides(other: IFlyer): Boolean {
        TODO("Not yet implemented")
    }

    override fun draw(drawer: Drawer) {
        TODO("Not yet implemented")
    }

    override fun move(deltaTime: Double) {
        TODO("Not yet implemented")
    }

    override fun split(): List<IFlyer> {
        TODO("Not yet implemented")
    }

    override fun update(deltaTime: Double): List<IFlyer> {
        TODO("Not yet implemented")
    }

    override val killRadius: Double
        get() = TODO("Not yet implemented")
    override val position: Vector2
        get() = TODO("Not yet implemented")
    override val ignoreCollisions = false

}

This is nearly good. At least it gives me things to fill in. If I can get to the point where my test runs, we can get down to actual work.

Oops?
I think that hack I put into collides may turn out to be a mistake. We’ll find out, but I think that calls to split may be what will make the ShipMonitor come up with a new ship. We’ll see.

My vague idea for how this would all work is that the Monitor will know the ship and can therefore detect when it’s gone and can therefore begin running its timer and when the time runs out, can therefore, next time it is called to collide, report that it is destroyed, which will cause it to be sent the message split, at which point it’ll return itself, thereby being placed back into the Flyers to resume its job.

If that wasn’t clear, and this doesn’t work, it won’t matter. If it does work, I think the code will make it clear. So don’t panic … yet. That’s my job.

My first pass through ShipMonitor gives me this:

class ShipMonitor(ship: Flyer) : IFlyer {
    override val canCollide = false
    override val ignoreCollisions = false
    override val killRadius: Double = Double.MIN_VALUE
    override val position: Vector2 = Vector2.ZERO
    
    override fun collides(other: IFlyer): Boolean = false

    override fun draw(drawer: Drawer) {
    }

    override fun move(deltaTime: Double) {
    }

    override fun split(): List<IFlyer> {
        TODO("Not yet implemented")
    }

    override fun update(deltaTime: Double): List<IFlyer> {
        TODO("Not yet implemented")
    }
}

Try to run my test, see what else needs tweaking. Green! We’ll see the changes as they arise in our upcoming code but now we can get down to work on the ShipMonitor tests. First, let’s reflect. No, first, let’s commit, then reflect. I think these changes are harmless, even if we don’t use the SHipMonitor after all. Commit: Initial interface IFlyer and ShipMonitor class.

Reflection

This was a bit grubby. I think that partly I didn’t use the tools correctly. There was probably a way to go through and change all references to Flyer to IFlyer, but I didn’t find it. Without that, mostly it was just somewhat grubby, compiling and plugging in IFlyer where Flyer had been.

One nasty bit is that I had to hammer collides (which should be renamed to collidesWith I think) to make it happy, and I was forced to implement some values and methods in IFlyer and SHipMonitor that I would otherwise have preferred not to. I’m not terribly bummed by that, because even as things are, the class above isn’t that awful. And we may be able to simplify once this all works.

We’ll press on with our test:

    @Test
    fun `initial monitor`() {
        val game = Game()
        val asteroid = Flyer.asteroid(Vector2(100.0, 100.0), Vector2(50.0, 50.0))
        val ship = Flyer.ship(Vector2(1000.0, 1000.0))
        game.add(asteroid)
        game.add(ship)
        val monitor: IFlyer = ShipMonitor(ship)
        game.add(monitor)
    }

Now the idea is that the ShipMonitor will know the ship, and will therefore — Oh! Idea! — can perhaps know that the ship is gone because it’ll be asked whether it collides with everything and can watch for the ship to go by on the carousel and if it doesn’t turn up, it’ll know the ship has been destroyed. Oh I think I like that.

My intuition about this idea was that the complex behavior could mostly drop out of the existing collision, update, move logic … somehow. It just seemed to me that there must be a pony in there somewhere.

Let’s elaborate the test. Here’s a cut at the ShipMonitor story:

  1. Game has ship, asteroids, and a ShipMonitor in Flyers.
  2. During collision checking, ShipMonitor notes that the Ship is there, keeps quiet,
  3. Things move.
  4. During collision checking, ship is hit. ShipMonitor still sees it
  5. Ship is removed, as are all the victims of collisions.
  6. Ship is sent split and, being a ship, doesn’t return anything.
  7. Game doesn’t care, things move, collisions are checked.
  8. ShipMonitor sees no ship, goes into active mode.
  9. On next collision cycle, active ShipMonitor reports itself colliding.
  10. Game removes ShipMonitor, sends it split.
  11. ShipMonitor returns itself to the split, continues as active until finally …
  12. Time to create a new ship. On the next call to split, ShipMonitor returns itself … and a new ship. ShipMonitor goes inactive.

That’s quite the scenario but I think it’s going to turn out to be simple. We’ll see. Write the test bit by bit.

    @Test
    fun `initial monitor`() {
        val sixtieth = 1.0/60.0
        val game = Game()
        val asteroid = Flyer.asteroid(Vector2(100.0, 100.0), Vector2.ZERO)
        val ship = Flyer.ship(Vector2(1000.0, 1000.0))
        game.add(asteroid)
        game.add(ship)
        val monitor: IFlyer = ShipMonitor(ship)
        game.add(monitor)
        assertThat(game.flyers.size).isEqualTo(3)
        game.update(sixtieth)
        game.processCollisions()
        assertThat(game.flyers.size).isEqualTo(3)
    }

This test hits one of the not implemented methods on ShipMonitor. It’s update. We’ll leave it empty for now. May have occasion to fill it in. It has to return a list: update might fire a missile, so it returns anything it wants to add. That may come in handy later:

    override fun update(deltaTime: Double): List<IFlyer> {
        return emptyList()
    }

Hm. Interesting error:

expected: 3
 but was: 4

Somehow we have come up with four objects. How did that happen? What is it?

My confusion comes from my hack that creates a new ship, and I think that given where it was, it was creating a new ship every time we processed collisions whether or not we had any collisions. Sorry. remove hack.

Now I need to cause a collision, and assert what should happen:

        ship.position = Vector2(100.0,100.0)
        game.update(sixtieth)
        game.processCollisions()
        assertThat(game.flyers.size).isEqualTo(3)
        assertThat(game.flyers.flyers).doesNotContain(ship)

Ship is gone. ShipMonitor should not be active now (but soon).

        assertThat(game.flyers.flyers).doesNotContain(ship)
        assertThat(monitor.active).isEqualTo(false)
        game.update(sixtieth)
        game.processCollisions()
        assertThat(monitor.active).isEqualTo(true)

Ship is gone. Monitor does not know it. Next time through, it does know it. Test will fail.

expected: true
 but was: false

And in ShipMonitor … We really only have two places to modify at this point, collides and update. In update let’s set the active flag to true and if we see our ship in collides, set it false.

    override fun update(deltaTime: Double): List<IFlyer> {
        active = true
        return emptyList()
    }

    override fun collides(other: IFlyer): Boolean {
        if (other === ship) active = false
        return false
    }

I show them in that order because that’s the order they occur in. I expect my test to succeed.

Here’s some of the test, shown because it’s not working as I expect:

        assertThat(game.flyers.size).describedAs("first collisions hit nothing").isEqualTo(3)
        ship.position = Vector2(100.0,100.0)
        game.update(sixtieth)
        game.processCollisions()
        assertThat(game.flyers.size).isEqualTo(3)
        assertThat(game.flyers.flyers).doesNotContain(ship)
        assertThat(monitor.active).describedAs("ship was here").isEqualTo(false)
        game.update(sixtieth)
        game.processCollisions()
        assertThat(monitor.active).describedAs("ship seen as gone").isEqualTo(true)

The “ship was here” assertion fails. the monitor seems not to have set the variable. Ah.

Remember where I futzed with collides in Flyer? We need to unfutz that.

    override fun collides(other: IFlyer):Boolean {
        if ( this === other) return false
        if (!this.canCollide || !other.canCollide) return false
        if ( this.ignoreCollisions && other.ignoreCollisions) return false
        val dist = position.distanceTo(other.position)
        val allowed = killRadius + other.killRadius
        return dist < allowed
    }

There is an issue lurking here. We do not know for sure whether what we’ll do is monitor.collides(ship) or ship.collides(monitor). Odds are, we do whichever one doesn’t work for us. We need at least to allow them to collide, so that whole rigmarole with canCollide was premature and wasteful. That won’t suffice to fix things, but it’s a start.

After removing that, I get a call to split in ShipMonitor. So what do we know? That ShipMonitor, despite the trick of giving it a negative kill radius, is colliding with someone.

How is that possible?

ShipMonitor.collides cannot return true:

    override fun collides(other: IFlyer): Boolean {
        if (other === ship) active = false
        return false
    }

Therefore Flyer.collides has returned true:

    override fun collides(other: IFlyer):Boolean {
        if ( this === other) return false
        if ( this.ignoreCollisions && other.ignoreCollisions) return false
        val dist = position.distanceTo(other.position)
        val allowed = killRadius + other.killRadius
        return dist < allowed
    }

But ShipMonitor.killRadius …

    override val killRadius: Double = Double.MIN_VALUE

Ah. Double.MINVALUE is a small positive fraction. I have in mind a large negative number. Grr.

Setting kill radius to -Double.MAX_VALUE makes the collision no longer occur, but since we’re asking something if it collides with the monitor, not the monitor whether it collides with something, it doesn’t get a chance to detect the collision or lack of it.

We need to do this differently. I think we may need … the dreaded double dispatch.

(We could “fix” all this by letting the ShipMonitor search for its ship. But we’d like the behavior to just drop out from normal play, in a fashion clear enough that we can live with it.)

OK, I think I see it Let me do it and show it to you. We’re going to have regular Flyers implement collides by deferring to the other. ShimMonitor will not do that. First, though I want to rename the method collides to collidesWith because it is a better name.

Renaming in the interface changes the concrete guys. Nice.

Now then:

class Flyer ...
    override fun collidesWith(other: IFlyer):Boolean {
        if ( this === other) return false
        if ( this.ignoreCollisions && other.ignoreCollisions) return false
        val dist = position.distanceTo(other.position)
        val allowed = killRadius + other.killRadius
        return dist < allowed
    }

Becomes:

    override fun collidesWith(other: IFlyer): Boolean {
        return other.collidesWithOther(this)
    }

    override fun collidesWithOther(other: IFlyer):Boolean {
        if ( this === other) return false
        if ( this.ignoreCollisions && other.ignoreCollisions) return false
        val dist = position.distanceTo(other.position)
        val allowed = killRadius + other.killRadius
        return dist < allowed
    }

I added collidesWithOther to the interface. Now for most situations, when we do a.collidesWith(b), we call b.collidesWithOther(a) and now we do the distance comparison, for any other except ShipMonitor. For ShipMonitor:

class ShipMonitor
    override fun collidesWith(other: IFlyer): Boolean {
        if (other === ship) active = false
        return false
    }

    override fun collidesWithOther(other: IFlyer): Boolean {
        return collidesWith(other)
    }

Now we are guaranteed to get called if either the first or second object in a collision check is ShipMonitor. Either we’re called directly and deal with it … or we’re called from the double dispatch and deal with it via collidesWithOther. Meanwhile, other objects see collidesWith and are then dealt with in collidesWithOther.

The test runs. Let’s review it:

    @Test
    fun `initial monitor`() {
        val sixtieth = 1.0/60.0
        val game = Game()
        val asteroid = Flyer.asteroid(Vector2(100.0, 100.0), Vector2.ZERO)
        val ship = Flyer.ship(Vector2(1000.0, 1000.0))
        game.add(asteroid)
        game.add(ship)
        val monitor = ShipMonitor(ship)
        game.add(monitor)
        assertThat(game.flyers.size).isEqualTo(3)
        game.update(sixtieth)
        game.processCollisions()
        assertThat(game.flyers.size).describedAs("first collisions hit nothing").isEqualTo(3)
        ship.position = Vector2(100.0,100.0)
        game.update(sixtieth)
        game.processCollisions()
        assertThat(game.flyers.size).isEqualTo(3)
        assertThat(game.flyers.flyers).doesNotContain(ship)
        assertThat(monitor.active).describedAs("ship was here").isEqualTo(false)
        game.update(sixtieth)
        game.processCollisions()
        assertThat(monitor.active).describedAs("ship seen as gone").isEqualTo(true)
    }

This test is getting long, more and more like not just a story but a novel. Let’s see whether, now that we have a sense of how things go, we can get down to smaller more clear tests.

    @Test
    fun `ShipMonitor collisions`() {
        val sixtieth = 1.0/60/0
        val ship = Flyer.ship(Vector2.ZERO)
        val asteroid = Flyer.asteroid(Vector2.ZERO, Vector2.ZERO)
        val monitor = ShipMonitor(ship)
        assertThat(monitor.active).isEqualTo(false)
        monitor.update(sixtieth)
        assertThat(monitor.active).describedAs("goes active after update").isEqualTo(true)
        monitor.collidesWith(asteroid)
        assertThat(monitor.active).describedAs("stays active after most collisions").isEqualTo(true)
        monitor.collidesWith(ship)
        assertThat(monitor.active).describedAs("goes inactive if it sees a ship").isEqualTo(false)
    }

The monitor starts inactive, goes active on update, stays active under collisions unless it sees a ship, in which case it goes inactive. Test is green. Now let’s drive the collision checking the other way.

    @Test
    fun `ShipMonitor collisions other way around`() {
        val sixtieth = 1.0/60/0
        val ship = Flyer.ship(Vector2.ZERO)
        val asteroid = Flyer.asteroid(Vector2.ZERO, Vector2.ZERO)
        val monitor = ShipMonitor(ship)
        assertThat(monitor.active).isEqualTo(false)
        monitor.update(sixtieth)
        assertThat(monitor.active).describedAs("goes active after update").isEqualTo(true)
        asteroid.collidesWith(monitor)
        assertThat(monitor.active).describedAs("stays active after most collisions").isEqualTo(true)
        ship.collidesWith(monitor)
        assertThat(monitor.active).describedAs("goes inactive if it sees a ship").isEqualTo(false)
    }

These checks are more direct: they check what happens when flyers and monitors check collisions. The issue under scrutiny is the active state of the ShipMonitor.

Now what else needs to be tested?

Well, I was going to say that if we collide with an active ShipMonitor, we want it to report true … but that’s not really valid. The ShipMonitor needs more than one state, and I’m not sure how to manage it.

We would really like to set the state to “looking for ship” and in that state, we’ll take note of ship, maybe setting state “have seen ship recently”, and then only if we’re in “looking for ship” on the next update, we know we have not seen the ship and then we go active.

Is this getting too intricate? I hope not. It seems clear enough to me …

I’m going to convert it to have an enum as a state.

Here’s what I’ve got. I’m not in a good place, I’ve been using my brain too much.

class ShipMonitor(val ship: Flyer) : IFlyer {
    override val ignoreCollisions = false
    override val killRadius: Double = -Double.MAX_VALUE
    override val position: Vector2 = Vector2.ZERO
    var state: ShipMonitorState = ShipMonitorState.HaveSeenShip

    override fun collidesWith(other: IFlyer): Boolean {
        if (state == ShipMonitorState.Active) return true
        if (other === ship) state = ShipMonitorState.HaveSeenShip
        return false
    }

    override fun collidesWithOther(other: IFlyer): Boolean {
        return collidesWith(other)
    }

    override fun draw(drawer: Drawer) {
    }

    override fun move(deltaTime: Double) {
    }

    override fun split(): List<IFlyer> {
        TODO("Not yet implemented")
    }

    override fun update(deltaTime: Double): List<IFlyer> {
        state = when (state) {
            ShipMonitorState.LookingForShip -> ShipMonitorState.Active
            else -> ShipMonitorState.LookingForShip
        }
        return emptyList()
    }
}

I need to update the tests to match the new state stuff. Here’s one: they’re both the same just with collisions reversed. I think we believe that works now.

    @Test
    fun `ShipMonitor collisions other way around`() {
        val sixtieth = 1.0/60/0
        val ship = Flyer.ship(Vector2.ZERO)
        val asteroid = Flyer.asteroid(Vector2.ZERO, Vector2.ZERO)
        val monitor = ShipMonitor(ship)
        assertThat(monitor.state).isEqualTo(ShipMonitorState.HaveSeenShip)
        monitor.update(sixtieth)
        assertThat(monitor.state).describedAs("looks for ship after update").isEqualTo(ShipMonitorState.LookingForShip)
        asteroid.collidesWith(monitor)
        assertThat(monitor.state).describedAs("stays looking after most collisions").isEqualTo(ShipMonitorState.LookingForShip)
        ship.collidesWith(monitor)
        assertThat(monitor.state).describedAs("goes back to seen if it sees a ship").isEqualTo(ShipMonitorState.HaveSeenShip)
    }

It’s 1400 hours and I started at 1005. Time for a break?

I’m still feeling pretty fresh, and despite stumbles, I think we’re in a good place. Is there a single step we can make to prove this concept and keep it or destroy it?

I think so. When the state is active, and we are offered the chance to collide, we should respond true, be removed, and split should be called on us, wherein we return ourselves and a new ship (in fact the old one would probably work).

Let’s write another test to check that. I think we can get this to work and if we do it’ll be sweet.

I just went down a long long rat-hole because the test up there says 1/60/0, which returns a NaN which converts object positions to NaN. I think I’d have preferred the exception, which is a rare thing for me to say.

Anyway, the test is passing so far:

Arrgh! Fatal flaw. My theory was that when the flag is ACTIVE, in collidesWith we’d return true, thus triggering a call to split. But if we that, we’ll record a collision with everything, and everything will be sent split. All the asteroids will explode. We can’t do that.

I’m not sure if there’s a way out of this. If the basic idea here is to work, we need to cause the game to think we’ve collided with something, but it can’t be anything real, I think we need another kind of IFlyer, that will just be in there to collide with ShipMonitor if it is active.

Is this getting too precious? I fear that it is.

I decide to stop let my brain refresh. Maybe this is OK, more likely it is not. But I’m now ready to give up … yet. Commit: ShipMonitor mid-process.

Summary

It is possible that this idea was too clever to live. It is possible that I can quickly make it work after a break. It is possible that I should throw it away. I’m going to rest and decide later.

Even before I rest, I think I have a saving idea. Can I roll a natural 20?



  1. As always we use “p-adjective” to mean “adjective for some fraction between zero and one”. Ideas can be half baked, so why not p-baked for p = 0.1547? 

  2. Premature optimization. On the other hand, adding another object to the flyers adds not one but N comparisons. Nothing to sneeze at. Whatever that means. I do have one idea, which is that if flyers knew collidsWithAny and a list of flyers, a clever object might be able to dismiss all candidates at once. Maybe we’ll see if that’s possible.