It’s my ball and if I want to play with it you’re not the boss of me.

Today, I feel like improving some of this code. It’s not particularly urgent but that’s what I want to do today, and you can’t stop me nanner nanner.

My basic plan is to get some more objects up in this batch. Of procedural code. Shields, missiles, gunner. And maybe some new things. We’ll see how it goes.

Gunner tab is 58 lines long, Shield tab is 24. We’ll start with Shield.

But first a word from our sponsor, Small Steps Taken Often.

I was thinking as I drove back from *$ this morning that while these example programs are very tiny, they are very much like the many much larger programs that I’ve worked on and that we’ve all worked on. They are made up of fairly small chunks.

Now if you just suddenly thought of some 1300 line monster method or function that you had to deal with, just hold up there bunkie, because even that monster was made up of smaller bits. It just had them all crammed together. If you were really unlucky, it even had them shuffled together, but the small chunks were in there.

They have to be, because our minds are not capable of holding a big 1300 line monster all at once. We can always inspect that monster carefully and find a little bit that can be understood, described, and pulled out into a function or method on its own.

And that means that refactoring, even in a large system, can always be done in small steps. True, it won’t be done until everyone uses all the small chunks we’ve pulled out, but we can put in those references to the small chunk one at a time.

Sometimes it seems impossible. Sometimes we want to give up. Sometimes we even should give up. But before you give up on refactoring something, ask yourself something like this:

Suppose you had to give up on a stage and announce to the world that this code, right here, was a mess and could not be refactored and you’d stake your life and reputation on that fact. And to prove it, you were going to submit that code to me, to Chet Hendrickson, to James Shore, to J B Rainsberger, to Ward Cunningham, to Kent Beck and then to a half a dozen actually smart people, and they would fail, do you hear me fail to refactor that code. And if through some strange impossible chance, they did refactor it, you would be exposed, there on that stage, as the one person who couldn’t refactor that code. Would you still say it couldn’t be refactored?

I can tell you my answer: I might say I don’t know how to refactor it. I might say I think it’s not worth refactoring. I might say a lot of things. But I’ve never seen code that people like those listed above just couldn’t refactor. And I never expect to.

Anyway, point is, it’s always the same, large and small, and it’s always possible to take small steps to make it better.

And that’s why I think these little game exercises have value in “real life”, because real life is a lot like this.

With that said, let’s do some darn work.

Shield Tab

-- Shields
-- RJ 20200819

function createShields()
    local img = readImage(asset.shield)
    local posX = 34
    local posY = 48
    Shields = {}
    for s = 1,4 do
        local entry = {img=img:copy(), pos=vec2(posX,posY), count=0, alive=true}
        table.insert(Shields,entry)
        posX = posX + 22 + 23
    end
end

function drawShields()
    pushStyle()
    tint(0,255,0)
    for i,shield in ipairs(Shields) do
        sprite(shield.img, shield.pos.x, shield.pos.y)
    end
    popStyle()
end

This is interesting, because all this code is about a collection of shields, each one being a table entry containing an image, a position, a count, and an alive flag. I kind of wonder what the count and alive flags are for.

We see that the Shields are stored in a global table called Shields, so let’s see who else may look at this collection.

Main calls these two methods, but never refers to the collection. Bombs check to see if they’ve hit a shield:

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

One test creates an empty Shields collection, so that the code above can run.

What about references to the count and alive thingies? Are those just copied and pasted from somewhere else?

The only reference to .count are on Gunner. I suspect we could drop that from the Shield. And .alive is referenced only in Gunner and Invader. Those two fields should be able to disappear.

When I remove those and run the program, everything is fine, but I notice something odd:

black marks

The bombs are making black marks on the shields. Those used to be red. When did that happen? Looking back at yesterday’s photos, I see red marks. Weird.

Revert. Still happens. This doesn’t matter, because we are going to do something far more exotic for shield damage, but I’m curious just what is going on.

Wouldn’t you just know, you get all preachy and righteous and bang something instantly happens to show how human and fallible you are. Life is great.

A quick review of the code convinces me that we’re setting the hits to red. But remember that tint command, where we tint the white shield green? I bet that killed out the red.

The description of tint says that the color is “multiplied” by the color that’s already there. If red*green is black, that explains it. When I turn off the tint, the red comes back. Well then, that’s interesting. For now, let’s leave in the tint and carry on.

Where we we? Oh, right, we re-remove count and alive from the shield tables. What about that code in Bomb? Look at the whole thread:

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

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

function Bomb:applyDamage(img, hit, imagePos)
    local relativePos = hit-imagePos
    for x = -1,1 do
        for y = -1,1 do
            img:set(relativePos.x + x, relativePos.y + y, 255,0,0)
        end
    end
end

All this code is here because there is nobody else to talk to about whether a bomb has hit a shield, so the bomb has to do it all itself, including knowing an awful lot about shields’ shape and size.

I honestly don’t know how this will sort out, but I feel sure that having a shield object will allow us to make it better. And I’m tempted to make another class to manage the collection of them. We’ll see about that. First, the Shield class:

Shield = class()

function Shield:init(img, pos)
    self.img = img
    self.pos = pos
end

function createShields()
    local img = readImage(asset.shield)
    local posX = 34
    local posY = 48
    Shields = {}
    for s = 1,4 do
        local entry = Shield(img:copy(), vec2(posX,posY))
        table.insert(Shields,entry)
        posX = posX + 22 + 23
    end
end

I just created a Shield instead of a table there in the loop. But now I can tell the object to draw itself, which is no big trick:

function Shield:draw()
    tint(0,255,0)
    sprite(self.img, self.pos.x, self.pos.y)
end

function drawShields()
    pushStyle()
    for i,shield in ipairs(Shields) do
        Shield:draw()
    end
    popStyle()
end

Well, it wouldn’t be a big deal if I had lower-cased Shield:

function drawShields()
    pushStyle()
    for i,shield in ipairs(Shields) do
        shield:draw()
    end
    popStyle()
end

And we’re back in business. Commit: initial Shield class.

Now we can look at the bomb stuff. To refresh my memory and yours:

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

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

function Bomb:applyDamage(img, hit, imagePos)
    local relativePos = hit-imagePos
    for x = -1,1 do
        for y = -1,1 do
            img:set(relativePos.x + x, relativePos.y + y, 255,0,0)
        end
    end
end

(I notice the rectanglesIntersect functions are also in the Bomb tab, and that should be addressed. But we’re on a mission right now.)

It’s not as clear as it might be what’s going on here. Let me rename a bit:

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

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

function Bomb:applyDamage(shieldImg, hit, shieldPos)
    local relativePos = hit-shieldPos
    for x = -1,1 do
        for y = -1,1 do
            shieldImg:set(relativePos.x + x, relativePos.y + y, 255,0,0)
        end
    end
end

It’s easier to see now that almost all this code is about the shield and almost none about the bomb. Major feature envy here. Let’s start at the bottom with applyDamage:

Meh! Can’t. Don’t have the shield object at this point. We’ll have to start top down. We’ll change this:

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

We’ll pass down the shield object:

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

That requires us to change this:

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

To this:

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

We had to create the temp shieldPos, but we’ll see about getting rid of it shortly. First I’d like to get this stuff moved to where it needs to be.

Rename hit to hitPos for “clarity”. (Yes, “position” would be better. Our local standard uses “pos” and we all understand it.)

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

Now we need to convert this:

function Bomb:applyDamage(shieldImg, hit, shieldPos)
    local relativePos = hit-shieldPos
    for x = -1,1 do
        for y = -1,1 do
            shieldImg:set(relativePos.x + x, relativePos.y + y, 255,0,0)
        end
    end
end

We convert to this:

function Bomb:applyDamage(shield, hitPos)
    local relativePos = hitPos - shieldPos
    for x = -1,1 do
        for y = -1,1 do
            shield.img:set(relativePos.x + x, relativePos.y + y, 255,0,0)
        end
    end
end

I expect this to work. It doesn’t. The bombs pass right through the shields. What did I miss, and why aren’t there tests for this?

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

Failed to rename hit in one of its locations. Renames are tricky in Codea, because there’s no refactoring browser, and not much of a useful replace operator. Fixing that, I see

Bomb:64: bad argument #2 to '__sub' (vec2)
stack traceback:
	[C]: in metamethod '__sub'
	Bomb:64: in method 'applyDamage'
	Bomb:59: in method 'damageShield'
	Bomb:49: in method 'killedShield'
	Bomb:32: in method 'update'
	Army:51: in method 'doUpdate'
	Army:41: in method 'update'
	Main:34: in function 'draw'

OK …

function Bomb:applyDamage(shield, hitPos)
    local relativePos = hitPos - shieldPos
    for x = -1,1 do
        for y = -1,1 do
            shield.img:set(relativePos.x + x, relativePos.y + y, 255,0,0)
        end
    end
end

Another rename fail:

function Bomb:applyDamage(shield, hitPos)
    local relativePos = hitPos - shield.pos
    for x = -1,1 do
        for y = -1,1 do
            shield.img:set(relativePos.x + x, relativePos.y + y, 255,0,0)
        end
    end
end

And we’re good.

I really wish I had tests, but in fact running the program is turning up the problems just as quickly as tests would: it just feels a bit more slimy.

I should be able to write a bomb damaging shield test, and for my sins I’ll do it now.

Wow, this is really hard. The basic setup is easy enough, something like this:

        _:test("Bomb damages shield", function()
            local bomb = Bomb(vec2(100,100))
            local shield = Shield(readImage(asset.shield), vec2(89,92))
            bomb:damageShield(shield)
            _:expect(???)
        end)

If this works, Bomb:applyDamage will be called and will damage the image in the shield. How can we detect that in some reasonable way?

I had thought to intercept the call to applyDamage, like this:

        _:test("Bomb damages shield", function()
            local bomb = Bomb(vec2(100,100))
            local shield = Shield(readImage(asset.shield), vec2(89,92))
            bomb.applyDamage = Bomb.interceptDamage()
            bomb:damageShield(shield)
            _:expect(pos).is(vec2(100,100))
        end)

function Bomb:interceptDamage(shield, hitPos)
    print("intercepted", shield, hitPos)
end

I wasn’t sure how I was going to get information out of the intercepted function, but it turns out that the print there prints nil nil for shield and hitPos. So somehow, my interception is being called but the parameters aren’t there.

This was deep in the bag of tricks anyway, so fiddling with it much more violates the rule of holes, or the rule of deep bags, or something.

What about this. I’ll insert this line:

function Bomb:applyDamage(shield, hitPos)
    local relativePos = hitPos - shield.pos
    for x = -1,1 do
        for y = -1,1 do
            shield.img:set(relativePos.x + x, relativePos.y + y, 255,0,0)
        end
    end
    shield.damagedAt = relativePos
end

And now I should be able to assert on that value:

        _:test("Bomb damages shield", function()
            local bomb = Bomb(vec2(100,100))
            local shield = Shield(readImage(asset.shield), vec2(89,92))
            bomb:damageShield(shield)
            _:expect(shield.damagedAt).is(vec2(100,100))
        end)

This fails as I expect, because I’ve put in the world coordinates not the relative. The failure is this:

12: Bomb damages shield -- Actual: (11.000000, 8.000000), Expected: (100.000000, 100.000000)

And that seems to me to be right, because I drew a picture before I selected the numbers 89 and 92 and 89+11 is 100 and 92+8 is 100.

So I sort of have a test.

        _:test("Bomb damages shield", function()
            local bomb = Bomb(vec2(100,100))
            local shield = Shield(readImage(asset.shield), vec2(89,92))
            bomb:damageShield(shield)
            _:expect(shield.damagedAt).is(vec2(11,8))
        end)

I did have to put special code in to make this testable. That’s troubling and needs to go on the list to be addressed. It might be that I’ll not have a better idea. We’ll find out.

I think the intercept should have worked, but it didn’t. Maybe I’ll chase that yak to shave it later, because intercepting things might be kind of useful.

I think now I’m ready to move behavior into Shield class, but first, it’s time for lunch.

Long morning, but I think I’m on solid ground. Let’s commit: refactor bomb-shield interaction, add bomb shield test.

See you in a while …

Friday Afternoon

I left you at about 1100 Friday and it is almost 1600 now. Despite the odds that it’s a rathole, I’ve decided to take another run at intercepting the applyDamage call, in order to test it.

Here’s the test now:

        _:test("Bomb damages shield", function()
            local bomb = Bomb(vec2(100,100))
            local shield = Shield(readImage(asset.shield), vec2(89,92))
            bomb:damageShield(shield)
            _:expect(shield.damagedAt).is(vec2(11,8))
        end)

I’ve patched the applyDamage to return the relative position used in that function.

Here’s Plan C:

        _:test("Bomb damages shield", function()
            local apply = function(object, shield, pos)
                print("apply", object, shield, pos)
            end
            local bomb = Bomb(vec2(100,100))
            bomb.applyDamage = apply
            local shield = Shield(readImage(asset.shield), vec2(89,92))
            bomb:damageShield(shield)
            _:expect(shield.damagedAt).is(vec2(11,8))
        end)

I define a function local to the test and replace the applyDamage in bomb with it. The result is that it prints the information I expect, and that the test itself fails, because my intercepting function doesn’t return the right stuff.

apply	table: 0x281b859c0	table: 0x281b96040	(100.000000, 100.000000)

12: Bomb damages shield -- Actual: nil, Expected: (11.000000, 8.000000)

Now let’s make my interceptor call the original:

        _:test("Bomb damages shield", function()
            local bomb = Bomb(vec2(100,100))
            local original = bomb.applyDamage
            local apply = function(object, shield, pos)
                print("apply", object, shield, pos)
                return original(object, shield, pos)
            end
            bomb.applyDamage = apply
            local shield = Shield(readImage(asset.shield), vec2(89,92))
            bomb:damageShield(shield)
            _:expect(shield.damagedAt).is(vec2(11,8))
        end)

Here, I’ve saved out the original applyDamage, called the saved function from within my interceptor, and then called the original. (I wanted to say object:original(shield,pos) but that doesn’t work, probably because of the syntactic sugar. Anyway, this does the print and the test passes. Now I can fetch out whatever information I want, and plunk it into the test. (However, I probably can’t get at everything I want yet.)

        _:test("Bomb damages shield", function()
            local hitAt
            local bomb = Bomb(vec2(100,100))
            local original = bomb.applyDamage
            local apply = function(object, shield, pos)
                hitAt = pos
                return original(object, shield, pos)
            end
            bomb.applyDamage = apply
            local shield = Shield(readImage(asset.shield), vec2(89,92))
            bomb:damageShield(shield)
            _:expect(hitAt).is(vec2(100,100))
        end)

Notice that I changed the expect to get the world position, not the relative position we had before.

This is OK with me for now, I just wanted to find out how to make interception work. It’s still pretty darn deep in the bag.

Now I can remove that special save from this:

function Bomb:applyDamage(shield, hitPos)
    local relativePos = hitPos - shield.pos
    for x = -1,1 do
        for y = -1,1 do
            shield.img:set(relativePos.x + x, relativePos.y + y, 255,0,0)
        end
    end
    shield.damagedAt = relativePos -- < remove this
end

Anyway, this was just a side project done in my off time, to see how to do an interception. I’ve learned a way to do it and am not entirely clear on why the first way didn’t work. Don’t care.

However …

We’re here to refactor

All this code in Bomb belongs in Shield in my view. Let’s start moving it.

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
    self:applyDamage(shield, hitPos)
    return true
end

function Bomb:applyDamage(shield, hitPos)
    local relativePos = hitPos - shield.pos
    for x = -1,1 do
        for y = -1,1 do
            shield.img:set(relativePos.x + x, relativePos.y + y, 255,0,0)
        end
    end
end

This becomes:

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
    shield:applyDamage(hitPos)
    return true
end

Together with this:

function Shield:applyDamage(hitPos)
    local relativePos = hitPos - self.pos
    for x = -1,1 do
        for y = -1,1 do
            self.img:set(relativePos.x + x, relativePos.y + y, 255,0,0)
        end
    end
end

This will break my deep in the bag of tricks test, but I expect the game to run. And yes, it does run. Now let’s move another method, or maybe two. We have this:

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
    shield:applyDamage(hitPos)
    return true
end

We see now that we have some seriously magical numbers in the damageShield method, and we can’t just push it over to Shield as it stands. What if we do this instead, up in killedShield

You know what? There’s an interesting question here. I’m glad we dug into this. Whose job it is to damage the actual shield bitmap? On the one hand, the bomb presumably knows how much damage it can do, but on the other hand, we don’t want to just be handing out our bitmap to other objects to fiddle with it.

I think I’ll settle this in the direction we have been heading: the shield will damage itself. We might let it call back to the bomb for information. Let’s see where this goes …

We’ve got this in Bomb:

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
    shield:applyDamage(hitPos)
    return true
end

To move that to Shield, we’ll have to pass in the bomb as a parameter, because we need both positions. We’ll do that, then see what we can do to clean it up.

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

We’ll call that:

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

This works. (The test is still borked. I feel like it served its real purpose, learning a deep trick, but let’s leave it around and see what happens.

Now we do know that we’re calling the damage function in a loop, and we’d like to terminate that loop early. On the other hand, we can’t possibly hit more than one shield. So what if, in the event of a collision, we called back to the bomb saying you’ve exploded? I think that’s better, but if that’s the case, we could have done that from the bomb side just as well.

I’ll revert that last change. Sure wish I had committed the first bit.

We’re back to this:

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
    shield:applyDamage(hitPos)
    return true
end

Now that call to rectanglesIntersectAt uses information about the shield that the bomb should not know. (And if we put it in Shield the reverse would be true.

What we really want to know is whether the bomb’s rectangle and the shield’s rectangle overlap, and if so, where. What we will want to know in the near future is whether the two objects have any non-zero bits in common.

Let’s commit the current working code and go after that. Commit: refactor applyDamage from Bomb to Shield. But first, I’ll ignore that test.

Bitmaps Intersect

I have an online party to go to in a few minutes, but let’s at least try a cut at this. Let’s see … we have two bitmaps of unknown shape (but kind of rectangular) who may be overlapping. In the one, the shield, we really only want to consider overlap if the other, the bomb, covers a colored bit in the shield. If the shield bit is black, no harm.

The same may not be true the other way. We may wish to treat the bombs as only intersecting if one of their colored bits hits a colored bit, or we may wish to check all the bits, colored or not.

What if the bomb, which is smaller, would generate its bits one by one and inquire of the shield whether it had hit anything?

We’ll surely want to check for rectangle overlap before we do this, we can’t go searching all the bits on the screen. But let’s try to build the basic bit checker stuff with some TDD.

This should be amusing.

I decided I needed an object, so wrote this test:

        _:test("Images intersect", function()
            local white = color(255)
            local b1 = Bitmap(2,2)
            b1.bits:set(0,0,white)
            b1.bits:set(1,1,white)
            _:expect(b1:hasBit(1,1)).is(true)
        end)

And this class:

Bitmap = class()

function Bitmap:init(w,h)
    self.bits = image(w,h)
end

function Bitmap:hasBit(x,y)
    local col = self.bits:get(x,y)
    return col ~= color(0)
end

That actually works. Nearly amazing. But I really don’t think we want to call our hasBit function a million times in a loop. On the other hand, make it work then make it right then make it fast. What do I actually want to ask of this bitmap?

I want to ask it whether in some rectangle in its local space, it has any non-black bits, I guess I’ll just write that:

        _:test("Bitmap rectangle", function()
            local white = color(255)
            local b1 = Bitmap(2,2)
            b1.bits:set(0,0,white)
            b1.bits:set(1,1,white)
            _:expect(b1:hasBitsIn(1,1,1,1)).is(true)
            _:expect(b1:hasBitsIn(1,0,1,1)).is(false)
        end)

I’m on the hairy edge of the party and tired to boot, but maybe I can make this work pretty readily:

function Bitmap:hasBitsIn(x1,y1,x2,y2)
    for x = x1,x2 do
        for y = y1,y2 do
            if self:hasBit(x,y) then return true end
        end
    end
    return false
end

I had high hopes for that but it gets an error, trying to fetch a pixel out of bounds. But that’s OK, because our Bitmap knows its own coordinates. So we can do:

function Bitmap:hasBitsIn(x1,y1,x2,y2)
    for x = x1,x2 do
        for y = y1,y2 do
            if self:inBounds(x,y) and self:hasBit(x,y) then return true end
        end
    end
    return false
end

And we’re left with the trivial concern of doing inBounds:

Oops, my bad I had misremembered that images start at 1,1 not 0,0. Slight revision and:

function Bitmap:hasBitsIn(x1,y1,x2,y2)
    for x = x1,x2 do
        for y = y1,y2 do
            if self:inBounds(x,y) and self:hasBit(x,y) then return true end
        end
    end
    return false
end

function Bitmap:inBounds(x,y)
    if x < 1 or y < 1 then return false end
    return x <= self.maxX and y <= self.maxY
end

That passes the first test but the second comes back true. And it should, because the rectangle (1,0,1,1) does overlap us. I need to fix that test:

No. It is too much. It’s party time and my brain is fried. I’ll revert this and come back tomorrow.

It’s tempting to save it, I’m sure it’s nearly good But it’ll be better tomorrow.

See you then …

Tomorrow + 1

Saturday did come, but I’d left this iPad unplugged, to let the battery discharge a bit, and I must have left the game running, because it was totally flat when I came back to work on this.

You’re wondering why I didn’t just plug it in. Well, when working, I put the iPad on my keyboard tray, and the wire won’t reach. Yes, I could move it, but it was a good excuse not to do any work yesterday.

Now it’s Sunday, and I won’t do much, since that’s our day for a leisurely breakfast and some TV and reading. But I think there’s time to do a bit.

It is too much

I’ve come to think that I’m making too big a deal of this tiny problem, generalizing, deciding how to be all “tell, don’t ask” and so on. My new plan is to assess where we are, and then see about programming directly to what we actually need, not what seems “right”. Then, if it isn’t right, we’ll make it right.1

Let’s have a butcher’s at the code now. Here’s what’s running in the game:

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
    shield:applyDamage(hitPos)
    return true
end

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

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)
   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

Friday I started trying to TDD a kind of bitmap object that could check for collisions, but it wasn’t going well and I was on a 20 minute deadline. Let’s try to rethink about this.

We want to detect whether an incoming bomb has actually struck a shield. The bomb is a 3x4 rectangle containing some fancy sprite work. The shield lives inside a 22x16 rectangle, but our plan is that bombs will damage the shield, removing some of the shield’s bits to show damage. A subsequent bomb does not hit the shield unless it actually hits a visible bit in the shield.

Now, it’s true that “make it fast” is last in our little mantra, but there is a natural progression to think about. The bomb might not yet be low enough to hit any shield. It might not be within the x borders of any shield. It might be too low to hit a shield. And then finally, it might be within the shield’s bounds but not hitting any visible bits in the image. It makes sense logically to make these checks in order.

I confess that I’m at least a bit tempted not to make the checks and to go right to checking the bits. We’ll have to write that code anyway, and it is both necessary and sufficient for our purpose. It just seems kind of stupid not to do the other checks first.

Well, the borders of stupid are where all the learning is, so what the heck, let’s do it. That being the case, let’s try again to TDD it.

Here’s a cut at a test. It’s a bit ambitious but we’ll see how it goes:

        _:test("rectangle covers visible bits", function()
            local white = color(255)
            local img = image(22,16) -- lower left quadrant white
            for x=1,11 do
                for y = 1,8 do
                    img:set(x,y,white)
                end
            end
            local shield = Shield(img,vec2(100,100))
            local hitNo = shield:hitBits(vec2(112,109),3,4)
            local hitYes = shield:hitBits(vec2(100,100),3,4)
            _:expect(hitNo).is(false)
            _:expect(hitYes).is(true)
        end)

This creates an image that’s white in the lower left quadrant, and creates a shield with that image, because I’m planning to TDD a method, hitBits, on Shield. The test then calls hitBits twice, once with a rectangle that’s outside the white, and once with a rectangle that’s inside. We’ll see what this test drives.

13: rectangle covers visible bits -- Tests:140: attempt to call a nil value (method 'hitBits')

No surprise here, the method doesn’t exist yet.

function Shield:hitBits(pos,w,h)
    return false
end

“Fake it ‘til you make it”, another Kent Beck gift. The first check should succeed, and the second fail.

13: rectangle covers visible bits -- Actual: false, Expected: true

So far so good. Now what?

Well, we know the dimensions of the bomb, w, h, so we can ask the Shield whether it has a bit at each location in the bitmap. How about this:

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

I’m positing a new function hasBit that accepts real coordinates and answers whether we have a visible bit at that position. Now to write that …

function Shield:hasBit(x,y)
    local xx = x - self.pos.x
    local yy = y - self.pos.y
    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

I compute the local values of x and y by subtracting our position. Then if the result is inside the image’s dimensions, I fetch the bit.

But wait. Is the other loop right? It’s going, e.g. from x to x + w. That should be w-1 I think. I’ll change it now but we need to write some tests to check that we’ve covered just the right bits.

Both the tests now pass. Let’s spatter some shots around and do some more checking.

I’ll add them one at a time. Arguably I should write a new test for each of these, but I’ll just extend this one.

            local hit = shield:hitBits(vec2(97,100),3,4)
            _:expect(hit).is(false)

This is just far enough off to the left to miss, if the code is correct. And it does. Now I’ll remove the -1 and it should fail.

And it does not fail. Ah. I see another issue.

When we subtract the rectangle position from our position, we get a zero-based value. These images, bless their hearts, and one-based. So …

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

Now, without the subtract - 1 in the outer method, I expect the 97 test to fail and not the others. But I’m scared, that’s more defects than I’m comfortable with.

But it fails as intended. However, the output is tricky to understand, as often happens when we put multiple expectations in a single test. I can’t be sure which test failed. We’ll address that in a moment, but first I’ll put back the - 1s.

OK, tests all good. I’ve made myself nervous, though, so I want more tests. And I’ll do them a bit more nearly correctly,

        _:test("rectangle covers visible bits", function()
            local shield = testShield()
            local hitNo = shield:hitBits(vec2(112,109),3,4)
            local hitYes = shield:hitBits(vec2(100,100),3,4)
            _:expect(hitNo).is(false)
            _:expect(hitYes).is(true)
            local hit = shield:hitBits(vec2(97,100),3,4)
            _:expect(hit).is(false)
        end)

function testShield()
    local white = color(255)
    local img = image(22,16) -- lower left quadrant white
    for x=1,11 do
        for y = 1,8 do
            img:set(x,y,white)
        end
    end
    return Shield(img,vec2(100,100))
end

Pull out the creation of the test shield. Let’s go the whole way and break up our first test, then do more.

        _:test("rectangle high right hits no visible bits", function()
            local shield = testShield()
            local hitNo = shield:hitBits(vec2(112,109),3,4)
            _:expect(hitNo).is(false)
        end)
        
        _:test("rectangle at origin hits visible bits", function()
            local shield = testShield()
            local hitYes = shield:hitBits(vec2(100,100),3,4)
            _:expect(hitYes).is(true)
        end)
        
        _:test("rectangle off to left misses visible bits", function()
            local shield = testShield()
            local hit = shield:hitBits(vec2(97,100),3,4)
            _:expect(hit).is(false)
        end)

Now for a few more tests just to increase certainty. I’ll do one that just barely hits the top of my image:

        _:test("rectangle just above hits visible bits", function()
            local shield = testShield()
            local hit = shield:hitBits(vec2(105,108),3,4)
            _:expect(hit).is(true)
        end)

I figure this just ticks us in the middle of the top row of visible bits. However, it fails:

16: rectangle just above hits visible bits -- Actual: false, Expected: true

My bad. 108 is above the first bits. This zero-based graphics and one-based bitmap thing will drive me crazy soon. The image goes from 1-8 putting bits into 100-107. So 108 is outside. Change the test to 107 … and it runs.

OK, I’m about convinced that this baby is working, and it’s about time for breakfast. But what about our very first test:

        _:test("rectangle high right hits no visible bits", function()
            local shield = testShield()
            local hitNo = shield:hitBits(vec2(112,109),3,4)
            _:expect(hitNo).is(false)
        end)

That can be tightened up, can’t it? The 109 can clearly be 108. Still works.

We might want to do some more tests. For now, some quick remarks and then breakfast. I’ll continue this article tomorrow (Monday) if today goes as I expect.

Remarks

The difference between zero-based graphics and one-based bitmaps has bitten me quite a few times in this exercise. We have a kind of clash of the representations. Lua arrays are 1-based, so it makes sense that images would be as well, as they are just arrays of color. But the screen on the iPad, like most screens, is zero-based. So when we need to convert between a screen coordinate and an image coordinate, we get all these off-by-one adjustments, and they’re hard to keep track of, especially in a head like mine.

What we could do, at least in principle, is create a new abstraction and use it. For example, instead of using Codea images directly, we could have an Image class that was 1-based, doing the 1-adjustments all inside that class.

Doing that should mean that most of our address adjustment would go away, at the cost of a function dispatch on every bit access. That might be too high a price to pay, but if we didn’t access the bits very often, it could be acceptable.

We could even imagine folding in operations larger than one bit in side, and doing the translation inside, much as we’ve done here in shield.

And there might be some “in-between” solution that we could put into Shield, to make these operations less error-prone.

For now, this morning, all I’ve got is the problem, but in the case in hand, our bitmap checking, I think we’ve resolved it.

One more thing

This went very smoothly, despite some worries about plus or minus one. The TDD helped by letting me focus on specific cases that helped me see whether the code was right (or nearly right, those darn +/- 1s).

But the other thing that helped was the one or two previous attempts, and my willingness to say Oh $%$# it and toss them out. That’s a very powerful tactic, for me at least. When it’s not going well, notice that it’s not going well, take a break, and start over.

Enough for Sunday. My tea is ready.

See you tomorrow!

(No zipped code today, I forgot to save it and I’m on Monday already. Next time.)

  1. That reminds me to mention this. I almost certainly learned “Make it work, make it right, make it fast” from Kent Beck. Kent mentioned on Twitter yesterday that he learned it from his father. Wherever you heard it, it’s a good way to think.