Having decided to move to CORNER mode from CENTER, we’ve got some work to do. I’m hoping this one isn’t interesting.

I’ve been unable to avoid thinking about this CENTER vs CORNER topic, and have some to a tentative plan.

Use only CORNER mode everywhere.
We’ll have to adjust everything to accommodate this change, but presumably the adjustments will be consistent, if not constant, and doing it only one way should pay off in minimizing confusion.
Define a center for bitmaps if needed.
I’m not sure if we’ll need to refer to an object’s center, but if we do, I propose to define it explicitly. For an even width or even height bitmap, I’ll pick an integer offset to use. At a guess right now, WIDTH//2 might be about right.
Look into image:copy for checking bitmap collisions.
We know we need to check the actual partially destroyed shield bitmap to determine the final results of a possible collision between bomb and shield. There is a function on image bitmaps called copy, that can copy a sub-rectangle. That might be useful to us here. We’ll see.

Beyond this, we’ll see what comes. I’m a bit disappointed, because I’d hoped to be actually checking bits for intersection by now, but sometimes a wild yak appears and you have to shave it.

Let’s get started.

Change Modes

I want to change to use CORNER everywhere, in rectMode, and spriteMode. There is also ellipseMode and textMode, which I think I’ll leave alone for now.

I plan to set these two modes at the top of draw and remove all other calls to them:

function draw()
    pushMatrix()
    pushStyle()
    rectMode(CORNER)
    spriteMode(CORNER)
    background(40, 40, 50)
...

I searched out and removed the other calls to rectMode. There were none to spriteMode or any other xMode.

Now comes the messy bit. Things now look odd:

everything looks odd

We can see down in the gunner block what has happened everywhere. The dots are still centered around the gunner position and think that’s its center. But it’s the lower left corner. Everything is shifted up and to the right by about half its width and height.

Ad the invaders, who’ve always been a bit causal about the edge, are just running right over it now.

I think some of this will be straightforward and some not so much. I’m very used to having my screen objects centered around their numeric position. I’m tempted to put in a center feature and do adjustments back and forth.

But let’s not. Let’s pretend we have a little tiny computer, and work with what we’ve got, which will be bitmaps that start at the lower left corner.

We do probably need to cater to some general parameters for where things go. I don’t want to predesign that. Let’s discover what we really need as we go. Let’s start with the gunner.

function setupGunner()
    Gunner = {pos=vec2(112,10),alive=true,count=0,explode=explode}
    GunMove = vec2(0,0)
end

function drawGunner()
    pushMatrix()
    pushStyle()
    if Gunner.alive then
        stroke(255)
        fill(255)
    else
        stroke(255,0,0)
        fill(255,0,0)
        Gunner.count = Gunner.count - 1
        if Gunner.count <= 0 then Gunner.alive = true end
    end
    rect(Gunner.pos.x, Gunner.pos.y, 16,8)
    stroke(255,0,0)
    fill(255,0,0)
    point(Gunner.pos.x, Gunner.pos.y)
    point(Gunner.pos.x-8, Gunner.pos.y)
    point(Gunner.pos.x+8, Gunner.pos.y)
    point(Gunner.pos.x, Gunner.pos.y+4)
    point(Gunner.pos.x, Gunner.pos.y-4)
    popStyle()
    popMatrix()
    Gunner.pos = Gunner.pos + GunMove
end

Wow, I’m kind of glad we started here, because right away we need to face up to what’s going on. The gunner is initialized with his center at (112,10), which is screen center x and 10 pixels up from the bottom. (Scale 4 pixels, but I think we should be able to ignore scale throughout.)

Gunner is 16 wide and 8 high. What if we did this:

    Gunner = {pos=vec2(112,10)-vec2(8,4),alive=true,count=0,explode=explode}

I could do that math in my head (or on paper, or ask Siri) but I’m thinking a bit about the generality of the situation,

We should remove those red dots, which were just there to show me how Codea treated the edges.

However, because we’re dealing with changes to the coordinates of all our things, I think I’d like to draw a bit of gridwork to use as sight lines to be sure everything draws where we think it does. (I know of no way to write a test to check if the screen looks right.)

I’ll start with a simple vertical line at … the middle of the screen … um … do I mean at 112 or 112 + 1? Or should it be 111? There is no middle on an even numbered width or height. This time, I’ll draw them both.

function drawGrid()
    pushStyle()
    line(111,0,111,256)
    line(112,0,112,256)
    popStyle()
end

I think 112 looks better, I’ll leave that in. And let’s put on a border:

function drawGrid()
    pushStyle()
    noFill()
    line(112,0,112,256)
    rect(0,0,224,256)
    popStyle()
end

border

I think that’ll be helpful, and we can remove it all at one go. I expect we’ll expand it as we work to align more objects.

Relating to objects and alignment, I’m feeling more pressure to put in the real bitmaps, because the relationship between rectangles and bitmaps feels weird and a bit risky, though it probably isn’t.

Or is it? The bitmap for aliens is 16x8, with the center 8 bits of the 16 being alien, and the outer 4 bits on each side being spacing. This is surely going to mess us up at some point.

But right now, we’re here to switch to CORNER mode.

I suspect that bombs are now neither hitting the shields nor the gunner correctly. Basically they act as though they are to the right (and upward) from where they appear to be. Now’s the time for my new rectangle code … and then we’ll have to call it correctly.

function rectanglesIntersectAt(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 nil end
   if bl1.x > tr2.x or bl2.x > tr1.x then return nil end
   mark = bl1
   return bl1
end

So this code assumes that we are calling it with the lower left corner of the rectangle, and its width and height. I’ll look and see whether it changes anything. It appears not to, which may be good, because it’s supposed to be equivalent to my old code. Or it may be bad, because corner vs center. Anyway let’s also look to see how the bomb collisions are coded:

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

function Bomb:killedShield()
    for i = 1,4 do
        local pos = ShieldPos[i]
        local img = Shields[i]
        local hit = self:damageShield(img,pos)
        if hit then return true end
    end
    return false
end

function Bomb:damageShield(img, shieldPos)
    --print("bomb,shield", self.pos, 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:killsGunner()
    -- some of these must be off by one?
    local gunnerTop = Gunner.pos.y + 4
    local bombBottom = self.pos.y - 2
    if bombBottom > gunnerTop then return false end
    -- missile is at the gunner level
    local gunnerLeft = Gunner.pos.x-8
    local bombRight = self.pos.x + 1
    if bombRight < gunnerLeft then return false end
    -- we have hit him or are on his right
    local gunnerRight = Gunner.pos.x+8
    local bombLeft = self.pos.x - 1
    if bombLeft > gunnerRight then return false end
    return true
end

Ah, yes, we have unique code for Gunner, which we originally copied for the shields, but since we ran into “issues”, we’ve not yet consolidated.

We can use our new checker code, but there’s an issue. First let’s put it in:

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

This is a bit wonky, since we have to translate back to boolean. Something should be done between these two uses of the same thing.

I don’t see why this will change anything, and I have that confused feeling coming over me, but I’ll run it and watch.

Well, as best as I can tell by eye, it’s working. I’m not at all sure just what changed, maybe just my perception but I’ll take it.

You can’t see it in the photos, and I’ll post a video later, but I’ve coded the gunner to turn red for a while, and then back to green:

function drawGunner()
    pushMatrix()
    pushStyle()
    if Gunner.alive then
        stroke(255)
        fill(255)
    else
        stroke(255,0,0)
        fill(255,0,0)
        Gunner.count = Gunner.count - 1
        if Gunner.count <= 0 then Gunner.alive = true end
    end
    rect(Gunner.pos.x, Gunner.pos.y, 16,8)
    stroke(255,0,0)
    fill(255,0,0)
    popStyle()
    popMatrix()
    Gunner.pos = Gunner.pos + GunMove
end

This is done by adding a count field to his table, and an alive flag. I wrote about that a few articles ago. I want to duplicate that behavior in the shields, so that I can better watch to be sure they are being hit when they should be.

Remind me to answer the question that should be in your mind right now: “Why doesn’t he write tests for these collisions?”

The shields are set up oddly:

function createShields()
    local img = image(22,16)
    for row = 1,16 do
        for col = 1,22 do
            img:set(col,row,0,255,0)
        end
    end
    Shields = {img}
    for s = 2,4 do
        table.insert(Shields,img:copy())
    end
    ShieldPos = {}
    local posX = 34+11
    local posY = 100
    for i = 1,4 do
        table.insert(ShieldPos, vec2(posX,posY))
        posX = posX + 22 + 23
    end
end

Remember? I have two parallel tables, one with the bitmap and one with the position. I’m sure I said that would be back to bite us, and it surely is.

We’re working here with legacy code. It has few tests if any, and it has grown over time and has not been cultivated, trimmed, and if it was fertilized it just had um fertilizer dumped on it.

We’re faced with a bit of a dilemma. We’re really here just to plug in the corner-focused logic. Ideally we’d just sweep through and do it and be done, then move on to something better. But as we make sure that these things are working, we’re faced with our zero test situation and our weird code, which really needs to be cleaned up.

Why don’t I write tests? I’ll tell you why. It’s because I don’t think they’ll help me. I’m very confident in the rectangle code, so I’m very confident that if I set up a bomb and a shield, or a bomb and a gunner, they’ll show a hit when they should and not when they shouldn’t.

What I’m not sure of is whether that corresponds to what we see on the screen–and I don’t know how to test that! I only know how to look at it.

I’d like to make the shields into first class objects, but for now I’ll just put some additional information into the Shields table and try to get rid of the ShieldPos table.

function createShields()
    local img = image(22,16)
    for row = 1,16 do
        for col = 1,22 do
            img:set(col,row,0,255,0)
        end
    end
    local posX = 34+11
    local posY = 100
    Shields = {}
    for s = 1,4 do
        local entry = {img=img:copy(), pos=vec2(posX,posY)}
        table.insert(Shields,entry)
        posX = posX + 22 + 23
    end
end

We build the bitmap, then put four copies into Shields, together with the position where the image belongs. Now we have to change the drawing, which looks like this:

function drawShields()
    local posX = 34+11 -- centered, 22 wide
    local posY = 100
    for i = 1,4 do
        sprite(Shields[i], posX, posY)
        posX = posX + 22 + 23
    end
end

We have to fetch out the image and position now, and we can do it with a more conventional loop:

function drawShields()
    for i,shield in ipairs(Shields) do
        sprite(shield.img, shield.pos.x, shield.pos.y)
    end
end

I think that’ll draw, but something else will surely break. And yes: the shields show up momentarily, but the collision code breaks:

Bomb:34: attempt to index a nil value (global 'ShieldPos')
stack traceback:
	Bomb:34: in method 'killedShield'
	Bomb:17: in method 'update'
	Army:86: in method 'draw'
	Main:29: in function 'draw'

No surprise, we just have to hunt down and fix all the references to ShieldPos. Codea’s find is nearly up to that task:

function Bomb:killedShield()
    for i = 1,4 do
        local pos = ShieldPos[i]
        local img = Shields[i]
        local hit = self:damageShield(img,pos)
        if hit then return true end
    end
    return false
end

We can convert this readily:

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

Then we find no other references. We should be runnable again.

It looks pretty good. I’ll make a movie for your delectation:

pretty good

That looks pretty darn good. Let’s put in that count/alive logic, though, so that we can see multiple hits on the shields.

        local entry = {img=img:copy(), pos=vec2(posX,posY), count=0, alive=true}

That part was easy. Using it is a bit harder. In the Gunner, I had a helper function. If these were objects, I’d have a method to call.

This is why values and tables of values are bad, kids. They make things that should be done consistently have to be done in ad hoc ways.

Settle down, Ron, we’re nearly at a good spot. We could commit now with no harm done. But let’s do the color change.

How do shields turn red? Now here, folks, is some real legacy crap code:

function Bomb:applyDamage(img, hit, imagePos)
    for x = 1,22 do
        for y = 1,16 do
            img:set(x,y,255,0,0)
        end
    end
end

We’re turning the whole bitmap from green to red. What’s up with that??? Oh. Originally we planned to zero out some pixels for damage. Maybe now we can figure out how to do that. If we can, it’ll be a step in the direction of damaging the real shields.

We have the info we need, probably. We have the image, the coordinates of the hit, and the coordinates of the image. The relative coordinates of the hit should be imagePos-hit, shouldn’t it?

What if we were to set just some pixels to red?

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

Woot! That works nearly well:

red marks on green shields

I have some broken tests, but I’m going to commit this as is: converted to CORNER, shields get red marks.

Now for those tests. I’m sure they’ll be a result of the corner conversion, but we’ll find out:

11: Bomb hits gunner -- Actual: true, Expected: false

A number of the checks in this are failing. The test looks ike this:

        _:test("Bomb hits gunner", function()
            Gunner = {pos=vec2(50,50)}
            -- gunner's rectangle is 16x8 centered.
            local bomb = Bomb(vec2(50,50))
            -- covers x = 42-58
            _:expect(bomb:killsGunner()).is(true)
            bomb.pos.y = 57
            _:expect(bomb:killsGunner()).is(false)
            bomb.pos.y = 56
            _:expect(bomb:killsGunner()).is(true)
            bomb.pos.x = 41
            _:expect(bomb:killsGunner()).is(true)
            bomb.pos.x = 40
            _:expect(bomb:killsGunner()).is(false)
            bomb.pos.x = 59
            _:expect(bomb:killsGunner()).is(true)
            bomb.pos.x = 60
            _:expect(bomb:killsGunner()).is(false)
        end)

Yes the assumptions are wrong here. Should be:

OK, I calculated all these in my head and haven’t run the test yet:

        _:test("Bomb hits gunner", function()
            Gunner = {pos=vec2(50,50)}
            -- gunner's rectangle is 16x8 on CORNER
            -- covers x = 50-65 y = 50,57
            local bomb = Bomb(vec2(50,50))
            -- bomb is 3x4, covers x = 50-52 y = 50-53
            _:expect(bomb:killsGunner()).is(true)
            bomb.pos.y = 58
            _:expect(bomb:killsGunner()).is(false)
            bomb.pos.y = 57
            _:expect(bomb:killsGunner()).is(true)
            bomb.pos.x = 48 -- covers x = 48,49,50
            _:expect(bomb:killsGunner()).is(true)
            bomb.pos.x = 47 -- 47,48,49
            _:expect(bomb:killsGunner()).is(false)
            bomb.pos.x = 65 -- 65,66,67
            _:expect(bomb:killsGunner()).is(true)
            bomb.pos.x = 66 -- 66,67,68
            _:expect(bomb:killsGunner()).is(false)
        end)

Let’s see what breaks and why.

Ha, take that unbelievers. Test runs!

We have another failure:

10: bombs get deleted -- attempt to index a nil value

That test is:

        _:test("bombs get deleted", function()
            Gunner = {pos=vec2(1000,1000)}
            local army = Army()
            local bomb = Bomb(vec2(122,10))
            army:dropBomb(bomb)
            _:expect(countTable(army.bombs)).is(1)
            for i = 1,10 do
                bomb:update(army,1)
                _:expect(countTable(army.bombs)).is(1)
            end
            bomb:update(army,1)
            _:expect(countTable(army.bombs)).is(0)
        end)

I wonder what’s nil. We don’t get enough traceback info from the framework to help. Our gunner isn’t set up right, maybe that’s the issue. I’ll fill him in properly.

        _:test("bombs get deleted", function()
            Gunner = {pos=vec2(112,10)-vec2(8,4),alive=true,count=0,explode=explode}
            local army = Army()
            local bomb = Bomb(vec2(122,10))
            army:dropBomb(bomb)
            _:expect(countTable(army.bombs)).is(1)
            for i = 1,10 do
                bomb:update(army,1)
                _:expect(countTable(army.bombs)).is(1)
            end
            bomb:update(army,1)
            _:expect(countTable(army.bombs)).is(0)
        end)

I’m starting to suspect that we got a hit on the gunner accidentally but let’s run the test and see what happens now … Same error.

Hm. Is this test checking that bombs that hit the Gunner are deleted? Or the ones that don’t? It sure looks like this bomb was intended to hit this Gunner.

We could read the code, or we could patch in a direct bit of code to do this outside the tests. That should give us a line that fails. A quick glance at the code can’t hurt, can it?

Somehow I get an idea. Bombs’ update checks the shield array, and we don’t have one set up. I’ll take a flyer and put it in.

        _:test("bombs get deleted", function()
            Shields = {}
            Gunner = {pos=vec2(112,10)-vec2(8,4),alive=true,count=0,explode=explode}
            local army = Army()
            local bomb = Bomb(vec2(122,10))
            army:dropBomb(bomb)
            _:expect(countTable(army.bombs)).is(1)
            for i = 1,10 do
                bomb:update(army,1)
                _:expect(countTable(army.bombs)).is(1)
            end
            bomb:update(army,1)
            _:expect(countTable(army.bombs)).is(0)
        end)

Woot! Test runs. That was it. Commit: tests green. Good time for a break, nearly time for lunch. Let’s sum up.

Summing Up

Today went fairly smoothly. We were probably OK with the old rectangle checker, but the new one is nicer anyway. I still might rename the variables for better readability.

Things are now off center. The shields and invaders are off: the gunner has been fixed. My inclination is not to do further geometric tweaking until I’ve put the real bitmaps in, and probably the animation as well, since we have it working somewhere in a spike.

As so often happens, when we scrap our work and start over, things go better a second time around.

No big discoveries, but I have a growing sense that despite the confusion that may come from things having origin at the corner rather than center, the overall logic will be simple enough and certainly more sensible than whatever we’d have had to do to deal with odd-even widths and centers in between pixels.

I suspect that Codea’s zero origin in graphics and 1 origin in bitmaps and tables may cause us to be a pixel off, but If so, I doubt we’ll see it and if we do, we’ll fix it.

We do need to figure out the area where the invaders scurry about. It’s certainly not centered, and I have no reason to think they are at the right height.

And soon, we get to figure out a good way to nibble away at the shields. That’ll be fun.

I feel that the earth has stopped moving under us here in invader land. Would that the same were true in Real Life™.

Good luck to you all. See you next time!

Invaders.zip