Let’s clean up that level handling a bit.

The new level handling works well, but it could benefit from being a bit less awkward and a bit more clear. I’ve got an idea. Here’s what we have now:

``````function Universe:defineLevels()
self.t10 = {}
self.t20 = {}
self.t30 = {}
self.t40 = {}
self.t50 = {}
self.t60 = {}
self.t70 = {}
self.t80 = {}
self.t90 = {}
self.drawLevels = {}
self.drawLevels.backgound = self.t10
self.drawLevels.ship = self.t20
self.drawLevels.saucer = self.t30
self.drawLevels.asteroid = self.t40
self.drawLevels.missile = self.t50
self.drawLevels.splat = self.t60
self.drawLevels.fragment = self.t70
self.drawLevels.score = self.t80
self.drawLevels.buttons = self.t90
end

function Universe:drawEverything()
local tables = {self.t10, self.t20, self.t30, self.t40, self.t50, self.t60, self.t70, self.t80, self.t90}
for i, tab in ipairs(tables) do
for k,o in pairs(tab) do
self:drawProperly(o)
end
end
end
``````

When people register, it’s like this:

``````function Ship:init(pos)
self.pos = pos or vec2(WIDTH, HEIGHT)/2
self.step = vec2(0,0)
self.scale = 2
self.drawLevel = U.drawLevels.ship
end

function Ship:makeRegisteredInstance(pos)
if Instance then U:deleteObject(Instance) end
local ship = Ship(pos)
Instance = ship
return ship
end
``````

Everyone takes a draw level from `U`, and then `U` uses it to fill in the appropriate table, in a two-step process for adding, because we don’t want to add when looping in the collision finder:

``````function Universe:addObject(object)
end

self.objects[k] = v
v.drawLevel[k] = v
end
end
``````

Here we grab the drawLevel from the object being added, and tuck it away in `objects` which is used for collisions, and in the appropriate `drawLevel` table, which the object holds onto as a token. It could be a string or anything, really.

Arguably, it’s risky passing those tables around to everyone. If they messed with it, bad things could happen. But our objects don’t come marching in here busting things up, they collaborate and they don’t mess around with things they shouldn’t mess with. The table is just a pointer anyway, so no harm done. Still, worth thinking about. Thinking is good.

Anyway, let’s clean this up. I don’t really like the definition of the drawLevels:

``````function Universe:defineLevels()
self.t10 = {}
self.t20 = {}
self.t30 = {}
self.t40 = {}
self.t50 = {}
self.t60 = {}
self.t70 = {}
self.t80 = {}
self.t90 = {}
self.drawLevels = {}
self.drawLevels.backgound = self.t10
self.drawLevels.ship = self.t20
self.drawLevels.saucer = self.t30
self.drawLevels.asteroid = self.t40
self.drawLevels.missile = self.t50
self.drawLevels.splat = self.t60
self.drawLevels.fragment = self.t70
self.drawLevels.score = self.t80
self.drawLevels.buttons = self.t90
end
``````

Why does it name those tables with weird names and then assign them to the keys people look up. Why can’t we just assign the tables directly, like this:

``````function Universe:defineLevels()
self.drawLevels = {}
self.drawLevels.backgound = {}
self.drawLevels.ship = {}
self.drawLevels.saucer = {}
self.drawLevels.asteroid = {}
self.drawLevels.missile = {}
self.drawLevels.splat = {}
self.drawLevels.fragment = {}
self.drawLevels.score = {}
self.drawLevels.buttons = {}
end
``````

That’s like twice as simple and since I’ve already made one mistake in that method yesterday, simple is better.

We can’t do that, however, because of this:

``````function Universe:drawEverything()
local tables = {self.t10, self.t20, self.t30, self.t40, self.t50, self.t60, self.t70, self.t80, self.t90}
for i, tab in ipairs(tables) do
for k,o in pairs(tab) do
self:drawProperly(o)
end
end
end
``````

Because the names `t10, t20` and so on defined the order. Let’s take it as written that we’ll use the names instead:

``````function Universe:drawEverything()
local drawingOrder = {"background", "ship", "saucer", "asteroid", "missile", "splat", "fragment", "score", "buttons" }
for i, name in ipairs(drawingOrder) do
for k,o in pairs(self.drawLevels[name]) do
self:drawProperly(o)
end
end
end
``````

This works, because given a table named `drawLevels`, these two Lua expressions are exactly the same thing:

``````drawLevels.xyz
drawLevels["xyz"]
``````

The former expression in Lua literally means the second.

So what is shown above works and is more clear through being simpler, and because it more explicitly expresses drawing order. Now we do know that some of those levels aren’t used, background and buttons, but my preference is to leave them there because they’re harmless and if we move the design to a better place, we’ll likely normalize the background and the buttons into real objects anyway.

I wouldn’t argue for putting those things in before they are needed. They’re in because they expressed our thinking at the beginning of designing all this, and our thinking now. If we remove those statements, we’re losing a bit of the expression of our design in the code. YMMV, but I’d leave them in now that they’re there.

Let’s commit: Simplified drawing order and drawLevels.

Now there’s one more thing. Remember that we needed to clear the asteroids table after exiting attract mode, before creating a new wave, and we had to do it longhand:

``````function Universe:newWave()
local pos
self.beatDelay = 1 -- second
self.timeOfNextWave = 0
local t = self.drawLevels.asteroid -- < ---
for k, o in pairs(t) do
t[o] = nil
end -- < ---
for i = 1, self:newWaveSize() do
if math.random(0,1) then
pos = vec2(0,math.random(HEIGHT))
else
pos = vec2(math.random(WIDTH), 0)
end
Asteroid(pos)
end
end
``````

We had to do that because of that weird t10 stuff, because those tables were permanent, and unlike most objects, the asteroids in attract mode never get destroyed.

We don’t have to do that any more, we can do this:

``````function Universe:newWave()
local pos
self.beatDelay = 1 -- second
self.timeOfNextWave = 0
self.drawLevels.asteroid = {}
for i = 1, self:newWaveSize() do
if math.random(0,1) then
pos = vec2(0,math.random(HEIGHT))
else
pos = vec2(math.random(WIDTH), 0)
end
Asteroid(pos)
end
end
``````

That’s a bit nicer. Commit: simplify clearing asteroids before new wave.

That’s all for now, save for the traditional summing of the up.

## Summing Up

One pattern I’ve noticed in these articles is that I tend to get things working, and usually pretty close to right, in one session, and then in a day or two I come back with a better idea. It seems to help me to step away. I think there are two things going on.

First, when my head is deep into the problem and current solution, there’s no room for more details. So getting my head out of the muck helps.

Second, I do often think about how the program works, and how it might work, and about how the language works, and about how everything in the world works … and sometimes that gives me ideas.

So stepping back … and thinking. Possibly something to try.

At a higher level, I’m just delighted that this little game has given me so many opportunities to think about our craft, profession, art, call it what you will. Throughout these articles, from the very first one back on May 11th, we’ve had a working version of the program.

Oh, admittedly the first version just had a round asteroid and no ship, but that code both served as the foundation for everything that has come since then, and it looked enough like an asteroid that you could show it to your gramma and say Look, Gramma, what I made!

And every day after that, things got better looking and better crafted.

This is still a very small program. But every program is made of small bits, and every program can be kept as clear as this one if we keep at it. It’s far from perfect but it’s even further from the worst I’ve ever seen – or, probably, the worst I’ve ever done.

Soon, we’ll need a new program to work on. Soon.

See you next time!

Asteroids.zip