Let’s do some damage!

Here we are on Monday. I’ve decided to call the previous article a wrap and publish it, then do this new one for Monday. The previous one is full of fail and today, today’s going to be my day.

I’m pretty confident in the new Shield;hitBits function, so I think this morning I’ll plug it into the program, and then work on displaying damage in the Shields. That will also let bombs drill down slowly through the shield, although that may take a while to see during gameplay.

We also need to use this same kind of thing for the bombs hitting the gunner, and if we’re going to have them damage the line at the bottom, there too.

The hitBits function looks like this:

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

Our present code for damaging shields is in the Bomb code, which troubles me, and it looks like this:

function Bomb:update(army, increment)
    ...
    if self:killedShield() then
        army:deleteBomb(self)
        return
    end
    ...
end

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

There in damageShield is where we need to add our call to hitBits, before we call applyDamage.

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

This nearly worked. I could see damage working the edges of the Shield, where it bends downward. But the bombs never dig in. That’s because, despite the damage looking black, it’s really red, because of the tinting. We’ll change that:

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, 0,0,0)
        end
    end
end

The set call now sets color to 0,0,0, which is black, and the value we need. Now with the bomb density turned up, we should get a good result.

shield damage

Well, yes and no. We’re definitely damaging deeper into the shields, but the damage patterns we’re making leave pixels around that get hit by the next bomb, but do not get destroyed. That’s not too surprising, since we’re just doing a few pixels right around the hit position, basically a 3x3 spot around the lower left corner.

We should clearly damage anything in the actual range of the bomb, and it seems to me that the bomb should dig downward as well. Also, we shouldn’t allow the damage to be offset to the corner, it should be equal on both sides.

I’ll try this:

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

better damage

That took just a little tuning. I tried y starting at -4 and decided that made the bombs dig a bit too well. And I added a zero to the set, to set transparency, because our background isn’t pure black, and the damaged shields were showing black rectangles once damaged.

My virtual helper on the Codea forum, Dave1707, did an interesting demo of more random damage, but his shields had lots more bits in them. I’m emulating the 224x256 display of the original game, and he was using the full power of the iPad screen.

I think this is good enough for now, but we could think about a bit more randomness in the explosion if it seems valuable. Right now, I vote no.

I think we can set the bomb dropping percentage back to normal, and commit: bombs check shield bits and damage appropriately.

Let’s reflect a bit.

Reflecting a Bit

(No pun intended.)

After enough fumbling to make me wish I didn’t tell the truth in these articles, the current implementation of bomb-shield collision and damage isn’t bad.

It’s also not great. I think we’ll review it tomorrow. I’m of a mind to do some more work this morning, but this article is already rather long, so I think I’ll wrap this one up after these remarks.

I still think I got too enamored of making the code “right”, and while I believe I was right to observe that the collision responsibility isn’t all in the right place, it was too soon to deal with it. Almost always, if I just look at little bits of code and move them where they seem to want to go, things get better. This time, they didn’t.

I have some ideas on why my usual small changes approach didn’t work well.

The code wasn’t really working

First, the code I was trying to improve wasn’t really fully working. It was only about 80 percent “there”, and refactoring code that’s not baked is risky.

My brain wasn’t ready

Second, while it’s true that I am inclined to simply look at code and move it to a better place, I usually have in mind at least a half-baked notion of where things belong. In this case, I didn’t, and still don’t.

Why don’t we know where this code belongs now, you may ask. We do have decent rectangle checking code and bitmap checking code, that much is true. But we’ll have almost exactly the same code for bombs vs gunner, and we have a similar but different problem for aliens eating shields when they start moving across at shield level.

I plan to wait for those stories to come up before I do very much to this code, having learned the lesson that I’m not ready. But I’m not going to run scared: we will look to see how to make this code right for the current situation.

The zero-one problem

The image is one-based, and positions are zero-based. That makes for a lot of fiddly plus-or-minus-one code. That’s easy to get wrong, and things don’t work right but it’s hard to see quite what’s going on.

I am sore tempted to resolve this problem somehow, but that would require me to build some kind of infrastructure thing for bitmaps and/or graphics, and there’s no legitimate reason to do that.

I had very few tests

Let’s get real here. If you’ve got rectangles containing random bits, and some of them are flying around, and you need to know whether they are intersecting, this is not code you want to be checking by eyeball. You need tests.

It would be sufficient to write tests on the existing code, though it’s particularly tedious when we’re pretty confident in our towering ability to code. I even resist it when I know the code’s not right. I’m a programmer, Jim, not a tester.

Except that TDD isn’t testing, it’s design and it’s programming, and it turned out to be really helpful when I finally got around to doing it. The result is a very decent suite of checks on the correctness of the hitBits function, and except for the fact that it lives on Shield, it looks to be easily generalized.

Starting over helps

This is a lesson I have learned well, and have difficulty applying even so. When I seem to be stuck on something that should be straightforward–or something tricky–it almost always pays off to revert the code, take a break, and then come back at it fresh.

I think that what happens is that we’ve begun to build up a lot of intuition about the problem and the solution, but that the code we have doesn’t reflect that learning. We’re not even aware of that learning yet, because the details of the code before us swamp out the bigger picture. Stepping away lets things come into balance.

But it’s so hard to give up, isn’t it? We’ve beaten our heads through other problems, and we can bash through this one as well.

But starting over helps.

And now for something …

I was originally just continuing the Friday article but decided instead to spit it at this morning and ship it. So that’s done, and it’s still only 0920, and I’ve got my chai, Margaritaville on the HomePod, half a charge on the iPad, and a granola bar, so let’s see what’s next.

From a game viewpoint, I think what should be next would be that bombs should be able to kill the gunner.

Have you noticed the inconsistent terms for the player thing? Is it bothering you? It’s bothering me, but my brain hasn’t solidly taken to a term. In the code it’s Gunner, but I think of it as the player sometimes. Oh well, that’s not the stupidest thing I ever did.1 We’ll carry on.

So we have half-decent detection on bomb-gunner collision, and we turn the gunner red as an indication of the hit. We’ll need to improve that to deal with the fact that the gunner isn’t really a rectangle, but that can wait for the basic functionality.

We’ve drawn a dummy count of lives and extra gunners at the bottom of the screen:

screen-shot

That’s just constant info right now:

function drawStatus()
    pushStyle()
    stroke(0,255,0)
    strokeWidth(1)
    line(8,16,216,16)
    textMode(CORNER)
    fontSize(10)
    text("3", 24, 4)
    text("CREDIT  00", 144, 4)
    tint(0,255,0)
    sprite(asset.play,40,4)
    sprite(asset.play,56,4)
    popStyle()
end

Suppose we had a fairly global variable called Lives, and we used it in the code above, like this:

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
    popStyle()
end

That’s a bit of a hack, but not terrible. Can’t be too bad, it works. Now where do we turn the gunner red?

function Bomb:update(army, increment)
    ...
    if self:killsGunner() then
        army:deleteBomb(self)
        Gunner:explode()
        return
    end
    ...
end

function explode()
    Gunner.alive = false
    Gunner.count = 120
end

Whoa! That’s interesting. Gunner isn’t a class, and so Gunner:explode just calls the global explode, passing it the Gunner, which it ignores. Weird. We were on a path to make the Gunner into an actual class but never got there.

Should we make it right, right now, or push forward to add our feature? Making the code right is almost always better, so let’s make the mistake of adding functionality to this not-so-very-right code. We’ll see how much trouble we get into, and how hard it is to make things right if and when we decide to.

There are two phases to a gunner explosion, and the bitmaps are available. Let’s first change the code to use those rather than just turn red.

function setupGunner()
    Gunner = {pos=vec2(104,32),alive=true,count=0,explode=explode}
    GunMove = vec2(0,0)
    GunnerEx1 = readImage(asset.playx1)
    GunnerEx2 = readImage(asset.playx2)
end

How long does the invader explosion last? We can use that as a guideline for the gunner. The invader explosion lasts for 15 ticks. We’ll follow that lead. We may have to make sure they’re the same kind of ticks, remember that we try to update every 1/60 second but my fast iPad sometimes updates every 1/120.

Right now the gunner counts down in draw, not in update, so we’ll surely have a timing problem across devices. Still not gonna deal with that now.

I’ll give it a starting count of 60, display X1 down to 45, x2 down to 30, then nothing until zero. That will simulate the pause between lives.

I started with this:

function drawGunner()
    pushMatrix()
    pushStyle()
        tint(0,255,0)
    if Gunner.alive then
        sprite(asset.play,Gunner.pos.x,Gunner.pos.y)
    else
        if Gunner.count > 45 then
            sprite(GunnerEx1, Gunner.pos.x, Gunner.pos.y)
        elseif Gunner.count > 30 then
            sprite(GunnerEx2, Gunner.pos.x, Gunner.pos.y)
        end
        Gunner.count = Gunner.count - 1
        if Gunner.count <= 0 then Gunner.alive = true end
    end
    popStyle()
    popMatrix()
    Gunner.pos = Gunner.pos + GunMove
end

It looks nearly OK:

first-explosion

I checked a video of the original, and noticed a couple of things. First, the explosion stages flicker back and forth a few times, not just 1 then 2 then done. Second, the bombs make an explosion when they hit, and I think that explosion is what damages the shield, that is, the bits that are on in the explosion turn off the bits in the shield. There’s no question that the damage is more random than what I’ve got now.

Finally, doing the tick in draw definitely makes the timing very fast, so we’ll need to move it to update time. But first let’s make it flicker.

flicker

To get that effect, I’m initializing Gunner.count to 120 (draw cycles) and processing it like this:

function drawGunner()
    pushMatrix()
    pushStyle()
    tint(0,255,0)
    if Gunner.alive then
        sprite(asset.play,Gunner.pos.x,Gunner.pos.y)
    else
        if Gunner.count > 90 then
            local c = Gunner.count//8
            if c%2 == 0 then
                sprite(GunnerEx1, Gunner.pos.x, Gunner.pos.y)
            else
                sprite(GunnerEx2, Gunner.pos.x, Gunner.pos.y)
            end
        end
        Gunner.count = Gunner.count - 1
        if Gunner.count <= 0 then Gunner.alive = true end
    end
    popStyle()
    popMatrix()
    Gunner.pos = Gunner.pos + GunMove
end

Now what about the lives? Can I “just” process those right there where I set the gunner back to alive? I think so, let’s try this:

function drawGunner()
    pushMatrix()
    pushStyle()
    tint(0,255,0)
    if Gunner.alive then
        sprite(asset.play,Gunner.pos.x,Gunner.pos.y)
    else
        if Gunner.count > 90 then
            local c = Gunner.count//8
            if c%2 == 0 then
                sprite(GunnerEx1, Gunner.pos.x, Gunner.pos.y)
            else
                sprite(GunnerEx2, Gunner.pos.x, Gunner.pos.y)
            end
        end
        Gunner.count = Gunner.count - 1
        if Gunner.count <= 0 then
            if Lives > 0 then
                Lives = Lives - 1
                Gunner.alive = true
            end
        end
    end
    popStyle()
    popMatrix()
    Gunner.pos = Gunner.pos + GunMove
end

That just about works. There is this odd thing where the gunner explodes (again) when it’s dead. We find that and change it:

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

Testing, I notice that you get an extra gunner, not just the 2. That’s to do with the handling of the fact that the count says 3, and the guns remaining is two. You have three lives, counting the one that’s playing. So we need to fix this.

This gives me the effect that I want:

function drawGunner()
    pushMatrix()
    pushStyle()
    tint(0,255,0)
    if Gunner.alive then
        sprite(asset.play,Gunner.pos.x,Gunner.pos.y)
    else
        if Gunner.count > 90 then
            local c = Gunner.count//8
            if c%2 == 0 then
                sprite(GunnerEx1, Gunner.pos.x, Gunner.pos.y)
            else
                sprite(GunnerEx2, Gunner.pos.x, Gunner.pos.y)
            end
        end
        Gunner.count = Gunner.count - 1
        if Gunner.count <= 0 then
            if Lives > 0 then
                Lives = Lives -1
                if Lives > 0 then
                    Gunner.alive = true
                end
            end
        end
    end
    popStyle()
    popMatrix()
    Gunner.pos = Gunner.pos + GunMove
end

That’s messy but seems to be correct. The number of gunners displayed below the line is one less than the number of lives you have, because you’ve got the one that’s above the line. I found a number of ways to make that code do odd things, including one version that counted lives down into the negative numbers very rapidly. That was amusing.

The fact that this logic is tricky makes me pretty sure that this “design” isn’t what we need, but it does seem to work. I’m still not happy with the delay between the explosion and the new gunner, so I’ll push those numbers up a bit.

Initializing to 240 and ending the explosion at 210 looks pretty good. One more thing seems fun:

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

That code at the bottom displays GAME OVER where the gunner would be if you had another one. Nearly good:

game over

I think that’s a wrap. Commit: Three lives and out.

Let’s reflect.

Reflection

reflection

That’s roughly what I see when I look at my reflection. No haircut since February sometime. I think I’m going for a ponytail. But that’s not really what I meant by reflection.

Today has gone nicely. I did have to do some visual tuning to get the timing right. And it was good that I looked at the old game to see that the gunner explosion flickers between states like it does. We’re trying to replicate its look and feel to the extent that we can reasonably do so.

The code in the Gunner display logic looks rather more like the old-style assembly code than I’d like. We did things in those days that we don’t need to do now, and therefore shouldn’t do.

The Gunner drawing function mixes drawing the gunner, drawing the explosion, timing the explosion, moving the gunner, and counting lives. (But it doesn’t handle game over when the lives are used up: it just doesn’t display anything.)

When we used to write these things in assembler (and I did write Spacewar in PDP-1 assembler), once we started doing something with an “object”, we tended to do everything, so the code often turned out to be just one darn thing after another. There’d be subroutines for capabilities we could use more than once: that saved space. But we’d rarely if ever create a new function just because we were doing something different from what we had been doing on the previous line. We might put in a comment like “now see if we’re still alive”, but that was about it..

That made those programs hard to maintain for the next person, or the same person whose memory had faded. I’m having great difficulty understanding the Space Invaders assembly, between that style of programming and the fact that I don’t know its assembly language very well.

Today, with computers that are vastly more powerful, we have the ability to lean much further toward making the code more readable, which we do with more functions, more objects, and with actively refactoring for clarity when things start getting like they are in this program.

But it’s working, isn’t it?

As an experiment, let’s keep pushing forward without so much restructuring, to see what we get. Then we’ll review as much mess as there is, and refactor to something that at least one of us considers nicer.

Along the way, we’ll be watching for places where the mess makes us go slower. There is absolutely no doubt in my mind, based on literally decades of doing this TDD/refactoring thing, that the clear code lets me go faster. But we’re in no hurry. We’re here to see what happens.

Stay tuned! See you tomorrow!

Invaders.zip

  1. Probably the stupidest thing I ever did was to buy an old Lamborghini.