GitHub Repo

Things are a bit ragged here and there. Let’s just trim them up a bit.

Maybe we should go after constants. In Flyer.kt, we have this random code:

const val SPEED_OF_LIGHT = 5000.0
const val UNIVERSE_SIZE = 10000.0

typealias Point = Vector2
typealias Velocity = Vector2
typealias Acceleration = Vector2

There are also some utility functions in the file:

fun Point.cap(): Point {
    return Point(this.x.cap(), this.y.cap())
}

fun Velocity.limitedToLightSpeed(): Velocity {
    val speed = this.length
    return if (speed < SPEED_OF_LIGHT) this
    else this*(SPEED_OF_LIGHT/speed)
}

fun Double.cap(): Double {
    return (this + UNIVERSE_SIZE) % UNIVERSE_SIZE
}

Unfortunately, with constants, we can’t do this:

const val UNIVERSE_CENTER = Point(UNIVERSE_SIZE/2, UNIVERSE_SIZE/2)

Turns out that const val can only be primitives.

There might be some extension functions elsewhere in the code as well.

I found this approach recommended:

object DbConstants {
        const val TABLE_USER_ATTRIBUTE_EMPID = "_id"
        const val TABLE_USER_ATTRIBUTE_DATA = "data"
}

Then you refer to things as DbConstants.TABLE_USER_ATTRIBUTE_EMPID, which is clearly 10X better than "_id".

I guess we should have an object U for our constants. Let’s try that. I’ll leave the code where it is, for now, in Flyer.kt.

That goes easily so far, with just a few places needing to say U.. Commit: Move universal constants to U. Presumably now I can put a Point in there.

object U {
    const val SPEED_OF_LIGHT = 5000.0
    const val UNIVERSE_SIZE = 10000.0
    val CENTER_OF_UNIVERSE = Point(UNIVERSE_SIZE/2, UNIVERSE_SIZE/2)
}

Now I can, I suppose, say:

class ShipFinalizer : IFinalizer {
    override fun finalize(flyer: Flyer): List<IFlyer> {
        if ( flyer.deathDueToCollision())
            flyer.position = U.CENTER_OF_UNIVERSE
        else
            flyer.position = Point(random(0.0,U.UNIVERSE_SIZE), random(0.0, U.UNIVERSE_SIZE))
        return emptyList()
    }
}

Tests agree, so far so good.

I can look for some other literal constants.

    private fun newShip(controls: Controls): Flyer {
        return  Flyer.ship(U.CENTER_OF_UNIVERSE, controls)
    }

I find this:

    private fun tooClose(other:IFlyer): Boolean {
        return (Point(5000.0, 5000.0).distanceTo(other.position) < safeShipDistance)
    }

This is the safety check for putting the ship back in. I think it needs to refer to ship.position. Bit of a bug there. We have no tests for the hyperspace. Bad Ron, no biscuit.

    private fun tooClose(other:IFlyer): Boolean {
        return (ship.position.distanceTo(other.position) < U.SAFE_SHIP_DISTANCE)
    }

I’ll test this in the game for now. No biscuit for sure. It seems to be working OK, which isn’t quite what we’d like to say about our testing. I make a note: test hyperspace return testing.

There’s probably some way to put an irritating note right into the code, but my yellow stickies will do just fine.

What other literals can I seek and destroy?

    @Test
    fun `capping works low`() {
        val ship = Flyer.ship( Vector2(1.0, U.UNIVERSE_SIZE/2))
        ship.velocity = Vector2(-120.0, -120.0)
        ship.update(tick)
        assertThat(ship.position.x).isEqualTo(U.UNIVERSE_SIZE-1)
        assertThat(ship.position.y).isEqualTo(U.UNIVERSE_SIZE/2 - 2)
    }

As I look at these, I think that the constants aren’t helping, especially the /2 ones. We’ll let it go for now.

    fun createContents(controls: Controls) {
        val ship = newShip(controls)
        add(ship)
        add(ShipMonitor(ship))
        add(ScoreKeeper())
        add(LifetimeClock())
        for (i in 0..7) {
            val pos = Point(random(0.0, 10000.0), random(0.0,10000.0))
            val vel = Velocity(1000.0, 0.0).rotate(random(0.0,360.0))
            val asteroid = Flyer.asteroid(pos,vel )
            add(asteroid)
        }
    }

I can change these as I did the one above, but I think we should have a random point defined in U:

    fun randomPoint() = Point(random(0.0, UNIVERSE_SIZE), random(0.0, UNIVERSE_SIZE))

Then use it here and in ShipFinalizer.

    fun createContents(controls: Controls) {
        val ship = newShip(controls)
        add(ship)
        add(ShipMonitor(ship))
        add(ScoreKeeper())
        add(LifetimeClock())
        for (i in 0..7) {
            val pos = U.randomPoint()
            val vel = Velocity(1000.0, 0.0).rotate(random(0.0,360.0))
            val asteroid = Flyer.asteroid(pos,vel )
            add(asteroid)
        }
    }

class ShipFinalizer : IFinalizer {
    override fun finalize(flyer: Flyer): List<IFlyer> {
        if ( flyer.deathDueToCollision())
            flyer.position = U.CENTER_OF_UNIVERSE
        else
            flyer.position = U.randomPoint()
        return emptyList()
    }
}

That’s more than enough for now. I smell food. I’ll pick this up tomorrow.

What Else?

What about the chance of ship destruction on emergence from hyperspace? Here’s the code, in ShipMonitor, that handles emergence:

    WaitingForSafety -> {
        if (safeToEmerge) {
            toBeCreated = listOf(shipReset())
            HaveSeenShip
        } else {
            startCheckingForSafeEmergence()
            WaitingForSafety
        }
    }

    private fun shipReset(): IFlyer {
        ship.velocity = Velocity.ZERO
        return ship
    }

Suppose we were to materialize a missile together with the ship, there in toBeCreated.

I try a spike and wind up with this:

    WaitingForSafety -> {
        if (safeToEmerge) {
            val ship = shipReset()
            val ret = mutableListOf(ship)
            if (random(0.0, 1.0) > 0.90) {
                val destroyer = Flyer.asteroid(ship.position, Velocity.ZERO, 100.0, 0)
                val splat = Flyer.splat(destroyer)
                ret.add(destroyer)
                ret.add(splat)
            }
            toBeCreated = ret
            HaveSeenShip
        } else {
            startCheckingForSafeEmergence()
            WaitingForSafety
        }
    }

What this now does is that whenever the ship rezzes, whether or not it was a hyperspace call or a collision that took it out, it has a 10 percent chance that we’ll drop into the if. If we drop into the if, we rez a destroyer asteroid and a splat at the same location as the ship. The destroyer destroys the ship, and the splat leaves a mark.

For a complete implementation, I’d have to check to see if we’re emerging from hyperspace. I’m too tired to think about this, so I’ll roll it back and save this tiny article.

A few good steps, carried forward in #135.