GitHub Decentralized Repo
GitHub Centralized Repo
GitHub Flat Repo

And he did give to the cat of two fishes of the sea, the tuna and the whitefish. And the cat saw that it was good, and he did eat.

Now that that’s out of the way, let’s get down to it.

Perhaps we’ll try to fire a missile today. Unless something else comes up.

I am rather certain that the original Asteroids game had some finite number of missiles, including only one at a time for the saucer. I’ll try to suss out that number in the next day or so. Because we’re not doing objects, and we’re trying to do a very flat program using little more than functions, we’ll have a fixed number of missiles and asteroids. The number of asteroids, I think, is limited to 44, four times eleven, with eleven being the highest number the game would ever create in a wave. We’ll see about missiles.

We’ll allow the player to fire at most one missile per jab at the “k” key, which is our “fire” button. If there’s no missile to allocate, the player still has to lift their finger and tap again. Why? Just seems right to me.

We’ll inch up on this, of course. Let’s start with the controls, where we have this:

var controls_left: Boolean = false
var controls_right: Boolean = false
var controls_accelerate: Boolean = false
var controls_fire: Boolean = false
var controls_hyperspace: Boolean = false

        keyboard.keyDown.listen {
            when (it.name) {
                "d" -> {controls_left = true}
                "f" -> {controls_right = true}
                "j" -> {controls_accelerate = true}
                "k" -> {controls_fire = true}
                "space" -> {controls_hyperspace = true}
//                "q" -> { insertQuarter()}
            }
        }
        keyboard.keyUp.listen {
            when (it.name) {
                "d" -> {controls_left = false}
                "f" -> {controls_right = false}
                "j" -> {controls_accelerate = false}
                "k" -> {controls_fire = false}
                "space" -> {
                    controls_hyperspace = false
                }
            }

So. We need another flag, I think, saying whether the ship can fire, which we’ll set to true at the beginning of things. When we see control_fire, we’ll check to see whether the player can_fire, and if so we’ll set can_fire to false and try to fire. We may or may not succeed, depending on whether there are missiles to be had. If, on the other hand, control_fire is false, we can set can_fire back to true.

Could we do something smarter?

Now, we could do something a bit more robust rather than this arm’s length setting of flags. We could change the listen code to call functions that we could implement over in the game tab. We could start with those functions just setting basically the same flags they set now, but we could make them more capable as needed.

But no …

As it happens, however, even in the more OO versions of the program, we just set flags, though those flags belong to the controls object over there. I think we’ll save the function-calling until we need it, if we ever do. This way is a bit like how it would be if we had an attached controller and all we could do is read out its switch values. More retro and we didn’t ever feel the need for more even in our other versions.

OK, so when do we want to do this? We’ll look at gameCycle:

fun gameCycle(
    spaceObjects: Array<SpaceObject>,
    width: Int,
    height: Int,
    drawer: Drawer,
    deltaTime: Double
) {
    for (spaceObject in spaceObjects) {
        if (spaceObject.type == SpaceObjectType.SHIP) {
            applyControls(spaceObject, deltaTime)
        }
        move(spaceObject, width, height, deltaTime)
    }
    for (spaceObject in spaceObjects) {
        draw(spaceObject, drawer)
    }
}

private fun applyControls(spaceObject: SpaceObject, deltaTime: Double) {
    if (controls_left) spaceObject.angle -= 250.0 * deltaTime
    if (controls_right) spaceObject.angle += 250.0 * deltaTime
    if (controls_accelerate) {
        val deltaV = Vector2(120.0, 0.0).rotate(spaceObject.angle) * deltaTime
        spaceObject.dx += deltaV.x
        spaceObject.dy += deltaV.y
    }
}

Well, it’s pretty clear roughly where the missile check needs to be.

Let’s sketch something in.

Hmm. While thinking, I refreshed my mind further on how we did this in other versions and it turns out we didn’t use another flag. Instead, if the game sees the control_fire flag as true, it sets it false and attempts to fire. Since the flag only goes true on the keyDown event, it won’t come up true again until we lift our finger and tap again. We’ll do that.

private fun applyControls(spaceObject: SpaceObject, deltaTime: Double) {
    if (controls_left) spaceObject.angle -= 250.0 * deltaTime
    if (controls_right) spaceObject.angle += 250.0 * deltaTime
    if (controls_accelerate) {
        val deltaV = Vector2(120.0, 0.0).rotate(spaceObject.angle) * deltaTime
        spaceObject.dx += deltaV.x
        spaceObject.dy += deltaV.y
    }
    if (controls_fire) fireMissile()
}

Perfect, except we need to implement fireMissile. A mere bagatelle, I’m sure.

private fun fireMissile() {
    controls_fire = false
    println("BANG!")
}

I can test this in the game. Remind me about writing tests, and TDD, and all that. Works perfectly, says

BANG!
BANG!
BANG!

Each BANG! came out immediately when I hit the “k” key and not again until I tapped again. Trust me, I wouldn’t lie to you. At least not about anything so unimportant.

A Digression

We’re about to create yet another kind of SpaceObject. We have them represented as an enum and a data class:

enum class SpaceObjectType(val points: List<Vector2>) {
    ASTEROID(asteroidPoints),
    SHIP(shipPoints)
}

data class SpaceObject(
    val type: SpaceObjectType,
    var x: Double,
    var y: Double,
    var dx: Double,
    var dy: Double,
    var angle: Double = 0.0,
)

We’ll want a MISSILE, and unless I miss my guess, it won’t be drawn as a set of points. So we’ll need to change how that enum works a bit.

And we should probably get around to some factory functions or something, pretty soon.

For now, I think I’ll make a missile be a very small rectangle.

private val missilePoints = 
    listOf(Vector2(-1.0, -1.0), Vector2(-1.0, 1.0), Vector2(1.0, 1.0), 
        Vector2(1.0, -1.0), Vector2(-1.0, -1.0))

enum class SpaceObjectType(val points: List<Vector2>) {
    ASTEROID(asteroidPoints),
    SHIP(shipPoints),
    MISSILE(missilePoints)
}

Now I need to figure out how to create one on the fly as it were.

I think we need an active flag to help us here.

data class SpaceObject(
    val type: SpaceObjectType,
    var x: Double,
    var y: Double,
    var dx: Double,
    var dy: Double,
    val active: Boolean = true,
    var angle: Double = 0.0,
) 

And I’ll create one, inactive, wherever we create our objects. Ah, yes, in program:

    program {
//        val image = loadImage("data/images/pm5544.png")
        val font = loadFont("data/fonts/default.otf", 64.0)
        val ship = SpaceObject(
            SpaceObjectType.SHIP,
            width/2.0,
            height/2.0,
            0.0,
            0.0,
            0.0,
        )
        val asteroid = SpaceObject(
            SpaceObjectType.ASTEROID,
            300.0,
            300.0,
            74.0,
            40.0,
        )
        val missile = SpaceObject(
            SpaceObjectType.MISSILE,
            0.0, 0.0,
            0.0, 0.0
            false
        )
        val spaceObjects = arrayOf(asteroid, ship,missile)

That should allow me to set a missile on the screen, maybe even move it.

I realize immediately that I don’t have access to the space objects yet. For now, let’s make them global.

lateinit var spaceObjects: Array<SpaceObject>
// initialized as above

private fun fireMissile() {
    controls_fire = false
    val ship = spaceObjects[1]
    val missile = spaceObjects[2]
    missile.x = ship.x+ 50.0
    missile.y = ship.y
    missile.dx = 15.0
    missile.dy = 0.0
    missile.active = true
}

One more thing will be needed but I think this might do something visible.

OK, that was a bit ragged. Here’s what I finally got working. The square missile you see is drifting slowly right as intended.

square missile

The final code had to be tweaked. I’m not sure where we should put active but I put it last for now:

lateinit var spaceObjects: Array<SpaceObject>

    program {
//        val image = loadImage("data/images/pm5544.png")
        val font = loadFont("data/fonts/default.otf", 64.0)
        val ship = SpaceObject(
            SpaceObjectType.SHIP,
            width/2.0,
            height/2.0,
            0.0,
            0.0,
            0.0,
        )
        val asteroid = SpaceObject(
            SpaceObjectType.ASTEROID,
            300.0,
            300.0,
            74.0,
            40.0,
        )
        val missile = SpaceObject(
            SpaceObjectType.MISSILE,
            0.0, 0.0,
            0.0, 0.0, 0.0,
            false
        )
        spaceObjects = arrayOf(asteroid, ship,missile)

private fun fireMissile() {
    controls_fire = false
    val ship = spaceObjects[1]
    val missile = spaceObjects[2]
    missile.x = ship.x+ 50.0
    missile.y = ship.y
    missile.dx = 15.0
    missile.dy = 0.0
    missile.active = true
}

It works as advertised, the square missile appears in front of the ship and drifts right. Of course, before you fire you can see it in the corner, because we’re not honoring the active flag. Let’s do that.

    for (spaceObject in spaceObjects) {
        if (spaceObject.active) draw(spaceObject, drawer)
    }

And that’ll solve the dot in the corner. Let’s commit this baby. Commit: can fire rudimentary square missile due east.

We’ve left a bit of a mess. No, quite a lot of mess. But I think we’re done for the morning, so let’s see what we’ve learned and what looms before us:

Summary

On the bright side, we added a new “type” of SpaceObject, the MISSILE, and arranged to draw it as a square. Kind of retro-cool, really, but we’ll want to change that to make it more of a dot.

We’ve made the array of space objects global, and honestly I think that’s OK, in the spirit of what we’re doing, which is a compact game implementation, basically limited to functions and simple struct type data. If we’re going to make it a public array, we shouldn’t also pass it to gameCycle. One thing or the other, not both.

However, we have a very ad-hoc way of deciding where the ship is, spaceObjects[1], as well as the missile in spaceObjects[2]. That cannot long endure, but I do think we’ll need to know all the numbers for asteroid count, missile count, and so on, so one way or another those objects will have well-known locations.

Clearly we’ll need to set the position and velocity of the missile relative to that of the ship, but we have done that before. And we’ll want to search through all the missile slots to find an available one. And we’ll need to decide how to time them out, which is going to require a value in the missile, I suppose, or a separate missile-timing mechanism.

Possibly the missile table needs to be separate from the other objects. I’m sure that in the original version they didn’t waste any extra bytes storing unused information. I’ll do some research before next time. And next time I think we’ll move toward a more nearly final space object array, and see what we can clean up.

I can’t wait to find out what I do next!