GitHub Repo

I’m renaming Interactions to Subscriptions. IDEA helps. What else?

I think subscriptions is a better word than interactions. The code looks like this:

class Subscriptions(
    val beforeInteractions: () -> Unit = {},
    val afterInteractions: (trans: Transaction) -> Unit = {_ -> },
    val interactWithScore: (score: Score, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithShip: (ship: Ship, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithMissile: (missile: Missile, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithShipChecker: (checker: ShipChecker, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithShipMaker: (maker: ShipMaker, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithWaveMaker: (maker: WaveMaker, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithShipDestroyer: (destroyer: ShipDestroyer, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithAsteroid: (asteroid: Asteroid, trans: Transaction) -> Unit = { _, _, -> },
)


    override val subscriptions: Subscriptions = Subscriptions(
        interactWithMissile = { missile, trans ->
            if (weAreCollidingWith(missile)) {
                trans.remove(this)
            }
        },
        interactWithShip = { ship, trans ->
            if (weAreCollidingWith(ship)) {
                trans.remove(this)
            }
        }
    )

    override fun callOther(other: InteractingSpaceObject, trans: Transaction) {
        other.subscriptions.interactWithAsteroid(this, trans)
    }

I might trouble myself to remove the redundant Subscriptions from those vals.

    override val subscriptions = Subscriptions(
        interactWithMissile = { missile, trans ->
            if (weAreCollidingWith(missile)) {
                trans.remove(this)
            }
        },
        interactWithShip = { ship, trans ->
            if (weAreCollidingWith(ship)) {
                trans.remove(this)
            }
        }
    )

Saying it twice seems sufficient. IDEA does the heavy lifting. Green. Commit: Remove explicit “Subscriptions” from creators of required val subscriptions.

I think that now that all the objects are separate, I can fold the view and probably the finalizers back into the class. There’s no need for multiples in either case.

There’s only one finalize that isn’t done, let’s fold it in:

class ShipFinalizer : IFinalizer {
    override fun finalize(spaceObject: ISpaceObject): List<ISpaceObject> {
        val ship = spaceObject as Ship
        if ( ship.deathDueToCollision()) {
            ship.position = U.CENTER_OF_UNIVERSE
            ship.velocity = Velocity.ZERO
            ship.heading = 0.0
        } else {
            ship.position = U.randomPoint()
        }
        return emptyList()
    }
}

We can plug that directly in to Ship:

    override fun finalize(): List<ISpaceObject> {
        if ( deathDueToCollision() ) {
            position = U.CENTER_OF_UNIVERSE
            velocity = Velocity.ZERO
            heading = 0.0
        } else {
            position = U.randomPoint()
        }
        return emptyList()
    }

Test. Oops, remove the init of the member variable, no longer needed. Green. Commit: remove IFinalizer and last remaining implementor, ShipFinalizer.

Let’s look for views.

Some are simple:

class MissileView {
    fun draw(missile: Missile, drawer: Drawer) {
        drawer.stroke = ColorRGBa.WHITE
        drawer.fill = ColorRGBa.WHITE
        drawer.circle(Point.ZERO, missile.killRadius*3.0)
    }
}

Missile can do that in line, if it doesn’t already:

class Missile ...
    override fun draw(drawer: Drawer) {
        drawer.fill = ColorRGBa.MEDIUM_SLATE_BLUE
        drawer.translate(position)
        drawer.stroke = ColorRGBa.WHITE
        drawer.fill = ColorRGBa.WHITE
        drawer.circle(Point.ZERO, killRadius*3.0)
    }

IDEA reminds me that NullView is unused. Remove it. Commit that as well.

The SplatView and AsteroidView are both complicated enough that they are worth keeping, but no need to be inheriting from the silly interface any more. I’ll move them to their own files.

While at it, I fix a problem that I only just noticed, which is that I’d commented out the random rotation of the Asteroid. They don’t spin, but they draw their same shape at various rotations set up at the time of creation.

IDEA kindly moves the class to its own file:

class AsteroidView {
    private val rock = defineRocks().random()

    fun draw(asteroid: Asteroid, drawer: Drawer) {
        drawer.stroke = ColorRGBa.WHITE
        drawer.strokeWeight = 16.0
        drawer.fill = null
        val sizer = 30.0
        drawer.scale(sizer, sizer)
        val sc = asteroid.scale()
        drawer.scale(sc,sc)
        drawer.rotate(asteroid.heading)
        drawer.stroke = ColorRGBa.WHITE
        drawer.strokeWeight = 8.0/30.0/sc
        drawer.scale(1.0, -1.0)
        drawer.lineStrip(rock)
    }

    private fun defineRocks(): List<List<Point>> {
        val rock0 = listOf(
            Point(4.0, 2.0),
            Point(3.0, 0.0),
            Point(4.0, -2.0),
            Point(1.0, -4.0),
            Point(-2.0, -4.0),
            Point(-4.0, -2.0),
            Point(-4.0, 2.0),
            Point(-2.0, 4.0),
            Point(0.0, 2.0),
            Point(2.0, 4.0),
            Point(4.0, 2.0),
        )
        val rock1 = listOf(
            Point(2.0, 1.0),
            Point(4.0, 2.0),
            Point(2.0, 4.0),
            Point(0.0, 3.0),
            Point(-2.0, 4.0),
            Point(-4.0, 2.0),
            Point(-3.0, 0.0),
            Point(-4.0, -2.0),
            Point(-2.0, -4.0),
            Point(-1.0, -3.0),
            Point(2.0, -4.0),
            Point(4.0, -1.0),
            Point(2.0, 1.0)
        )
        val rock2 = listOf(
            Point(-2.0, 0.0),
            Point(-4.0, -1.0),
            Point(-2.0, -4.0),
            Point(0.0, -1.0),
            Point(0.0, -4.0),
            Point(2.0, -4.0),
            Point(4.0, -1.0),
            Point(4.0, 1.0),
            Point(2.0, 4.0),
            Point(-1.0, 4.0),
            Point(-4.0, 1.0),
            Point(-2.0, 0.0)
        )

        val rock3 = listOf(
            Point(1.0, 0.0),
            Point(4.0, 1.0),
            Point(4.0, 2.0),
            Point(1.0, 4.0),
            Point(-2.0, 4.0),
            Point(-1.0, 2.0),
            Point(-4.0, 2.0),
            Point(-4.0, -1.0),
            Point(-2.0, -4.0),
            Point(1.0, -3.0),
            Point(2.0, -4.0),
            Point(4.0, -2.0),
            Point(1.0, 0.0)
        )
        return listOf(rock0,rock1,rock2,rock3)
    }
}

The Point lists there are copies of the ones I copied from the original Asteroids program data.

I think the SplatView is complicated enough to retain as well:

class SplatView(lifetime: Double) {
    private val rot = Random.nextDouble(0.0, 360.0)
    private var sizeTween = Tween(20.0,100.0, lifetime)
    private var radiusTween = Tween(30.0, 5.0, lifetime)
    private val points = listOf(
        Point(-2.0,0.0), Point(-2.0,-2.0), Point(2.0,-2.0), Point(3.0,1.0), Point(2.0,-1.0), Point(0.0,2.0), Point(1.0,3.0), Point(-1.0,3.0), Point(-4.0,-1.0), Point(-3.0,1.0)
    )

    fun draw(splat: Splat, drawer: Drawer) {
        drawer.stroke = ColorRGBa.RED
        drawer.fill = ColorRGBa.RED
        drawer.rotate(rot)
        for (point in points) {
            val size = sizeTween.value(splat.elapsedTime)
            val radius = radiusTween.value(splat.elapsedTime)
            drawer.circle(size*point.x, size*point.y, radius)
        }
    }
}

Move that to its own file as well.

And now ShipView:

class ShipView {
    fun draw(ship: Ship, drawer: Drawer) {
        val points = listOf(
            Point(-3.0, -2.0),
            Point(-3.0, 2.0),
            Point(-5.0, 4.0),
            Point(7.0, 0.0),
            Point(-5.0, -4.0),
            Point(-3.0, -2.0)
        )
        drawer.scale(30.0, 30.0)
        drawer.rotate(ship.heading )
        drawer.stroke = ColorRGBa.WHITE
        drawer.strokeWeight = 8.0/30.0
        drawer.lineStrip(points)
    }
}

We’re green and the graphics look right. Commit: Break out all Views.

Gotta go watch the game. This will have to do for now.

Simple changes, code slightly better, I think.

Later

Moved draw into Subscriptions, implemented by calling existing draw method where applicable, removed others. Test, commit: draw now runs on subscriptions.

Subscriptions now include:

class Subscriptions(
    val beforeInteractions: () -> Unit = {},

    val interactWithMissile: (missile: Missile, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithScore: (score: Score, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithShip: (ship: Ship, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithShipChecker: (checker: ShipChecker, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithShipDestroyer: (destroyer: ShipDestroyer, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithShipMaker: (maker: ShipMaker, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithWaveMaker: (maker: WaveMaker, trans: Transaction) -> Unit = { _, _, -> },
    val interactWithAsteroid: (asteroid: Asteroid, trans: Transaction) -> Unit = { _, _, -> },

    val afterInteractions: (trans: Transaction) -> Unit = {_ -> },

    val draw: (drawer: Drawer) -> Unit = {_ -> },
)

We could add update and finalize. There are four classes that don’t use update, and eight that return the default for finalize. We’d have to adjust finalize to use a transaction but that should probably be done for consistency anyway.

Perhaps soon. Or perhaps we should split the hierarchy? I don’t know, now that we have this subscriptions model, why not just use it?