GitHub Repo

Tidying. Why are we all setting asteroid velocity?

I was moving some literal constants to U, the Home of Universal Constants and Otherwise Useful Values, and noticed that everyone and their brother is setting asteroid velocity. They all go the same speed, in different directions. Let’s make that a bit easier, shall we?

        fun asteroid(pos:Point, vel: Velocity, killRad: Double = 500.0, splitCount: Int = 2): SolidObject {
            return SolidObject(
                position = pos,
                velocity = vel,
                killRadius = killRad,
                isAsteroid = true,
                view = AsteroidView(),
                finalizer = AsteroidFinalizer(splitCount)
            )
        }

That’ll become:

    companion object {
        fun asteroid(pos:Point, vel: Velocity = U.randomVelocity(U.ASTEROID_SPEED), killRad: Double = 500.0, splitCount: Int = 2): SolidObject {
            return SolidObject(
                position = pos,
                velocity = vel,
                killRadius = killRad,
                isAsteroid = true,
                view = AsteroidView(),
                finalizer = AsteroidFinalizer(splitCount)
            )
        }

And now I’ll go clean up all the callers.

There were 24 tests, most of which could use the default. A few were checking positions or otherwise needed the velocity they provided. Commit: tidying.

I’ve reviewed all the classes, looking for magic numbers, and I think most of them are now isolated to U, which is a good thing. And reading the code, it is mostly pretty simple and clear, with the possible exception of the ShipMonitor:

class ShipMonitor(val ship: SolidObject) : ISpaceObject {
    override var elapsedTime: Double = 0.0
    var state = HaveSeenShip
    var safeToEmerge = false
    var asteroidTally = 0

    fun hyperspaceFailure(random0thru62: Int, asteroidCount: Int): Boolean {
        // allegedly the original arcade rule
        return random0thru62 >= (asteroidCount + 44)
    }

    private fun hyperspaceWorks(): Boolean {
        val ran = Random.nextInt(0,63)
        return !hyperspaceFailure(ran, asteroidTally)
    }

    override fun interactWith(other: ISpaceObject): List<ISpaceObject> {
        if (state == LookingForShip) {
            if (other == ship)
                state = HaveSeenShip
        } else if (state == WaitingForSafety){
            if (other is SolidObject && other.isAsteroid) {
                asteroidTally += 1
            }
            if (tooClose(other)) safeToEmerge = false
        }
        return emptyList() // no damage done here
    }

    override fun interactWithOther(other: ISpaceObject): List<ISpaceObject> {
        return this.interactWith(other)
    }

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

    fun startCheckingForSafeEmergence() {
        // assume we're OK. interactWith may tell us otherwise.
        safeToEmerge = true
        asteroidTally = 0
    }

    private fun tooClose(other:ISpaceObject): Boolean {
        return if (other !is SolidObject) false
        else (ship.position.distanceTo(other.position) < U.SAFE_SHIP_DISTANCE)
    }

    override fun update(deltaTime: Double): List<ISpaceObject> {
        elapsedTime += deltaTime
        var toBeCreated: List<ISpaceObject> = emptyList()
        state = when (state) {
            HaveSeenShip -> LookingForShip
            LookingForShip -> {
                elapsedTime = 0.0
                WaitingForTime
            }
            WaitingForTime -> {
                if (elapsedTime < 3.0)
                    WaitingForTime
                else {
                    startCheckingForSafeEmergence()
                    WaitingForSafety
                }
            }
            WaitingForSafety -> {
                if (safeToEmerge) {
                    toBeCreated = makeEmergenceObjects()
                    HaveSeenShip
                } else {
                    startCheckingForSafeEmergence()
                    WaitingForSafety
                }
            }
        }
        return toBeCreated
    }

    private fun makeEmergenceObjects(): List<ISpaceObject> {
        return when (emergenceIsOK()) {
            true -> {
                listOf(shipReset())
            }
            false -> {
                val splat = SolidObject.splat(ship)
                val destroyer = SolidObject.shipDestroyer(ship)
                listOf(splat, destroyer, shipReset())
            }
        }
    }

    private fun emergenceIsOK() = notInHyperspace() or hyperspaceWorks()

    private fun notInHyperspace() = (ship.position == U.CENTER_OF_UNIVERSE)
}

Did I say “possible”? Let me just go strike that right out. This is too complicated, even though it does the job. We now have a new capability, the begin and finishInteraction calls, which should let us simplify a bit. We might also want to break the object up, as we did with WaveChecker and WaveMaker. Two simple objects is better than one complicated one most any time.

I think we’ll save working on that one for another day. These afternoon exercises are usually more just tidying, and ShipMonitor needs a fresh brain. I’ll try to have one by tomorrow, day after by the latest, depending on Amazon.

See you next time!