If you’re like me, you’re wondering what space invaders eat. The answer will surprise you.

When the invaders get near the bottom of the screen, let’s face it, they’ve come a long way, and they are quite understandably hungry. Among the things they eat are your shields. And stay tuned, because I think in later articles it’s going to get worse.

So far, the invaders don’t have much interesting behavior. They march, they animate, they explode when missiles hit them. That’s about it. When they inevitably get to the bottom area of the screen, however, they begin to encounter the shields, and, well, they eat the shields. They do this, of course, by erasing the bits they encounter.

I’ve been unable to find any videos that show this happening, but we know from the game’s overall documentation that the green of the shields and gunner are caused by green cellophane stuck to the glass. So when a bomb or invader got that low in the original game, it would turn green. That would be easy to emulate (except for the part green part white transition) but the fact is, I don’t want to do that. I’m going to leave them white, for greater contrast.

We can guess from our other collision handling code how this will go: we’ll detect whether the invader’s rectangle overlaps the shield’s, and if it does, whether any bits intersect the shield’s, and if they do, we’ll erase those bits. We might optionally just do the erase, since the checking and then erasing is probably twice the cost of just erasing. No harm done if we erase bits that aren’t there.

I suspect that when this is done, we’ll have behavior in places that it shouldn’t be. In fact, we already have that case with this code:

function Missile:handleShieldHits()
    for i,shield in ipairs(Shields) do
        local hit = self:damageShield(shield)
        if hit then return true end
    end
    return false
end

function Bomb:killsShield()
    for i,shield in ipairs(Shields) do
        local hit = self:damageShield(shield)
        if hit then return true end
    end
    return false
end

This is discernibly duplication, which should be removed. And although these functions are not in the Shield arena, they basically refer to nothing but shields. To make a cleaner place to work, let’s move this stuff over to the Shield tab.

Note that this function is not really an instance method, since it processes all the shields. I’ve got an idea for how to handle that, and it looks like this:

function Shield:checkForShieldDamage(anObject)
    for i,shield in ipairs(Shields) do
        local hit = anObject:damageShield(shield)
        if hit then return true end
    end
    return false 
end

This method doesn’t access self, so it can be called against the Shield class, and that’s how we intend it to be used. Let’s plug it into Missile:

function Missile:update()
    if self.explodeCount ~= 0 or self.v == 0 then return end
    self.pos = self.pos + vec2(0,4)
    Shield:checkForShieldDamage()
    self:handleOffScreen()
end

If my cunning plan has worked, this should just work as written. My record suggests that this may not eventuate.

Right you are. We have to pass in the projectile:

function Missile:update()
    if self.explodeCount ~= 0 or self.v == 0 then return end
    self.pos = self.pos + vec2(0,4)
    Shield:checkForShieldDamage(self)
    self:handleOffScreen()
end

Now it works. We can do the same for the Bomb now:

function Bomb:checkCollisions(army)
    if Shield:checkForShieldDamage(self) or self:killsGunner() or self:killsLine() then
        self:explode(army)
    end
end

Right again. Note that the Bomb makes use of the function’s return flag, and Missile does not. That’s interesting but we’ll not look into that now. Let’s commit: move shield collision handling to Shield.

Now it seems to me that we should be able to call this function from some convenient place in invader processing, to our ultimate advantage. The invaders surely do not have all the protocol they need to deal with this new responsibility but we’ll run into that real soon.

We’re going to need to check every live invader against every shield. Will that be inefficient? Maybe, but we are all about making it work, making it right, making it fast if necessary. We’ll try not to do anything outright silly, but the main thing will be to make it work.

Let’s see what invaders do now.

They don’t do much, but they do have this update:

function Invader:update(motion, army)
    if self.alive then
        self.picture = (self.picture+1)%2
        self:possiblyDropBomb(army)
        self.pos = self.pos + motion
        if self:overEdge() then
            army:invaderOverEdge(self)
        end
        return false
    else
        return true
    end
end

We can surely plug our shield stuff into the alive block.

Forgive me, but I’m going to use the guard clause pattern here:

function Invader:update(motion, army)
    if not self.alive then return true end
    self.picture = (self.picture+1)%2
    self:possiblyDropBomb(army)
    self.pos = self.pos + motion
    if self:overEdge() then
        army:invaderOverEdge(self)
    end
    return false
end

I like that pattern, and my house my rules.

You know what, I’m going to commit that: refactor invader update to guard clause. I still remember not having enough save points yesterday.

Now we can “just” plug in our shield call and see what explodes. Or … just because I’m always up for something odd, let’s see if we can TDD it and drive out the necessary changes with tests.

I kind of went a bit too deep with this, but I wanted to think about how to set it up. Here’s what we have so far:

        _:test("invader eats shield", function()
            local simg = readImage(asset.shield) -- 22x16
            local i1img = readImage(asset.inv11)
            local i2img = readImage(asset.inv12)
            local shield = Shield(simg, vec2(100,100)) -- thru 121,115
            Shields = {shield}
            local iPos = vec2(110,110)
            local invader = Invader(iPos, {i1img,i2img}, nil, nil)
            Shield:checkForShieldDamage(invader)
        end)

That fails wanting this:

14: invader eats shield -- Shields:67: attempt to call a nil value (method 'damageShield')

That’s the first missing invader method. The existing versions of that method are:

function Missile:damageShield(shield)
    local shieldPos = shield.pos
    local hitPos = rectanglesIntersectAt(self.pos,1,4, shieldPos,22,16)
    if hitPos == nil then return false end
    if not shield:hitBits(self.pos,1,4) then return false end
    shield:applyDamage(hitPos, self)
    self.explodeCount = 15
    return true
end

function Bomb:damageShield(shield)
    local shieldPos = shield.pos
    local hitPos = rectanglesIntersectAt(self.pos,3,4, shieldPos,22,16)
    if hitPos == nil then return false end
    if not shield:hitBits(self.pos,3,4) then return false end
    shield:applyDamage(hitPos, self)
    return true
end

Those are mysteriously alike aren’t they? We need to remove that duplication. But for now, let’s copy that pattern. Apparently the rule is to check whether you want to damage the shield, and if you do tell it where and who.

function Invader:damageShield(shield)
    local shieldPos = shield.pos
    local hitPos = rectanglesIntersectAt(self.pos,16,8, shieldPos,22,16)
    if hitPos == nil then return false end
    shield:applyDamage(hitPos, self)
    return true
end

I’ve decided not to check for overlapping bits, just go ahead and deal the damage. That will cause a new failure in our test.

14: invader eats shield -- Shields:17: attempt to call a nil value (method 'shieldHitAdjustment')

Easy, we don’t need to adjust, we eat where we sit.

function Invader:shieldHitAdjustment()
    return vec2(0,0)
end
14: invader eats shield -- Shields:19: attempt to call a nil value (method 'explosionBits')

We need to return our picture. I think the test is done: I need some kind of assert so I won’t be confused later, but the proof will be on the screen.

function Invader:explosionBits()
    return self.picture
end

And the test:

        _:test("invader eats shield", function()
            local simg = readImage(asset.shield) -- 22x16
            local i1img = readImage(asset.inv11) -- 16x8
            local i2img = readImage(asset.inv12)
            local shield = Shield(simg, vec2(100,100)) -- thru 121,115
            Shields = {shield}
            local iPos = vec2(110,110)
            local invader = Invader(iPos, {i1img,i2img}, nil, nil)
            local answer = Shield:checkForShieldDamage(invader)
            assert(answer).is(true)
        end)

Hm, oops:

14: invader eats shield -- Shields:47: attempt to index a number value (local 'source')

I suspect that picture isn’t set up. Let’s assert on that.

            assert(invader.picture).isnt(nil)

That wasn’t it. Let’s look at Shields:47. “And the lord looked and he saw that the code was …”

function Shield:clearFromBitmap(tx,ty, source)
    for x = 1,source.width do
        for y = 1,source.height do
            r,g,b = source:get(x,y)
            if r+g+b > 0 then
                self.img:set(tx+x-1, ty+y-1, 0,0,0,0) 
            end
        end
    end
end

Hm. The message says that source … oh. Picture is the index of the picture, not the picture. We should have:

function Invader:explosionBits()
    return self.sprites[self.picture+1]
end

And I think that’s going to be the wrong picture, but on the screen it may not be obvious. Anyway the test should run now:

I wish I hadn’t thought and written “assert” when I meant expect:

        _:test("invader eats shield", function()
            local simg = readImage(asset.shield) -- 22x16
            local i1img = readImage(asset.inv11) -- 16x8
            local i2img = readImage(asset.inv12)
            local shield = Shield(simg, vec2(100,100)) -- thru 121,115
            Shields = {shield}
            local iPos = vec2(110,110)
            local invader = Invader(iPos, {i1img,i2img}, nil, nil)
            local answer = Shield:checkForShieldDamage(invader)
            _:expect(answer).is(true)
        end)

Test runs. Now I’ll turn off bombs to leave the shields pristine, and watch what happens.

Um, I think I forgot to call my nice new function. Doing that may help:

function Invader:update(motion, army)
    if not self.alive then return true end
    self.picture = (self.picture+1)%2
    self:possiblyDropBomb(army)
    self.pos = self.pos + motion
    Shield:checkForShieldDamage(self)
    if self:overEdge() then
        army:invaderOverEdge(self)
    end
    return false
end

It works. I adjusted the starting position of the invaders by one pixel, so that they’d wipe the top of the shield: they were one pixel below it when they first stepped in. I thought it was too slow, but after a few runs I think it is OK. I do see what seems to me to be occasional slowdowns but I’m not sure yet that all the updates are clocked. We’ll look at that in a moment. First, I’ll turn bombs back on (maybe that should be a switch on screen) and commit: invaders eat shields.

So there’s a feature done. But what about this duplication:

function Invader:damageShield(shield)
    local shieldPos = shield.pos
    local hitPos = rectanglesIntersectAt(self.pos,16,8, shieldPos,22,16)
    if hitPos == nil then return false end
    shield:applyDamage(hitPos, self)
    return true
end

function Bomb:damageShield(shield)
    local shieldPos = shield.pos
    local hitPos = rectanglesIntersectAt(self.pos,3,4, shieldPos,22,16)
    if hitPos == nil then return false end
    if not shield:hitBits(self.pos,3,4) then return false end
    shield:applyDamage(hitPos, self)
    return true
end

function Missile:damageShield(shield)
    local shieldPos = shield.pos
    local hitPos = rectanglesIntersectAt(self.pos,1,4, shieldPos,22,16)
    if hitPos == nil then return false end
    if not shield:hitBits(self.pos,1,4) then return false end
    shield:applyDamage(hitPos, self)
    self.explodeCount = 15
    return true
end

That’s a lot of duplication right there, and while none of those methods is terribly likely to change, the changes are small but important. We consider the projectile size. We know the shield size on the wrong side of the line. We use the projectile size twice except in the invader “projectile”, because we’ve decided not to check the bits individually. Frankly, I think we should probably not do that check at all. I don’t think it’ll make any visible difference. I’ll comment out those two if statements and run the game.

As expected, I can see no difference. We’ll remove that nicety, which will make the three functions even more similar. I did notice that the bombs don’t dig through the shield as readily as they used to. I remember that I changed the rectangular damage a bit. Where was that?

function Shield:clearBombRectangle(tx,ty)
    for x = 1,3 do
        for y = 1,3 do
            self.img:set(tx+x-1, ty+y-1, 0,0,0,0)
        end
    end
end

I suspect that should be parameterized. However, the main thing, I think, is to dig one pixel deeper and one pixel higher, but no wider. We’ll try that:

function Shield:clearBombRectangle(tx,ty)
    for x = 1,3 do
        for y = 0,4 do
            self.img:set(tx+x-1, ty+y-1, 0,0,0,0)
        end
    end
end

That’s better, but I noticed now that the missiles no longer shoot through the shields. Somewhere our tuning has “fixed” that. I’m going to revert my messing about, then revert to this morning’s first version to see if it was today’s work that caused the problem.

Some quick checking tells me that it’s something I did since the last commit. I’ve got “invaders eat shields” checked out now, so let’s look again at that call that I commented out. Could that be it? I thought I put it back and still saw the problem.

The thing is this: in this code:

function Missile:damageShield(shield)
    local shieldPos = shield.pos
    local hitPos = rectanglesIntersectAt(self.pos,1,4, shieldPos,22,16)
    if hitPos == nil then return false end
    if not shield:hitBits(self.pos,1,4) then return false end
    shield:applyDamage(hitPos, self)
    self.explodeCount = 15
    return true
end

The call to hitBits returns true if any bits in the actual image of the projectile intersect actual bits of the shield. If they do not, we treat is as “not hitting yet” and the next update allows the projectile to move further, doing deeper damage. So if we remove those calls, we make it harder for the projectile to do damage. We don’t want that effect.

So we can’t remove duplication by dropping those calls, even if we plan not to use the bitmaps for the invaders eating shields.

We could split the function and remove duplication on at least the first half. Or we could do a trick that I saw Kent Beck do almost two decades ago.

Kent was refactoring some code, as a demo to a class. There were two functions that were nearly alike. Similar to our case here, the one version didn’t need to do one of the things that the other one did. Kent changed the one so that it did the thing anyway, making it worse but still correct. Then he refactored out to remove the duplication he had just created, and in the process, the extra work went away. It was magical.

I can’t promise magic, but I can do the first part, putting the duplication in:

function Invader:damageShield(shield)
    local shieldPos = shield.pos
    local hitPos = rectanglesIntersectAt(self.pos,16,8, shieldPos,22,16)
    if hitPos == nil then return false end
    if not shield:hitBits(self.pos,16,8) then return false end
    shield:applyDamage(hitPos, self)
    return true
end

Now these functions are exactly the same except for their differing width and height values. I’m going to run the thing just to see if the bitmap checking kills us.

I think it’s OK, especially since by the time they get down to shield level, there will presumably be fewer of them. Now to remove the duplication.

I think we want to move the function into Shield. Presently it’s called by Shield:

function Shield:checkForShieldDamage(anObject)
    for i,shield in ipairs(Shields) do
        local hit = anObject:damageShield(shield)
        if hit then return true end
    end
    return false 
end

Let’s just call it on ourself, passing in the object we have in hand:

function Shield:damage(anObject)
    local hitPos = rectanglesIntersectAt(anObject.pos,3,4, self.pos,22,16)
    if hitPos == nil then return false end
    if not self:hitBits(anObject.pos,3,4) then return false end
    self:applyDamage(hitPos, anObject)
    return true
end

To be called by:

function Shield:damage(anObject)
    local hitPos = rectanglesIntersectAt(anObject.pos,3,4, self.pos,22,16)
    if hitPos == nil then return false end
    if not self:hitBits(anObject.pos,3,4) then return false end
    self:applyDamage(hitPos, anObject)
    return true
end

The problem now is the 3 and 4, which are specific to the bomb that I copied. We need the width and height of anObject. I’m sure they’ll be glad to tell us:

function Shield:damage(anObject)
    local w,h = anObject:bitSize()
    local hitPos = rectanglesIntersectAt(anObject.pos,3,4, self.pos,22,16)
    if hitPos == nil then return false end
    if not self:hitBits(anObject.pos,3,4) then return false end
    self:applyDamage(hitPos, anObject)
    return true
end

This won’t work until things understand that message but that’s easy:

function Invader:bitSize()
    return 16, 8
end

function Bomb:bitSize()
    return 3, 4
end

function Missile:bitsize()
    return 1, 4
end

In for a penny, I’ll remove all the damageShield methods and then see what explodes … or doesn’t. Oh, wait, I didn’t use w and h, did I?

function Shield:damage(anObject)
    local w,h = anObject:bitSize()
    local hitPos = rectanglesIntersectAt(anObject.pos,w,h, self.pos,22,16)
    if hitPos == nil then return false end
    if not self:hitBits(anObject.pos,w,h) then return false end
    self:applyDamage(hitPos, anObject)
    return true
end

I’m not terribly surprised to see this fail, I’ve surely forgotten something. The message is:


Main:86: bad argument #-2 to '__add' (vec2)
stack traceback:
	[C]: in metamethod '__add'
	Main:86: in function 'rectanglesIntersect'
	Main:77: in function 'rectanglesIntersectAt'
	Shields:75: in method 'damage'
	Shields:67: in method 'checkForShieldDamage'
	Invader:30: in method 'update'
	Army:41: in method 'update'
	GameRunner:38: in method 'doUpdate'
	GameRunner:33: in method 'update'
	Main:27: in function 'draw'

The function in question is this:

function rectanglesIntersect(bl1,w1,h1, bl2,w2,h2)
   local tr1 = bl1 + vec2(w1,h1) - vec2(1,1)
   local tr2 = bl2 + vec2(w2,h2) - vec2(1,1) -- <--- 86
   if bl1.y > tr2.y or bl2.y > tr1.y then return false end
   if bl1.x > tr2.x or bl2.x > tr1.x then return false end
   return true
end

Presumably w2,h2 didn’t make a decent vector. Which makes me wonder about my w,h function. I see that in Missile I didn’t capitalize it, which makes me wonder what I got instead. Turns out bitsize is the word size in lua, so we returned 32, nil. I’ll fix that call:

function Missile:bitSize()
    return 1, 4
end

But we’d best rename that function soon. First make it work.

Hm, same error. Now I’m even more confused. A quick check tells me that we’re returning 16,8, which is invader size, which makes sense. Why, then, the fail?

A little more digging tells me that it is bl2 that is the problem: it’s nil.

Ah. Hoist on my own petard. This function is called from our class function:

function Shield:checkForShieldDamage(anObject)
    for i,shield in ipairs(Shields) do
        local hit = self:damage(anObject)
        if hit then return true end
    end
    return false 
end

Self is nil there. We mean shield:

function Shield:checkForShieldDamage(anObject)
    for i,shield in ipairs(Shields) do
        local hit = shield:damage(anObject)
        if hit then return true end
    end
    return false 
end

And everything works. Don’t you just love it when the bug is a simple oversight but takes several minutes to find. Yeah, me too.

Anyway, we’re good. Commit: refactor damage over to shield.

Now, if you’ll forgive me, I’m going to go get lunch. We’re just two hours in and I’m hungry,

Now then …

We’ve got a reasonably nice new feature, with aliens destroying shields as they run into them. I’ll include a video for the people who like those.

eat

All three forms of shield damage are rather nicely packaged into the Shields tab, with triple duplication (triplication?) removed for a better design. The removal of the duplication involved implementing a standard protocol in invaders, bombs, and missiles, to wit:

function Missile:explosionBits()
    return self.explosion
end

function Missile:shieldHitAdjustment()
    return vec2(2,-1)
end

function Missile:bitSize()
    return 1, 4
end

In all cases those methods return either a constant or very near to it. Back in the Smalltalk days it always seemed that the good designs often worked like this: you’d follow the chain down and down and at the bottom someone would return something trivial. I think it’s a sign of a nice design.

There were some stumbles, notably that Shield function that isn’t really a method but was using self. That whole step troubles me, and I have an idea about it: perhaps too clever an idea. We’ll try it and see.

We pulled Lua’s ability to return more than one result from a function out of our um pocket, and in my view it made the code easier to read, since the caller really needed w and h broken out.

Overall, despite confusion over that self, things went well and we made progress every few minutes. I’m happy with the day.

Over the past few days, we’ve been trying to add features and in the same go, leave the code a better place than we found it. I feel that we’ve been pretty successful with that.

We still have a few features to add, such as destroying the gunner if the invaders get down there, and the saucer, and some tuning, and and and. We’re going to wish we could fire sideways when the invaders get down to us, but we can’t.

We’ll do those features, and we’ll keep looking at how to make the code a better place to live. There’s work that can be done there as well.

So far, three (3! (no, not three factorial, plain three)) people have admitted reading these articles. I plan to keep writing them until I’m tired of them in any case, but if you do read them and enjoy them, please let me know. If you don’t enjoy them, don’t tell me that, but tell me what you’d prefer that I write about. If you’re not reading them, don’t tell me.

I’ll see the three of you next time!

Invaders.zip