GitHub Decentralized Repo
GitHub Centralized Repo
GitHub Flat Repo

We need to fix safeToEmerge and let’s do zigzag. Features! Woot!

Safe to emerge needs to check for saucer missiles as well as ship ones. Surely we have a test. I replicate one of the existing tests:

    @Test
    fun `safeToEmerge detects saucer missiles`() {
        createGame(4,4,4)
        val timer = Timer(Ship, U.ShipDelay) {}
        assertThat(safeToEmerge(timer)).isEqualTo(true)
        withAvailableSaucerMissile { missile-> missile.active = true }
        assertThat(safeToEmerge(timer)).isEqualTo(false)
    }

This should fail nicely. Sure enough it does. Fix it. The code starts like this:

fun safeToEmerge(timer: Timer): Boolean {
    if ( Saucer.active) return false
    if (activeMissiles(SpaceObjects).isNotEmpty()) return false
    for (asteroid in activeAsteroids(SpaceObjects)) {
        if ( asteroid.position.distanceTo(U.CenterOfUniverse) < U.SafeShipDistance ) return false
    }
    return true
}

And finishes like this:

fun safeToEmerge(timer: Timer): Boolean {
    if ( Saucer.active) return false
    if (activeMissiles(SpaceObjects).isNotEmpty()) return false
    if (activeSaucerMissiles(SpaceObjects).isNotEmpty()) return false
    for (asteroid in activeAsteroids(SpaceObjects)) {
        if ( asteroid.position.distanceTo(U.CenterOfUniverse) < U.SafeShipDistance ) return false
    }
    return true
}

Should be green. Yes. Commit: safeToEmerge now considers saucer missiles.

So that’s nice. What about zig-zagging? In the centralized version, it goes like this:

    fun zigZag() {
        timeSinceSaucerSeen = 0.0
        velocity = newDirection(Random.nextInt(4)) * speed * direction
    }

    fun newDirection(direction: Int): Velocity = directions[min(max(0, direction), 3)]

private val directions = listOf(
    Velocity(1.0, 0.0), Velocity(1.0, 0.0), Velocity(0.7071, 0.7071), Velocity(0.7071, -0.7071)
)

Four possibilities, two straight ahead, one upward, one downward. It zigzags every 1.5 seconds. We’ll do that with a Timer, no doubt.

What might we want to test here?

  1. We can set up a timer. But we know we can.
  2. The timer time is whatever we want. But why?
  3. The zigZag function keeps you moving in the same general direction, right to left or left to right. Probably worth a test.
  4. ?

I’m probably a bad person but I’m not seeing much to test here.

I’ll put in the new timer. I think I’ll use 1.1 seconds for now, not 1.5, and I have an even better idea saved up.

fun newSaucer(): SpaceObject = SpaceObject(
    SpaceObjectType.SAUCER,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    false
).also {
    val firingTimer = Timer(it, 0.5, true, {true}) { fireSaucerMissile() }
    addComponent(it, firingTimer)
    val zigZagTimer = Timer(it, 1.1, true, {true}) { zigZag() }
    addComponent(it, zigZagTimer)
    addComponent(it, SaucerTimer(it))
}

Now I need a zigZag function.

fun zigZag() {
    Saucer.velocity = newDirection(Random.nextInt(4)) * saucerSpeed
}

fun newDirection(direction: Int): Vector2 = directions[min(max(0, direction), 3)]

private val directions = listOf(
    Vector2(-1.0, 0.0), Vector2(-1.0, 0.0),
    Vector2(-0.7071, -0.7071), Vector2(-0.7071, 0.7071))

I tested this in game. Bad Ron, no biscuit. It initially turned around and went backward so I negated all the vectors. The reason was that saucerSpeed changes sign right after the saucer is initialized. The above works as intended.

I really should at least test that.

    @Test
    fun `saucer zigZag continues in same general direction`() {
        val saucer = newSaucer()
        activateSaucer(saucer)
        assertThat(saucer.velocity.x).isGreaterThan(0.0)
        zigZag()
        assertThat(saucer.velocity.x).isGreaterThan(0.0)
        activateSaucer(saucer)
        assertThat(saucer.velocity.x).isLessThan(0.0)
        zigZag()
        assertThat(saucer.velocity.x).isLessThan(0.0)
    }

That runs as intended. Let me do some describedAs work:

    @Test
    fun `saucer zigZag continues in same general direction`() {
        val saucer = newSaucer()
        activateSaucer(saucer)
        assertThat(saucer.velocity.x)
            .describedAs("starts left to right").isGreaterThan(0.0)
        zigZag()
        assertThat(saucer.velocity.x)
            .describedAs("continues left to right").isGreaterThan(0.0)
        activateSaucer(saucer)
        assertThat(saucer.velocity.x)
            .describedAs("switches to right to left").isLessThan(0.0)
        zigZag()
        assertThat(saucer.velocity.x)
            .describedAs("continues right to left").isLessThan(0.0)
    }

That’s nearly good. However, we don’t have a check that all four directions work. In principle one of them could have the wrong sign.

    @Test
    fun `saucer directions are all in same general direction`() {
        for (direction in saucerDireections) {
            assertThat(direction.x).isLessThan(0.0)
        }
    }

That’ll do just fine. Commit: saucer zigzags every 1.1 seconds.

Let’s sum up, that’s enough for an afternoon’s frolicking.

Summary

We had not yet checked saucer missiles for ship emergence. Now we do. We also check ship missiles. Can they kill the ship? I’m not sure, let’s look. We do not:

private fun checkCollisions() {
    checkAllMissilesVsAsteroids()
    checkSaucerMissilesVsShip()
    if ( Ship.active ) checkShipVsAsteroids(Ship)
    if ( Saucer.active ) checkAllMissilesVsSaucer(Saucer)
}

In principle then, we don’t need to check ship missiles for emergence, but let’s leave it. We might change that rule.

Anyway, having done that, we implemented zigZag and then tested it. As usual the tests were easy: I just didn’t quite have the gumption to do them first. It has been a bad couple of days for test-first, and not the first such. I know it’s better. I can feel the increased tension without them … and yet, often I don’t do them.

Humans are strange.

But we have another key feature, the zigzag, so we have improved the game and done little if any harm to the code. A satisfactory afternoon!

See you next time!