GitHub Decentralized Repo
GitHub Centralized Repo
GitHub Flat Repo

Today we’ll start bringing things a bit more into a sensible structure. Just a bit.

Again today, the cat has accepted my humble offering of fish in a dish and has ceased meowing and clawing at my leg to get my attention. Life is good.

I did the promised research last night, and if my understanding of the original code is correct, I have learned that the original game allowed for 26 asteroids, not 44, and that there were two saucer missiles possible, and four ship missiles. Missile flight time was 18 frames, and I have not been able to determine just what frame time might be. I had previously estimated it as 1/6 of a second, but I’m not sure why. The online games that claim to be “original” have a missile flight distance of just under one screen height, and a flight time of what seems to me to be about one second.

We’ll take these numbers under advisement, and then do whatever seems right.

Yesterday we hammered in a very short list of instances of SpaceObject:

        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)

Using that list, we got access to the ship and missile and placed a slow-moving missile on the screen:

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
}

Today, I think we’ll start on these things, which seem to me to be things we need:

Create Game Objects

Create a fixed array of missiles, ship, saucer, and asteroids;

Save in a convenient location, either the ship or its index, the saucer or its index, the lengths of the missile and asteroid sections of the array, and the index of the first asteroid;

Initialized all of these to inactive.

Start Game (V 0.1)

Provide a function that creates some asteroids and a ship that we can fly around.

Fire Missiles (V 0.2)

Arrange to fire up to N missiles from the ship, with suitable direction and velocity.

That will surely be more than we will do this morning, and by tomorrow this list may change, but it seems to me to have roughly the shape of what needs doing.

Let’s Get Started

I wonder whether I can do some TDD on the object creation. Let’s try it.

    @Test
    fun `initial array creation`() {
        val objects: Array<SpaceObject> = createInitialObjects(6, 26) // number of missiles, number of asteroids
        val mCount = objects.count { it.type == SpaceObjectType.MISSILE}
        assertThat(mCount).isEqualTo(6)
    }

That’s my plan. Should be straightforward.

fun createInitialObjects(missileCount: Int, asteroidCount: Int): Array<SpaceObject> {
    val objects = mutableListOf<SpaceObject>()
    for ( i in 1..missileCount) {
        objects.add(newMissile())
    }
    return objects.toTypedArray()
}

private fun newMissile(): SpaceObject {
    return SpaceObject(SpaceObjectType.MISSILE, 0.0, 0.0, 0.0, 0.0, 0.0, false)
}

That runs green. I’m not sure it’s quite what we want. Let’s push on and guess at what we might want.

    @Test
    fun `initial array creation`() {
        val objects: Array<SpaceObject> = createInitialObjects(6, 26) // number of missiles, number of asteroids
        val mCount = objects.count { it.type == SpaceObjectType.MISSILE}
        assertThat(mCount).isEqualTo(6)
        assertThat(Ship.type).isEqualTo(SpaceObjectType.SHIP)
        assertThat(Ship).isEqualTo(objects[6])
    }

Here, I’m supposing that there will be a global Ship, that it will be a ship, and that it will equal the 6th element of the array.

fun createInitialObjects(missileCount: Int, asteroidCount: Int): Array<SpaceObject> {
    val objects = mutableListOf<SpaceObject>()
    for ( i in 1..missileCount) {
        objects.add(newMissile())
    }
    Ship = newShip()
    objects.add(Ship)
    return objects.toTypedArray()
}

private fun newMissile(): SpaceObject 
    = SpaceObject(SpaceObjectType.MISSILE, 0.0, 0.0, 0.0, 0.0, 0.0, false)

private fun newShip(): SpaceObject 
    = SpaceObject(SpaceObjectType.SHIP, 0.0, 0.0, 0.0, 0.0, 0.0, false)

I think we can let this rest for now. I’ll change the main to call this, and change fireMissile a bit.

private fun fireMissile() {
    controls_fire = false
    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
}

I’m tentatively letting objects 0 and 1 be saucer missiles. The code doesn’t express that idea at all. But anyway #2 is surely a missile. We’ll enhance this before we’re done today, making it more capable if not actually better.

This should allow me to fire a missile … but as it stands I think the ship will be invisible, because we have set everything inactive and we don’t draw inactive things. Let’s create and call a new method. I’ll even test it:

    @Test
    fun `start game makes ship active`() {
        val objects = createInitialObjects(6, 26)
        assertThat(Ship.active).isEqualTo(false)
        startGame()
        assertThat(Ship.active).isEqualTo(true)
    }

That requires this:

fun startGame() {
    Ship.active = true
}

The test should run green. It even does. Play. Ah. Start needs to set the ship to the middle of the screen. Change the test.

    @Test
    fun `start game makes ship active`() {
        val objects = createInitialObjects(6, 26)
        assertThat(Ship.active).isEqualTo(false)
        startGame()
        assertThat(Ship.active).isEqualTo(true)
        assertThat(Ship.x).isEqualTo(Width/2)
        assertThat(Ship.y).isEqualTo(Height/2)
    }

Those globals, Width and Height, do not exist. I’ll add them and use them in the main.

fun startGame() {
    Ship.active = true
    Ship.x = Width/2 + 0.0
    Ship.y = Height/2 + 0.0
}

The + 0.0 converts the values to Double without upsetting Kotlin, which doesn’t believe you can convert integers to double. It’s not wrong.

Curiously, the ship still does not appear. A println tells me that draw is not being called for the ship. Can it be that the Ship is not active? Or not in the array?

I forgot to call startGame in the main! That’ll explain it. The game now correctly fires a missile to the east.

Let’s commit this: initial createInitialObjects and startGame.

A Few Missiles

Let’s go a half-step further and allow firing more than one missile over and over, which is what happens now. I won’t stop them, I’ll just start them, but I should be able to get four of them going.

I’ll write a test for this, with a bit of hackery.

    @Test
    @Test
    fun `can fire four missiles`() {
        spaceObjects = createInitialObjects(6, 26)
        assertThat(activeMissiles()).isEqualTo(0)
        fireMissile()
        assertThat(activeMissiles()).isEqualTo(1)
        fireMissile()
        assertThat(activeMissiles()).isEqualTo(2)
        fireMissile()
        assertThat(activeMissiles()).isEqualTo(3)
        fireMissile()
        assertThat(activeMissiles()).isEqualTo(4)
        fireMissile()
        assertThat(activeMissiles()).isEqualTo(4)
    }

    fun activeMissiles(): Int {
        return spaceObjects.count { it.type == SpaceObjectType.MISSILE && it.active == true}
    }

This will fail getting 1 expecting 2.

expected: 2
 but was: 1

I love it when a plan comes together.

Implement. Change this:

fun fireMissile() {
    controls_fire = false
    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
}

To, um, this:

fun fireMissile() {
    controls_fire = false
    val missile: SpaceObject = availableShipMissile() ?: return
    missile.x = Ship.x+ 50.0
    missile.y = Ship.y
    missile.dx = 15.0
    missile.dy = 0.0
    missile.active = true
}

fun availableShipMissile(): SpaceObject? {
    for ( i in 2..5) {
        if (!spaceObjects[i].active) {
            return spaceObjects[i]
        }
    }
    return null
}

Test is green. In the game, I can fire four missiles.

ship and four square missiles

I am not proud of this code. But it works, so we’ll commit: Ship can fire four missiles once.

Let’s sum up and talk about this code.

Summary

The code here is particularly um well not as um that is .. ok, it’s really not good. What are some things not to like?

The availableShip code knows too much: it might be OK to know that the ship’s missiles start at index 2, but it’s certainly not OK to know how many there are.

We can’t seem to decide whether createInitialObjects unconditionally initializes spaceObjects or not. In the main, it’s used as a function, and in the tests, one time I use it to get a temporary array and once I use it to set the spaceObjects global.

The spaceObjects global name should be capitalized..

I think that globals are all over the system right now. They should be consolidated somewhere, and I’m honestly not sure whether they’re all together now or not.

We probably shouldn’t use globals at all. Maybe we could at least have a single struct that holds everything we need?

The code is generally ragged-feeling and ad hoc. And I think I know why.

Why, Ron?

When I work with objects, almost everything I do goes into an object somewhere. If I create a global value, there’s a pretty serious reason for it. When I create a function, it’s almost invariably a method on some object. I don’t always get the objects just right, and over time, new objects arise, old ones disappear, things move from one object to another … but there are always a number of meaningful objects that serve as collection points for related ideas.

Here, I have no convenient objects to hang ideas on. I have a dumb SpaceObject and an array of those. The SpaceObjects do have fields, but they don’t even have methods to answer simple questions or make changes. Everything is done from “outside” the object. I need to create globals because I have no place to hang things I need to remember.

I’ve created, so far, four files that are accumulating code ideas: InitialTests, TemplateKt (main), Game, and SpaceObject. The things that are in those files mostly seem to me to belong under those headings, but there’s nothing that stops me from putting a function in one of those that makes no sense. As a possible example, fireMissile is in Game. Why? Because checking the controls is in the Game. Are those good decisions? It’s not clear.

Now the original Asteroids, written in assembler, was essentially one big file, with lots of functions at the top level, and lovely code that branched around to useful locations. We’re not quite that flat but we’re trying to do a version of the code whose only structuring is the breakout of functions.

So what, Ron?

The result is that, because I have no handy standing guidance for where things should go, I am using up critical parts of my brain working out where to put things. Because I have no methods, and can’t have them, I am led to write code that indexes into arrays, and that accesses fields from wherever I happen to be.

The result is raggedy code. I’m juggling one or two more balls than I should be, so I drop one from time to time. I think we’ll find that we can clean up the code, and I’ll start a bit of that next time, but it will get ragged again.

Is there a lesson here?

I think there is a lesson, which is that objects help me a lot. I can’t remember how good I was back in the day when we programmed without objects, with just functions and structures. I may have been better than I am now, from practice if nothing else, and I probably wasn’t as good as I thought I was. And, for sure, today’s tools, especially objects, very clearly help me.

Is there a lesson for you here? I wouldn’t presume to say, but I’d certainly guess that when you get too many balls in the air, your effectiveness will decline, you’ll write more defects, and your code won’t be as clear and neat. And with fewer balls in the air, I’d bet you’ll do better. But you have to decide on your most effective range for juggling ideas, and your own best tools for managing them.

But I’d certainly bet that fewer in the air is better and that therefore, providing hooks on which to hang ideas will pay off.

I’ll try to think of some hooks that are consistent with this all-functions design. I hope I come up with some, because I don’t like this raggedy code.

I hope you’ll join me next time!