An expanding universe

Today I am broadcasting from BMW of Ann Arbor, where they are checking my car’s fluid balance or something. I’m stuck here for a while and I plan to expand the Universe class started yesterday. Started twice, you’ll recall, once that didn’t work and again, with things going better.

Let’s review what worked yesterday, as a hint for what might work today. The process was:

  • Forward Main’s touched to U.universe, as a method call, colon not dot.
  • In Universe init, add table Touched
  • Copy over Main’s functions touched and addTouched

Today’s mission is a bit more tricky. The Drawn table is supported by two other tables, Adds and Removes, which are used to add things, and remove things, outside the main drawing loop. The draw code does a lot. It looks like this:

function Universe:draw()
    U.Tick = U.Tick + 1
    remove()
    add()
    moveAll()
    interactAll()
    drawAll()
end

However, it is already moved to Universe. Let’s try this: the U table has a function add that adds new objects to the Drawn table. Let’s move the Adds to Universe class, and make everyone talk to it. While we’re at it, let’s change the name of that function to addObject. First that refactoring:

-- Main
-- The Universe

U = {}
U.universe = Universe()
U.Tick = 0
U.Adds ={}
U.Drawn = {}
U.Removes = {}
U.Touched = {}
U.Running = true
U.MissileKillDistance = 20
U.kill = function(objToRemove)
    table.insert(U.Removes, objToRemove)
end
U.addObject = function(objToAdd)
    table.insert(U.Adds, objToAdd)
end

And the corresponding changes in the objects:

Button = class()

function Button:init(x, y)
    self.name = string.format("Button %f %f", x, y)
    self.pos = vec2(x,y)
    self.radius = 50
    self.pressed = false
    self.capturedID = nil
    self.cannotCollide = true
    addTouched(self)
    U.addObject(self)
end

Explosion = class()

function Explosion:init(ship)
    U.addObject(self)
    self.timeOut = 100
    self.pos = ship.pos
    self.cannotCollide = true
end

Missile = class()

Missile.count = 0

function Missile:init(ship)
    self.name = "missile" .. Missile.count
    Missile.count = Missile.count + 1
    self.pos = ship.pos + self:aWaysOutInFront(ship)
    local velocity = vec2(0,1):rotate(math.rad(ship.heading))
    self.vel = ship.vel + velocity
    self.maxDistance = WIDTH*1.4
    self.distance = 0
    U.addObject(self)
end

Ship = class()

    Ship.MaxMissiles = 50
    Ship.TicksBetweenMissiles = U.MissileKillDistance + 1
    Ship.ThrustAmount = vec2(0,0.02)
    Ship.TurnAmount = 1
    Ship.MaxSpeed = 3
-- do more of this

function Ship:init(shipNumber)
    self.name = string.format("Ship %d", shipNumber)
    self.missileLoad = Ship.MaxMissiles
    self.timeLastFired = -Ship.TicksBetweenMissiles
    self.shipNumber = shipNumber
    
    self.pos = self:initialPosition()
    self.heading = self:initialHeading()
    self.vel = vec2(0,0)
    self.controls = { 
        left   = self:controlButton(HEIGHT/5),
        right  = self:controlButton(2*HEIGHT/5),
        thrust = self:controlButton(3*HEIGHT/5),
        fire   = self:controlButton(4*HEIGHT/5)
    }
    U.addObject(self)
end

Does this run? Let me try it. Yes, in fact it does. A simple renaming should work, unless I had missed one. Now I’ll extend Universe to have the Adds table and to implement Universe:addObject. Then I’ll connect the U.addObject function to call that, and change the references in Universe to its own table.

Ah, no, that’s not quite it. Universe is still calling the global functions add and remove. So first I’ll change those to reference the Universe’s Add table:

Universe = class()

function Universe:init(x)
    self.Touched = {}
    self.Adds = {}
end

function Universe:addObject(anObject)
    table.insert(self.Adds, anObject)
end

-- Main

function add()
    for _, obj in ipairs(U.universe.Adds) do
        U.Drawn[obj] = obj
    end
    U.universe.Adds = {}
end

This works. I forgot to change the second reference to Adds to refer to U.universe.Adds, which broke things for a moment. When I do the removes in a moment, I’ll try to do better. Now I can implement the add function on Universe and do it all internally:

function Universe:draw()
    U.Tick = U.Tick + 1
    remove()
    self:addInNewObjects()
    moveAll()
    interactAll()
    drawAll()
end

function Universe:addInNewObjects()
    for _, obj in ipairs(self.Adds) do
        U.Drawn[obj] = obj
    end
    self.Adds = {}
end

I renamed that method while I was at it. This time my problem was forgetting the self: on the call. Anyway this is working. Now we can remove the add function from Main … which works … and then redirect all the U.addObject calls to the universe.

However, this is a bit irritating. We’re going to have to say U.universe:addObject, because at the moment the universe is only known to U. My original plan was to wait till the end and then set U to be the universe instance instead of the current U table. What would be a smoother refactoring?

It’s time to think of some options and then pick one:

  • Proceed as planned. Convert U.addObject to U.universe:addObject` and then convert them back.
  • Or, a bit ugly, we could have the Universe instance, to be named U real soon now, contain a pointer to self, so that references to U.universe would just work, then remove them at leisure.
  • Replace U the table with U the universe now, and give U the universe the U table, which it would use to implement whatever it needs until it sucks the juices out. Unfortunately, there’s really only one more function in U the table. Most of the other references to U are accessing U’s various values, like Tick.
  • Just go for it. Add the same initializers to U the Universe as U the table has, and then just swap the object right now, so that U becomes the Universe. See what breaks, and how hard it is to fix.

I’ve decided to try that last thing, more as an information-gathering Spike than as an actual plan. Most of the work has to be done anyway.

Universe = class()

function Universe:init(x)
    self.Touched = {}
    self.Adds = {}
    self.Tick = 0
    self.Adds ={}
    self.Drawn = {}
    self.Removes = {}
    self.Touched = {}
    self.Running = true
    self.MissileKillDistance = 20
end

-- The Universe

U = {}
U.universe = Universe()
U.Tick = 0
U.Adds ={}
U.Drawn = {}
U.Removes = {}
U.Touched = {}
U.Running = true
U.MissileKillDistance = 20
U.kill = function(objToRemove)
    table.insert(U.Removes, objToRemove)
end
U.addObject = function(objToAdd)
    U.universe:addObject(objToAdd)
end
U.universe.oldU = U
U = U.universe

I’ve saved the old U in the universe, and then just slammed in the universe instance into U. I’ll run and see what happens. The main thing that blows up is that people like addTouched are now referring to U.universe and the U universe does not know itself. Try a one line addition:

U.universe.oldU = U
U = U.universe
U.universe = U

OK, good experiment but maybe the change is premature. Gives us something to think about though. Objects like Button add themselves like this:

function Button:init(x, y)
    self.name = string.format("Button %f %f", x, y)
    self.pos = vec2(x,y)
    self.radius = 50
    self.pressed = false
    self.capturedID = nil
    self.cannotCollide = true
    addTouched(self)
    U.addObject(self)
end

They’re calling that function on U, with a dot not a colon. What if we just changed it though? Well, then Ship would do it, and then, I imagine, Missile and Explosion. Let’s change those as they turn up. After changing Ship and Missile, the game almost plays, until a Ship tries to die. In the picture below, notice that there is a bullet about to strike from behind, and we’ve blown up trying to do the kill:

Ship:105: attempt to call a nil value (field 'kill')
stack traceback:
	Ship:105: in method 'die'
	Main:57: in function 'interactAll'
	Universe:24: in method 'draw'
	Main:37: in function 'draw'

The code in question is this:

function Ship:die()
    U.kill(self)
    Explosion(self)
end

There are two problems here. First, that will have to say U:kill instead of U.kill, and the Universe doesn’t implement kill anyway. Let’s change the call, catch the error, and add the capability:

function Universe:kill(anObject)
    self.oldU.kill(anObject)
end

OK, all I did here was to refer the kill back to the old U table. That blows up on inserting the Explosion, which has not yet been converted to colon instead of dot. With that done … hmm. We get an odd message:

Universe:46: attempt to index a nil value (field 'oldU')
stack traceback:
	Universe:46: in field 'kill'
	Missile:46: in method 'die'
	Main:57: in function 'interactAll'
	Universe:24: in method 'draw'
	Main:37: in function 'draw'

I think that’s telling me that the Universe U doesn’t know table U in itsoldU field. But I thought it was in there. But maybe not. The explosion kills itself using U.kill. Let me change that to U:kill’ and see what happens … same thing. OK, where is oldU? A quick print assures me that it’s really nil. Let’s look at that code again:

-- The Universe

U = {}
U.universe = Universe()
U.Tick = 0
U.Adds ={}
U.Drawn = {}
U.Removes = {}
U.Touched = {}
U.Running = true
U.MissileKillDistance = 20
U.kill = function(objToRemove)
    table.insert(U.Removes, objToRemove)
end
U.addObject = function(objToAdd)
    U.universe:addObject(objToAdd)
end
U.universe.oldU = U
U = U.universe
U.universe = U

That sure looks good to me. Ah! Read the message, Ron: it’s a Missile trying to die and it is still saying U.kill. I changed the wrong U.kill. Fix that and the game is running. Time to commit, and to talk about something that may be bothering you.

What’s going on here? Aren’t you just fixing bugs? Isn’t this grotesque hackery? How can this be reasonable?

Back in the olden days, in Smalltalk, we used to do something like this: We’d make some base change, like swapping a Universe in for the U table, then run the code. It would break, giving us what’s called a “walkback” in Smalltalk, basically opening a debugger on the line that broke. Usually, as here, the indicated change is clear, and we’d just make it.

And so far, the changes are clear. Mostly we’ve been converting dots to colons. I’d not have been confused at all had I changed them all at once, back when I mentioned the idea. Instead, I decided to go one at a time and although the message pointed right at Missile, I missed it.

So, I argue that this isn’t a big change that we are debugging. Instead it is a sensible change (swap U table and U universe), followed by letting the computer tell us where the necessary changes are. And we’re now in a very good place: the game is running fine, and there is only one reference to U-table’s kill function, inside Universe. Universe wants to move the Removes table over, and then use it in its draw cycle:

Universe = class()

function Universe:init(x)
    self.Touched = {}
    self.Adds = {}
    self.Tick = 0
    self.Adds ={}
    self.Drawn = {}
    self.Removes = {}
    self.Touched = {}
    self.Running = true
    self.MissileKillDistance = 20
end

function Universe:addObject(anObject)
    table.insert(self.Adds, anObject)
end

function Universe:draw()
    U.Tick = U.Tick + 1
    self:removeDeadObjects()
    self:addInNewObjects()
    moveAll()
    interactAll()
    drawAll()
end

function Universe:addInNewObjects()
    for _, obj in ipairs(self.Adds) do
        self.Drawn[obj] = obj
    end
    self.Adds = {}
end

function Universe:removeDeadObjects()
    for _, obj in ipairs(self.Removes) do
        self.Drawn[obj] = nil
    end
    self.Removes = {}
end

function Universe:touched(touch)
    for _,obj in ipairs(self.Touched) do
        obj:touched(touch)
    end
end

function Universe:addTouched(anObject)
    table.insert(self.Touched, anObject)
end

function Universe:kill(anObject)
    table.insert(self.Removes, anObject)
end

This seems to be working fine. As I’ve gone along, I’ve removed a lot of the U-table functionality. Let’s see if we can remove it all and see what happens. What I expect is that something will explode, because we still have those functions in Main that should be moved to Universe. But we might get lucky – and we do! It all runs, with Main like this:

-- HAVE YOU PUSHED TO GITHUB TODAY?

-- S3 Spacewar

-- The Universe

U = Universe()
U.universe = U

function setup()
    Ship(1)
    Ship(2)
    U.Running = true
end

function touched(touchid)
    U.universe:touched(touchid)
end

function draw()
    U.universe:draw()
end

function moveAll()
    for _, obj in pairs(U.Drawn) do
        obj:move()
    end
end

function interactAll()
    for _, a in pairs(U.universe.Drawn) do
        for _, b in pairs(U.universe.Drawn) do
            if colliding(a,b) then
                a:die()
                b:die()
            end
        end
    end
end

function drawAll()
    background(40, 40, 50)
    for _, obj in pairs(U.universe.Drawn) do
        obj:draw()
    end
end

function addTouched(anObject)
    U.universe:addTouched(anObject)
end

function clip_to_screen(vec)
    return vec2(vec.x%WIDTH, vec.y%HEIGHT)
end

function colliding(obj1, obj2)
    if obj1 == nil then return false end
    if obj2 == nil then return false end
    if obj1.cannotCollide then return false end
    if obj2.cannotCollide then return false end
    if obj1 == obj2 then return false end
    local d = obj1.pos:dist(obj2.pos)
    local result = d < U.MissileKillDistance
    return result
end

Time to commit again, then pull out the redundant references to U.universe, and move those functions over. We can do those one at a time and if we’re wise, we’ll commit after each one. See you next time!