Let’s make missiles explode, and continue improving this design.

In the original game, missiles seem to explode in at least two cases: when you shoot your own shield, and when the missile flies past all the invaders toward the top of the screen. In the case of hitting the shield, the missile does damage. Exploding in outer space, not so much.

In the present design, or, as it should properly be called, in the present situation, the Missile is represented by a global table named, cleverly, Missile, which is drawn by Main, and managed by the Army:

function drawMissile()
    if Missile.v == 0 then return end
    rect(Missile.pos.x, Missile.pos.y, 2,4)
    TheArmy:processMissile(Missile)
end

function Army:processMissile(aMissile)
    if aMissile.pos.y <= self:saucerTop() then
        self:checkForKill(aMissile)
    else
        Missile.v = 0
    end
end

function Army:checkForKill(missile)
    for i, invader in ipairs(self.invaders) do
        if invader:killedBy(missile) then
            missile.v = 0
            return
        end
    end
    for b,bomb in pairs(self.bombs) do
        if bomb:killedBy(missile) then
            missile.v = 0
            bomb:explode(self)
            return
        end
    end
end

There’s more futzing around than that. The army updates the missile’s position during update, and both bombs and invaders know how to detect that they’ve been killed by the missile. Five different tabs refer to the missile, although two of them, bomb and invader, only refer to a missile that they’ve been passed as a parameter. They both know the dimensions of the missile, however, and they use different values.

Arguably, we should be happy with those different values. We can think of them as a sort of “kill radius”, but their real purpose, if they have one, is to adjust the chance of a missile and a bomb, or a missile and an invader, colliding.

We could proceed from where we are on this. We do have detection of the missile being above the invaders, so we could trigger the explosion effect and time it out similarly to how we do it elsewhere. And when the army checks to see if the missile has hit a bomb or an invader, it could also check for shield collision.

On the positive side, we could argue that those two places are already dealing with the missile, and any new functionality will be hung below those two points, so little harm is done. On the negative side, it makes the code a bit messier than it is now.

Because we have refreshed our commitment to cleaning up this campground, let’s see if we can quickly turn the missile into an object. If we can, it should make things at least incrementally more clear and generally better.

I would note at this point that there is only one missile, by the nature of the game, and that it should belong to the Gunner, since he fires it. So we’ll move in that direction.

First, a blank Missile class:

-- Missile
-- RJ 20200901

Missile = class()

function Missile:init(x)
end

function Missile:draw()
end

function Missile:update()
end

I went out on a limb and assumed there would be an update method.

How’s this thing created? In Main setup:

function setup()
    Runner = GameRunner()
    runTests()
    createShields()
    createBombTypes()
    setupGunner()
    Missile = {v=0, p=vec2(0,0)}
     ...

Let’s let the Gunner do that. What’s setupGunner doing?

function setupGunner()
    Gunner = {pos=vec2(104,32),alive=true,count=0,explode=explode}
    GunMove = vec2(0,0)
    GunnerEx1 = readImage(asset.playx1)
    GunnerEx2 = readImage(asset.playx2)
end

Hmm, I had forgotten that the Gunner isn’t an object either. No matter for now, we’ll still give him what will become the only pointer to the missile:

function setupGunner()
    local missile = {v=0, p=vec2(0,0)}
    Gunner = {pos=vec2(104,32),alive=true,count=0,explode=explode,missile=missile}
    GunMove = vec2(0,0)
    GunnerEx1 = readImage(asset.playx1)
    GunnerEx2 = readImage(asset.playx2)
end

Now the global should be dead and the world should be broken.

Well, curiously enough, that’s not the case. Missiles seem to fire just fine. Why is that? That’ll be interesting. Check fireMissile.

function fireMissile()
    if Missile.v == 0 then
        Missile.pos = Gunner.pos + vec2(7,5)
        Missile.v = 1
    end
end

That might just init enough into a table to make it work, but there has to be more to it than that. I’ll just continue chasing the global to get rid of it. This is like TDD but with really weird tests.

Ah. I realized quickly that there is only one assignment to Missile and it is this:

Missile = class()

The game doesn’t care what kind of table it gets, so long as it can jam stuff into it. We’re flying the whole class around, fun. I’ll go through the process I intended anyway, changing all the references:

function drawMissile()
    local missile = Gunner.missile
    if missile.v == 0 then return end
    rect(missile.pos.x, missile.pos.y, 2,4)
    TheArmy:processMissile(missile)
end

That will fix that. But then there’s this:

function Army:update()
    updateGunner()
    if Missile.v > 0 then Missile.pos = Missile.pos + vec2(0,4) end
    local continue = true
    while(continue) do
        continue = self:nextInvader():update(self.motion, self)
    end
    for b,bomb in pairs(self.bombs) do
        b:update(self)
    end
end

function updateGunner()
    if Gunner.alive then
        Gunner.pos = Gunner.pos + GunMove + vec2(5*Gravity.x,0) 
        Gunner.pos.x = math.max(math.min(Gunner.pos.x,208),0)
    end
end

We should leave missile updating to the Gunner, it seems to me, and since we have an actual object going, we can do it pretty close to right, like this:

function Missile:update()
    if self.v > 0 then self.pos = self.pos + vec2(0,4) end
end

Then we’ll call it:

function updateGunner()
    self.missile:update()
    if Gunner.alive then
        Gunner.pos = Gunner.pos + GunMove + vec2(5*Gravity.x,0) 
        Gunner.pos.x = math.max(math.min(Gunner.pos.x,208),0)
    end
end

This probably won’t work, but I need a lead for what to change next, so I’ll run.

Yeah, well, can’t say self to Gunner yet.

function updateGunner()
    Gunner.missile:update()
    if Gunner.alive then
        Gunner.pos = Gunner.pos + GunMove + vec2(5*Gravity.x,0) 
        Gunner.pos.x = math.max(math.min(Gunner.pos.x,208),0)
    end
end

I really want this guy to be an object too, but I’m not going to try to change two at once if I don’t have to. The idea is to get in here, get the missile object and get out.

Our call to update fails because the Missile seems not to be a class:

function setupGunner()
    local missile = {v=0, p=vec2(0,0)}
    Gunner = {pos=vec2(104,32),alive=true,count=0,explode=explode,missile=missile}
    GunMove = vec2(0,0)
    GunnerEx1 = readImage(asset.playx1)
    GunnerEx2 = readImage(asset.playx2)
end

Time to make that local be the instance:

function setupGunner()
    local missile = Missile()
    Gunner = {pos=vec2(104,32),alive=true,count=0,explode=explode,missile=missile}
    GunMove = vec2(0,0)
    GunnerEx1 = readImage(asset.playx1)
    GunnerEx2 = readImage(asset.playx2)
end

And fill in Missile:init:

function Missile:init(x)
    self.v = 0
    self.p = vec2(0,0)
end

I really don’t think those values make much sense, but once they’re all inside, we can deal with that. For now, we’re trying to make an object that is compatible with everyone’s expectations. Try to run again.

Error in Gunner:31, and we also have a failing test. No surprise on the latter, it’s surely building a table.

Gunner:31 is the creation method. The Missile tab needs to be left of the Gunner tab, because Codea compiles in order.

No, it’s worse than that. The tests smashed the global Missile. I just commented that out for now.

Game starts up properly, but now firing the missile does nothing.

function fireMissile()
    if Missile.v == 0 then
        Missile.pos = Gunner.pos + vec2(7,5)
        Missile.v = 1
    end
end

We connect that to our new and improved Missile object:

function fireMissile()
    local missile = Gunner.missile
    if missile.v == 0 then
        missile.pos = Gunner.pos + vec2(7,5)
        missile.v = 1
    end
end

I could have saved some editing by assigning to local capital-M missile, but that violates my naming standards, so I did the right thing. For this once.

Now missiles fire, and destroy invaders properly. But if you fire one for a clear miss, it goes offscreen and you can never fire again: the top of screen check is surely not doing the right thing. Our update has just this:

function Missile:update()
    if self.v > 0 then self.pos = self.pos + vec2(0,4) end
end

Where’s the check for running away?

function Army:processMissile(aMissile)
    if aMissile.pos.y <= self:saucerTop() then
        self:checkForKill(aMissile)
    else
        Missile.v = 0
    end
end

Note that this code has a bug in it. It should be referring to the parameter aMissile, but it’s hammering the global, which is now our class definition. With that fixed, I think the current problem may be fixed:

function Army:processMissile(aMissile)
    if aMissile.pos.y <= self:saucerTop() then
        self:checkForKill(aMissile)
    else
        aMissile.v = 0
    end
end

And it is. The game appears to play correctly.

Also, somewhat curiously, the tests run without the code that created the mistaken global that killed our class:

        _:before(function()
            --Missile = {v=0, p=vec2(0,0)}
            createBombTypes()
            Line = image(1,1)
        end)

That’s the only reference to the string “missile” in the tests, so I’m going to delete it and back away slowly. What we’ve learned, of course, is that we don’t have any useful tests for missiles, but that’s not as big a concern as if we had one that was broken.

Or maybe it’s really a bigger concern. Anyway, the game appears to be working correctly.

Commit: Missile class, first release.

Let’s see about getting an explosion up at the top of the missile’s travel. We’ll need to read in the explosion. I think we might as well do that on the Missile init, as it will happen only once.

function Missile:init(x)
    self.v = 0
    self.p = vec2(0,0)
    self.explosion = readImage(asset.player_shot_exploding)
end

Now let’s move the check for off-screen over to the missile: the question properly belongs to it.

That was actually done in Army:

function Army:processMissile(aMissile)
    if aMissile.pos.y <= self:saucerTop() then
        self:checkForKill(aMissile)
    else
        aMissile.v = 0
    end
end

Let’s just leave the checking for kill bit (if the missile is alive) and not the other bit:

function Army:processMissile(aMissile)
    if aMissile.v > 0 and aMissile.pos.y <= self:saucerTop() then
        self:checkForKill(aMissile)
    end
end

This belongs elsewhere, but we’re implementing a feature now, not refactoring, and we’ll try to keep ourselves from multi-tasking.

We do need to do the check but now we can do it in update, which is presently:

function Missile:update()
    if self.v > 0 then self.pos = self.pos + vec2(0,4) end
end

First we do this:

function Missile:update()
    if self.v > 0 then 
        self.pos = self.pos + vec2(0,4)
        if self.pos.y > TheArmy:saucerTop() then
            self.v = 0
        end
    end
end

That stops the missile as before, and lets us fire another. Now to explode. Let’s look and see how people do that.

It seems like the done thing is to have a field explodeCount and use that to know to display the explosion and then, when it counts down, to remove oneself from the field. I expect this will take us a cut or two to get just right: we have people checking the missile’s v field right now and that’ll all have to change. They should be telling the missile what they want anyway.

First we need to move draw to the right place. Yes, that’s refactoring, but I see no choice. In setup:

function drawMissile()
    local missile = Gunner.missile
    if missile.v == 0 then return end
    rect(missile.pos.x, missile.pos.y, 2,4)
    TheArmy:processMissile(missile)
end

We’ll just rip this out and send draw:

function drawMissile()
    Gunner.missile:draw()
end

And then:

function Missile:draw()
    if self.v == 0 then return end
    rect(self.pos.x, self.pos.y, 2,4)
    TheArmy:processMissile(self)
end

I expect this to work, and it does. Now for the exploding bit. I expect this to get intricate, and it does. I did this in one try, no clue yet if it works:

function Missile:draw()
    if self.v == 0 then return end
    if self.explodeCount > 0 then
        sprite(self.explosion, self.pos.x, self.pos.y)
        self.explodeCount = self.explodeCount - 1
        if self.explodeCount == 0 then
            self.v =  0
        end
    else
        rect(self.pos.x, self.pos.y, 2,4)
    end
    if self.v > 0 then TheArmy:processMissile(self) end
end

function Missile:update()
    if self.explodeCount == 0 and self.v > 0 then 
        self.pos = self.pos + vec2(0,4)
        if self.pos.y > TheArmy:saucerTop() then
            self.explodeCount = 15
        end
    end
end

I expect trouble but will try it. Yeah:

Missile:13: attempt to compare number with nil
stack traceback:
	Missile:13: in method 'draw'
	Main:52: in function 'drawMissile'
	Main:35: in function 'draw'

Got to init explodeCount in init. And it works:

missile explodes

The explosion is off-center, of course, because of zero origins. Let’s leave it exploding above the missile position, but move it left by 4 pixels:

function Missile:draw()
    if self.v == 0 then return end
    if self.explodeCount > 0 then
        sprite(self.explosion, self.pos.x - 4, self.pos.y)
        self.explodeCount = self.explodeCount - 1
        if self.explodeCount == 0 then
            self.v =  0
        end
    else
        rect(self.pos.x, self.pos.y, 2,4)
    end
    if self.v > 0 then TheArmy:processMissile(self) end
end

That works better:

missle centered

This feature works. Commit: missile explodes above invaders.

We can ship this. The game is better than it was, and the code is better than it was. But let’s refactor a bit more.

Here’s Main draw:

function draw()
    pushMatrix()
    pushStyle()
    noSmooth()
    rectMode(CORNER)
    spriteMode(CORNER)
    background(40, 40, 50)
    showTests()
    stroke(255)
    fill(255)
    scale(4) -- makes the screen 1366/4 x 1024/4
    translate(WIDTH/8-112,0)
    fill(255)
    drawGrid()
    Runner:draw()
    drawGunner()
    drawMissile()
    drawShields()
    drawStatus()
    popStyle()
    popMatrix()
    Runner:update()
end

I hate that method. It’s all line by line and it’s not clear what belongs to whom. But for now, we have a GameRunner object, called Runner. Let’s move all that drawing below it inside:

function GameRunner:draw()
    TheArmy:draw()
    drawGunner()
    drawMissile()
    drawShields()
    drawStatus()
end

That seems as reasonable as we could imagine, at least until we get more objects up in this thing. This:

function drawMissile()
    Gunner.missile:draw()
end

Can be inlined into Runner:

function GameRunner:draw()
    TheArmy:draw()
    drawGunner()
    Gunner.missile:draw()
    drawShields()
    drawStatus()
end

But we seem to be committed to the Gunner managing the missile. So that properly belongs to drawing the Gunner. So let’s put it there instead:

function drawGunner()
    pushMatrix()
    pushStyle()
    tint(0,255,0)
    Gunner.missile:draw()
    if Gunner.alive then
        sprite(asset.play,Gunner.pos.x,Gunner.pos.y)
    else
        if Gunner.count > 210 then
            local c = Gunner.count//8
            if c%2 == 0 then
                sprite(GunnerEx1, Gunner.pos.x, Gunner.pos.y)
            else
                sprite(GunnerEx2, Gunner.pos.x, Gunner.pos.y)
            end
        end
        Gunner.count = Gunner.count - 1
        if Gunner.count <= 0 then
            if Lives > 0 then
                Lives = Lives -1
                if Lives > 0 then
                    Gunner.alive = true
                end
            end
        end
    end
    popStyle()
    popMatrix()
end

Wow, that’s an exciting function, isn’t it? The issue is that it’s toggling between the two different explosion images that the gunner has, and doing it in drawing time rather than updating time. It might be better to do that in updating time, but right now we mostly handle these things in draw. I suspect that will give us timing problems on slower processors.

But we are here right now just to improve where we draw the missile, and this looks right to me.

A weird thing has just happened. The missile explosion above the saucer line has turned green. We’ve certainly changed the order of things, but this is saying to me that someone has left a green tint where they shouldn’t have.

Yes, it’s right at the top of the drawGunner. We should move that inside gunner’s draw method, if it had one. For now, we’ll just push it down below processing the missile. That works.

One more thing. Missiles in the real game tend to explode in red at top of screen, because there’s red cellophane up there. Let’s do that just for fun:

function Missile:draw()
    if self.v == 0 then return end
    pushStyle()
    if self.explodeCount > 0 then
        tint(255,0,0)
        sprite(self.explosion, self.pos.x - 4, self.pos.y)
        self.explodeCount = self.explodeCount - 1
        if self.explodeCount == 0 then
            self.v =  0
        end
    else
        rect(self.pos.x, self.pos.y, 2,4)
    end
    popStyle()
    if self.v > 0 then TheArmy:processMissile(self) end
end

red missiles

That looks pretty nice.

HOWEVER, we’re getting a bit loose here. We were improving the code, then suddenly decided to add a tiny feature. We’re not very clever over here chez moi, and we try to juggle no more than one ball at a time. Your balls may vary. Um. Anyway, we should settle down a bit. Things are working.

Commit: move drawing to Runner. Red missile explosion.

These commits are typically hitting three or four tabs. That’s not unusual when you’re moving code around, but it does mean that we should be careful. Let’s take one more look at Main’s draw and see if we should improve it:

function draw()
    pushMatrix()
    pushStyle()
    noSmooth()
    rectMode(CORNER)
    spriteMode(CORNER)
    background(40, 40, 50)
    showTests()
    stroke(255)
    fill(255)
    scale(4) -- makes the screen 1366/4 x 1024/4
    translate(WIDTH/8-112,0)
    fill(255)
    drawGrid()
    Runner:draw()
    popStyle()
    popMatrix()
    Runner:update()
end

OK. Some of that is about the game, some is about the tests. Let’s keep the pushes and pops for safety but move some of the detailed stuff into the GameRunner, who really cares about corners and such.

function draw()
    pushMatrix()
    pushStyle()
    background(40, 40, 50)
    showTests()
    drawGrid()
    Runner:draw()
    popStyle()
    popMatrix()
    Runner:update()
end

function GameRunner:draw()
    pushMatrix()
    pushStyle()
    noSmooth()
    rectMode(CORNER)
    spriteMode(CORNER)
    stroke(255)
    fill(255)
    scale(4) -- makes the screen 1366/4 x 1024/4
    translate(WIDTH/8-112,0)
    TheArmy:draw()
    drawGunner()
    drawShields()
    drawStatus()
    popStyle()
    popMatrix()
end

The drawGrid is largely useless now, so I’ll remove that entirely.

Everything plays fine. Commit: move game-related drawing status to GameRunner.

I think we’ve done enough good for the morning. Let’s retrospect.

Retrospective

(I was tired of saying “Summing Up”.)

Today has gone very nicely. We’re still low on TDD style, but our lack of tests hasn’t hurt us lately. Perhaps we should look at the tests we have and see whether there’s more we’d like to do.

We added a new feature (and a half) with a red explosion when the missile flies past all the invaders. To do that, instead of just hammering it in, we created a new class, Missile, and used it as a platform for the missile and its new capability. We refactored code from the Main tab and the Gunner tab over into the missile, and made the world a bit nicer in so doing.

We then improved the Main tab’s draw function substantially, moving all the game-related setup into the GameRunner. In the course of the morning, we touched at least these tabs:

  • Main
  • GameRunner
  • Army
  • Gunner
  • Missile *Tests

I don’t think we touched Invader, Bomb, or Shields. So we made some fairly wide-ranging improvements, but they were all done in very small steps.

I did step into a trap when I implemented the red color right in the middle of a refactoring, but the trap didn’t spring and I got away with it. One has to use judgment in these things, of course. Sometimes, you’re right there and you know you can do a thing that you know has to be done, so you do it. But other times, same situation, and you already have enough on your mind, so the best thing is to wait. This one was pretty simple.

However, I didn’t really pause and think about whether doing it was a good idea. I just jumped at the bait. That’s perhaps not ideal. I do have pretty good reflexive experience from over a half-century of programming, but as you’ve seen if you’ve followed my articles, sometimes I jump for the bait and fall into a deep hole. It’s best to be at least a bit thoughtful.

But I’m not going to beat myself up, that doesn’t help. I’m happy with how the morning has gone and ready for a bite of lunch.

See you next time!

Invaders.zip