I’ve convinced myself that a global would be a better way to handle constants than the singleton. Kill your darlings.

The current syntax:

Constants:instance():yourNameHere()

is just too much. It seems to me to be obscuring what’s going on. Let’s try something like this:

Constant:yourNameHere()

Should be an easy conversion but we’ll preserve both forms at least for a while.

One question is where to create the global. I think it should be at the top of GameRunner’s init:

function GameRunner:init(numberOfLives)
    Constant = Constants()
    self.lm = LifeManager(numberOfLives or 3, self.spawn, self.gameOver)
    Shield:createShields()
    Player:newInstance()
    TheArmy = Army()
    self.sounder = Sound()
    self.line = image(208,1)
    for x = 1,208 do
        self.line:set(x,1,255, 255, 255)
    end
    self:resetTimeToUpdate()
    self.weaponsTime = 0
end

Now we should be able to change references at will:

function Army:initMemberVariables()
    self.weaponsAreFree = false
    self.armySize = 55
    self.invaderCount = 0
    self.invaderNumber = 1
    self.overTheEdge = false
    self.motion = Constants:instance():invaderSideStepVector()
    self.stepDown = Constants:instance():invaderDownStepVector()
    self.bombDropCycleLimit = 0x30
    self.bombCycle = 0
    self.saucer = Saucer(self)
    self.score = 0
end

becomes:

function Army:initMemberVariables()
    self.weaponsAreFree = false
    self.armySize = 55
    self.invaderCount = 0
    self.invaderNumber = 1
    self.overTheEdge = false
    self.motion = Constant:invaderSideStepVector()
    self.stepDown = Constant:invaderDownStepVector()
    self.bombDropCycleLimit = 0x30
    self.bombCycle = 0
    self.saucer = Saucer(self)
    self.score = 0
end

Test. Works. Let’s do the rest of the changes in Army, then test and commit:

function Army:runSaucer()
    if self.saucer:isRunning() then return false end
    if self:yPosition() > Constant:saucerRunningLimit() then return false end
    if math.random(1,20) < 10 then return false end
    self.saucer:go(Player:instance():shotsFired())
    return true
end

function Army:processMissile(aMissile)
    if aMissile.v > 0 and aMissile.pos.y <= Constant:missileTop() then
        self:checkForKill(aMissile)
    end
end

Looks good. Commit: convert Army to new constant format.

function Saucer:go(shotsFired)
    self.startPos, self.stopPos, self.step = Constant:saucerStartInfo()
    if shotsFired%2==1 then
        self.step = -self.step
        self.startPos,self.stopPos = self.stopPos,self.startPos
    end
    self.alive = true
    self.pos = self.startPos
    self.exploding = 0
    Runner:soundPlayer():play("ufoLo")
end

Works. Commit: converted Saucer to new constant format.

function MarshallingCenter:marshalArmy(army)
    local bottomY = Constant:invadersBottomY()
    local startY = bottomY + 5*16
    local invaders = {}
    for row = 1,5 do
        for col = 11,1,-1 do
            local p = vec2(col*16, startY-row*16)
            table.insert(invaders, 1, Invader(p,self.vaders[row], self.scores[row], army))
        end
    end
    return invaders
end

Commit: converted MarshallingCenter to new constant format.

function Missile:handleOffScreen()
    if self.pos.y > Constant:missileTop() then
        self.explodeCount = 15
    end
end


function Missile:explosionColor()
    return self.pos.y > Constant:missileTop() and color(255,0,0) or color(255)
end

Works: commit: converted Missile to new constant format.

OK, I think I like that better. Now, for a bit of safety, let’s rename the class Constants to GameConstants. The class name is a bit too close to the global name.

Done. Commit: rename Constants class to GameConstants.

Display Improvements

We have some display anomalies. The Touch to Start is right on top of the top row of invaders, and the test results are too low:

Let’s move the Touch to Start up by 24 pixels, a rank and a half, and see if that leaves room for the tests.

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

This isn’t good. This text is being drawn in regular screen scale. We should draw it in game scale. That’s going to be a bit tricky. The same will be true for the tests, I imagine.

This will take a bit of organization. Or … we do have the ability to compute the scale factors. Let’s use them “in reverse” to position the Touch to Start and tests.

We can get scale from this method:

function GameRunner:scaleAndTranslationValues(W,H)
    local gameScaleX = 224
    local gameScaleY = 256
    rectMode(CORNER)
    spriteMode(CORNER)
    local sc = math.min(W/gameScaleX, H/gameScaleY)
    local tr = W/(2*sc)-gameScaleX/2
    return sc,tr
end

We’ll also want to get at the saucer level and go a bit above it. With a little refactoring, we extract that information:

function GameConstants:saucerStartInfo()
    local saucerY = self:saucerY()
    return vec2(8,saucerY), vec2(208,saucerY), vec2(2,0)
end

function GameConstants:saucerY()
    return self:invadersBottomY() + 0x50
end

The saucerY used to be inlined. I broke it out so that I could do this:

function drawTouchToStart()
    pushStyle()
    stroke(255)
    fill(255)
    fontSize(50)
    local sc = Runner:scaleAndTranslationValues(WIDTH,HEIGHT)
    local y = (Constant:saucerY() + 0x10)*sc
    text("Touch to Start", WIDTH/2, y)
end

Here I compute the y coordinate as if scaled as 16 above the saucer. Then I scale it and use it.

I decided to do the same thing with the tests:

function showTests()
    if not CodeaUnit then return end
    if not CodeaVisible then return end
    pushMatrix()
    pushStyle()
    fontSize(50)
    textAlign(CENTER)
    ...
    local sc = Runner:scaleAndTranslationValues(WIDTH,HEIGHT)
    local y = (Constant:saucerY() + 0x20)*sc
    text(Console, WIDTH/2, y)
    popStyle()
    popMatrix()
end

And the result is quite nice:

new start

So that’s fine.

Would you believe …

I did the GameConstants refactoring late last night after having heard enough of the “debate”. Would you believe that I have an idea that I think might be a bit better? Well, I have. It goes like this:

How Codea Lua Classes Work

A Codea Lua class is just a table. As a table, it contains hashed key-value pairs, where each value is either a simple value like a string or number, or a function. If it’s a function, it acts as a method, and if it’s a value, it’s a member. Lua includes a bit of syntactic sugar to translate

myObject:someOp(a,b,c)

into

myObject.someOp(myObject, a, b, c)

A function defined as

function MyClass:someOp(a,b,c)

Is sugared into

MyClass.someOp = function(self,a,b,c)

Then there’s one more trick, which is that when we write

MyClass = class()

The class function sets up the initial table in the global variable MyClass with enough stuff to create new instances and call their init. Voila! Classes and instances.

What if there can be only one?

If we only want one of these things, which is the case for our constants, what if instead of this:

GameConstants = class()
...
Constant = GameConstants()

What if we just said this:

Constant = {}

And then defined all our methods directly on Constant?

Constant would behave exactly like an instance of GameConstants. But since it’s not defined as a class, you can’t make another instance. There can be only one.

I’m going to try that in a moment, but first I want to talk about all this switching around.

It’s just one damn thing after another

One of the things I learned rather late in life was that despite my excellent design sense and my amazing ability to imagine how an interface should be, I was far from perfect at it. You know that thing that happens when you adopt someone else’s library, and nothing seems quite right? Well, when I’d create my own designs without using them, and finally got around to using them … nothing seemed quite right. Ideas that seem good in the head often turn out to be not quite as good in the code.

So I learned to try things as a user of the things, not just the designer of the things. In essence that’s what we’re doing when we use TDD1 on a new object: we become its first user, and as the user, our experience feeds back into the design, making it better.

In this little program I’ve tried many things and then changed them. Most recently there have been various attempts to reduce the number of global variables, use of singletons, and so on. Yesterday I decided that for constants, I preferred a global to the singleton syntax.

Today, I’m going to build everything into that global, which will result in saving … approximately one line. Why? Not to save the line, but because I want to try the technique, and I think it will be just a bit nicer.

Here goes!

Constants, Phase III

At least III. Maybe IV or even MCMLXVII. Presently our constants class looks like this:

GameConstants = class()

local singleton

function GameConstants:instance()
    if not singleton then singleton = Constants() end
    return singleton
end

function GameConstants:init()
end

function GameConstants:invadersBottomY()
    return 0x78
end

function GameConstants:saucerRunningLimit()
    return self:invadersBottomY() - 2*self:invaderDownStep()
end

... and more ...

In GameRunner, we create our instance, Constant:

function GameRunner:init(numberOfLives)
    Constant = GameConstants()
    self.lm = LifeManager(numberOfLives or 3, self.spawn, self.gameOver)
    Shield:createShields()
    ...

I’ll remove that line right now. Poof, gone. Now in GameConstants, I’ll rename it to Constant, make it a table, not a class, remove the init and singleton stuff, and replace the name GameConstants with Constant throughout:

Constant = {}

function Constant:invadersBottomY()
    return 0x78
end

function Constant:saucerRunningLimit()
    return self:invadersBottomY() - 2*self:invaderDownStep()
end

function Constant:invaderDownStep()
    return 8
end
... and more ...

If I’m not sorely mistaken, everything should run just as before. Oddly enough, I’m not sorely mistaken. I’ll rename the GameConstants tab to Constant, and I think I’ll add a warning comment because this is a new technique in our code base.

-- Constant
-- RJ 20201008
-- Note: not a class, a single object

Constant = {}

Commit: Constant converted to single object.

We’ll live with this a while. Possibly there will be some reason not to like it, but I think it’ll be OK.

You might be wondering about member variables rather than functions. We can have those in the usual fashion. A method on Constant can define and refer to self.myVar just as we would in an instance, because whenever we call a method, Constant itself is passed in. If we wanted to define a bunch of them, we’d give it an initialize or init method. Right now, even our literal constants are returned from methods, so we don’t use this ability, but we could.

What else? I think that’s enough for one article, and I have a video call at 9, so let’s wrap up.

Wrapping Up

So, a few things in this article, which encompasses two sessions, a quick one last night and another this morning.

We revised the handling of constants not once but twice, to what I think is a good result. And we improved the look of the startup display a bit. I think the new constants object helped with that, because it gave me rather quick access to the information I needed to adjust the display.

I did use the scaling information from GameRunner in doing that. That’s OK, because Main knows the Runner, but it’s a bit odd that the GameRunner would have scaling built in, given that its main function is running the game. Possibly that would be better if broken out somewhere. I don’t see where offhand, and it’s just the one thing. It used to be used only once. Now it’s used three times. That does argue for giving it some attention.

But we probably won’t. We have other invaders to fry.

I think I like the new Constant object. It has just the single global, its name, and there is only one by its very nature. It’s a true singleton in that sense, and unless we for some reason need to change the constants of the universe, I think this design approach will stand.

What will we do tomorrow? I honestly don’t know.

Oh, and a request. If you are reading these articles, and have questions or comments, do feel free to tweet at me or even email questions or ask on the Codea forum. I will benefit from the feedback and will answer your questions if I can. If you want to remain anonymous, let me know and I’ll refer to you as “A Reader” or something.

See you soon!

Invaders.zip


  1. Test-Driven Development, a technique where we write a failing test, make it pass, improve the code, repeat. (I’ve been asked to define my terms more often, so I thought I’d try it.)