I woke up this morning certain that some of my bitmap calculations were STILL off. Something has to be done.

The first problem is probably that I’m waking up at 6 AM thinking about bitmaps, but among the things one could be worried about these days, that one’s not too bad. But I’m a bit concerned about whether I’ve positioned the shield damage correctly. It’s surely no big deal in the scheme of things, but I make too many mistakes mapping these coordinates, in a situation where the code patterns should be consistent and simple.

Here’s the code in question:

function Shield:applyDamage(hitPos)
    local relativePos = hitPos - self.pos
    for x = -1,3 do -- one pixel each side of bomb
        for y = -3,3 do
            self.img:set(relativePos.x + x, relativePos.y + y, 0,0,0,0)
        end
    end
end

What concerns me is whether the x coordinates -1 through 3 actually represent one pixel on each side of the bomb. It might be that they do. In part, it depends on what hitPos is. That logic is over in Bomb:

function Bomb:killedShield()
    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)
    return true
end

We have to dig deeper to see what comes back from rectanglesIntersectAt:

function rectanglesIntersectAt(bl1,w1,h1, bl2,w2,h2)
    if rectanglesIntersect(bl1,w1,h1, bl2,w2,h2) then
        return bl1
    else
        return nil
    end
end

OK. That will return the first argument, namely the position of the bomb’s lower left corner. So that’s the value that goes into the applyDamage function:

function Shield:applyDamage(hitPos)
    local relativePos = hitPos - self.pos
    for x = -1,3 do -- one pixel each side of bomb
        for y = -3,3 do
            self.img:set(relativePos.x + x, relativePos.y + y, 0,0,0,0)
        end
    end
end

The one-base vs zero-base stuff here drives me crazy. But we can think this way. Suppose that the bomb hit us when its lower left corner hitPos was on the shield’s lower left corner, self.pos. Then relativePos would be (0,0). But the point in our bitmap would be (1,1). So if we want to iterate x over one pixel on each side, it should go 0,1,2,3,4, shouldn’t it?

I always get to this point uncertain. I would normally draw a picture and for some reason I’ve not been doing that as often as I’d like.

Anyway, I now believe that my x loop is off by one. But I want to examine the screen more closely to double check.

Let’s freeze the bomb on screen and see how it compares to the damage. We’ll probably want to color the damage and bomb differently. That will mean that the shield needs to have its tint turned off.

Whatever we do here, we’ll revert it after we see what we want to see.

I’ll spare you the several places I had to hammer to check this out, but it’s very clear that yes, in fact, the damage is off to the left by one pixel. So this is the fix:

function Shield:applyDamage(hitPos)
    local relativePos = hitPos - self.pos
    for x = 0,4 do -- one pixel each side of bomb
        for y = -3,3 do
            self.img:set(relativePos.x + x, relativePos.y + y, 0,0,0,0)
        end
    end
end

Now, however, the explosion graphic is too far to the left. That’s here:

function Bomb:explode(army)
    self.pos = self.pos - vec2(3,3)
    self.army = army
    self.explodeCount = 15
end

The y coordinate is pretty much a matter of taste, but the x needs to be 2. Here’s a short movie showing that it does look better now:

explode OK

I’ll commit that: adjust bomb-shield damage and explosion position.

I noticed something that I think is nice. The original game has green cellophane across the screen at the level of the shields and gunner, turning everything green, including the bombs and their explosions. Since we’re just tinting the shields green, that means that our explosions stay white.

Why do I think that’s nice? Remember that I’m fairly sure that the original game damages the shields by erasing wherever the bomb explosion has been displayed. (I’m not entirely certain of this, because there’s some code in the original game that suggests that it has saved the pixels under the explosion and restores them. But the damage that’s left really looks just like the bits of the explosion display.

Anyway, with the shields green and the explosion also green, it seemed to me that it would be hard to see the explosion. I just watched one of the on-line videos, and yes, in fact it is often difficult to see the explosion of the bomb, though you do see the hole it leaves.

Right now, our bomb damage is irritatingly rectangular. One possibility is just to add some randomness to the bits we destroy, but another would be to build a method that, given the bomb explosion bitmap, erased the corresponding bits in the shield.

I should really be taking an early turn at the shower, but I’m here now and want to try this. I suppose this is just a new implementation of applyDamage:

function Shield:applyDamage(hitPos)
    local relativePos = hitPos - self.pos
    for x = 0,4 do -- one pixel each side of bomb
        for y = -3,3 do
            self.img:set(relativePos.x + x, relativePos.y + y, 0,0,0,0)
        end
    end
end

Here comes my doggone zero-one concern again. I’m sure this signals clashing abstractions, which should probably be fixed with a better single abstraction, if only I knew what it was.

Here we are, working in somewhat messy legacy code, becoming more and more certain that something is wrong, but the pressure to get something done and the lack of a clearly better idea presses us to add in more features, without improving the space we’re working in.

But it’s all local to this one function, isn’t it? So it’s not so bad, is it?

Sound familiar? It does to me, and it has rarely led me to a place of peaceful happiness. Here I go, jumping in again.

Our bombs are sized 3x6, and the explosion is 6x8. No matter what we do, we can’t get a six centered on a three, so we’ll need to decide which side gets the extra pixels.

Given hitPos, we’re clearing pixels from x = 0 through 4, y = -3 through 3. That’s a five-wide space, and our explosion is six wide, and higher.

Meh. I’m usually pretty good at these things, and so either I’ve had a brain malfunction, or these abstractions are weird. Either is possible. I’m going to blame the abstractions for now.

Let’s code up a function that clears the bits in one bitmap based on the presence of non-zero bits in another. Then we’ll use it.

function clearFromBitmap(target,tx,ty, source,sx,sy)
    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
                target:set(tx+x-1, ty+y-1, 0,0,0) 
            end
        end
    end
end

That actually seems right to me. This could be a bad sign, or a good one. Now to call it from here:

function Shield:applyDamage(hitPos)
    local relativePos = hitPos - self.pos
    for x = 0,4 do -- one pixel each side of bomb
        for y = -3,3 do
            self.img:set(relativePos.x + x, relativePos.y + y, 0,0,0,0)
        end
    end
end

Well, all of that except the first line is useless, so …

function Shield:applyDamage(hitPos)
    local relativePos = hitPos - self.pos
    
end

At this point, no pun intended. relativePos is the zero-based point in the shield where the hit occurred. We want to be 2 pixels left of that and 3 down. So why isn’t this right:

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

I realized that I wasn’t using the source parameters sx and sy, and that I was clearing to zero rather than transparent zero. I’ve wound up with this:

function clearFromBitmap(target,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
                target:set(tx+x-1, ty+y-1, 0,0,0,0) 
            end
        end
    end
end

There’s a new zero in the target:set, setting opaqueness to zero. And this looks pretty decent:

bomb damage

By Jove, I think we’ve got it! Imma commit this before the computer forgets what we did: Shield damaged by explosion bitmap.

And this, plus some reflection, might be enough for this morning, although it’s only 0730.

Reflection

My early-morning concern over the code was accurate, I was one pixel to the left of where I had intended to be. It took a lot of reasoning to convince me, and even then I finally modified the program to stop animation at the critical point so that I could look at the result.

The core issue, a bug in my brain, is that I’m used to zero-based coordinates, and we’ve got zero-based screen coordinates and one-based bitmap coordinates.

If we have a bomb B at screen position B.pos, that means, in our present situation, that the bomb bit indexed (1,1) is at B.pos. And if our target is at T.pos, the target bit indexed (1,1) is at T.pos.

Where is the bit in the T bitmap that corresponds to the (1,1) bit in B? We can reason from the simplest example, where the two are positioned at the same location.

Then B.pos-T.pos is (0,0). Where’s that in T’s bitmap? It’s at (1,1), obviously, because we set it up that way.

Suppose we wanted to use B to clear bits in T, given that B and S are co-located at the lower left. This should work:

for x = 1,B.width do
    for y = 1,B.height do
        if B has bit at x,y) then
            T clear bit at x,y
        end
    end
end

What if we want to specify the target bit as other than the lower left. We’ll clearly want to provide our tx and ty as values between 1 and width or height, because that’s the natural representation.

So when we call our loop above with tx=1 and ty=1, we expect it to behave as it does not, copying into bit (1,1) and so on.

So if we provide tx and ty, we must subtract one from the T clearing. And that’s what we did:

function clearFromBitmap(target,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
                target:set(tx+x-1, ty+y-1, 0,0,0,0) 
            end
        end
    end
end

This reasoning keeps me convinced that we’ve done it right, and the fact that the display looks so good convinces me even more.

But it seems that we’re stuck with this little bump. Somewhere in our code there needs to be an accommodation between the zero-based screen coordinates and the one-based bitmap ones.

One possibility might be to create a new class Bitmap, and implement its get and set with zero-based inputs, and then there’d just be the one address adjustment inside.

What’s holding me back from doing that?

Well, first, my mind doesn’t quite encompass this issue, although it’s beginning to become more clear. It comes to me right this instant that there is a simple translation of (1,1) between the one model and the other, and translations are perfectly natural to me. So it should be a simple enough matter to embed that, as discussed just above, in a new class for bitmaps.

But second, I feel like putting this class in place would be wasteful, because the code is working now and it’s “not that bad”. The improvement doesn’t seem to deliver enough value.

That could actually be correct, if we are’t going to have much more of this kind of work. We have most of the collisions working now, other than the bomb-missile collision, and that “should” be straightforward.

We’re facing a very standard concern in working with code that isn’t quite right. We have it mostly under control–or if not, we dare not touch it–and while changes to it do take a bit longer than seems right, and we get it wrong a bit more often than seems right, well, we get where we need to go.

It’s not clear that we would make things better by changing out our old, admittedly weak abstractions for new untried ones. We’re smart and optimistic, so we think we could do it. But we’re old and experienced, and we know things are always harder than we expect. And we’re tired and under pressure … so we let it go just a bit longer.

In our specific case, the only idea I have so far is to write a cover class for the bitmap that translates between zero-based and one-based. That’s not obviously what we need. Our only two bitmap-related needs, if I recall, are to answer whether two bitmaps have non-zero bits in common, given an offset of one into the other, and to clear the bits in one that correspond to non-zero bits in the other, again under an offset.

Those are a long way off from getting or setting a single bit.

Before we give up …

Let’s take a look at all the bitmap code we have.

There’s this one, which gives the lie to my concern that we don’t do single-bit getting:

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

That’s called from this one, which is checking all the bits in a map:

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

These are operating at the level of Shield rather than a bitmap per se, but that’s OK. Moving on we have:

function clearFromBitmap(target,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
                target:set(tx+x-1, ty+y-1, 0,0,0,0) 
            end
        end
    end
end

This is a free-standing function, not a method on Shield. It could be put into that class if we wanted, quite easily, if it mattered. It might improve symmetry if nothing else.

Let’s see what else there is.

We have one test that uses set, but that can be ignored. No one else uses set but our clear function. What about get?

The only two uses of get are in the functions above.

I think that speaks for leaving well enough alone, with the possible exception that we should move clearFromBitmap into the Shield class, just to keep things together.

Let’s do that, and then I have a very bad idea to think about.

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

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

OK, it’s all together in Shield and, if anything, it makes it more clear than it was before that this is feature envy in Shield, calling for some features in a class that doesn’t exist. Yes, the shield does have the bitmap, but arguably, it shouldn’t be messing with it.

I think soon we’ll do something that should give us a better sense of where to push the design, and that will be damaging the line at the bottom of the screen, if we decide to do that.

In the original game, the bombs explode when they hit the green dividing line at the bottom of the screen, and they leave little holes in the line. It gets more ragged as the game goes on.

This is really just an artifact of the way the bomb explosion clears any bits that it is on top of. If we wanted to replicate that behavior, we’d have to create a bitmap for the line, detect collision with it, and damage it much as we’ve done here. All this for holes in a one-pixel thick line. We might not do that.

Oh, commit: clear bitmap moved into Shield class.

I do want to make the bombs explode when they hit that line, however, because now they just drop down the screen and explode out of sight. That should be easy if I can figure out where the line is. It turns out to be in drawStatus:

function drawStatus()
    pushStyle()
    stroke(0,255,0)
    strokeWidth(1)
    line(8,16,216,16)
    textMode(CORNER)
    fontSize(10)
    text(tostring(Lives), 24, 4)
    text("CREDIT  00", 144, 4)
    tint(0,255,0)
    local addr = 40
    for i = 0,Lives-2 do
        sprite(asset.play,40+16*i,4)
    end
    if Lives == 0 then
        textMode(CENTER)
        text("GAME OVER", 112, 32)
    end
    popStyle()
end

I’d prefer to get rid of that magic number but for now:

function Bomb:update(army, increment)
    if self.explodeCount > 0 then return end
    self.pos.y = self.pos.y - (increment or 1)
    self.shape = self.shape + 1
    if self.shape > 4 then self.shape = 1 end
    if self:killedShield() then
        self:explode(army)
        return
    end
    if self:killsGunner() then
        self:explode(army)
        Gunner:explode()
        return
    end
    if self.pos.y < 0 then
        self:explode(army)
        return
    end
end

If we change that last bit to < 16 it should be about right.

Yes, that’s perfect. I’ll try to get you a snapshot.

bomb line

Commit: bombs explode on divider line.

That’ll do it for today. Let’s reflect.

Reflection

This program is slowly but surely getting messier. It’s not terrible. Almost all the functionality is roughly where it belongs, but the objects and methods don’t seem to be helping us as much as they should.

We have far too many magic numbers, like the 16 we just used, representing where the status bar line is. There are magic numbers representing the size of bitmaps for bombs and shields and gunners. I’m not even certain at this moment whether we’re still killing the gunner based on his rectangle, or whether we’re checking his bits. Since the bit checking is inside Shield, I’d bet that we’re not. A quick check tells me that we’re just checking the gunner’s rectangle:

function Bomb:killsGunner()
    if not Gunner.alive then return false end
    local hit = rectanglesIntersectAt(self.pos,3,4, Gunner.pos,16,8)
    if hit == nil then return false end
    return true
end

That’s not quite fair, since the gunner bitmap is almost half empty due to its little turret and the fact that it’s 16 pixels in the bitmap but only 13 at its widest point. So clearly we’re going to want to do the bit checking for Bomb:killsGunner. That’s good. Doing that will surely generate some duplication, and removing that should improve the overall design.

gunner

Maybe that’ll be tomorrow’s story.

We also need to do scoring, and to decide whether there’s value in allowing two players, and whether we want the attract mode implemented.

I’m not sure at this point that we’ll learn a lot from that, and since the point of this exercise is to learn things … we may not do it.

For now, we’ve improved our destruction and perhaps improved the code at least a touch. Not bad for a rainy morning.

See you next time.

Invaders.zip