Thinking about tests and refactoring.

We’re in the middle of improving the code by moving toward a more object-oriented style. At least I think it is and will be an improvement. A reader on the Codea forum mentioned that they had deleted the tests tab from their copy of the program. Normally I’d think that would be a Very Bad Idea, but in the present case, the tests aren’t carrying their weight. How might we improve their usefulness.

I released an actual defect a few days ago, where if you tried to fire a missile, you got an error as the missile tried to access a variable, ang, that was no longer supposed to be in use. Yesterday, in the middle of changing things, asteroids had no velocity and just materialized at ship center and sat there. I think I forgot to mention what happened on the screen but I believe I mentioned the error that caused it.

I think I’ll try briefly to write a test to test the behavior of missiles. If it seems easy enough and seems useful, maybe it will inspire me to write more.

Here’s missile creation:

function Missile:init(ship)
    function die()
        self:die()
    end
    self.pos = ship.pos
    self.step = U.missileVelocity:rotate(ship.radians) + ship.step
    U.missiles[self] = self
    tween(3, self, {}, tween.easing.linear, die)
end

Missiles travel at a constant speed which is the sum of their own starting velocity and that of the ship. Note in passing that step and missileVelocity are the same idea, in units of one draw cycle. Reading this code I feel they should have the same kind of name, either “step” or “velocity” but not both.

Why? Because if the code uses two kinds of names for one kind of thing, the reader, including the author, is likely to think that they are two different kinds of things. This leads to confusion and possibly to error.

We’ll tuck that concern away for a moment, we’re here to think about a test.

What’s to test here? With this code in place and working, I’m not very inclined to test it. Had I thought of a test before writing it, or refactoring it to this state, or whatever happened, the test might have looked like this:

        _:test("Missile fired at rest", function()
            local ship = createLocalShip()
            local missile = Missile(ship)
            _:expect(missile.step).is(U.missileVelocity)
        end)

There’s an issue with this, however, because:

function createShip()
    Ship.pos = vec2(WIDTH, HEIGHT)/2
    Ship.radians = 0
    Ship.step = vec2(0,0)
end

The ship creation function creates a global Ship. (Editor’s Note: This turns out to be wrong!) Since we’re running our test while the game is running, we’re going to mess things up if we just go ahead and create one. Since we’ll have testing on our mind, no big deal but it seems wrong. For now, I’ll just go ahead and create a global Ship to see what the test does:

Well. That didn’t work, because Ship isn’t global, it is local to all the ship-creating and drawing code. There’s no way to access it at all. Right now, even the Universe doesn’t know where the ship is. It probably will know, as we move forward, but right now, it doesn’t.

What we see here is a perfect example of why people don’t test. It’s hard. Things are not accessible, so we can’t test them. But I’m determined to try this test, so I’m going to put a getShip function into the Ship tab:

function getShip()
    return Ship -- used in testing. try to make this unnecessary
end

Now I can test. It’s still a bit awkward but it should work:

        _:test("Missile fired at rest", function()
            createShip()
            local ship = getShip()
            local missile = Missile(ship)
            _:expect(missile.step).is(U.missileVelocity)
        end)

Now let’s check whether it goes in the right direction:

        _:test("Missile fired north", function()
            createShip()
            local ship = getShip()
            ship.radians = math.pi/2
            local missile = Missile(ship)
            local mx = missile.step.x
            local my = missile.step.y
            _:expect(mx).is(0)
            _:expect(my).is(U.missileVelocity.y)
        end)

I point the ship north and fire. I expect the x and y values of U:missileVelocity to be exchanged. (Actually, I know they may not be exact but first run the test this way.)

Oops:

            _:expect(my).is(U.missileVelocity.x)

Helps if you say what you mean.

The test nearly passes. mx is 1E-16. We need a bit of leeway in our test and I believe I’ve changed CodeaUnit to allow a second parameter to is representing the allowed error:

        _:test("Missile fired north", function()
            createShip()
            local ship = getShip()
            ship.radians = math.pi/2
            local missile = Missile(ship)
            local mx = missile.step.x
            local my = missile.step.y
            _:expect(mx).is(0, 0.001)
            _:expect(my).is(U.missileVelocity.x, 0.001)
        end)

So far so good. We need to be sure that the ship’s velocity is added in. So far we’ve just checked the starting velocity. I could perhaps have written a combined test in one go, but I prefer to go inch by inch when I’m in the dark. Here’s one more test:

        _:test("Missile fired from moving ship", function()
            createShip()
            local ship = getShip()
            ship.step = vec2(1,1)
            local missile = Missile(ship)
            local mx = missile.step.x
            local my = missile.step.y
            _:expect(mx).is(U.missileVelocity.x + 1, 0.001)
            _:expect(my).is(U.missileVelocity.y + 1, 0.001)
        end)

This one sets the ship moving diagonally 1x1 and tests that the missile velocity has added that in. Looking at that test, I see that a conceivable error (very unlikely given how we program) would be to reverse the coordinates. So I’ll change the test:

        _:test("Missile fired from moving ship", function()
            createShip()
            local ship = getShip()
            ship.step = vec2(1,2)
            local missile = Missile(ship)
            local mx = missile.step.x
            local my = missile.step.y
            _:expect(mx).is(U.missileVelocity.x + 1, 0.001)
            _:expect(my).is(U.missileVelocity.y + 2, 0.001)
        end)

That’s better. Does it run? In fact it does.

So there we go. It wasn’t too hard to test the missile firing, and had we done them before the firing, they might have carried their weight. Whether they carry their weight now or not isn’t so clear, but my practice is to keep tests until they really don’t test anything.

I’m still feeling that testing a visual game like this with microtests is difficult. They’re tricky to write, because you have to deal with math and rounding as you write them, and just looking at the screen and code seems so much easier.

It’s a puzzlement. Sometimes microtesting (TDD) is incredibly valuable. Sometimes having the tests is incredibly valuable. Sometimes it isn’t. The best advice I have is to get good at microtesting/TDD and then decide in the moment what to do.

Refactoring? Or Function?

We have a running version of the code, and we are part-way through refactoring Asteroid into a reasonable class. In doing this refactoring, we have also moved some additional functionality into Universe, which, like the real Universe, contains universal constants and applies the rules. We’re only part-way there with our Universe.

Probably the biggest piece of missing functionality (do you hate that word? I do!)

Probably the most important missing function in the game is that when Asteroids hit the ship, the ship explodes. If you’re out of ships, game over. If not, you get points for destroying the asteroid, and you respawn one of your remaining ships.

This will be fun to write and I think it should be interesting and straightforward. I think it’ll be horrible for game play during development, because with the iPad in typing position, the ship is nearly impossible to drive.

And it seems like a less than ideal time to be adding capability when the Asteroid is only half refactored.

On the other hand, I’ve been saying that refactoring can always be done in little steps, and in the “Real World”, management sometimes wants a feature Right Flaming Now, so let’s do at least the rudiments of asteroid colliding with ship. Maybe we’ll defer the graphical fanciness of the explosion or something.

So:

Asteroid Destroys Ship

Where do we do collisions? Universe:findCollisions:

function Universe:findCollisions()
    for i,a in pairs(self.asteroids) do
        for k,m in pairs(self.missiles) do
            if m.pos:dist(a.pos) < a:killDist() then
                scoreAsteroid(a)
                splitAsteroid(a, self.asteroids)
                m:die()
            end
        end
    end
end

So that’s nice. We should check the ship in the same loop over asteroids, rather than do the whole loop twice. Does it matter if we check the ship before or after we check missiles? If we check missiles first, it’s possible that a missile will take out the asteroid that is already close enough to destroy us. On the other hand, if it’s already close enough, maybe we should die.

Anyway, we don’t immediately deactivate the asteroid even if we hit it. It’s going to depend on just how we write this.

I’m inclined to treat checking asteroid against ship after the missile check:

The code is going to look even more nasty, which is a sign that it needs improvement. Let’s refactor that nest a little bit first:

function Universe:findCollisions()
    for i,a in pairs(self.asteroids) do
        self:checkMissileCollisions(a)
    end
end

function Universe:checkMissileCollisions(asteroid)
    for k,m in pairs(self.missiles) do
        if m.pos:dist(asteroid.pos) < asteroid:killDist() then
            scoreAsteroid(asteroid)
            splitAsteroid(asteroid, self.asteroids)
            m:die()
        end
    end
end

I’ve pulled out that inner bit. I expect this to work. Wish I had some tests to be sure … the game plays, I can still shoot asteroids. Hard to get this wrong.

In passing, I’m wondering what would happen if more than one asteroid were within range of a single asteroid. Interesting question but not what we’re after just now.

Now to put in the code to check for asteroid-ship collisions:

function Universe:findCollisions()
    for i,a in pairs(self.asteroids) do
        self:checkMissileCollisions(a)
        self:checkShipCollision(a)
    end
end

function Universe:checkShipCollision(asteroid)
    
end

When we think about what to put in the new function, we realize the same thing that we realized in the test we just wrote: the Universe doesn’t know where the ship is. The reason is that Ship is local to the Ship tab. Let’s move it to Universe. The code runs, so I’m not too scared about diverting. It’s always risky, though. I could use my new hack function getShip(). Maybe that’s safer for now. Yeah, it’s more conservative and we can be reasonably confident that we’ll move Ship explicitly soon. Oh! Why not do it in Universe:init():

function Universe:init()
    self.ship = getShip() -- hack, fix this
    self.asteroids = {}
    self.missiles = {}
    self.missileVelocity = vec2(MissileSpeed,0)
end

Now we can write the collision checker as if everything were OK, leaving the only hack in init. We can also improve our test by removing its call to getShip(). That’s for later: we have our new feature hat on right now.

function Universe:checkShipCollision(asteroid)
    if self.ship.pos:dist(asteroid.pos) < asteroid.killDist() then
        scoreAsteroid(asteroid)
        splitAsteroid(asteroid, self.asteroids)
        killShip()
    end
end

We note the duplication in scoring and splitting. We posit the existence of killShip, which I’ll implement here for now. I want to do something visible, like we did with Splat earlier, to be a placeholder for the ship explosion. Saying those words tells me what to do:

function killShip()
    Explosion(ship)
end

We need an explosion object. New class, like Splat.

I’ve had to do about ten minutes’ work here, including creation of the Explosion class and changes to Universe:

Explosion = class()

function Explosion:init(ship)
    self.pos = ship.pos
    self.step = vec2(0,0)
    U.explosions[self] = self
end

function Explosion:draw()
    pushStyle()
    pushMatrix()
    translate(self.pos.x, self.pos.y)
    fontSize(30)
    text("BLAMMO", 0, 0)
    popMatrix()
    popStyle()
end


function Universe:init()
    self.ship = getShip() -- hack, fix this
    self.asteroids = {}
    self.missiles = {}
    self.explosions = {}
    self.missileVelocity = vec2(MissileSpeed,0)
end


function Universe:draw()
    self:drawAsteroids()
    self:drawExplosions()
end

I think that’s everything. The result is not ideal but evocative:

ship hit

We crash when an asteroid hits the ship. We can see that the asteroid is splitting and the score has accumulated. The error is that the value of ship sent to Explosion is nil. I think that’s because the universe is created before the ship is, so the getShip gets a nil. We’ll move ship creation out of Main:

function setup()
    U = Universe()
    U:createAsteroids()
    Score = 0
    --displayMode(FULLSCREEN_NO_BUTTONS)
    createButtons()
    createShip() -- move this
end

And into Universe:

function Universe:init()
    createShip() -- hack, fix this when ship is an object?
    self.ship = getShip() -- hack, fix this
    self.asteroids = {}
    self.missiles = {}
    self.explosions = {}
    self.missileVelocity = vec2(MissileSpeed,0)
end

I notice that I’m rushing. The reason is that it has now been perhaps 20 minutes since the program worked, and when things don’t work for that long at a stretch, I feel that I’m in trouble. Anyway, we should at least get a better error now. Hm, no, still a nil ship in Explosion. What have I done?

function killShip()
    Explosion(ship)
end

Well if you don’t give it a ship you can’t expect it to do much.

function killShip()
    Explosion(self.ship)
end

Right expect that killShip isn’t a method on Universe, it’s a free global. I don’t think it belongs as a method on Universe, but on ship, that’s why I wrote it that way. Anyway …

function killShip()
    Explosion(U.ship)
end

blammo

It works. I’ll test further. The ship, of course, isn’t dead at all, but that’s as I intended for this first explosion.

With the ship moving, we can see more than one blammo per collision. That’s no surprise, since we’re checking all the asteroids and we may well hit several the baby ones in the next cycle.

blammos

We’ll need to kill the ship to avoid this, which should be no problem. We now have a place to produce a nice-looking ship explosion, and we’re on the cusp of needing to do something about respawning a dead ship.

I’m going to commit this code now, under “ship explodes blammo”.

Now before we release this thing, let’s see about that hackery in creating the ship. How are we handling that elsewhere? When we create asteroids, it’s like this:

function Universe:createAsteroids()
    for i = 1,4 do
        local a = Asteroid()
        self.asteroids[a] = a
    end
end

Universe creates the object and put it into the asteroids table in Universe. What about Missile, though:

function Missile:init(ship)
    function die()
        self:die()
    end
    self.pos = ship.pos
    self.step = U.missileVelocity:rotate(ship.radians) + ship.step
    U.missiles[self] = self
    tween(3, self, {}, tween.easing.linear, die)
end

Missiles have to know to put themselves into the Universe, because they are created from the ship. We could change that so that the ship asks the universe to create a missile, but that seems off. For now, I think Ship should be more like asteroid, and that we should remove the local Ship from Ship tab and use U.ship in place of it.

Then we should change the creation to return the table to Universe to store it, which will make the operation more normal. Two steps, since Universe does have U.ship now.

No. I don’t like that idea. Ship will soon be an object, and at that point we’ll need to convert all the references to Ship to self. Lets not do that twice. We’ll just make createShip return the Ship and let the code refer to Ship for now. That makes Universe slightly better and leaves Ship not really worse off:

function createShip()
    Ship.pos = vec2(WIDTH, HEIGHT)/2
    Ship.radians = 0
    Ship.step = vec2(0,0)
    return Ship
end
function Universe:init()
    self.ship = createShip()
    self.asteroids = {}
    self.missiles = {}
    self.explosions = {}
    self.missileVelocity = vec2(MissileSpeed,0)
end

Everything still works. Commit: remove ship creation hacks from Universe.

Whoa! I noticed a lot of files wanting to be updated. Apparently I fumble-fingered the Blammo commit and missed the select all button in favor of the Main one below. Not the first time I’ve done that. Anyway all the files are done now … except the tests:


        _:test("Missile fired at rest", function()
            createShip()
            local missile = Missile(U.ship)
            _:expect(missile.step).is(U.missileVelocity)
        end)
        
        _:test("Missile fired north", function()
            createShip()
            U.ship.radians = math.pi/2
            local missile = Missile(U.ship)
            local mx = missile.step.x
            local my = missile.step.y
            _:expect(mx).is(0, 0.001)
            _:expect(my).is(U.missileVelocity.x, 0.001)
        end)
        
        _:test("Missile fired from moving ship", function()
            createShip()
            U.ship.step = vec2(1,2)
            local missile = Missile(U.ship)
            local mx = missile.step.x
            local my = missile.step.y
            _:expect(mx).is(U.missileVelocity.x + 1, 0.001)
            _:expect(my).is(U.missileVelocity.y + 2, 0.001)
        end)

Commit: “tests access ship correctly as U.ship”.

I think we’re done for the morning, we’re about two or two and a half hours in.

Summing Up

Yesterday we stopped in the middle of refactoring asteroids into real objects. The program worked fine throughout yesterday and ran fine at the end of the session, showing that we can do a large refactoring in small steps, often if not always. (I assume “always” and believe “often”. I assume “always” because I don’t know if I can until I see the actual code.)

Today we tried writing some tests, to see whether we felt they were helpful enough to encourage us to write more. Honestly, I’m not feeling it. I’ll continue to try to remain alert for places to test, but the game format doesn’t seem amenable to many tests.

Imagine what we’re working on now, killing the ship when it’s hit by an asteroid. We could test that our distance-checking code works but it rather obviously works (he said). But the real work for ship explosions takes place over time. The explosion has to start and run and look right. The ship has to disappear and some time later, has to respawn. I don’t begin to know how to test that sort of thing with Codea unit, but it’ll be easy enough to see on screen whether it’s working or not.

So, right now, despite my great love for microtesting / TDD, I’m not finding much opportunity for it.

But the feature?

But what about the feature? We bowed to “management pressure” to put in a feature that impacted asteroids while we were in the middle of refactoring them. I think folks would usually be afraid about that and say something like “We’re in the middle of refactoring that area and we can put in that feature when we’re done”.

That is what we refer to in the trade as a CLM, a Career Limiting Move. At best it’s likely to get you told not to do any more refactoring. At worst, they’ll replace you with someone who understands the company’s pressing business need to have imaginary ships produce imaginary explosions when run over by imaginary asteroids. Business needs are like that.

But the fact is that Blammo went into the system quite nicely. There were a few oddities, where sometimes we wrote an object-oriented function and sometimes a regular one, and sometimes we even got a little confused. But we were only confused for a few minutes at a time, and the feature took less than a half hour from start to finish.

So it’s all good, right?

Well, maybe. Or maybe we just got away with it this time. I was feeling edgy there about the 20 minute mark. That’s usually a sign that I’m hitting the limits on what I can keep in my head at one time. So if the problem had been just a bit harder, or I had been unable to simplify it down quite as far as I did, I might have fallen into a pit of debugging, or even had to revert and start over.

I hate reverting and starting over, but I’ve learned (if not absorbed) that starting over always makes it come out better, and often sooner than sticking with it. Certainly had I needed to do real debugging, it would have almost certainly have been better to revert.

But I hate to revert, and might not have, so I’m glad it didn’t get the few points harder than it was, or this might have looked like a debacle.

As it is, we have a new feature sketched into place, and it’s time for lunch!

See you next time!



--# Main
-- Asteroids
-- RJ 20200511

Touches = {}
Ratio = 1.0 -- draw time scaling ratio
Score = 0

function setup()
    U = Universe()
    U:createAsteroids()
    Score = 0
    --displayMode(FULLSCREEN_NO_BUTTONS)
    createButtons()
end

function draw()
    Ratio = DeltaTime/0.0083333
    --displayMode(FULLSCREEN_NO_BUTTONS)
    checkButtons()
    pushStyle()
    background(40, 40, 50)
    U:draw()
    drawButtons()
    drawShip()
    moveShip()
    U:drawMissiles()
    drawSplats()
    drawScore()
    popStyle()
    U:findCollisions()
end

function drawScore()
    local s= "000000"..tostring(Score)
    s = string.sub(s,-5)
    fontSize(100)
    text(s, 200, HEIGHT-60)
end

function touched(touch)
    if touch.state == ENDED or touch.state == CANCELLED then
        Touches[touch.id] = nil
    else
        Touches[touch.id] = touch
    end
end

--# TestAsteroids
-- TestAsteroids
-- RJ 20200511
    
function testAsteroids()
    CodeaUnit.detailed = true

    _:describe("Asteroids First Tests", function()

        _:before(function()
            -- Some setup
        end)

        _:after(function()
            -- Some teardown
        end)
        
        _:test("Hookup", function()
            _:expect( 2+1 ).is(3)
        end)
        
        _:test("Random", function()
            local min = 100
            local max = 0
            for i = 0,1000 do
                local rand = math.random()*2*math.pi
                if rand < min then min = rand end
                if rand > max then max = rand end
            end
            _:expect(min < 0.01).is(true)
            _:expect(max > 6.2).is(true)
        end)
        
        _:test("Rotated Length", function()
            for i = 0, 1000 do
                local rand = math.random()*2*math.pi
                local v = vec2(1.5,0):rotate(rand)
                local d = v:len()
                _:expect(d > 1.495).is(true)
                _:expect(d < 1.505).is(true)
            end
        end)
        
        _:test("Some rotates go down", function()
            local angle = math.rad(-45)
            local v = vec2(1,0):rotate(angle)
            local rvx = v.x*1000//1
            local rvy = v.y*1000//1
            _:expect(rvx).is(707)
            _:expect(rvy).is(-708)
        end)
        
        _:test("Bounds function", function()
            _:expect(U:keepInBounds(100,1000)).is(100)
            _:expect(U:keepInBounds(1000,1000)).is(0)
            _:expect(U:keepInBounds(1001,1000)).is(1)
            _:expect(U:keepInBounds(-1,1000)).is(999)
        end)
        
        _:test("Missile fired at rest", function()
            createShip()
            local missile = Missile(U.ship)
            _:expect(missile.step).is(U.missileVelocity)
        end)
        
        _:test("Missile fired north", function()
            createShip()
            U.ship.radians = math.pi/2
            local missile = Missile(U.ship)
            local mx = missile.step.x
            local my = missile.step.y
            _:expect(mx).is(0, 0.001)
            _:expect(my).is(U.missileVelocity.x, 0.001)
        end)
        
        _:test("Missile fired from moving ship", function()
            createShip()
            U.ship.step = vec2(1,2)
            local missile = Missile(U.ship)
            local mx = missile.step.x
            local my = missile.step.y
            _:expect(mx).is(U.missileVelocity.x + 1, 0.001)
            _:expect(my).is(U.missileVelocity.y + 2, 0.001)
        end)

    end)
end

--# Shapes
RR1 = {
  vec4(0.000000, 2.000000, 2.000000, 4.000000),
  vec4(2.000000, 4.000000, 4.000000, 2.000000),
  vec4(4.000000, 2.000000, 3.000000, 0.000000),
  vec4(3.000000, 0.000000, 4.000000, -2.000000),
  vec4(4.000000, -2.000000, 1.000000, -4.000000),
  vec4(1.000000, -4.000000, -2.000000, -4.000000),
  vec4(-2.000000, -4.000000, -4.000000, -2.000000),
  vec4(-4.000000, -2.000000, -4.000000, 2.000000),
  vec4(-4.000000, 2.000000, -2.000000, 4.000000),
  vec4(-2.000000, 4.000000, 0.000000, 2.000000)
}

RR2 = {
  vec4(2.000000, 1.000000, 4.000000, 2.000000),
  vec4(4.000000, 2.000000, 2.000000, 4.000000),
  vec4(2.000000, 4.000000, 0.000000, 3.000000),
  vec4(0.000000, 3.000000, -2.000000, 4.000000),
  vec4(-2.000000, 4.000000, -4.000000, 2.000000),
  vec4(-4.000000, 2.000000, -3.000000, 0.000000),
  vec4(-3.000000, 0.000000, -4.000000, -2.000000),
  vec4(-4.000000, -2.000000, -2.000000, -4.000000),
  vec4(-2.000000, -4.000000, -1.000000, -3.000000),
  vec4(-1.000000, -3.000000, 2.000000, -4.000000),
  vec4(2.000000, -4.000000, 4.000000, -1.000000),
  vec4(4.000000, -1.000000, 2.000000, 1.000000)
}

RR3 = {
  vec4(-2.000000, 0.000000, -4.000000, -1.000000),
  vec4(-4.000000, -1.000000, -2.000000, -4.000000),
  vec4(-2.000000, -4.000000, 0.000000, -1.000000),
  vec4(0.000000, -1.000000, 0.000000, -4.000000),
  vec4(0.000000, -4.000000, 2.000000, -4.000000),
  vec4(2.000000, -4.000000, 4.000000, -1.000000),
  vec4(4.000000, -1.000000, 4.000000, 1.000000),
  vec4(4.000000, 1.000000, 2.000000, 4.000000),
  vec4(2.000000, 4.000000, -1.000000, 4.000000),
  vec4(-1.000000, 4.000000, -4.000000, 1.000000),
  vec4(-4.000000, 1.000000, -2.000000, 0.000000)
}

RR4 = {
  vec4(1.000000, 0.000000, 4.000000, 1.000000),
  vec4(4.000000, 1.000000, 4.000000, 2.000000),
  vec4(4.000000, 2.000000, 1.000000, 4.000000),
  vec4(1.000000, 4.000000, -2.000000, 4.000000),
  vec4(-2.000000, 4.000000, -1.000000, 2.000000),
  vec4(-1.000000, 2.000000, -4.000000, 2.000000),
  vec4(-4.000000, 2.000000, -4.000000, -1.000000),
  vec4(-4.000000, -1.000000, -2.000000, -4.000000),
  vec4(-2.000000, -4.000000, 1.000000, -3.000000),
  vec4(1.000000, -3.000000, 2.000000, -4.000000),
  vec4(2.000000, -4.000000, 4.000000, -2.000000),
  vec4(4.000000, -2.000000, 1.000000, 0.000000)
}

Rocks = {RR1,RR2,RR3,RR4}
--# Ship
-- Ship
-- RJ 20200520

local Ship = {}

local rotationStep = math.rad(1) -- one degree in radians

function getShip()
    return Ship -- used in testing. try to make this unnecessary
end

function createShip()
    Ship.pos = vec2(WIDTH, HEIGHT)/2
    Ship.radians = 0
    Ship.step = vec2(0,0)
    return Ship
end

function drawShip()
    local sx = 10
    local sy = 6
    pushStyle()
    pushMatrix()
    translate(Ship.pos.x, Ship.pos.y)
    rotate(math.deg(Ship.radians))
    strokeWidth(2)
    stroke(255)
    line(sx,0, -sx,sy)
    line(-sx,sy, -sx,-sy)
    line(-sx,-sy, sx,0)
    popMatrix()
    popStyle()
end

function moveShip()
    if Button.left then Ship.radians = Ship.radians + rotationStep end
    if Button.right then Ship.radians = Ship.radians - rotationStep end
    if Button.fire then if not Ship.holdFire then fireMissile() end end
    if not Button.fire then Ship.holdFire = false end
    actualShipMove()
end

function actualShipMove()
    if Button.go then
        local accel = vec2(0.015,0):rotate(Ship.radians)
        Ship.step = Ship.step + accel
        Ship.step = maximize(Ship.step, 3)
    end
    finallyMove(Ship)
end

function finallyMove(ship)
    U:moveObject(ship)
end

function maximize(vec, size)
    local s = vec:len()
    if s <= size then
        return vec
    else
        return vec*size/s
    end
end

function fireMissile()
    Ship.holdFire = true
    Missile(Ship)
end

--# Button
-- Button
-- RJ 20200520

Button = {}
local Buttons = {}

function createButtons()
    local dx=50
    local dy=200
    table.insert(Buttons, {x=dx, y=dy, name="left"})
    table.insert(Buttons, {x=dy, y=dx, name="right"})
    table.insert(Buttons, {x=WIDTH-dx, y=dy, name="fire"})
    table.insert(Buttons, {x=WIDTH-dy, y=dx, name = "go"})
end

function checkButtons()
    Button.left = false
    Button.right = false
    Button.go = false
    Button.fire = false
    for id,touch in pairs(Touches) do
        for i,button in ipairs(Buttons) do
            if touch.pos:dist(vec2(button.x,button.y)) < 50 then
                Button[button.name]=true
            end
        end
    end
end

function drawButtons()
    pushStyle()
    ellipseMode(RADIUS)
    textMode(CENTER)
    stroke(255)
    strokeWidth(1)
    for i,b in ipairs(Buttons) do
        pushMatrix()
        pushStyle()
        translate(b.x,b.y)
        if Button[b.name] then
            fill(128,0,0)
        else
            fill(128,128,128,128)
        end
        ellipse(0,0, 50)
        fill(255)
        fontSize(30)
        text(b.name,0,0)
        popStyle()
        popMatrix()
    end
    popStyle()
end

--# Asteroid
-- Asteroid
-- RJ 20200520

local DeadAsteroids = {}
local Vel = 1.5

Asteroid = class()

function Asteroid:init()
    self.pos = vec2(math.random(WIDTH), math.random(HEIGHT))
    self.shape = Rocks[math.random(1,4)]
    self.scale = 16
    local angle = math.random()*2*math.pi
    self.step = Ratio*vec2(Vel,0):rotate(angle)
end

function Asteroid:killDist()
    local s = self.scale
    if s == 16 then return 64 elseif s == 8 then return 32 else return 16 end
end

function killDeadAsteroids(asteroids)
    for k,a in pairs(DeadAsteroids) do
        asteroids[a] = nil
    end
    DeadAsteroids = {}
end

function scoreAsteroid(asteroid)
    local s = asteroid.scale
    local inc = 0
    if s == 16 then inc = 20
    elseif s == 8 then inc = 50
    else inc = 100
    end
    Score = Score + inc
end

function splitAsteroid(asteroid, asteroids)
    if asteroid.scale == 4 then
        Splat(asteroid.pos)
        DeadAsteroids[asteroid] = asteroid
        return
    end
    asteroid.scale = asteroid.scale//2
    asteroid.angle = math.random()*2*math.pi
    local new = Asteroid()
    new.pos = asteroid.pos
    new.scale = asteroid.scale
    asteroids[new] = new
    Splat(asteroid.pos)
end

function Asteroid:draw()
    pushMatrix()
    pushStyle()
    translate(self.pos.x, self.pos.y)
    scale(self.scale)
    strokeWidth(1/self.scale)
    for i,l in ipairs(self.shape) do
        line(l.x, l.y, l.z, l.w)
    end
    popStyle()
    popMatrix()
end

function Asteroid:move()
    U:moveObject(self)
end


--# Splat
-- Splat
-- RJ 20200521

local Splats = {}

local Vecs = {
vec2(-2,0), vec2(-2,-2), vec2(2,-2), vec2(3,1), vec2(2,-1), vec2(0,2), vec2(1,3), vec2(-1,3), vec2(-4,-1), vec2(-3,1)
}

function drawSplats()
    for k, splat in pairs(Splats) do
        splat:draw()
    end
end

Splat = class()

function Splat:init(pos)
    local die = function()
        Splats[self] = nil
    end
    self.pos = pos
    Splats[self] = self
    self.size = 2
    self.diameter = 6
    self.rot = math.random(0,359)
    tween(4, self, {size=10, diameter=1}, tween.easing.linear, die)
end

function Splat:draw()
    pushStyle()
    pushMatrix()
    translate(self.pos.x, self.pos.y)
    fill(255)
    stroke(255)
    rotate(self.rot)
    local s = self.size
    for i,v in ipairs(Vecs) do
        ellipse(s*v.x, s*v.y, self.diameter)
    end
    popMatrix()
    popStyle()
end

--# Missile
-- Missile
-- RJ 20200522

Missile = class()

function Missile:init(ship)
    function die()
        self:die()
    end
    self.pos = ship.pos
    self.step = U.missileVelocity:rotate(ship.radians) + ship.step
    U.missiles[self] = self
    tween(3, self, {}, tween.easing.linear, die)
end

function Missile:die()
    U.missiles[self] = nil
end

function Missile:draw()
    ellipse(self.pos.x, self.pos.y, 6)
end

function Missile:move()
    U:moveObject(self)
end

--# Universe
-- Universe
-- RJ 20200523

Universe = class()

local MissileSpeed = 2.0

function Universe:init()
    self.ship = createShip()
    self.asteroids = {}
    self.missiles = {}
    self.explosions = {}
    self.missileVelocity = vec2(MissileSpeed,0)
end

function Universe:draw()
    self:drawAsteroids()
    self:drawExplosions()
end

function Universe:createAsteroids()
    for i = 1,4 do
        local a = Asteroid()
        self.asteroids[a] = a
    end
end

function Universe:findCollisions()
    for i,a in pairs(self.asteroids) do
        self:checkMissileCollisions(a)
        self:checkShipCollision(a)
    end
end

function Universe:checkShipCollision(asteroid)
    if self.ship.pos:dist(asteroid.pos) < asteroid:killDist() then
        scoreAsteroid(asteroid)
        splitAsteroid(asteroid, self.asteroids)
        killShip()
    end
end

function Universe:checkMissileCollisions(asteroid)
    for k,m in pairs(self.missiles) do
        if m.pos:dist(asteroid.pos) < asteroid:killDist() then
            scoreAsteroid(asteroid)
            splitAsteroid(asteroid, self.asteroids)
            m:die()
        end
    end
end

function killShip()
    Explosion(U.ship)
end

function Universe:moveObject(anObject)
    local pos = anObject.pos + Ratio*anObject.step
    anObject.pos = vec2(self:keepInBounds(pos.x, WIDTH), self:keepInBounds(pos.y, HEIGHT))    
end

function Universe:keepInBounds(value, bound)
    return (value+bound)%bound
end

function Universe:drawExplosions()
    for k,e in pairs(self.explosions) do
        e:draw()
    end
end

function Universe:drawMissiles()
    pushStyle()
    pushMatrix()
    fill(255)
    stroke(255)
    for k, missile in pairs(self.missiles) do
        missile:draw()
    end
    popMatrix()
    popStyle()
    for k, missile in pairs(self.missiles) do
        missile:move()
    end
end

function Universe:drawAsteroids()
    pushStyle()
    stroke(255)
    fill(0,0,0, 0)
    strokeWidth(2)
    rectMode(CENTER)
    for i,asteroid in pairs(self.asteroids) do
        asteroid:draw()
        asteroid:move()
    end
    popStyle()
    killDeadAsteroids(self.asteroids)
end



--# Explosion
Explosion = class()

function Explosion:init(ship)
    self.pos = ship.pos
    self.step = vec2(0,0)
    U.explosions[self] = self
end

function Explosion:draw()
    pushStyle()
    pushMatrix()
    translate(self.pos.x, self.pos.y)
    fontSize(30)
    text("BLAMMO", 0, 0)
    popMatrix()
    popStyle()
end