A bit of an idea about marching, and thoughts about spikes.

I seem to naturally think about the code I’m working on, from time to time, even when I’m not specifically working on it. If I am actually working on it, I’m also writing one of these articles. Just thinking, well, that’s just thinking.

Should we scrap this spike?

One larger question on my mind just now is what to do with this spike. As constant readers know, a spike is a short experimental program written to help think about solving a problem, with intent to throw away the program and keep the ideas. It’s always tempting to keep a spike, and rarely ever a good idea.

I am feeling the temptation to keep this one. I think there is always that temptation. After all, I’ve worked and thought hard to get this piece of um code, and it seems wasteful to throw it away.

My standing advice is “Always throw it away”. Whatever we write, informed by this new knowledge, will surely be better than the strange piece of um code we have here. It’s tempting, however, to refactor this, to turn it into something we can build on.

I think refactoring it might take longer than rewriting it. But doing the refactoring might tell us a bit about refactoring in the many situations where we either cannot rewrite the thing, or should not.

Should not? Yes. I have been involved in many system rewrites over the years, and my view is that they are rarely if ever successful. Yes, if you have to move to a new kind of computer, or a different language for some good external reason, there may be no choice. But you are in for a long grind and my advice is “Run away!”

What’s better? I think refactoring the system is generally better. So perhaps there is a lesson to be learned in trying to refactor this program so as to build on it.

What about events?

I was thinking about how to make the invaders march downward. We’ve got a trigger variable, reverse that tells us when to reverse direction, and in the real game, the invaders should step downward when they reverse. I have an idea for that.

There is a variable that tells us which direction to step the invaders, and we change the sign of that when they reverse. If we added a bit of negative Y, they’d also step downward. But that downward step should only be applied to the first step in the new direction, and after that they should step horizontally.

My idea was to set the step with a negative Y on reverse, and after the last rank has stepped, clear the Y part of the step variable, so that they’ll step horizontally until the other edge.

Building on that idea, I noticed that we already have a similar kind of thing going on on the first rank, because that’s where we define the step amount to move from side to side:

function update(elapsed)
    if elapsed >= lastElapsedTime + timeToUpdate then
        if updateRow == 0 and reverse then
            updateStep = - updateStep
            reverse = false
        end
        lastElapsedTime = elapsed
        ranks[updateRow+1].offset = ranks[updateRow+1].offset + updateStep
        updateRow = (updateRow + 1)%5
    end
end

That updateRow == 0 is what I’m talking about. Essentially that manages an event at the beginning of the first rank. To make them move downward, I’d change it like this:

function update(elapsed)
    if elapsed >= lastElapsedTime + timeToUpdate then
        if updateRow == 0 and reverse then
            updateStep = - updateStep - vec2(0,2)
            reverse = false
        end
        lastElapsedTime = elapsed
        ranks[updateRow+1].offset = ranks[updateRow+1].offset + updateStep
        updateRow = (updateRow + 1)%5
    end
end

If I stop there, they’ll start marching diagonally downward after they hit the right edge. Let’s make a movie of that, it should be amusing.

move ever downward

So after we do the last row, we should zero out the y coordinate of updateStep:

function update(elapsed)
    if elapsed >= lastElapsedTime + timeToUpdate then
        if updateRow == 0 and reverse then
            updateStep = - updateStep - vec2(0,2)
            reverse = false
        end
        lastElapsedTime = elapsed
        ranks[updateRow+1].offset = ranks[updateRow+1].offset + updateStep
        updateRow = (updateRow + 1)%5
        if updateRow == 0 then updateStep.y = 0 end -- <---
    end
end

Now they step down once and proceed across after that:

step down then across

Just right except for the boundary detection: they probably go too far toward each edge before they turn around. That’s just a matter of tuning a couple of numbers.

The event idea was this. Now we have two magical things happening, maybe three:

  • detecting the edge (presently sets a variable)
  • detecting that we’re about to start rank zero
  • detecting that we’ve just finished the last rank.

Various magical things happen based on those events. So it’s making me think that perhaps there should be a notion of “event” built into the product, that would isolate out the condition detection part and the “what to do” part, into a data structure of some kind that would make all these things seem similar instead of each one having its own odd if block.

It’s too soon to decide that, and far too soon to build it (I think) but I want to have it in mind, since this code is already rather messy.

But It’s Just a Spike!

But it’s just a spike, and we can just throw it away, and we are supposed to throw it away. Then we can build something that makes more sense.

I’m reminded, though, of Mary Rose Cook’s invaders in 30 minutes. That was a great little demonstration, and I loved it.

If it was supposed to be the first step on the road to a space invaders game, however, we’d want to go forward from right there. We wouldn’t want to say to our customer, “That was just a demo, now we’ll start on the real thing”.

So I am very very tempted to evolve from where we are right now.

For now, I’ll draw some conclusions and close out this little session.

Summing Up

Well, so far today I’ve added one sub-expression and one line of code. The good news is that that was sufficient to make the invaders march downward a notch on each reversal. So in terms of “game value”, I’ve accomplished a lot.

The overall design we have here is rather interesting.

We have five ranks of invaders, each one with its own base position at the lower left hand corner of the rank. Each invader is drawn relative to that value, like this:

            local pos = invader.p + rankEntry.offset
            ...
            sprite(v, pos.x, pos.y)

That’s nearly good. We haven’t handled the selection of the sprite yet, to do the walking, but we had that in an earlier iteration and it’ll be easy to put it back.

We have separation of drawing from updating, although both of them look a bit messy:

function drawInvaders()
    scale(4)
    for r,rankEntry in ipairs(ranks) do
        for i,invader in ipairs(rankEntry.invaders) do
            local pos = invader.p + rankEntry.offset
            if updateStep.x > 0 and pos.x > 224 - 16 then
                reverse = true
            elseif updateStep.x < 0 and pos.x < 0 then
                reverse = true
            end
            local v = vader1
            sprite(v, pos.x, pos.y)
        end
    end
end

function update(elapsed)
    if elapsed >= lastElapsedTime + timeToUpdate then
        if updateRow == 0 and reverse then
            updateStep = - updateStep - vec2(0,2)
            reverse = false
        end
        lastElapsedTime = elapsed
        ranks[updateRow+1].offset = ranks[updateRow+1].offset + updateStep
        updateRow = (updateRow + 1)%5
        if updateRow == 0 then updateStep.y = 0 end
    end
end

We haven’t done anything about our cannon/player, nor the shields, nor about firing missiles down from the invaders. The hardest part of these will probably be emulating the behavior of the original program, which has all kinds of arcane rules about which missiles fire, at which times, from which locations.

Before we work on that, we need either to begin with a fresh sheet of paper, or refactor this one until it’s maintainable. I welcome Twitter feedback on which to do.

See you next time!

The Code (such as it is)

-- invadersSpike

function setup()
    ranks = {}
    for rank = 0,4 do
        local rankTable = {}
        rankTable.rank = rank
        rankTable.invaders={}
        rankTable.offset = vec2(0,0)
        table.insert(ranks,rankTable)
        for col = 0,10 do
            table.insert(rankTable.invaders, {p=vec2(col*16,rank*16)})
        end
    end
    vader1 = readImage(asset.documents.Dropbox.inv11)
    vader2 = readImage(asset.documents.Dropbox.inv12)
    vaders = {vader1,vader2}
    updateRow = 0
    updateStep = vec2(2,0)
    reverse = false
    lastElapsedTime = ElapsedTime
    timeToUpdate = 10/60
    --timeSearch()
end

function draw()
    update(ElapsedTime)
    background(40, 40, 50)
    rectMode(CENTER)
    noFill()
    stroke(255)
    strokeWidth(2)
    rect(WIDTH/2, HEIGHT/2, 224*4, 256*4)
    spriteMode(CORNER)
    text(DeltaTime, 100,100)
    translate(WIDTH/2-224*2, HEIGHT/2)
    drawInvaders()
end

function drawInvaders()
    scale(4)
    for r,rankEntry in ipairs(ranks) do
        for i,invader in ipairs(rankEntry.invaders) do
            local pos = invader.p + rankEntry.offset
            if updateStep.x > 0 and pos.x > 224 - 16 then
                reverse = true
            elseif updateStep.x < 0 and pos.x < 0 then
                reverse = true
            end
            local v = vader1
            sprite(v, pos.x, pos.y)
        end
    end
end

function update(elapsed)
    if elapsed >= lastElapsedTime + timeToUpdate then
        if updateRow == 0 and reverse then
            updateStep = - updateStep - vec2(0,2)
            reverse = false
        end
        lastElapsedTime = elapsed
        ranks[updateRow+1].offset = ranks[updateRow+1].offset + updateStep
        updateRow = (updateRow + 1)%5
        if updateRow == 0 then updateStep.y = 0 end
    end
end

function timeSearch()
    local t = os.time()
    for i = 1,1000000 do
        local max = -1
        for j,a in ipairs(invaders) do
            if a.p.x > max then
                max = a.p.x
            end
        end
    end
    print(os.time() - t)
end