One of these two things is not like the other …

Here, in Paper™, are to-scale pictures of our Codea invaders, on the left, and the original game, from an on-line video, on the right:

two games

Both pictures are taken near the beginning of the game, with the invaders still in their first, starting rows. My original plan was to take some measurements for laying out our game. The pictures surprise me.

The overall proportions seem to fit nicely into 224x256. And the vertical spacing of at least some items seems about right. But the invaders are clearly smaller in the original. I’ve not measured carefully to see if they are shorter as well.

Note that the original’s shields seem proportionately taller than ours. Yet we know that they are the same size: I built ours from the original bitmaps and they have the same dimensions. It’s possible that the original display does not have “square” pixels, that is, different pixels per inch in horizontal and vertical. The iPad pixels are square.

My basic plan is not to worry about that. We’re here to create a game that works like the original but if it looks a bit different, that’s probably OK.

We do see a big difference in where things are starting out. Here are my approximate readings. Each square on the picture is 16x16.

  • Top row of invaders have their tops at Y = 256-64 = 192
  • Top row of invaders must start at 192-8 or 184?
  • Each invader row is 16 pixels below the one above
  • Tops of shields are at Y = 64, putting their base at Y = 48
  • Green line at Y = 16
  • Player cannon appears centered on Y = 32? I’ll try him at 32.
  • Lives and cannons and credits between Y = 0 and 16
  • Score 1 / Hi Score / Score 2 between Y = 248 and 224

Let’s set some of those values and take a look.

So we have this weird code to set up the Gunner, left over from when I avoided subtraction in the conversion to CORNER mode:

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

Well, that was expedient if not clever. We want Y = 32 for this, and 112-8 is probably about 104:

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

The shields:

function createShields()
    local img = readImage(asset.shield)
    local posX = 34
    local posY = 24
    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

Have I mentioned how easy these are to find with our new separate tabs for Gunner and Shields? In fact, they are easier to find.

shields and gunner

These look OK so far, let’s move the invaders down so that the fifth row starts at Y = 184. They are 16 apart, so the first row should be at 184-4*16 or 120.

Oh look, we init the invaders top down:

function Army:init()
    local vader11 = readImage(asset.documents.Dropbox.inv11)
    local vader12 = readImage(asset.documents.Dropbox.inv12)
    local vader21 = readImage(asset.documents.Dropbox.inv21)
    local vader22 = readImage(asset.documents.Dropbox.inv22)
    local vader31 = readImage(asset.documents.Dropbox.inv31)
    local vader32 = readImage(asset.documents.Dropbox.inv32)
    local vaders = {
        {vader31,vader32},
        {vader21,vader22}, {vader21,vader22},
        {vader11,vader12}, {vader11,vader12}
    }
    self.invaders = {}
    for row = 1,5 do
        for col = 1,11 do
            local p = vec2(col*16, 256-row*16)
            table.insert(self.invaders, 1, Invader(p,vaders[row]))
        end
    end
    self.invaderNumber = 1
    self.overTheEdge = false
    self.motion = vec2(2,0)
    self.updateDelay = 1/65 -- less than 1/60th by a bit
    self:resetTimeToUpdate()
    self.bombs = {}
end

So we should be able to just change that 256 to 184.

all three

So that looks nearly good. I’ll compare that picture with the original later.

I notice that it takes forever for the missile to go off screen after passing all the invaders. We need to bring it down, but not too far because it has to be able to hit the saucer, which will be one row above the invaders. I’ll try 184 + 24 or 208. That’s still in Main, and I had to search for it.

function drawMissile()
    if Missile.v == 0 then return end
    rect(Missile.pos.x, Missile.pos.y, 2,4)
    Missile.pos = Missile.pos + vec2(0,0.5)
    if Missile.pos.y > 256 or TheArmy:checkForKill(Missile) then
        Missile.v = 0
    end
end

Change that 256 to 208. That still looks too high. I decide to engage in some egregious hackery:

function drawMissile()
    if Missile.v == 0 then return end
    rect(Missile.pos.x, Missile.pos.y, 2,4)
    Missile.pos = Missile.pos + vec2(0,0.5)
    if Missile.pos.y > TheArmy.invaders[55].pos.y + 24 or TheArmy:checkForKill(Missile) then
        Missile.v = 0
    end
end

What in the $%#@ is this:

TheArmy.invaders[55].pos.y + 24

Well my foes and ah my friends, that’s 24 pixels above the current height of the last alien, who is in the top row. Let’s clean that up. The value we have in mind is “past the top of the saucer”. So let’s ask the army for that:

function drawMissile()
    if Missile.v == 0 then return end
    rect(Missile.pos.x, Missile.pos.y, 2,4)
    Missile.pos = Missile.pos + vec2(0,0.5)
    if Missile.pos.y > TheArmy:saucerTop() or TheArmy:checkForKill(Missile) then
        Missile.v = 0
    end
end

function Army:saucerTop()
    return self.invaders[#self.invaders].pos.y + 24
end

Now the value is still a bit hacked but the hackery is where it belongs, inside the army.

We have moved from ripping a table out of the Army and ruthlessly rummaging through it, to asking the Army for a value that we need.

But the guideline is “tell, don’t ask”. We do need to know whether the missile is dead but could we engage the army in helping us with this a bit more? What if we said this:

function drawMissile()
    if Missile.v == 0 then return end
    rect(Missile.pos.x, Missile.pos.y, 2,4)
    Missile.pos = Missile.pos + vec2(0,0.5)
    TheArmy:processMissile(Missile)
end

function Army:processMissile(aMissile)
    if aMissile.pos.y <= self:saucerTop() then
        self:checkForKill(aMissile)
    else
        Missile.v = 0
    end
end

My first attempt, above, has a delightful unexpected effect:

kill whole column

The game is much easier now that one shot kills a whole column of invaders. We need to change this:

function Army:checkForKill(missile)
    for i, invader in ipairs(self.invaders) do
        if invader:killedBy(missile) then
            return true
        end
    end
    return false
end

We’ll do this instead:

function Army:checkForKill(missile)
    for i, invader in ipairs(self.invaders) do
        if invader:killedBy(missile) then
            missile.v = 0
            return
        end
    end
end

Perhaps unfortunately, that fixes the bug and makes us shoot the invaders one at a time. Busy, busy.

I think we can commit this: Scale to match arcade.

Looking Back

Not much to see here. We still have some magic numbers for positioning things, and some of them are even more magical than they were.

Missile handling is awkward, because the missile has no brains and we can’t tell it to die or the like. And it’s being cycled every draw cycle, not every update cycle. We’re starting to hear it whispering “I’d like to be a real object”.

But it’s not the only one. The shields and gunner are in their own tabs, but they’re not objects yet either. To every thing there is a season, and it’s not their season yet.

This update went rather quickly and without much difficulty.

I just realized, because I was thinking about the text at the top and bottom, that the test text no longer shows on the screen, because we’re displaying the grid on top of it.

That’s not quite right. The grid is OK, but there was code in the draw method that cleared the background with a fill. With that deleted, the test results show up:

A quick commit: test results show again.

Now for that stuff at the bottom. The green line actually gets damaged by bombs that fall that low. We’ll put that on the list. For now, let’s draw the line and maybe the count of lives and spare cannons.

I’m going to add a function, drawStatus to the Main draw, and put the green line and spare cannons in there. A bit of fiddling and we have this:

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

And it looks like this:

bottom line

One more thing. In the real game, the shields and player are tinted green (by green cellophane on the screen). Let’s do that, just for fun, and then call it a morning.

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

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

And it looks like this:

all-green

Commit: Draw screen bottom info, color shields and gunner green.

We’ll have to make some of that stuff live, as we go forward, but our look is a lot closer to the original game.

Let’s sum up.

Summing Up

Kind of a relaxed day today. It started with some fiddly drawing and scaling in Paper™, followed by some very simple adjustments in coordinates. Then a quick and dirty bottom of screen layout, to add a little more of the original look and feel.

We definitely have magic numbers all over. Those would be better brought together somewhere–almost anywhere–and we can probably do that in a single pass over the code looking for numbers. In due time.

See me putting off cleaning up some code that I know is a bit messy? It’s not really bothering me, but it does slow me down a bit when I’m looking for a number to change it. Not much, though, because the numbers tend to be in the obvious functions, so we’re not very troubled.

But of course, putting off these simple code improvements means we incur a continued cost. I’m judging that it’s not high enough to matter just yet.

One rather nice thing is that we moved from a hacked solution to having the missile time out to a fairly neat solution that is packaged in the class that knows the necessary information, namely Army. This was a clear example of “feature envy”, where one class is doing work that naturally belongs elsewhere. We noticed that, we moved it, and we noticed that it was still asking rather than telling, and we improved it.

These tiny steps toward “better” are all it takes.

I saw one of those supposedly heartening sayings on Twitter yesterday:

It does not matter how slowly you go as long as you do not stop. – Confucius

There’s truth there, as there is in so many of these clichéd sayings. This applies both to our code, and to our own learning. So a little bit better is better, and it doesn’t really take much to hold back the encroaching cruft, and it doesn’t really take much to give us a much better feeling about ourselves and our work.

So. Take that for what it’s worth, or what you paid for it.

Looking forward, from a design standpoint, I’d like to get the missile, the shields, and the gunner all the way to objects, and I’d like to see if we can improve what’s in Main a bit, since its draw method is long:

function draw()
    pushMatrix()
    pushStyle()
    rectMode(CORNER)
    spriteMode(CORNER)
    background(40, 40, 50)
    showTests()
    stroke(255)
    fill(255)
    scale(4) -- makes the screen 1366/4 x 1024/4
    translate(WIDTH/8-112,0)
    fill(255)
    drawGrid()
    TheArmy:draw()
    drawGunner()
    drawMissile()
    drawShields()
    drawStatus()
    popStyle()
    popMatrix()
    TheArmy:update()
end

We can surely at least package that a bit better.

Functionally, there are missile explosions to deal with, and the gunner has a fancy explosion, and we need to count down gunners and end the game.

We need scoring, and the saucer. And probably if we were really cool, we’d save the high score.

Since we’re not going to package this into XCode and ship it, unless someone steps up to pair with me, we may skip some of those features unless we think there’s learning to be had.

You’ll find out when I do. See you next time!

Invaders.zip