Saucer Scoring, then whatever makes sense to me at the time.

I’m actually starting this article Tuesday night, avoiding the “debate”. Our mission is to implement the variable scoring for the saucer in invaders. It’s described this way:

The score for shooting the saucer ranges from 50 to 300, and the exact value depends on the number of player shots fired. The table at 0x1D54 contains 16 score values, but a bug in the code at 0x044E treats the table as having 15 values. The saucer data starts out pointing to the first entry. Every time the player’s shot blows up the pointer is incremented and wraps back around. Here is the table. You have to append a trailing “0” to every value to get the three digit score.

; 208D points here to the score given when the saucer is shot. It advances 
; every time the player-shot is removed. The code wraps after 15, but there
; are 16 values in this table. This is a bug in the code at 044E (thanks to
; Colin Dooley for finding this).
;
; Thus the one and only 300 comes up every 15 shots (after an initial 8).
1D54: 10 05 05 10 15 10 10 05 30 10 10 10 05 15 10 05 

There are five entries of 050, eight entries of 100, two 150s, and only one 300. The 300 score comes up every 15 shots (after an initial eight). It should come up every 16, but again – the code has a bug.

I decided to write a trivial but comprehensive test for this feature. To make that reasonable, I needed to refactor the Player:fireMissile() function, to pull out the firing logic:

function Player:fireMissile()
    if not self.alive then return end
    if self.missile.v == 0 then
        self:unconditionallyFireMissile()
    end
end

function Player:unconditionallyFireMissile(silent)
    self.missile.pos = self.pos + vec2(7,5)
    self.missile.v = 1
    self.missileCount = self.missileCount + 1
    if not silent then SoundPlayer:play("shoot") end
end

I put in the silent flag so that the tests aren’t noisy. Then I removed our old test that just checked that some score came out, and wrote this one:

        _:test("Saucer Score depends on shots fired", function()
            local army = Army()
            Gunner = Player()
            local s = army.saucer
            _:expect(s:score()).is(100)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(50)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(50)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(100)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(150)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(100)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(100)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(50)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(300)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(100)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(100)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(100)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(50)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(150)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(100)
            Gunner:unconditionallyFireMissile(true)
            _:expect(s:score()).is(100)
        end)

I just typed in the first 15 values (plus a zero) and then wrapped around to the first again.

Then let’s define the table in Saucer:

function Saucer:init(army, optionalShotsFired)
    self.army = army
    self.exploding = 0
    self.scores = {100,50,50,100,150,100,100,50,300,100,100,100,50,150,100}
    if optionalShotsFired == nil then
        self.alive = false
    else
        self:go(optionalShotsFired)
    end
end

I typed the table here independently of the test, referring to the code as shown above again. This should give me a reasonable chance of a fair test.

Now to implement the new score function:

function Saucer:score()
    return self.scores[Gunner:shotsFired()%15 + 1]
end

This seems trivial. Just wrap shots fired at 15 and index into our 1-based table.

And the test runs:

32: Saucer Score depends on shots fired  -- OK
(16 times)

So that’s good. Nothing has ever been that easy in my whole life. I wonder what’s wrong. Anyway commit: saucer scores depend on shots fired.

That’ll do for Tuesday night, we’ll pick this up again tomorrow morning.

Wednesday’s Child

… full of woe, if I’m not mistaken. Sure am glad I wasn’t born today. Nor was I born yesterday, so don’t think you can put anything over on me. I’ve got my weather eye peeled and my powder dry. Whoa, hold up there big fella, we’ve got work to do.

But first, a word from our sponsor:

Extract Method

My favorite refactoring, by far, is Extract Method, where we find a block of code that “means something”, and pull it out into a separate method, calling it from the place of extraction. In recent days, I’ve used this refactoring with two different purposes.

One purpose is to improve the clarity of an existing method. A rule of thumb that I was taught was that a method should either “do some one thing” or call other methods. (That pattern is often called Composed Method.) A single method shouldn’t do two things. If two things need to be done, it should call two methods, one for each thing.

One recent example of that is at the very beginning of Space Invaders 52, where we were working toward implementing Player drawing as a single call. Feel free to check out that article and come back.

We also used Extract Method to improve clarity just last time in Space Invaders 58, where the Army init was a long mass of code and wound up substantially simplified, although still not squeaky clean:

function Army:init(player)
    self.player = player
    self:marshalTroops()
    self.weaponsAreFree = false
    self.armySize = 55
    self.invaderCount = 0
    self.invaderNumber = 1
    self.overTheEdge = false
    self.motion = vec2(2,0)
    self:defineBombs()
    self.bombDropCycleLimit = 0x30
    self.bombCycle = 0
    self.saucer = Saucer(self)
    self.score = 0
end

This needs one more extract to meet my arbitrary Wednesday standards so let’s do it now. Up near the top (but not at the top, what’s up with that), we have a call to marshalTroops. Other than that we have a call to `defineBombs, down in the middle somewhere, and the rest is initializing member variables. That there are so many suggests that this is a bit too much of a God object and perhaps needs splitting. But for now, we can at least make this a lot simpler.

I would say in general that we should init our own variables first, then call the specialized initializers. So I’ll begin by moving the two method calls down to the bottom:

function Army:init(player)
    self.player = player
    self.weaponsAreFree = false
    self.armySize = 55
    self.invaderCount = 0
    self.invaderNumber = 1
    self.overTheEdge = false
    self.motion = vec2(2,0)
    self.bombDropCycleLimit = 0x30
    self.bombCycle = 0
    self.saucer = Saucer(self)
    self.score = 0
    self:marshalTroops()
    self:defineBombs()
end

I expect this to work, so I’ll test it. That works just fine. Now I’l just extract all that setting stuff:

function Army:init(player)
    self:initMemberVariables(player)
    self:marshalTroops()
    self:defineBombs()
end

function Army:initMemberVariables(player)
    self.player = player
    self.weaponsAreFree = false
    self.armySize = 55
    self.invaderCount = 0
    self.invaderNumber = 1
    self.overTheEdge = false
    self.motion = vec2(2,0)
    self.bombDropCycleLimit = 0x30
    self.bombCycle = 0
    self.saucer = Saucer(self)
    self.score = 0
end

Now we can more easily see the pattern of this complicated init function. To my taste, the code is a bit more clear. I can generally ignore all three of the lower methods, and if I need to look further, I’ll surely only need to look at one of them.

Extract Method for clarity.

The other case where I’ve used Extract Method lately has been in breaking out a function simply to test it. Last night’s exercise was a perfect example.

We went from this:

function Player:fireMissile()
    if not self.alive then return end
    if self.missile.v == 0 then
        self.missile.pos = self.pos + vec2(7,5)
        self.missile.v = 1
        self.missileCount = self.missileCount + 1
        if not silent then SoundPlayer:play("shoot") end
    end
end

To this:

function Player:fireMissile()
    if not self.alive then return end
    if self.missile.v == 0 then
        self:unconditionallyFireMissile()
    end
end

function Player:unconditionallyFireMissile(silent)
    self.missile.pos = self.pos + vec2(7,5)
    self.missile.v = 1
    self.missileCount = self.missileCount + 1
    if not silent then SoundPlayer:play("shoot") end
end

My immediate reason for that refactoring was to give me access to a method that fired missiles, so that I could check the mystery saucer score algorithm. But this is a righteous refactoring on its own. Let me refactor a bit more:

function Player:fireMissile()
    if self.alive and self.missile.v == 0 then
        self:unconditionallyFireMissile()
    end
end

function Player:unconditionallyFireMissile(silent)
    self.missile.pos = self.pos + vec2(7,5)
    self.missile.v = 1
    self.missileCount = self.missileCount + 1
    if not silent then SoundPlayer:play("shoot") end
end

Now we see more clearly how the first method deals with the conditions, and the second deals with the actual firing. We could consider some renaming here for further improvement, but here’s my real point:

It’s true that I pulled out that method because I wanted to unconditionally fire a missile, for a test. But the Composed Method pattern would suggest breaking up fireMissile anyway.

This approach does lead to the existence of tiny little methods, including even one line methods. This is a much more popular style in Smalltalk, where I learned most of my OO, than it Java, where people mostly learn from stack overflow. In Smalltalk we used to say that when you wanted to know how some method worked, you’d drill down and down and finally, at the very bottom, someone would return a constant and it would all unwind with the right answer.

It very much feels like that, and I like it. YMMV, as I’ve said before. I’m here to show you what I do, and what happens to me when I do it. Your mission is to decide what you’re going to do and deal with your own outcomes. I’ve got enough going on here without dealing with your stuff. 😀

So, that’s a word from Extract Method. Now let’s do some more work. First, commit: small refactorings, Army and Player.

What Now?

What’s left on the list of actual game features that might need doing?

  • Ability to start and restart the game
  • Retain high scores
  • Two-player mode

And here’s a partial list from Space Invaders 53:

  • 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.
  • 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.

I’m a bit more comfortable now that things are in the right places, in that we’ve moved some. Others are still probably able to be improved. But that’s not very interesting, is it?

Cleaning up timing might be interesting, but if it ain’t broke, why fix it?

Touch to Start

Let’s do this. It feels like about the right size. Here’s the story:

When the program starts, the tests display (if CodeaUnit is present). Then a banner appears saying “Touch to Start Game”. When the player touches that, the game starts. When the last gunner dies, the game briefly displays “Game Over” as it does now, and then returns to “Touch to Start Game”.

When the player touches the banner for the first time, make the test results disappear.

I’ve made that into two stories, since there’s rather an obvious split there.

The story doesn’t say what should be going on on the screen during the “Touch to Start Game” phase. I’m inclined to let the invaders march, and maybe provide a simple attract mode at a later date, where the Gunner moves and shoots.

How should we do this?

I suppose there are may ways. I see two main possibilities. First, we could extend GameRunner to have the relevant states. Alternatively, we could create a top-level state managing thing that toggles between the modes, firing up a new GameRunner as needed.

That latter way sounds better to me. Let’s see what the code thinks.

GameRunner just plain runs the game, down to detecting that all the gunners are gone, at which points it displays “Game Over” and the invaders keep on marching. There is no general “Game Over” transition, it’s just that there are no more lives to spawn. When the players explode, they put on their little show, then wait a while, and ask for a respawn. The GameRunner has decided not to tell the player to spawn, so nothing more can happen. Except that the invaders continue to march.

I’d like to try a simple experiment, where if the game is over, I can touch the screen and make a new GameRunner and see what happens. Or, perhaps, restart the existing one. Making a new one will be easier, I think.

So in Main, we have:

function setup()
    runTests()
    parameter.boolean("Cheat", false)
    Runner = GameRunner()
    SoundPlayer = Sound()
    invaderNumber = 1
    Lives = 4
    Line = image(208,1)
    for x = 1,208 do
        Line:set(x,1,255, 255, 255)
    end
end

Everything after the parameter is “startGame”, down to creating the Line. Let’s refactor. Here comes Extract Method again, plus a little reordering:

function setup()
    runTests()
    parameter.boolean("Cheat", false)
    Line = image(208,1)
    for x = 1,208 do
        Line:set(x,1,255, 255, 255)
    end
    startGame()
end

function startGame()
    Runner = GameRunner()
    SoundPlayer = Sound()
    invaderNumber = 1
    Lives = 4
end

Now in Main:touched:

function touched(touch)
    Runner.player:touched(touch)
end

Let’s do this:

function touched(touch)
    if Lives ~= 0 then
        Runner.player:touched(touch)
    else
        if touch.state == ENDED then
            startGame()
        end
    end
end

As this stands, I think that touching the screen when the game is over should restart everything. I’m goin in, cover me.

Ah. I forgot. We don’t use that Lives variable any more. It should have been deleted. We need to ask the LifeManager. Main doesn’t have access to that but GameRunner does. Let’s ask GameRunner for livesRemaining().

function touched(touch)
    if Runner:livesRemaining() ~= 0 then
        Runner.player:touched(touch)
    else
        if touch.state == ENDED then
            startGame()
        end
    end
end

But GameRunner doesn’t know how many lives remain, so:

function GameRunner:livesRemaining()
    return self.lm:livesRemaining()
end

Now then try again. And it works, as we see here in the video. The transition is abrupt, you scarcely notice but the invaders have rewound and the game runs again.

over

OK, proof of concept succeeds. We can restart the game by calling that startGame function. Now let’s think about what we “really” want. Something like:

Program starts, test results display, B: invaders start marching. Maybe the player starts shooting, getting killed, looking exciting. Soon, test results go away. “Touch to Start” show up somewhere away from the scene, probably up top. But inside the game area because of scaling. When touched, maybe the screen should blank for a second or two, then start the game. When game is over, for now, go back to point B.

We need a way to turn off the test display. At present we have, in Main:

function draw()
    pushMatrix()
    pushStyle()
    background(40, 40, 50)
    showTests()
    Runner:draw()
    popStyle()
    popMatrix()
    Runner:update()
end

Let’s have a new function hideTests() that we can ship with CodeaUnit after an update.

After a brief discussion with the voices in my head, I come up with this:

function runTests()
    if not CodeaUnit then return end
    testsVisible(true)
    local det = CodeaUnit.detailed
    CodeaUnit.detailed = false
    Console = _.execute()
    CodeaUnit.detailed = det
end

function testsVisible(aBoolean)
    CodeaVisible = aBoolean
end

function showTests()
    if not CodeaUnit then return end
    if not CodeaVisible then return end
    pushMatrix()
    pushStyle()
    fontSize(50)
    ...

Now I should be able to turn off the test display. Let’s, for now, patch that into the touch so I can have them disappear at the second game start:

function touched(touch)
    if Runner:livesRemaining() ~= 0 then
        Runner.player:touched(touch)
    else
        if touch.state == ENDED then
            testsVisible(false)
            startGame()
        end
    end
end

I don’t like that function name any more. I’ll consult the voices again but for now this should do the trick I have in mind.

over no tests

The top of the screen looks pretty blank after the restart, doesn’t it? So that’s good.

I’m going to commit: screen touch restarts after game over.

Digression: Lives Remaining

I’d like to call out what we did there with lives remaining. We could have done this in Main:

    Runner.lm:livesRemaining()

That would have worked. But it relies on Runner’s particular implementation of life tracking, and the protocol of an object that currently is really only a concern of GameRunner.

So instead we implemented that trivial method on GameRunner:

function GameRunner:livesRemaining()
    return self.lm:livesRemaining()
end

That’s better, in my view, because it allows GameRunner to manage lives any way it chooses, while still making information available where it’s needed.

This is why one winds up with so many one-line methods. Which I like, and you, well, you get to like them or not. Tell you this, though, it’s easier to get a one-line method right than a two-line one.

I’m coming to the end of my allotted programming time here but let’s see if we can’t at least get the touch to start to appear and take effect at the very beginning. We could do that trivially by not calling startGame from setup. However, then there would be no GameRunner, no one to ask about lives remaining.

I think I’ll hack that a bit with good intentions to clean it up when we get this thing where we need it. We remove the start call from setup:

function setup()
    runTests()
    parameter.boolean("Cheat", false)
    Line = image(208,1)
    for x = 1,208 do
        Line:set(x,1,255, 255, 255)
    end
end

Now Runner is nil. Therefore:

function touched(touch)
    if Runner and Runner:livesRemaining() ~= 0 then -- <---
        Runner.player:touched(touch)
    else
        if touch.state == ENDED then
            testsVisible(false)
            startGame()
        end
    end
end

I just checked that Runner exists before forwarding. Now I expect just the tests on screen until I touch.

That’s not quite what happens. There’s a game running. I think it’s from the tests. Let’s do this in Tests tab:

        _:after(function()
            Gunner = nil
        end)

I think that nilling the Gunner should do the trick. But it does not. There’s a game running, with sound off. Well, Main draw is this:

function draw()
    pushMatrix()
    pushStyle()
    background(40, 40, 50)
    showTests()
    Runner:draw()
    popStyle()
    popMatrix()
    Runner:update()
end

The fact that it’s drawing and updating tells me that there’s a Runner there. I’m not sure where it came from. I’m going to nil it in setup.

function setup()
    runTests()
    parameter.boolean("Cheat", false)
    Line = image(208,1)
    for x = 1,208 do
        Line:set(x,1,255, 255, 255)
    end
    Runner = nil
end

Perfect. Now draw explodes, as it should. Therefore:

function draw()
    pushMatrix()
    pushStyle()
    background(40, 40, 50)
    showTests()
    if Runner then Runner:draw() end
    popStyle()
    popMatrix()
    if Runner then Runner:update() end
end

Again, this is getting messy but I plan to sort it all out in due time. For now, make it work.

over 3

That works as anticipated, nothing shows but tests until I touch, then the game starts.

Now let’s do “Touch to Start”:

function draw()
    pushMatrix()
    pushStyle()
    background(40, 40, 50)
    showTests()
    if Runner then 
        Runner:draw()
    else
        drawTouchToStart()
    end
    popStyle()
    popMatrix()
    if Runner then Runner:update() end
end

This seems reasonable, now then

function drawTouchToStart()
    pushStyle()
    stroke(255)
    fill(255)
    fontSize(50)
    text("Touch to Start", WIDTH/2, HEIGHT-300)
end

touch

That looks good. It won’t come up yet on Game Over, because we only do this when Runner is nil. So we have to nil it when we discover that the game is over:

function touched(touch)
    if Runner and Runner:livesRemaining() ~= 0 then
        Runner.player:touched(touch)
    else
        Runner = nil
        if touch.state == ENDED then
            testsVisible(false)
            startGame()
        end
    end
end

Again, this is messy but I’m sure we’ll sort it out next time. Now I expect the process to work clear through, first game to next.

It works, but the Touch doesn’t come up after Game Over. Ah. That nil doesn’t get set until we touch. That won’t do.

Our only other hook right now is in draw, so it has to go there.

function draw()
    pushMatrix()
    pushStyle()
    background(40, 40, 50)
    showTests()
    if Runner then 
        Runner:draw()
    else
        drawTouchToStart()
    end
    popStyle()
    popMatrix()
    if Runner then Runner:update() end
    if Runner and Runner:livesRemaining() == 0 then
        Runner = nil
    end
end

Now it better work. Well, it does but I don’t like it. What happens now is that as soon as the Game is over, the screen blanks back to the Touch to Start, with nothing else on screen. I don’t really want to kill the Runner after all, just check for lives.

Still trying to make it work, still committed to making it right after it works.

And it works as intended. Here’s the screen after a game ended:

game end

So now I think it’s working as intended. I can’t think how I would have TDD’d this. It’s more of a spike, to learn how to do this, except that I intend to save it, since it does work.

Commit: touch to start implemented.

We’ll figure out a more nearly right way to do this next time. For now, let’s sum up.

Summing Up

A bit of a long session today, perhaps 2.5 hours instead of 2. I’ll manage somehow.

We spoke of Extract Method, my favorite refactoring, and how it tends to generate small methods that do one necessary thing.

We used a trivial forwarding method on GameRunner to get access to lives remaining. This is to be preferred over ripping the guts out of GameRunner and asking the guts questions. That is called haruspicy and it is generally considered to be an obsolete and overly invasive form of information gathering.

Trust me, if they want to do exploratory surgery, you’re in a situation you’d rather not be in. Much better if they can forward their questions to an x-ray machine. Or just ask you if your tummy hurts. It might suffice. But I digress. We like forwarding messages over drilling down to find information.

Then we decided to work on the touch to start story and have a rudimentary and somewhat random version working, with most of the work being done in Main. Not to call my shot yet, but I’m thinking in terms of some kind of MachineState object that manages the various top-level displays and actions, like displaying tests, displaying high scores, running attraction, running the real game, and so on. We’ll see. You can be pretty sure that we’ll develop that thing, or whatever thing, incrementally.

That is the way.

See you next time!

Invaders.zip