Things we might do. And I’m thinking about conditionals. The next trick may surprise you.

Yesterday, I just almost randomly chose things to do, rather than make a list of what’s needed and choose from it. That may seem a bit odd. Aren’t we always supposed to be working on the most valuable thing? Doesn’t that mean we should have a list of things, ordered by value?

Well, as Kent Beck used to say, and probably still does, “That’s one way …”. Now whether you’re a team or just an individual on their iPad, it does make sense to think about all the things one might do, lest one forget something. But the more we immerse ourselves in a product, the less likely we are to forget anything. And in a team, there are many of us and someone is sure to remember important things, so if we are really communicating, the important stuff will come up.

Or so it seems to me. If you want a list, by all means make one. Lists are a good idea.

And, as it happens, I want to make a list today, and I’m not sure that I want to work on anything that’s in it. Here is a list of the things I can think of that the game might need, in the order I think of them.

  • Saucer is too probable now: it’s set to appear a lot so that I can test it.
  • General weapons timing looks OK to me, but I know it’s not matching the original game. I “should” look into that.
  • Once the game is over, you have to reset it to play again. Perhaps there should be a touch to start thing.
  • Attract mode might be fun to do. I have my doubts. If there’s learning to be had, we could do it.
  • Test results should disappear once the game is under way.
  • We should be sure that if someone imports the game, they don’t have to import CodeaUnit. One way would be to include it.
  • There are odd timing things and count-downs and such going on, and I wish they were more consistent and similar.
  • I feel that some important functionality is in the “wrong” place.
  • I feel that some things are just generally messy.

Those are the things that come to mind. The list brings up a larger topic:

Larger Topic

The game basically works. A few tweaks and we could consider it complete. Why, then, do I concern myself with putting the code in “the right place”, with converting a perfectly good nest of if statements to a strategy method, with “improving” the code?

Arguably, in a program of this size, we need not concern ourselves with these nicetie. The program is small and we can understand it well enough. Why do these things?

I do have reasons.

First, every decent big program is made up of small chunks. In principle, each of those chunks should be understandable. And those chunks are often as big as our whole program here. So if we learn how to make this program more understandable, we’ll be learning how to make any program more understandable.

Second, the place to study, practice, and learn how to test and refactor, if we have the luxury, shouldn’t be on our giant legacy program that no one dares touch because it’s the life blood of the company. Working on a smaller product like this is a good place to learn, and I’m hoping that you’ll learn from what you see here, and I’m hoping that you’ll build something of your own.

Third, I enjoy it. I like solving the puzzles that come up when I’m trying to create something. Some people work in their wood shop. I work in my code shop. I wish that were as popular a topic as Adam Savage’s work, because a YouTube income would be nice. But if the three of you enjoy it, that’s enough. If I enjoy it, that’s enough.

Moving right along …

Thinking about conditionals

Yesterday, we removed some fairly complex nests of conditionals, eliminating some and isolating others into more expressive understandable forms. We eliminated one set with a “strategy” method, a stored method that we changed out as the player moved from alive, to exploding, to having passed on to a higher plane.

I did all that, at least in part, because of the received wisdom that conditional statements are “bad” and shouldn’t be used, or at least their use should be limited.

Now I’m not entirely convinced that they’re bad, but at least by my standards of taste, what we did yesterday made the system’s code a lot better than it was. So that got me thinking about removing conditionals in general. Is it possible to remove all conditionals from a program? If we could, should we?

Now it’s clear on the face of it that some behavior of a program is “conditioned” by other behavior. If we have lives left, we spawn a new player, else game over. Clearly there’s a conditional in there.

If we loop over a collection, there’s a conditional going on in there somewhere. But we might argue that it’s the occurrence of if statements, and switches and the like that we’re against, and magical conditions inside loops don’t trouble us.

So this thinking leads me to want to look at some of the explicitly conditional behavior in Space Invaders, to see how we might remove it, and to decide whether we like what we did.

Game Over?

As I wrote the above, I got an idea about “Game Over”. Right now we have this code:

function GameRunner:requestSpawn()
    if Lives > 0 then
        Lives = Lives - 1
        self.player:spawn()
        self:setWeaponsDelay()
    end
end

Not terrible at all, although we currently have to initialize Lives to 4 to provide three lives, which is a bit naff. But we could handle that easily I’m sure, perhaps just by reordering the statements shown above. Or something. I’m sure we could.

But imagine a different design. Imagine a table that contained three instances of Player, and an instance of GameOverPlayer, an object that just sits there displaying “Game Over”. We’d just pop the top thing off the list, tell it to go, and we’re all set. Want more lives? Put more Players in the bucket.

We wouldn’t even have to put instances into the table. We could have a table of methods:

{ Player.spawn, Player.spawn, Player.spawn. Player.gameOver }

Then we’d just increment Lives and execute the table entry.

In thinking about this, I noticed a flaw in the game. No, not a flaw. An actual defect.

I got to thinking “how does Game Over happen, I don’t see it in that loop?” So I ran the game, let my guy die and two things happen.

Game Over comes up … but so does a fourth player, and he’s playable. It appears that a fifth one does not appear. It appears that somewhere along the way in improving Player, I broke the game over behavior. Super. We were just talking about that. Let’s fix it.

Fixing Game Over

Now if I recall correctly, Game Over gets displayed in some weird place like Main. I’ll seek it out.

function drawStatus()
    pushStyle()
    tint(0,255,0)
    sprite(Line,8,16)
    textMode(CORNER)
    fontSize(10)
    text(tostring(Lives), 24, 4)
    text("SCORE " .. tostring(Score), 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
    drawSpeedIndicator()
    popStyle()
end

So that’s fine, but what’s with getting four lives? Here’s the spawn again:

function GameRunner:requestSpawn()
    if Lives > 0 then
        Lives = Lives - 1
        self.player:spawn()
        self:setWeaponsDelay()
    end
end

Well, it’s clear enough that when there’s one life left, we will spawn a new player, that’s fine, but then Main will see zero Lives and display Game Over. What is less clear is that my supposed hack of Lives to 4 to get 3 in fact gives four lives, the fourth of which has to play on top of a mistaken Game Over sign.

And the display of players at the bottom of the screen is wrong as well, displaying the correct count of available lives, but one fewer icons than should be there. That’s in the displayStatus as well, which shows lives-2 icons. That used to be right but when we changed the sequence those other things should have been changed as well.

I’m going to go for chai now, but when I get back let’s talk about the underlying causes of this defect.

Chai and Causality

So why do we have these sudden defects around Lives? Reasons include:

Ron made a mistake
In rearranging the Player code, I made at least one mistake, arguably more. In particular, I changed where the player request happens, and that caused me to think that the display was wrong (which it may have been) and that I could fix it by hammering Lives. I believe I used the word “hammering”. But why did that happen?
I was working on something else
I think I was working on something else when I noticed that problem. I jumped to a conclusion, to get it off my plate. This is bad. But we can’t replace Ron. What’s behind that mistake?
Lives is global
It’s used in seven places, in two tabs, not counting one use in the tests, which is the ignored test that reminds me of this hack. Why does the fact that it’s a global get accused of causing this defect?
Globals are all over
We try in our programming to keep like things together and unlike things separate. But globals don’t follow that rule. They are accessible all over, and invariably they are accessed from all over, because if we didn’t want to do that we wouldn’t make them global.

If I’d been a bit more careful, I would have looked up all the accesses to Lives and considered them carefully. I don’t remember if I even looked. Clearly I didn’t look hard enough.

Is that on me? Yes, sure, not that I care. What I care about is what I can do so that I don’t have to rely on me quite so heavily. The tool, of course, is encapsulation, in this case, keeping all the lives stuff in one place, not two or three or seven.

So I’m going to fix the defect, but I’m going to try to fix the underlying problem as well, the existence of a global Lives variable and the use of it in multiple places in the system.

Hm, in other news, I seem to have no Internet. I wonder what’s up with that? I wonder if I have cable. No. Someone will call it in. I’ll carry on.

I had wanted to double check game play, to see what number of lives displays, and how many icons are displayed in the original. I guess I’ll just define what should happen in my view.

During play, with a live Player, the number of lives displayed should include the existing player, and the number of icons should be the number of backup players. So in a three-life game, when the first player is operating, the display should say 3 and display two icons.

Arguably, when the operating player is destroyed, the number of lives should immediately count down, but the number of icons should stay the same until one is taken up as the active player, at which point there should be one less icon.

My current plan is not to do that, just to have the number be the number of lives available and the icons one less. Except when you’re on your last guy. Hmm. OK, it goes like this:

3 P P
2 P
1
0

Optionally it could just go 3,2,1 and then nothing once Game Over comes up. We’ll deal with that in due time.

Since our problem can be considered to be encapsulation, and since encapsulation is nicely solved with objects, I think I’d like to TDD a new object, a LifeManager or something like that. This object will be intended to be used by the GameRunner to manage the life stuff. Let’s TDD.

TDDing LifeManager

        _:test("LifeManager provides three lives", function()
            local lm = LifeManager(3)
        end)

This is enough to break and demand a LifeManager object:

LifeManager = class()

function LifeManager:init()
end

Now what do we want it to do? I want to try that idea I had up early in the article. Let’s have LifeManager tell whoever asks what to do. It’ll return one of two items, which we’ll pass in on init, together with the number of lives we want. My tentative plan is that the items will be methods to be executed, but the LifeManager won’t care. For the test, I’ll pass in strings (which could be method names of course).

How about this:

        _:test("LifeManager provides three lives", function()
            local lm = LifeManager(3, "spawn", "gameOver")
            _:expect(lm:next()).is("spawn")
            _:expect(lm:next()).is("spawn")
            _:expect(lm:next()).is("spawn")
            _:expect(lm:next()).is("gameOver")
        end)

Let’s do it. I went too far and coded this:

function LifeManager:init(count, live, dead)
    self.lives = {}
    for i = 1,count do
        self.lives:insert(live)
    end
    self.lives:insert(dead)
    self.life = 1
end

function LifeManager:next()
    local result = self.lives[self.life]
    self.life = math.min(self.life+1, #self.lives)
end

I do expect the test to run. Well, except that you can’t call insert that way. So:

function LifeManager:init(count, live, dead)
    self.lives = {}
    for i = 1,count do
        table.insert(self.lives,live)
    end
    table.insert(self.lives,dead)
    self.life = 1
end

Well, except you have to return the result:

function LifeManager:next()
    local result = self.lives[self.life]
    self.life = math.min(self.life+1, #self.lives)
    return result
end
28: LifeManager provides three lives  -- OK

We’ll add one more call to that test to check the endplay that I went out on a limb and implemented:

        _:test("LifeManager provides three lives", function()
            local lm = LifeManager(3, "spawn", "gameOver")
            _:expect(lm:next()).is("spawn")
            _:expect(lm:next()).is("spawn")
            _:expect(lm:next()).is("spawn")
            _:expect(lm:next()).is("gameOver")
            _:expect(lm:next()).is("gameOver")
        end)

Still works. And I see no conditionals here. Interesting.

Now I’m guessing how this thing wants to be used, but I’ve been around the block here and so in addition to next, which consumes a life, I think we want current, which returns the current life without incrementing:

Let’s expand our test to ask for current after the last of the three nexts that return “spawn”. It should return “spawn”. I think we’re going to find this harder to do than we might like. Why? Because we’ve incremented self.life already:

        _:test("LifeManager provides three lives", function()
            local lm = LifeManager(3, "spawn", "gameOver")
            _:expect(lm:next()).is("spawn")
            _:expect(lm:next()).is("spawn")
            _:expect(lm:next()).is("spawn")
            _:expect(lm:current()).is("spawn")
            _:expect(lm:next()).is("gameOver")
            _:expect(lm:next()).is("gameOver")
        end)
28: LifeManager provides three lives -- Tests:312: attempt to call a nil value (method 'current')

This demands the method, and I’d like to write this:

function LifeManager:current()
    return self.lives[self.life]
end

I expect that to fail, and it does:

28: LifeManager provides three lives  -- Actual: gameOver, Expected: spawn

I think we have to increment before returning, so that current can work. So we’ll have to init life to zero:

function LifeManager:init(count, live, dead)
    self.lives = {}
    for i = 1,count do
        table.insert(self.lives,live)
    end
    table.insert(self.lives,dead)
    self.life = 0
end

function LifeManager:next()
    self.life = math.min(self.life+1, #self.lives)
    return self.lives[self.life]
end

function LifeManager:current()
    return self.lives[self.life]
end

Test runs. But I see duplication. Fixing it shows something interesting:

function LifeManager:init(count, live, dead)
    self.lives = {}
    for i = 1,count do
        table.insert(self.lives,live)
    end
    table.insert(self.lives,dead)
    self.life = 0
end

function LifeManager:next()
    self.life = math.min(self.life+1, #self.lives)
    return self:current() -- <---
end

function LifeManager:current()
    return self.lives[self.life]
end

Test still runs, of course, but now the code says explicitly that we update to a current pointer and then return the then-current result. I like that: it expresses what’s going on, and the connection between next and current rather nicely.

I think we’re going to want some additional methods on LifeManager, perhaps “how many are left” or something like that, when we go to display the life count and the icons. Let’s wait on that until we get the manager in play and see what we really want. That’s better than speculating.

The plan will be for GameRunner to have a LifeManager, and that anyone who wants to know anything about lives has to use the runner or the life manager to do it. That’s going to change a fair amount of code, or break it, depending on what we do.

Let’s put the object in place in GameRunner and proceed from there:

Here’s what I think I want to say:

function GameRunner:init()
    self.lm = LifeManager(3, self.spawn, self.gameOver)
    self.player = Player()
    TheArmy = Army(self.player)
    self:resetTimeToUpdate()
    self.weaponsTime = 0
end

I want to pass two methods into the LM and have them come back. So I need to implement those two methods:

function GameRunner:spawn()
    self.player:spawn()
    self:setWeaponsDelay()
end

function GameRunner:gameOver()
    assert(false,"game over")
end

Now I’ll use the LM in requestSpawn, which looks like this:

function GameRunner:requestSpawn()
    if Lives > 0 then
        Lives = Lives - 1
        self.player:spawn()
        self:setWeaponsDelay()
    end
end

That will just become this:

function GameRunner:requestSpawn()
    self.lm:next()()
end

If I’m right, that fetches the method and executes it. I expect the game to play properly, with weird life count and icons, but three lives. Let’s see why I’m wrong.

GameRunner:75: attempt to index a nil value (local 'self')
stack traceback:
	GameRunner:75: in function <GameRunner:74>
	GameRunner:71: in method 'requestSpawn'
	Player:98: in method 'manageSpawning'
	Player:93: in method 'manageCount'
	Player:86: in method 'update'
	GameRunner:58: in method 'update60ths'
	GameRunner:47: in method 'update'
	Main:32: in function 'draw'

That’s here:

function GameRunner:spawn()
    self.player:spawn() -- <---
    self:setWeaponsDelay()
end

Remember yesterday when I didn’t need to pass in self? Today I need to, or to do something a bit different. For now:

function GameRunner:requestSpawn()
    self.lm:next()(self)
end

Ha! Three players appear and die, then this happens:

GameRunner:80: game over
stack traceback:
	[C]: in function 'assert'
	GameRunner:80: in function <GameRunner:79>
	GameRunner:71: in method 'requestSpawn'
	Player:98: in method 'manageSpawning'
	Player:93: in method 'manageCount'
	Player:86: in method 'update'
	GameRunner:58: in method 'update60ths'
	GameRunner:47: in method 'update'
	Main:32: in function 'draw'

As intended! Sweet.

Before moving on, I want to think about that (self) and why we needed it today and not yesterday.

Today, the LifeManager:next returns a method, the actual function pointer into our method table. Lua has syntactic sugar that translates thing:foo() into thing.foo(thing) when it compiles, and the method is compiled as if you had included self as the first parameter. So we have no colon in our statement to tell Lua to do that substitution, se we have to do it ourselves. Yesterday, we had this:

    self.drawingStrategy(self)

Which we could rewrite as

    self:drawingStrategy()

And let Lua do the work. Let’s see how we could replicate that here. We can’t just say this:

function GameRunner:requestSpawn()
    self:self.lm:next()()
end

Because Lua can’t understand it. It also doesn’t like this:

function GameRunner:requestSpawn()
    self:(self.lm:next())()
end

The difference is that yesterday we actually defined (and redefined) a method, by assigning an existing method into self.drawingStrategy. We could do that here:

function GameRunner:requestSpawn()
    self.spawningMethod = self.lm:next()
    self:spawningMethod()
end

That works but it’s boring. We’ll stick with this:

function GameRunner:requestSpawn()
    self.lm:next()(self)
end

Let’s move on. We’re getting our gameOver method called. What should it do?

Well, it should display Game Over at the bottom of the screen and if current behavior is to be followed, then nothing else should happen until we restart the program. In due time we’ll insert another quarter or something.

Displaying Game Over is done in Main’s drawStatus function:

function drawStatus()
    pushStyle()
    tint(0,255,0)
    sprite(Line,8,16)
    textMode(CORNER)
    fontSize(10)
    text(tostring(Lives), 24, 4)
    text("SCORE " .. tostring(Score), 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
    drawSpeedIndicator()
    popStyle()
end

I think GameRunner should do that, so let’s move the whole thing over there. Huh! Curiously, GameRunner already calls it, it just doesn’t own it.

So moving it over is trivial, and I rename it into the GameRunner space and call it with self:. I’ll spare you reading that because I want to start making changes for it to call on the LifeManager for info. Let’s deal with the life count and the icons. And how about at least sort of grouping that stuff together.

function GameRunner:drawStatus()
    pushStyle()
    tint(0,255,0)
    sprite(Line,8,16)
    textMode(CORNER)
    fontSize(10)
    text("SCORE " .. tostring(Score), 144, 4)
    tint(0,255,0)
    text(tostring(Lives), 24, 4)
    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
    drawSpeedIndicator()
    popStyle()
end

I think the life count used to be white but now it’s going to be green. It’s in the green area of the screen so maybe it’s always green.

I think the plan is to display the number of lives you actually have, and one fewer icons than that. We don’t want to look at the Lives global any more but we do need that fact.

I think I want to start with this:

function GameRunner:drawStatus()
    pushStyle()
    tint(0,255,0)
    sprite(Line,8,16)
    textMode(CORNER)
    fontSize(10)
    text("SCORE " .. tostring(Score), 144, 4)
    tint(0,255,0)
    local lives = self.lm:livesRemaining() -- <---
    text(tostring(lives), 24, 4)
    local addr = 40
    for i = 1,lives-1 do
        sprite(asset.play,40+16*i,4)
    end
    if lives == 0 then
        textMode(CENTER)
        text("GAME OVER", 112, 32)
    end
    drawSpeedIndicator()
    popStyle()
end

Let’s TDD the livesRemaining function:

        _:test("LifeManager knows lives remaining", function()
            local lm = LifeManager(3, "spawn", "gameOver")
            _:expect(lm:livesRemaining()).is(3)
            lm:next()
            _:expect(lm:livesRemaining()).is(2)
            lm:next()
            _:expect(lm:livesRemaining()).is(1)
            lm:next()
            _:expect(lm:livesRemaining()).is(0)
            lm:next()
            _:expect(lm:livesRemaining()).is(0)
        end)

That seems right. Let’s implement the function. Here’s the easy part:

function LifeManager:livesRemaining()
    
end

So the number of lives remaining. Do we want to count the current life? I think we do. But we’re initializing to zero not 1. Let’s work backward. If self.life is equal to #self.lives, the answer is zero lives remaining: we’ve consumed the last one. That suggests that the answer is #self.lives - self.life. But that means that at the beginning, we’ll get 4. I expect my test will fail if I put that in. So I’ll put that in and we’ll see:

And indeed it fails:

~~~lua29: LifeManager knows lives remaining – Actual: 4, Expected: 3 ~~~

But all the tests fail, not just the first one. They’re all off by one but I don’t expect that. What do I expect?

Well, when the first call goes, self.life is zero, so 4-0 is 4 and I expected that failure. But after we call next() should the answer go down to 2? No, it shouldn’t. It stays at three until the current player dies. So the test is wrong, should be:

        _:test("LifeManager knows lives remaining", function()
            local lm = LifeManager(3, "spawn", "gameOver")
            _:expect(lm:livesRemaining()).is(3)
            lm:next()
            _:expect(lm:livesRemaining()).is(3)
            lm:next()
            _:expect(lm:livesRemaining()).is(2)
            lm:next()
            _:expect(lm:livesRemaining()).is(1)
            lm:next()
            _:expect(lm:livesRemaining()).is(0)
            lm:next()
            _:expect(lm:livesRemaining()).is(0)
        end)

This is confusing isn’t it? But I think that’s right and now I expect only the first check to fail. And it does:

29: LifeManager knows lives remaining  -- Actual: 4, Expected: 3

We need to handle the call before next has happened specially:

function LifeManager:livesRemaining()
    return #self.lives - math.max(self.life,1)
end

I think that’s the ticket.

Tests run. We’re already using this call in drawStatus:

function GameRunner:drawStatus()
    pushStyle()
    tint(0,255,0)
    sprite(Line,8,16)
    textMode(CORNER)
    fontSize(10)
    text("SCORE " .. tostring(Score), 144, 4)
    tint(0,255,0)
    local lives = self.lm:livesRemaining()
    text(tostring(lives), 24, 4)
    local addr = 40
    for i = 1,lives-1 do
        sprite(asset.play,40+16*i,4)
    end
    if lives == 0 then
        textMode(CENTER)
        text("GAME OVER", 112, 32)
    end
    drawSpeedIndicator()
    popStyle()
end

I think this ought to operate about as intended now. At first it shows three (But only two icons, is that a concern?)

three before

Then when the player appears we see this, which is certainly correct:

three

Then after one goes down:

two

And finally when they’re all gone we get Game Over displayed, but also our assertion crash in the gameOver method:

over

If we just remove that assert, I think the game plays as it did before.

function GameRunner:gameOver()
    return
end

I put the return in there in the spirit of “this page intentionally blank”, to indicate that it wasn’t just forgotten.

And yes, the game plays as it did before, coming to a “Game Over” state, no more players emerge, the invaders keep marching. We can commit this code: moved to LifeManager in GameRunner.

Well, except that I can’t. Working Copy’s new version has lost the connection to the files and I can’t figure out how to get it back. I’m sure Anders will help but I still have no cable. We’ll just have to live without code management for a while.

Anyway, let’s quickly sum up.

Summing Up

Surprisingly, a thing that I had been thinking about, the weird handling of the global Lives, and removing conditional statements, lined up with a defect that I hadn’t noticed after the code rearranging of yesterday.

So we fixed the defect by creating a new object, LifeManager, using TDD. We plugged it in and made it work for us. And, incidentally, it includes no explicit if statements at all. It accomplishes that with an odd trick. It creates a table of as many lives as you ask for, followed by an end marker. The user of LifeManager gets to say what values should be returned, but LifeManager does that however it wants.

Naturally, we could just save the two values and do something simpler than the table. And perhaps we should. But I wanted to try it and see if I liked it. On the one hand, I do like it, because it acts like a little cache of items and when the items run out, they’re out. On the other hand, it is an odd trick and we could surely code something that didn’t need the table.

Something like

return self.life>self.max and self.deadReturn or self.liveReturn

Now that’s pretty darn close to a conditional but it’s more of a guideline than an actual rule.

Anyway, for now, we have the behavior cornered in an object where we can implement it any way we wish.

Everything went quite smoothly, it seems to me, and I’m pleased with the result. I hope you are as well. If not, let me know. And if you are well, let me know about that too.

See you next time!

Invaders.zip