GitHub Decentralized Repo
GitHub Centralized Repo
GitHub Flat Repo

Let’s get started on the saucer firing at the ship, which I think is the largest remaining “requirement”. Added: My testing game is weak today.

In this version of the game, we’re limiting the ship to four simultaneous missiles, and I plan to limit the saucer to two. Those figures came from my study of the original game.

In that game, I think that the distinction between what kind of missile it was was based on its index in the table of objects. We’re not quite that retro, and I’m seriously considering that we should give the saucer missiles their own type. We currently have these types:

enum class SpaceObjectType(val points: List<Vector2>, val killRadius: (SpaceObject)->Double) {
    ASTEROID(asteroidPoints, asteroidRadius),
    SHIP(shipPoints, shipRadius),
    SAUCER(saucerPoints, saucerRadius),
    MISSILE(missilePoints, missileRadius)
}

We could easily add another type and then replicate our current style of searching for what we need, like this:

fun activeMissiles(spaceObjects: Array<SpaceObject>): List<SpaceObject> =
    missiles(spaceObjects).filter { it.active}

HOWEVER.

The current code for firing missiles from the ship does know about table positions. In those days I intended positions 0 and 1 to be for the saucer, and 2,3,4, and 5 to be for the ship, and the code looks like this:

fun fireMissile() {
    Controls.fire = false
    withAvailableMissile { missile ->
        missile.position = Ship.position + U.MissileOffsetFromShip.rotate(Ship.angle)
        missile.velocity = Ship.velocity + U.MissileVelocity.rotate(Ship.angle)
        missile.active = true
    }
}

fun withAvailableMissile(action: (SpaceObject) -> Unit) {
    for (i in 2..5) {
        if (!SpaceObjects[i].active) return action(SpaceObjects[i])
    }
}

I very much like the withAvailableMissile function, which executes its action if it finds an inactive missile. I don’t like the 2..5 quite so much. We could promote those numbers to universal constants of course, to give them a bit more stature. Or we could move to something more like the other searches.

If we were really interested in tight, fast code, we might well stick with things like the 2..5 trick. I confess that I am far more interested in code that is easy to understand and to modify as needs must. I suppose that if I had been forged in the fires of game development, my leanings might be different, but I’m here to work out how to do clear, changeable code and then change it as needed to evolve the product.

I’m more than a little curious about slots 0 and 1. Where’s createGame?

fun createGame(missileCount: Int, asteroidCount: Int) {
    Score = 0
    val objects = mutableListOf<SpaceObject>()
    for (i in 1..missileCount) objects.add(newMissile())
    Ship = newShip()
    objects.add(Ship)
    Saucer = newSaucer()
    objects.add(Saucer)
    for (i in 1..asteroidCount) objects.add(newAsteroid())
    SpaceObjects = objects.toTypedArray()
}

Now this is a bit weird. Suppose we were given a missile count of 2. We wouldn’t have missiles in positions 2..5 at all! That would be bad. I hope we’re calling it with at least 6 everywhere, and I bet we’re not.

Mostly we use U.MissileCount, which is in fact 6, and there are tests that use other values than 6, notably 4. I think we can assume (at the standard risk) that those are OK, becuase the tests run.

I think we should go to having missiles have two types, and change createGame to have three arguments, saucer missile count, ship missile count, asteroid count. How can we do this in small steps?

Let’s do something like this:

  1. Add the new type to the enum;
  2. Change the internals of createGame to use its missile count to create two saucer missiles and count minus two ship missiles;

No, how about this?

  1. Assume that when you call createGame, you intend to create two saucer missiles and the rest ship missiles;
  2. Have createGame call a new createGame with three parameters, 2, missileCount-2 and asteroidCount;
  3. Have the new createGame do the thing.
  4. Convert to the new one if and when we need to.

I do think that some tests may break, the ones that are passing in values other than 6 for missile count, but we’ll deal with that if it happens.

Let’s Do It

fun createGame(missileCount: Int, asteroidCount: Int) {
    createGame(2, missileCount-2, asteroidCount)
}

fun createGame(saucerMissileCount: Int, shipMissileCount: Int, asteroidCount: Int) {
    Score = 0
    val objects = mutableListOf<SpaceObject>()
    for (i in 1..saucerMissileCount) objects.add(newSaucerMissile())
    for (i in 1..shipMissileCount) objects.add(newMissile())
    Ship = newShip()
    objects.add(Ship)
    Saucer = newSaucer()
    objects.add(Saucer)
    for (i in 1..asteroidCount) objects.add(newAsteroid())
    SpaceObjects = objects.toTypedArray()
}

That demands newSaucerMissile and the new enum type.

enum class SpaceObjectType(val points: List<Vector2>, val killRadius: (SpaceObject)->Double) {
    ASTEROID(asteroidPoints, asteroidRadius),
    SHIP(shipPoints, shipRadius),
    SAUCER(saucerPoints, saucerRadius),
    MISSILE(missilePoints, missileRadius),
    SAUCER_MISSILE(missilePoints, missileRadius)
}

fun newSaucerMissile(): SpaceObject {
    return SpaceObject(SpaceObjectType.SAUCER_MISSILE, 0.0, 0.0, 0.0, 0.0, 0.0, false)
        .also { spaceObject ->
            val missileTimer = Timer(spaceObject, U.MissileTime, true) { timer -> deactivate(timer.entity) }
            addComponent(spaceObject, missileTimer)
        }
}

I’m going well over the top here with all this speculation, but I’m really just copying how missiles work.

We need to run the tests and see if any break. I am optimistic but not certain. This one breaks, and it should:

    @Test
    fun `initial array creation`() {
        createGame(6, 26) // number of missiles, number of asteroids
        val mCount = SpaceObjects.count { it.type == SpaceObjectType.MISSILE}
        assertThat(mCount).isEqualTo(6)
        assertThat(Ship.type).isEqualTo(SpaceObjectType.SHIP)
        assertThat(Ship).isEqualTo(SpaceObjects[6])
    }

The count should be 4 and we should check the saucer missiles. Adjust test:

    @Test
    fun `initial array creation`() {
        createGame(6, 26) // number of missiles, number of asteroids
        val sCount = SpaceObjects.count { it.type == SpaceObjectType.SAUCER_MISSILE}
        assertThat(sCount).isEqualTo(2)
        val mCount = SpaceObjects.count { it.type == SpaceObjectType.MISSILE}
        assertThat(mCount).isEqualTo(4)
        assertThat(Ship.type).isEqualTo(SpaceObjectType.SHIP)
        assertThat(Ship).isEqualTo(SpaceObjects[6])
    }

Expect green. Green it is. Commit: SAUCER_MISSILE type installed, saucer missiles created, minimal testing.

I think we’re ready to try firing missiles. What are the rules? A quick look at another version tells me that we try to fire a saucer missile every half second, and that some percentage of them are targeted. We’ll not worry about that yet.

How shall we do this? I think we’ll add another timer to the Saucer, this one with a half-second cycle, and we’ll try to fire a missile every time it ticks. We could make it greedy, so that it’ll keep trying once the time has run out, as we do with safe emergence, but we’ll leave that out for now. Let’s try a test. There’s a ship-firing test that I’ll kind of crib from.

    @Test
    fun `saucer can fire two missiles`() {
        createGame(2,4, 4)
        assertThat(activeSaucerMissileCount())
            .describedAs("initialized inactive").isEqualTo(0)
        fireSaucerMissile()
        assertThat(activeSaucerMissileCount())
            .describedAs("not time yet").isEqualTo(0)
        updateTimers(0.5)
        fireSaucerMissile()
        assertThat(activeSaucerMissileCount())
            .describedAs("first missile free").isEqualTo(1)
        fireSaucerMissile()
        assertThat(activeSaucerMissileCount())
            .describedAs("cannot fire immediately").isEqualTo(1)
        updateTimers(0.5)
        fireSaucerMissile()
        assertThat(activeSaucerMissileCount())
            .describedAs("second missile free").isEqualTo(2)
        updateTimers(0.5)
        fireSaucerMissile()
        assertThat(activeSaucerMissileCount())
            .describedAs("no third missile").isEqualTo(2)
    }

Kind of long but it tells the story. We need fireSaucerMissile:

fun fireSaucerMissile() {
    withAvailableSaucerMissile { missile: SpaceObject ->
        missile.active = true
    }
}

fun withAvailableSaucerMissile(action: (SpaceObject) -> Unit) {
    SpaceObjects.filter { it.type == SpaceObjectType.SAUCER_MISSILE}
        .forEach { 
            if ( ! it.active ) {
                it.active = true
                return
            }
        }
}

This is a bit rough, but it tells me that I’m not quite on the right track. I don’t want the test to do the timing. I just want to try to fire three missiles and get two of them. The timing will be checked separately.

    @Test
    fun `saucer can fire two missiles`() {
        createGame(2,4, 4)
        assertThat(activeSaucerMissileCount())
            .describedAs("initialized inactive").isEqualTo(0)
        fireSaucerMissile()
        assertThat(activeSaucerMissileCount())
            .describedAs("first missile free").isEqualTo(1)
        fireSaucerMissile()
        assertThat(activeSaucerMissileCount())
            .describedAs("second missile free").isEqualTo(2)
        fireSaucerMissile()
        assertThat(activeSaucerMissileCount())
            .describedAs("no third missile").isEqualTo(2)
    }

This should run, I think. And it does. Commit: fireMissile function activates saucer missile if available.

Let’s see what’s left to do.

  • Add timer to saucer to try to fire every half second.
  • Put the Saucer’s missile at a reasonable position.
  • Give the Sauce’s missile a reasonable velocity.
  • Roll the dice for targeted missile.
  • Implement targeting.

Probably more, but at least that. For now, I think I’ll set the saucer missile’s position, before I forget it, to the saucer’s position:

fun fireSaucerMissile() {
    withAvailableSaucerMissile { missile: SpaceObject ->
        missile.position = Saucer.position
        missile.active = true
    }
}

Now a timer. I think I’d best write a test for it. But doggone it, I know I can just do it. I am weak. I promise to test this but I want to do it.

fun newSaucer(): SpaceObject = SpaceObject(
    SpaceObjectType.SAUCER,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    false
).also {
    val firingTimer = Timer(it, 0.5, true, {true}) { fireSaucerMissile() }
    addComponent(it, firingTimer)
    addComponent(it, SaucerTimer(it))
}

Grr. I didn’t write a test and so I spent time debugging. You’d think I’d learn.

Here’s the bug: I wasn’t calling the action, in this function:

fun withAvailableSaucerMissile(action: (SpaceObject) -> Unit) {
    SpaceObjects.filter { it.type == SpaceObjectType.SAUCER_MISSILE}
        .forEach {
            if ( ! it.active ) action(it)
        }
}

That’s also wrong. Try this:

fun withAvailableSaucerMissile(action: (SpaceObject) -> Unit) {
    SpaceObjects.filter { it.type == SpaceObjectType.SAUCER_MISSILE}
        .forEach {
            if ( ! it.active ) return action(it)
        }
}

That works. How do I know? As the saucer sails across, it drops two missiles in its path a half second apart. then nothing happens until those expire, and then it does two more.

I think that’s about as expected, but if I had written this test it would have been a lot quicker to get it working.

The test might look like this:

    @Test
    fun `saucer auto-fires two missiles`() {
        createGame(2,4, 4)
        Saucer.active = true
        assertThat(activeSaucerMissileCount())
            .describedAs("initialized inactive").isEqualTo(0)
        updateTimers(0.51)
        assertThat(activeSaucerMissileCount())
            .describedAs("first missile free").isEqualTo(1)
        updateTimers(0.51)
        assertThat(activeSaucerMissileCount())
            .describedAs("second missile free").isEqualTo(2)
        updateTimers(0.51)
        assertThat(activeSaucerMissileCount())
            .describedAs("no third missile").isEqualTo(2)
    }

Initially I forgot to make the Saucer active, so of course it never fired. Took me a while to realize that.

We are green. Commit: Saucer drops immobile missile every half second if inactive slot available.

I think we would like to give the saucer missile a random velocity. We’ll let it continue to start from saucer center, for now.

fun fireSaucerMissile() {
    withAvailableSaucerMissile { missile: SpaceObject ->
        missile.velocity = U.MissileVelocity.rotate(Random.nextDouble(360.0))
        missile.position = Saucer.position
        missile.active = true
    }
}

I expect this to be interesting on screen. Here’s a short movie to demonstrate.

saucer firing missiles

I think that’s working as intended, but I’m not sure I’m happy with just two missiles. And it turns out that the missiles don’t kill the ship. Where do we check that?

private fun checkCollisions() {
    checkAllMissilesVsAsteroids()
    if ( Ship.active ) checkShipVsAsteroids(Ship)
    if ( Saucer.active ) checkAllMissilesVsSaucer(Saucer)
}

Right, we don’t check yet. Let’s fix that:

private fun checkCollisions() {
    checkAllMissilesVsAsteroids()
    checkSaucerMissilesVsShip()
    if ( Ship.active ) checkShipVsAsteroids(Ship)
    if ( Saucer.active ) checkAllMissilesVsSaucer(Saucer)
}

private fun checkSaucerMissilesVsShip() {
    for (missile in activeSaucerMissiles(SpaceObjects)) {
        checkShipVsMissile(Ship,missile)
    }
}

private fun checkShipVsMissile(ship: SpaceObject, missile: SpaceObject){
    if ( colliding(ship,missile)) {
        deactivate(ship)
        deactivate(missile)
    }
}

I need activeSaucerMissiles.


fun activeSaucerMissiles(spaceObjects: Array<SpaceObject>): List<SpaceObject> =
    saucerMissiles(spaceObjects).filter { it.active }

fun saucerMissiles(spaceObjects: Array<SpaceObject>): List<SpaceObject> =
    spaceObjects.filter { it.type == SpaceObjectType.SAUCER_MISSILE }

For testing, I’m going to give the saucer more missiles. I’m fortunate in that it randomly hits me in one pass, and I decide to give it four, not two missiles, on the regular. Which reminds me, we have a bug in that 2..5 code.

fun withAvailableMissile(action: (SpaceObject) -> Unit) {
    for (missile in missiles(SpaceObjects)) {
        if (! missile.active) return action(missile)
    }
}

As we stand, saucer missiles will kill the ship and will not kill asteroids. We could change that, of course. I’m not sure what the original game does.

I think we’re good-ish. Commit: Saucer can fire four missiles at half second intervals. Saucer missiles can kill ship and not kill asteroids.

Let’s sum up.

Summary

On the bright side, we have the saucer firing random missiles that can kill the ship. However, the feature is only partially tested, and some of those tests were done after the fact, not before. Before would have been better. It’s quite possible that I’d have noticed the issues that confused me sooner. It’s hard to be sure about that, but I was certainly feeling the lack of those tests in my confidence level.

To expand on that, an interesting thing is that having developed a reasonable amount of experience with test-first development, I can sense in myself a different level of confidence when I’m test-driving than when I’m just putting in code untested, with just game-play testing. If I were truly wise, I’d pay more attention to that feeling, and bear down to write the tests. But sometimes the code I need seems clear to me and the test does not.

Today I put in a lot of code for creating the SAUCER_MISSILE without tests. I was copying the code for other objects, so it was reasonably likely to be correct, but there was a certain amount of fiddling to get things right. I am not as proud of that as I would be had I tested it all into place. But I am still pleased that it’s in and working.

There are some ragged edges that need attention. In particular, the code that executes a block when it finds an available object:

fun withAvailableSaucerMissile(action: (SpaceObject) -> Unit) {
    SpaceObjects.filter { it.type == SpaceObjectType.SAUCER_MISSILE}
        .forEach {
            if ( ! it.active ) return action(it)
        }
}

fun withAvailableMissile(action: (SpaceObject) -> Unit) {
    for (missile in missiles(SpaceObjects)) {
        if (! missile.active) return action(missile)
    }
}

These should be the same. We do have the saucerMissiles() function now, so:

fun withAvailableSaucerMissile(action: (SpaceObject) -> Unit) {
    for ( saucerMissile in saucerMissiles(SpaceObjects)) {
        if (! saucerMissile.active) return action(saucerMissile)
    }
}

fun withAvailableMissile(action: (SpaceObject) -> Unit) {
    for (missile in missiles(SpaceObjects)) {
        if (! missile.active) return action(missile)
    }
}

There’s rather a bit of duplication there. Can we remove it? I’m not sure. It’ll do for now. Commit: withAvailable… functions look alike now.

Another ragged edge: I almost forgot to deal with the 2..5 assumption for ship missiles. That would have caused some kind of chaos and while we can be more confident now that it uses the standard search, we have no tests for that. Ragged.

A Bug!!

Playing the game I notice a bug. Ship can emerge when there are saucer missiles on the screen. That feature needs improvement. Make a note.

Bottom line for the morning: not bad. A bit weak on the testing. The game is more capable now, but the new code deserves less confidence than it might. I owe myself some tests and you know it’s hard1 to psych up to do tests after the fact.

I’ll try. I’m only human2. See you next time!



  1. Ain’t it hard … 

  2. As far as you know …