GitHub Repo

Let’s have the saucer fire randomly. Should be about what I need for an afternoon session.

It appears that the large saucer fires randomly, every half second. Let’s see about doing that. There are a number of things that have to work, many of which are not yet implemented.

  1. Creation of missiles by other than a ship.
  2. Possibly a different color or look to them?
  3. Missiles should be able to shoot down missiles.
  4. Missiles should be able to destroy the ship (presently cannot).

We’ll start seeing some N-squared effect, as we implement N things that can mutually destroy each other. We may wish to find some way to improve that.

Among the many things we also need are a decent ship explosion animation, and, ideally, the ability to fully use the transaction that we send in on interaction. Fact is, only the removals are honored:

    fun cycle(drawer: Drawer, seconds: Double) {
        val deltaTime = seconds - lastTime
        lastTime = seconds
        tick(deltaTime)
        beginInteractions()
        processInteractions()
        finishInteractions()
        draw(drawer)
    }

    fun processInteractions() {
        val toBeRemoved = removalsDueToInteraction()
        if ( toBeRemoved.size > 0 ) {
            knownObjects.removeAndFinalizeAll(toBeRemoved)
        }
    }

    fun removalsDueToInteraction(): MutableSet<ISpaceObject> {
        val trans = Transaction()
        knownObjects.pairsToCheck().forEach { p ->
            val first = p.first
            val second = p.second
            first.callOther(second, trans)
            second.callOther(first, trans)
        }
        return trans.removes
    }

I think what’s been holding me back from this is a fear that some tests will break. Let’s make the transaction come back, step by step.

First, from the removals function:

    fun removalsDueToInteraction(): Transaction {
        val trans = Transaction()
        knownObjects.pairsToCheck().forEach { p ->
            val first = p.first
            val second = p.second
            first.callOther(second, trans)
            second.callOther(first, trans)
        }
        return trans
    }

That makes the processInteractions unhappy. Rename the removals method:

    fun processInteractions() {
        val toBeRemoved = changesDueToInteractions()
        if ( toBeRemoved.size > 0 ) {
            knownObjects.removeAndFinalizeAll(toBeRemoved)
        }
    }

And fix up this method to work as it always has:

    fun processInteractions() {
        val trans = changesDueToInteractions()
        val toBeRemoved = trans.removes
        if ( toBeRemoved.size > 0 ) {
            knownObjects.removeAndFinalizeAll(toBeRemoved)
        }
    }

We should be green with existing functionality. Now let’s apply the whole transaction:

    fun processInteractions() {
        val trans = changesDueToInteractions()
        val toBeRemoved = trans.removes
        knownObjects.applyChanges(trans)
    }

Despite my fears, the test changes are all simple, just allow a transaction to come back and then check inside it. Not worth reading.

Optimize the above:

    fun processInteractions() {
        knownObjects.applyChanges(changesDueToInteractions())
    }

Commit: interactions can now add as well as remove objects.

Now let’s see about firing some missiles from the saucer. They fire every half second, in a random direction.

I’m going to just code this up. Forgive me.

    override fun update(deltaTime: Double, trans: Transaction) {
        timeSinceSaucerSeen += deltaTime
        timeSinceLastMissileFired += deltaTime
        if (timeSinceSaucerSeen > 1.5) {
            timeSinceSaucerSeen = 0.0
            zigZag()
        }
        if (timeSinceLastMissileFired > 0.5 ) {
            timeSinceLastMissileFired = 0.0
            fire(trans)
        }
        position = (position + velocity * deltaTime).cap()
    }
    
    fun fire(trans: Transaction) {
        trans.add(Missile(this))
    }

Now for fire … we need to be able to produce a Missile from a Saucer. We can create them from a Ship:

class Missile(
    shipPosition: Point,
    shipHeading: Double = 0.0,
    shipKillRadius: Double = 100.0,
    shipVelocity: Velocity = Velocity.ZERO
): ISpaceObject, InteractingSpaceObject, Collider {
    constructor(ship: Ship): this(ship.position, ship.heading, ship.killRadius, ship.velocity)

Should be able to do a new constructor:

    constructor(saucer: Saucer): this(saucer.position, Random.nextDouble(360.0), saucer.killRadius, saucer.velocity)

I expect the saucer to spew missiles madly. And we do. All the missiles below are from the sauce.

missiles all over

Missiles don’t kill the ship. We need to add that.

        interactWithMissile = { missile, trans ->
            checkCollision(missile, trans)
        },

And I decide to add a Splat to stand in for the real ship explosion when it happens.

    private fun checkCollision(other: Collider, trans: Transaction) {
        if (weAreCollidingWith(other)) {
            trans.remove(this)
            trans.add(Splat(this))
        }
    }

That does work, but when, finally, one of the saucer’s missiles kills me, the missile is not killed and carries on. We need this:

class Missile ...
        interactWithShip = { ship, trans ->
            if (checkCollision(ship)) { trans.remove(this) }
        },

Tests go green after I adjust one for the surprise splat when the Ship is killed. We do need more interaction tests, I think.

And I think missiles should be able to kill missiles, though your chances are low since their diameter is 10, which is, well, about the size of those dots. We’ll save that for next time, I need to get ready for tonight’s zoom.

Summary

Saucer Missiles went in smoothly. We did need to change things in a few places, which is of course a sign of troublesome coupling. That is a characteristic of the present design, especially the interaction, which is done a vs b and b vs a rather than considering a pair a,b. We’re generating duplication and coupling and probably should work on that.

We first did an important unplanned thing, making interactions fully implement use of Transaction, which they had not yet done.

Here’s our status on our list of things needed:

  1. √ Creation of missiles by other than a ship.
  2. X Possibly a different color or look to them?
  3. X Missiles should be able to shoot down missiles.
  4. √ Missiles should be able to destroy the ship (presently cannot).

Not bad for a quick afternoon session. See you next time!