Let’s damage shields from below, and try to continue small steps toward ‘better’, whatever we mean by that.

If you mistakenly fire your missile while under a shield, your missile is supposed to blow a hole in it. This seems entirely fair. My guess is that this decision should be made at or near the missile update:

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

Seems like we want to put the check for this right before or after the check for saucer top. Before makes more sense to me, because it’s earlier in time. It won’t matter to the computer, but the sequence makes sense to me. Let’s refactor a bit. First, I don’t like nested if statements, and I do like guard clauses, so let’s convert to the guard clause pattern:

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

Now we have operational code and conditional code in the same method. Let’s refactor again, this time to the composed method pattern:

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

function Missile:handleOffScreen()
    if self.pos.y > TheArmy:saucerTop() then
        self.explodeCount = 15
    end
end

Nice. It’s pretty clear now where our shield handling code should be … but there’s an issue. Let’s type in our intention first, and then discuss it:

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

A shield hit and an offscreen event can’t occur at the same time. In principle we should deal with making sure that we only call the off screen function if the shield has not been hit. In practice, the condition in handleOffScreen should manage the situation just fine. But it’s a bit weird and we’ll try to keep it in mind.

Now we need to write handleShieldHits. Sooner or later you always get to the tricky bit. First, let’s review how bombs deal with this situation. It turns out not to be very intricate, but it is a few levels deep:

function Bomb:update(army, increment)
    if self.explodeCount > 0 then return end
    self.cycle = (self.cycle +1)%3
    if self.cycle ~= 0 then return end
    self.pos.y = self.pos.y - (increment or 4)
    self.shape = (self.shape + 1)%4
    self:checkCollisions(army)
end

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

function Bomb:explode(army)
    self.pos = self.pos - vec2(2,3)
    self.army = army
    self.explodeCount = 15
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

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

We probably want something similar to killsShield and damageShield and applyDamage for our lack of missile control. Note that applyDamage is a Shield function, as is hitBits, and we’ll certainly want to use those if we can.

The code above is a bit complicated because we wanted to check only as many shields as we had to for collisions, since a given bomb can only damage one thing. Thus the early exit inside killsShield. Let’s follow the pattern, with a copy and paste approach.

Yes, that’s bad, because it creates duplication. It’s also good, because it creates duplication, and if we can figure out how to remove it, we’ll improve the whole program. So we’re going in.

I rather hate this code and am having trouble getting myself to cut and paste the first bit. Let’s see if we can do better, and if we can, see whether we can improve the bomb-shield interaction. In any case we’ll have some duplication further down, where the cut and paste isn’t quite so offensive.

If it doesn’t work, I’ll revert. Therefore commit: preparatory refactoring for missile-shield collision.

Now then. The bomb code above loops over the shields and asks questions and then based on answers it inflicts damage on them. That’s not as good as it might be. We’ll try to do better. However, there is no general structure for processing the shield group, so for now we’ll do the loop ourselves. I’m feeling the need for an object here.

How about this:

function Missile:handleShieldHits()
    for i,shield in ipairs(Shields) do
        local hitShield = shield:attack(self.pos, 1,2)
        if hitShield then
            return
        end
    end
end

We’ll attack the shield, providing the location and size of the attack (our missile) and it’ll return true if it was damaged (and it will damage itself). If it returns true then we need to …

Oh hell. It’s not that simple. We need to apply damage using the shield’s explosion bitmap. Hold on, it’s time to go for chai. Maybe the ride will clear my head and give me an idea.

OK, well, the ride didn’t help. Maybe the caffeine in the chai will.

Quarters

I want to digress here for a moment to talk about quarters. The coins. They’re just shy of 25mm, for the folks who don’t speak quarters.

I like to tip the barista, if that’s what you call the dude or dudette who makes your chai, typically a dollar for my five dollar drink. And I’ve taken to tipping in quarters, because I have a jar full of coins, and since I try never to pay cash for anything, I don’t usually have enough one dollar bills.

So, every day, I need four quarters out of the coin jar. How do you get four quarters out of a coin jar? I’m glad you asked.

Take the jar and shake it up and down vigorously for a few seconds. This works best if you put your hand over the top of the jar. If the reason for this is not obvious, try it once without and then you’ll understand.

The shaking will bring quarters to the top of the coins in the jar and you just pick them out and go on your way.

No, I’m serious. It works. I even think I know why it works.

But I digress.

Shield Damage

Where were we? Oh yes, missiles damaging shields. There’s no doubt in my mind that I can replicate the bomb-shield interaction, at the cost of some duplication, and I’d bet that if we do that we can remove at least some of the duplication to our advantage. But the design we have feels wrong to me, and I really don’t want to replicate a poor design. What to do?

As I start this paragraph, here’s my plan: First, we’ll think about how this problem could be solved nicely. Then, if that solution seems incredibly easy, we’ll try it. If it seems rather easy, we might try TDDing it. If it seems any harder than that, we’ll paste in the old solution and work from there to improve the code.

So, the thinking.

We have a shield, with a containing rectangle and some bits representing the actual state of the shield. We have a projectile with a defining rectangle and an explosion pattern if it explodes. We want to detect whether our projectile’s rectangle strikes any actual bits in the shield, and if it does, we want to destroy the bits where our defining rectangle is, and any bits hit by the bits of the explosion. If the destruction happens, we want to display the explosion and terminate the action of the projectile.

There are four shields, and the operations of checking inside rectangle, checking whether bits are hit, and removing bits are increasingly expensive. Therefore we would like to do as little processing as possible.

The logic really seems to be this:

eachShield ->
inRectangle -> 
hitsBits -> 
clearBits -> 
stop projectile; show explosion; quit checking.

Grr. That’s really pretty much what the existing code does. I’m not at all sure I can type in something much better. I’ll review the existing code … no, I won’t even do that. I’m going to cut and paste it and review that as I go.

Does this seem waffly to you? If you think about most reading you do about programming, it surely does. If you think about your own thoughts building something you don’t know quite how to build, I suspect this will have some familiarity. Of course, if you’re smart enough to instantly know the right answers, please just send me a link to your blog, I’d like to learn that.

Here we go.

Cut and Paste Solution

Cut/paste the first method:

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

Now the second:

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

I changed the values of the hit rectangle from 3,4 to 1,2 because the missile is … oh. FOUR pixels high. 1,4 is correct. Clearly the object should know its size instead of pasting these numbers all around. This, boys and girls, is why cutting and pasting code is not the best idea.

Now I think it’s fair to imagine that the shield’s hitBits function is going to work. I am not sure how it’s going to handle apply damage but again it seems reasonable that it will nearly work. I’m going to run it just to see what it does.

stopped

The missile stops when it hits the shield, because the game crashes with this message:

Shields:36: bad argument #1 to 'get' (number has no integer representation)
stack traceback:
	[C]: in method 'get'
	Shields:36: in method 'hasBit'
	Shields:25: in method 'hitBits'
	Missile:54: in method 'damageShield'
	Missile:44: in method 'handleShieldHits'
	Missile:32: in method 'update'
	Gunner:69: in function 'updateGunner'
	Army:38: in method 'update'
	GameRunner:38: in method 'doUpdate'
	GameRunner:33: in method 'update'
	Main:27: in function 'draw'

I’ve never seen that message before that I recall, but clearly we’ve asked an image to fetch a non-integer bit. Let’s look at hitBits to see what might be going on:

function Shield:hitBits(pos,w,h)
    for x = pos.x,pos.x+w-1 do
        for y = pos.y,pos.y+h-1 do
            local has = self:hasBit(x,y)
            if has then return true end
        end
    end
    return false
end

function Shield:hasBit(x,y)
    local xx = x - self.pos.x + 1
    local yy = y - self.pos.y + 1
    if xx < 1 or xx > 22 or yy < 1 or yy > 16 then return false end
    local r,g,b = self.img:get(xx,yy)
    return r + b + g > 0
end

So xx or yy was not an integer. That’s certainly interesting. I wonder what they were. If they weren’t integer, then pos.x or pos.y wasn’t an integer either. Could we be getting fractional positions on our missile? I don’t see how. I hate to debug something like this. Let’s first force these guys to integer:

function Shield:hasBit(x,y)
    local xx = (x - self.pos.x + 1)//1
    local yy = (y - self.pos.y + 1)//1
    if xx < 1 or xx > 22 or yy < 1 or yy > 16 then return false end
    local r,g,b = self.img:get(xx,yy)
    return r + b + g > 0
end

Interesting. Now we get this message:

Shields:54: bad argument #1 to 'set' (number has no integer representation)
stack traceback:
	[C]: in method 'set'
	Shields:54: in method 'clearBombRectangle'
	Shields:18: in method 'applyDamage'
	Missile:55: in method 'damageShield'
	Missile:44: in method 'handleShieldHits'
	Missile:32: in method 'update'
	Gunner:69: in function 'updateGunner'
	Army:38: in method 'update'
	GameRunner:38: in method 'doUpdate'
	GameRunner:33: in method 'update'
	Main:27: in function 'draw'

Now we’re getting the error in set. I gotta know. I’m going to put an assert in the hasBit function.

function Shield:hasBit(x,y)
    local xxx = x - self.pos.x + 1
    local yyy = y - self.pos.y + 1
    local xx = xxx//1
    local yy = yyy//1
    if xx ~= xxx or yy  ~= yyy then
        print(xx,xxx, yy,yyy)
        assert(false)
    end
    if xx < 1 or xx > 22 or yy < 1 or yy > 16 then return false end
    local r,g,b = self.img:get(xx,yy)
    return r + b + g > 0
end

That prints this:

20.0	20.589672021568	2.0	2.0

Is this calling for some TDD-style tests? It might be. I still don’t know quite what’s going on. Is x wrong, or self.pos.x?

function Shield:hasBit(x,y)
    if x ~= x//1 then
        print("x", x)
        assert(false)
    end
    if self.pos.x ~= self.pos.x//1 then
        print("pos", self.pos.x)
        assert(false)
    end
    ...

This prints

x	98.984486557543

We’ve somehow managed to call in here with a wild fractional value of x. I am sore tempted to revert. But I am even more tempted to figure it out.

function Shield:hitBits(pos,w,h)
    if pos.x ~= pos.x//1 then
        print("hitBits", pos,w,h)
        assert(false)
    end
    for x = pos.x,pos.x+w-1 do
        for y = pos.y,pos.y+h-1 do
            local has = self:hasBit(x,y)
            if has then return true end
        end
    end
    return false
end

This says

hitBits	(98.342675, 49.000000)	1	4

So we have here a pos vector of (98.mumble, 49). That’s called from here:

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)
    return true
end

So the missile position’s x value isn’t an integer. Why not? Well now I’m glad you asked me that question. The missile’s x position is this:

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

So the gunner’s position isn’t an integer. Of course it isn’t because of this:

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

That gravity code can produce a float, and generally will. Should we force the Gunner position to an integer? I think we should. We should probably round it:

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)
        Gunner.pos.x = math.floor(Gunner.pos.x + 0.5)
    end
end

I’ve also left the protection in the getter, which seems prudent. We’ll talk about that in a moment if I can get off this hanging update.

tear

The missile now rips a large hole in the shield and continues upward. No big surprise: we’re not killing it when it collides. I’m not entirely sure how it got such a big hole but we’ll deal with that shortly.

In addition, now it takes a substantial tipping of the iPad to make the gunner slide from side to side, since we’re not accepting fractional adjustments to his position. I think that’s probably OK, though it might mean we should adjust the motion differently. I rather liked the fact that you could tilt more and move him faster. Anyway it’s working as one would expect.

I think I’ll commit this as “missile tears through shield” just to provide another save point.

Now about the end play. Here’s our code now:

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)
    return true
end

Well, we’re not stopping the missile, that’s for sure. We might do it here or possibly it should be done wherever the return of true is handled. I suspect here. And the damage is surely not right, at least I don’t see how it could be. First to stop the missile:

All we have to do to make the missile explode is set its explodeCount. That’s weird but first we’ll do that:

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

nice holes

That actually stops the missile, runs the explosion (in red), and makes a fairly reasonable hole. Let’s look at how the applyDamage works:

function Shield:applyDamage(hitPos, bomb)
    local relativePos = hitPos - self.pos - vec2(2,1)
    self:clearBombRectangle(relativePos.x, relativePos.y)
    self:clearFromBitmap(relativePos.x, relativePos.y, BombExplosion)
end

Well, it’s using the wrong explosion, because it uses the literal BombExplosion. Let’s make it ask the damaging object for its explosion:

function Bomb:explosionBits()
    return BombExplosion
end

function Missile:explosionBits()
    return self.explosion
end

function Shield:applyDamage(hitPos, bomb)
    local relativePos = hitPos - self.pos - vec2(2,1)
    self:clearBombRectangle(relativePos.x, relativePos.y)
    self:clearFromBitmap(relativePos.x, relativePos.y, bomb:explosionBits())
end

That works, and now we get smaller damage down below, which is consistent with the game, I think:

Clearly the clearBombRectangle can’t be quite right. Let’s look at it:

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

Now I vaguely recall that there was a downward adjustment of some kind in the clearing, so we may not be as done as we think, but let’s ask the projectile for its clearanceRectangle, as we did with its bitmap. Let’s have it return x1,xn, y1,yn.

Another glance at applyDamage shows us adjusting the damage downward. In the case of the missile hitting the shield, we’ll want that to be upward, if anything. Let’s change applyDamage to accept only the projectile as a parameter, and get all the necessary info from it.

This will break everything but we’ll quickly find the spots. First the calls:

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(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(self)
    self.explodeCount = 15
    return true
end

Then the inner guys:

function Shield:clearBombRectangle(projectile)
    x1,xn,y1,yn = projectile:clearArea() -- bitmap relative
    for x = x1,xn do
        for y = y1,yn do
            self.img:set(x-1, y-1, 0,0,0,0)
        end
    end
end

This can’t be quite right. The projectile can give real or local coordinates for the area it wants to clear, but it can’t provide coordinates relative to the shield. So let’s do the clearRectangle from Bomb and make it work.

Nope, I’m confused. Back to the save point. I wish I’d done another. But revert is the right thing to do.

OK, where were we? Or more to the point, where were we? We’re at the tear-through point. First, stop the missile:

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 missile stops on hitting the shield and we get credible damage. Commit: missile stops after hitting shield.

It’s like when you’re fighting a boss: you make lots of save points.

OK, change to use the right bitmap:

function Shield:applyDamage(hitPos, bomb)
    local relativePos = hitPos - self.pos - vec2(2,1)
    self:clearBombRectangle(relativePos.x, relativePos.y)
    self:clearFromBitmap(relativePos.x, relativePos.y, bomb:explosionBits())
end

function Bomb:explosionBits()
    return BombExplosion
end

function Missile:explosionBits()
    return self.explosion
end

This uses the correct bitmap to destroy bits. It looks pretty good. Commit: use correct shield-destroying bitmaps.

Now our concern is really this:

function Shield:applyDamage(hitPos, bomb)
    local relativePos = hitPos - self.pos - vec2(2,1)
    self:clearBombRectangle(relativePos.x, relativePos.y)
    self:clearFromBitmap(relativePos.x, relativePos.y, bomb:explosionBits())
end

The relativePos there is adjusted by that vec2, which adjusts the position of the explosion and rectangle clearing from the position of the projectile. That value was fiddled until the bomb to shield explosions looked right. Let’s just fetch it from the projectile:

function Shield:applyDamage(hitPos, bomb)
    local relativePos = hitPos - self.pos - bomb:shieldHitAdjustment()
    self:clearBombRectangle(relativePos.x, relativePos.y)
    self:clearFromBitmap(relativePos.x, relativePos.y, bomb:explosionBits())
end

function Bomb:shieldHitAdjustment()
    return vec2(2,1)
end

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

I guessed at the missile value. I think it’s centered OK but it should do damage a bit deeper into the shield. I’ll run to see if I like it:

No, it’s offset to the right. The 2 was important.

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

This looks pretty good. Here’s the effect of one shot at the shield:

good

Commit: missile damages shield nicely. projectile returns adjustment.

I’m going to call a break at this point, it’s about time for lunch. We might want to change the way that the size of the cleared rectangle is determined, which might make the missile-shield collision look better. A bit of fiddling before I go makes me want to tweak that value a bit.

Anyway, see you after lunch!

After Lunch

As promised.

The rectangle-clearing code is this:

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

So this clears a 5x5 square at the coordinates tx,ty. Those are already offset by the bomb position adjustment, which centers the effect and nudges it downward for bombs and upward for missiles. I think it looks OK on screen, so will leave it for now. In principle we could fetch the values from the projectile, but for now I see no point in that.

Update: after some gameplay, changed my mind, adjusted it from 0-4 to 1-3 for now:

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

The holes looked too rectangular before, now they look more like damage.

I do want to fix the missile effect being red when the missile hits the shield. In the real game it would be green, because the shields are under green cellophane. Despite my great respect for tradition, I think I’ll make the effect white down low and red up high only.

function Missile:draw()
    if self.v == 0 then return end
    pushStyle()
    if self.explodeCount > 0 then
        tint(self:explosionColor()) -- <---
        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

function Missile:explosionColor()
    return self.pos.y > TheArmy:saucerTop() and color(255,0,0) or color(255)
end

So that’s nice. Commit: missiles explode red when high else white.

Now we have another issue, which is that once I put that rounding into the gunner movement, it’s hard to move by tilting, and impossible by touching in the hot areas. Here’s our code:

function touched(touch)
    local fireTouch = 1171
    local moveLeft = 97
    local moveRight = 195
    local moveStep = 0.25
    local x = touch.pos.x
    if touch.state == ENDED then
        GunMove = vec2(0,0)
        if x > fireTouch then
            fireMissile()
        end
    end
    if touch.state == BEGAN or touch.state == CHANGED then
        if x < moveLeft then
            GunMove = vec2(-moveStep,0)
        elseif x > moveLeft and x < moveRight then
            GunMove = vec2(moveStep,0)
        end
    end
end

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)
        Gunner.pos.x = math.floor(Gunner.pos.x + 0.5)
    end
end

Clearly the rounding is losing my moveStep, which is only 0.25, and it’s making the effect of tilting be zero until suddenly it isn’t. That won’t do.

First, I’ll make moveStep be 1, which should work for the hot areas. It makes the touch response pretty snappy, but I like it. Now for gravity, we want either a step of 1 or zero, I suppose. So:

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

function effectOfGravity()
    local effect = 0.1
    local gx = Gravity.x
    return gx > effect and 1 or gx < -effect and -1 or 0
end

I tried 0.2 for the effect and it took too much tilting. 0.1 is much better. I really do prefer the proportional effect that I had before, more tilt, more speed. It was much easier to control. As it is now, you have to move rather far back to stop the gunner where you want him.

I’ll play with it and see. It’s probably easy enough to let the effect be proportional and then deal with the non-integers where it matters. Gameplay is more important than historical accuracy, and anyway, the original game had a TILT feature. Yes, it really did.

One more commit: adjusted gunner motion for both hot areas and gravity.

Let’s wrap this baby up.

Wrapping Up

So that was interesting. I don’t think we’re there yet, but after a bit of thrashing, the new missile-shield interaction went in pretty easily. Most of the operational code is in the shield, and I like the way it calls out to the projectiles to get the information it needs.

Notice that I made all those calls into methods, not just accessors to literal values. That’s certainly a smidge slower, but it’s more like what one should do, in my opinion. And in one case at least, we needed the method, because the base information was in two different forms.

In Smalltalk, where I learned the most about object-oriented programming (despite also using C++, Ruby, Java, Python, and surely others), there are no direct accessors. If you want a value from some object you must call a method. This provides flexibility in the event that you need it, and consistency. You don’t have to remember whether to call a method or fetch directly: there’s only one way.

So I’m used to that. I rather wish I were more consistent about doing that, since rummaging around in other people’s drawers is rude. Maybe we’ll more toward more of that as we improve the code.

As for the code, it’s actually a bit better, and it’s quite consistent between bombs and missiles, using almost entirely the same code. We’ll want to look at the two outermost collision methods:

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

That looks a lot like feature envy to me, so perhaps we should put that capability over on Shield somehow and consolidate one more level. It’s not really a Shield function, since it iterates over many shields, but it’s surely more shield-ish than both bomb-ish and missile-ish.

There are probably other refactoring code improvements we could make, and we’ll look for those. As for functionality, one big item is that the invaders eat the shields if they get that low, and of course they kill the gunner if they hit him.

The various waves start at different levels. There’s refinement to be done in the bomb-dropping, both to avoid bombs dropping through invaders lower down, which is just tacky, but also to emulate the choice of columns that drop bombs, and the dropping of the targeted one that always drops right over the gunner.

And the saucer. And multi-player, should we decide to do that. And attract mode, ditto.

And sounds. As yet, I’ve not been able to find the original sounds to use. I may have to see whether the wayback machine has them. Or create some.

Lots to do. Is anyone reading these? Should I keep writing them? Let me know.

See you then … if you’re out there.

Invaders.zip