GitHub Repo

Got this thing, might as well use it. Let’s eliminate WaveMaker.

If I were to use TellMeWhen in WaveChecker, I think I could eliminate WaveMaker entirely.

WaveChecker looks like this:

class WaveChecker: ISpaceObject, InteractingSpaceObject {
    private var sawAsteroid = false
    var elapsedTime = 0.0

    override fun update(deltaTime: Double, trans: Transaction) {
        elapsedTime += deltaTime
    }

    override fun callOther(other: InteractingSpaceObject, trans: Transaction) = Unit
    override val subscriptions = Subscriptions (
        beforeInteractions = { sawAsteroid = false},
        interactWithAsteroid = { _, _ -> sawAsteroid = true },
        afterInteractions = this::makeWaveInDueTime
    )

    private fun makeWaveInDueTime(trans: Transaction) {
        if ( elapsedTime > 1.0  ) {
            elapsedTime = 0.0
            if (!sawAsteroid) {
                elapsedTime = -5.0 // judicious delay to allow time for creation
                trans.add(WaveMaker(4))
            }
        }
    }
}

I’ll follow the pattern for the SaucerChecker, with a waiting flag.

class WaveChecker: ISpaceObject, InteractingSpaceObject {
    private var asteroidsMissing = true
    var makingWave = false
    var numberToCreate = 4

    override fun update(deltaTime: Double, trans: Transaction) {}
    override fun callOther(other: InteractingSpaceObject, trans: Transaction) = Unit

    override val subscriptions = Subscriptions (
        beforeInteractions = { asteroidsMissing = true},
        interactWithAsteroid = { _, _ -> asteroidsMissing = false },
        afterInteractions = this::makeWaveInDueTime
    )

    private fun makeWaveInDueTime(trans: Transaction) {
        if ( asteroidsMissing && !makingWave ) {
            makingWave = true
            TellMeWhen (4.0, trans) {
                makeWave(it)
                makingWave = false
            }
        }
    }

    private fun makeWave(trans: Transaction) {
        for (i in 1..numberToCreate) {
            trans.add(Asteroid(U.randomEdgePoint()))
        }
    }
}

We aren’t making a wave; we see missing asteroids; we makeWaveInDueTime, which sets makingWave so we won’t do it again and uses TellMeWhen to actually makeWave and reset the waiting flag. This works as advertised in the game. I think it’ll break tests.

Yes, the waveChecker tests need work. I’m tempted to redo them with the new game-centric testing style.

class WaveCheckerTest {
    @Test
    fun `checker creates wave after 4 seconds`() {
        val mix = SpaceObjectCollection()
        val ck = WaveChecker()
        mix.add(ck)
        val game = Game(mix)
        game.cycle(0.1)
        assertThat(mix.any { it is TellMeWhen }).isEqualTo(true)
        assertThat(mix.size).isEqualTo(2) // checker and TMW
        game.cycle(4.2)
        assertThat(mix.size).isEqualTo(5) // asteroids plus checker
        ck.update(0.5, Transaction())
    }

    @Test
    fun `does not create wave if asteroid present`() {
        val mix = SpaceObjectCollection()
        val ck = WaveChecker()
        mix.add(ck)
        val a = Asteroid(U.randomEdgePoint())
        mix.add(a)
        val game = Game(mix)
        game.cycle(0.1)
        assertThat(mix.any { it is TellMeWhen }).isEqualTo(false)
        assertThat(mix.size).isEqualTo(2) // checker and asteroid
        game.cycle(4.2)
        assertThat(mix.size).isEqualTo(2) // checker and asteroid
    }
}

That seems to me to suffice, given the new style. Now I should be able to delete WaveMaker entirely. First WaveMakerTest … then the interactWithWaveMaker, which no one ever used, and then WaveMaker. All gone. Commit: Waves created by WaveChecker, WaveMaker removed.

Let’s sum up for now.

Summary

The TellMeWhen object saved us an entire class and made testing wave making easier. We are down about 30 lines from the previous total code size.

One thing that I don’t love is the need for two flags, one used to see whether action is needed and then the one to show action is pending. In the old scheme we got around that by dumping the checker and recreating it after the making was done. I’ll think about whether there is a better formulation for the code, but this isn’t bad. And we could refactor it for greater meaning.

Is this better?

class WaveChecker: ISpaceObject, InteractingSpaceObject {
    private var asteroidsMissing = true
    var makingWave = false
    var numberToCreate = 4

    override fun update(deltaTime: Double, trans: Transaction) {}
    override fun callOther(other: InteractingSpaceObject, trans: Transaction) = Unit

    override val subscriptions = Subscriptions (
        beforeInteractions = { asteroidsMissing = true},
        interactWithAsteroid = { _, _ -> asteroidsMissing = false },
        afterInteractions = this::makeWaveIfNeeded
    )

    private fun makeWaveIfNeeded(trans: Transaction) {
        if ( asteroidsMissing && !makingWave ) {
            makeWaveSoon(trans)
        }
    }

    private fun makeWaveSoon(trans: Transaction) {
        makingWave = true
        TellMeWhen(4.0, trans) {
            makeWave(it)
            makingWave = false
        }
    }

    private fun makeWave(trans: Transaction) {
        for (i in 1..numberToCreate) {
            trans.add(Asteroid(U.randomEdgePoint()))
        }
    }
}

Might be a bit more readable. I’m not sure what I think about that.

Overall, it’s better this afternoon than it was this morning. I think the TellMeWhen may turn out to be quite useful. It has replaced a couple of objects already, and made the flow of events a bit more clear. We’ll see. I hope you’ll join me next time!