A report on last night, and a ‘major’ refactoring. P.S. There is no such thing as a major refactoring.

## Report

Again last night, I did a bit of programming without concurrent writing. Frankly, it’s much more relaxing. I accomplished three things:

1. Adjusted motion speed to draw-cycle time;
2. Implemented simple scoring;

We’ll briefly review how those were done: they’re all rather simple.

It turns out that Codea’s draw cycle time isn’t always one sixtieth of a second as I thought. On fast enough iPads, like this one, it runs at 1/120 if it can. If drawing lags behind, not finishing in time for the next cycle, it steps down to 1/60. It can even step down to 1/30 if it needs to.

I discovered this when I thought I was observing a variance in speed of things on the screen. I did some tests and it became clear that my iPad was running at 1/120. An inquiry to the Codea forum confirmed the information above.

The speed at which things move in the version we last looked at is based on `Vel`, which I set to 1.5 by inspection. That means that a moving object moves a distance of 1.5 in screen coordinates every draw cycle. The screen is 1366 wide by 1024 high. Pixels are “square”, so that a 45 degree angle looks like 45 degrees.

So an object going straight up would need 1024/1.5 cycles, or 683 cycles, to move from bottom to top. Dividing by 120 gives us about 5.7 seconds to cross the screen vertically. Eyeballing the screen tells me that’s about right.

But if we run this code on an iPad that cycles at 1/60, everything slows down by a factor of two. We’d like to do something to speed it back up, and what we can do is move twice as far in each cycle.

Codea maintains a global `DeltaTime`, which is the actual time between the previous draw cycle and the current one. The 1/120 cycle time is about 0.0083333 seconds. Suppose we set:

``Ratio = DeltaTime/0.0083333``

Then if we’re on a slow iPad, `Ratio` will be about `0.01666/0.0083333`, or about 2. If we multiply our increments to position by `Ratio`, we should get a constant speed for things.

And that’s what I did. At the top of the main `draw`, I calculate Ratio:

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

Only asteroids and missiles move so far. Splats do not move, they just expand. Splats use a tween, which is already enumerated in seconds, so they should be OK. So in the moving code, we have these changes:

``````function moveAsteroid(asteroid)
local step = Ratio*vec2(Vel,0):rotate(asteroid.angle)
local pos = asteroid.pos + step
asteroid.pos = vec2(keepInBounds(pos.x, WIDTH), keepInBounds(pos.y, HEIGHT))
end

function Missile:move()
self.pos = self.pos + Ratio*self.vel
self.pos = vec2(keepInBounds(self.pos.x, WIDTH), keepInBounds(self.pos.y, HEIGHT))
end``````

So that’s speed correction, for now. The fact that we have to remember to put it into everything that moves is troubling. Surely I’ll forget something, so it would be good to find a way to centralize that facility.

I also note that the main `draw` is getting pretty messy, and it irritates me that moving an asteroid and moving a missile are done differently. I’d like to clean that up.

## Simple Scoring and Hit Radius

Asteroids scores 20 for breaking a large asteroid, 50 for a medium, and 100 for a small. I did a quick and dirty scoring feature, like this:

In `setup`, I set `Score` to zero. Then in `findCollisions`, I call `score(asteroid)`:

``````function findCollisions()
local KillDist = 50
for i,a in pairs(Asteroids) do
for k,m in pairs(Missiles) do
if m.pos:dist(a.pos) < killDist(a) then
scoreAsteroid(a)
splitAsteroid(a)
m:die()
end
end
end
end``````

Then in the Asteroid tab:

``````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``````

We compute score based on asteroid size and add it into Score. I thought at first of looking the score up in a table but decided to go with the `if` for now.

Finally, I display the score near the end of `draw`, calling `drawScore`, which looks like this:

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

Convert score to string, slam on leading zeros, take five digits, display. Codea does have a `format` function but this seemed more in the spirit of 1979. Here’s a pic: Notice the circles in each of the asteroids? I popped those in to see whether just checking the distance between missile and asteroid center would suffice for collisions. What we see in that picture is that the implementor of the original Asteroids was very careful to shape them so that a simple distance check would work well.

I did that in three new commits, between 9:30 and 10:00PM last night.

I’ll spare you the code for now and make sure there’s a copy at the end.

## And Now What?

Let’s think about what features we need, or may need, and what code improvements are indicated.

Features include ship movement, ship destruction when hit by asteroids, multiple ships per “game”, perhaps a two-player mode, limiting shots to four at a time, hyperspace, and the two saucers that come out and try to kill you. And maybe some sounds. I suppose one could even have an idling mode like the real game. And probably there are more features I’ve forgotten. Ha! Game restarting after all your ships are gone, for one almost forgotten example.

Code improvements include, hmm …

Normalize Style
I prefer the object-oriented style of `Splat` and `Missile`, and would like to convert `Asteroid` and `Ship` to be compatible with that.
Drawing, Moving, Colliding
The current approach to drawing and moving and finding collisions is tangled at best. The “right thing” in a video game is to draw everything, move everything, check for collisions and other events, and then loop back to draw everything. We’re working OK now but it’s confusing and likely to lead to mistakes, especially as we add in saucers and such.
Object Collections
There’s no uniform handling of the various objects, and there are random global variables around, holding groups of things, or just providing shared access like `Button` does. It would be nice to centralize management of all the “stuff” somehow. That way we’d know where to look for things when we need to change them.
Game Creation
Everything is set up in, well, `setup`, but there are tables that are initialized only in their declaration, spread among tabs, and other oddities that I’ve surely forgotten but that will turn up when we start looking around
Magic Numbers
There are magic numbers all around, speeds and ratios and scores and angles. These ought to be brought together in as few places as possible, and perhaps even given better names. If there are required relationships among them, those should be made explicit.

And surely there’s more.

And this, mind you, is the mess we’ve created in less than 500 lines of code, counting blank lines. We may have another 500 to go, at a random guess, and the going won’t get smoother.

So I’d like to spend a bit of time today smoothing out some of these bumps.

Where shall we start?

## The Universe

I think I’d like to start at the top, with the universe. We have a number of nearly global structures, mostly tables and some constants. Things are fairly well isolated into tabs now, but there are still the collections at the top of most of the tabs, and some code, like the `findCollisions` that knows where to look for things.

Now there are some fancy patterns we could use to pass collections around when needed, but the Codea `draw` cycle limits us in that nothing is passed into that function, which means that anything we did pass around would have to be discovered there and passed in. That might still be desirable: we’ll see. Right now the structure isn’t clear enough in my mind for me to imagine a perfect better one.

I propose a new class, `Universe,` with one instance, `U`, that will contain all the collections, magic constants, and global behaviors that we need. We’ll move things to it slowly, to get a feeling for whether it’s working out.

I think the first thing to move into it will be the `Asteroids` collection. I’m thinking it’s big enough to be interesting and hoping that it’s small enough not to be a problem. If it is, we’ll try something else.

So here goes. With the Codea-provided `touch` function and the comments removed, and my header comment added, it looks like this:

``````-- Universe
-- RJ 20200523

Universe = class()

function Universe:init()
end

function Universe:draw()
end``````

So far so good. My tentative plan is that `Universe` has member variables that are the various global tables and values we use throughout the program. We might bury values another level down in something like `U.constants.speed` or the like. We’ll see.

My plan is that Main will create the universe and the universe will create everything else. We’ll do this incrementally of course.

In Main:

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

I just created a Universe and told it to create the asteroids. If I run now, nothing good happens, because there will be no asteroids. Now to fix that:

``````function Universe:createAsteroids()
createAsteroids()
end``````

That works, trivially, because there’s already a function over in Asteroids to do that. I want the Asteroids table inside the Universe. Right now, it’s a local in Asteroid tab, as is `DeadAsteroids`, the table that stores moribund asteroids until it is safe to edit the table.

``````-- Asteroid
-- RJ 20200520

Asteroids = {}
local Vel = 1.5

function createAsteroids()
for i = 1,4 do
local a = createAsteroid()
Asteroids[a] = a
end
end``````

My plan is to remove the global `Asteroids` here, create a member variable named `asteroids` in `Universe`, and pass that collection to the creator and whoever else needs it. This will break stuff and Codea doesn’t have much refactoring support, by which I mean none.

``````Universe = class()

function Universe:init()
self.asteroids = {}
end

function Universe:draw()
end

function Universe:createAsteroids()
createAsteroids(self.asteroids)
end``````

And in Asteroid tab:

``````function createAsteroids(asteroids)
for i = 1,4 do
local a = createAsteroid()
asteroids[a] = a
end
end``````

Now a quick search for who else needs that global. Inside the tab we find a few references. I think I’ll just take them one by one:

``````function drawAsteroids()
pushStyle()
stroke(255)
fill(0,0,0, 0)
strokeWidth(2)
rectMode(CENTER)
for i,asteroid in pairs(Asteroids) do
drawAsteroid(asteroid)
moveAsteroid(asteroid)
end
popStyle()
end``````

This loops over all the asteroids. if we pass it the asteroids collection, it’ll do fine, though it will need to pass it on to the kill function, I reckon. (I’m starting to wish this Asteroid thing was an object but no matter.)

Let’s make Main call U:draw() and begin to move things like moving and drawing the asteroids into U.

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

In Universe:

``````function Universe:draw()
drawAsteroids(self.asteroids)
end``````

And in Asteroid:

``````function drawAsteroids(asteroids)
pushStyle()
stroke(255)
fill(0,0,0, 0)
strokeWidth(2)
rectMode(CENTER)
for i,asteroid in pairs(asteroids) do
drawAsteroid(asteroid)
moveAsteroid(asteroid)
end
popStyle()
end``````

I took a flyer and passed the collection into the kill operation. Better look at it and clean it up:

``````function killDeadAsteroids(asteroids)
asteroids[a] = nil
end
end``````

There’s one more reference to the old global left in the Asteroid tab (assuming I’ve spotted them all):

``````function splitAsteroid(asteroid)
if asteroid.scale == 4 then
Splat(asteroid.pos)
return
end
asteroid.scale = asteroid.scale//2
asteroid.angle = math.random()*2*math.pi
local new = createAsteroid()
new.pos = asteroid.pos
new.scale = asteroid.scale
Asteroids[new] = new
Splat(asteroid.pos)
end``````

The `splitAsteroid` function is called from … Main tab:

``````function findCollisions()
local KillDist = 50
for i,a in pairs(Asteroids) do
for k,m in pairs(Missiles) do
if m.pos:dist(a.pos) < killDist(a) then
scoreAsteroid(a)
splitAsteroid(a)
m:die()
end
end
end
end``````

Let’s move that to U and have Main just call it. Then we have legit access to the asteroids collection.

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

Note that I’m using the asteroids collection in U twice there, the second time passing it to the splitter.

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

At this point I want to run the program and see what explodes. I’m hoping it’s just asteroids.

And it runs. By Jove, I think we’ve done it. One Codea search to see if there is a capital-A “Asteroids” left but I don’t expect to find one … and there is not.

We have successfully created the Universe and have moved some key functions into it, creation of the asteroids, plus their drawing, plus collision detection. We’ve hidden the `Asteroids` global, although we’ve replaced it with another global, `U`, a much more capable object and a rather nice place for things to live.

That’s enough for today. I’ll sum up, and paste another copy of the code down below the starting version.

In due time, I plan to keep the current version live in a standard place, as suggested by Dave1707, but for today, we’ll keep it here.

## Summing Up

We’ve observed some systematic structural concerns in our code. It’s the sort of thing that might have tempted us to do some kind of major rewriting of parts of it. We resisted that bravely, and instead created a very simple object with only four methods, `init`, `draw`, `createAsteroids`, and `findCollisions`.

Three of those methods were trivial and the fourth was just moved, with a light edit, from another location. It went smoothly and we were never deeply confused, and things work.

I’m a bit less happy than I’d be if I had lots of micro-tests, but I still see no good way to test the things I’m worried about. Maybe we’ll talk about that next time. In any case the game still plays as before.

So we’ve done an hour’s work or less, and taken a decent first step toward an improved structure. I’ve noticed that we’d have done well to have imported `createAsteroids` as we did the find, but we can do that another time. And there’s a lesson there, which is that there’s a perfectly good first step to consolidating things in place, and another step to take if and when we want to.

To me, there is a big lesson here, which is that we can always (OK, nearly always) improve the code in small safe steps, without rewriting or stalling the project while we clean things up. And that’s a much better way to go, because the Powers That Be really don’t like it when projects stall.

I like to say that there is no such thing as a “major refactoring”, and this little example shows us why I say that. There are always small safe steps to be taken. We just have to find them.

See you next time!

## The Starting Code

As of starting new work Saturday:

``````--# Main
-- Asteroids
-- RJ 20200511

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

function setup()
print("Hello Asteroids!")
print(WIDTH, HEIGHT)
Score = 0
--displayMode(FULLSCREEN_NO_BUTTONS)
createButtons()
createAsteroids()
createShip()
end

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

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

function findCollisions()
local KillDist = 50
for i,a in pairs(Asteroids) do
for k,m in pairs(Missiles) do
if m.pos:dist(a.pos) < killDist(a) then
scoreAsteroid(a)
splitAsteroid(a)
m:die()
end
end
end
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 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(keepInBounds(100,1000)).is(100)
_:expect(keepInBounds(1000,1000)).is(0)
_:expect(keepInBounds(1001,1000)).is(1)
_:expect(keepInBounds(-1,1000)).is(999)
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 = {}

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

function drawShip()
local sx = 10
local sy = 6
pushStyle()
pushMatrix()
translate(Ship.pos.x, Ship.pos.y)
rotate(Ship.ang)
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.ang = Ship.ang + 1 end
if Button.right then Ship.ang = Ship.ang - 1 end
if Button.fire then if not Ship.holdFire then fireMissile() end end
if not Button.fire then Ship.holdFire = false 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()
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

Asteroids = {}
local Vel = 1.5

function createAsteroids()
for i = 1,4 do
local a = createAsteroid()
Asteroids[a] = a
end
end

function createAsteroid()
local a = {}
a.pos = vec2(math.random(WIDTH), math.random(HEIGHT))
a.angle = math.random()*2*math.pi
a.shape = Rocks[math.random(1,4)]
a.scale = 16
return a
end

function drawAsteroids()
pushStyle()
stroke(255)
fill(0,0,0, 0)
strokeWidth(2)
rectMode(CENTER)
for i,asteroid in pairs(Asteroids) do
drawAsteroid(asteroid)
moveAsteroid(asteroid)
end
popStyle()
end

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

Asteroids[a] = nil
end
end

function deathSize()
local i = 0
for k, a in pairs(DeadAsteroids) do
i = i + 1
end
return i
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)
if asteroid.scale == 4 then
Splat(asteroid.pos)
return
end
asteroid.scale = asteroid.scale//2
asteroid.angle = math.random()*2*math.pi
local new = createAsteroid()
new.pos = asteroid.pos
new.scale = asteroid.scale
Asteroids[new] = new
Splat(asteroid.pos)
end

function drawAsteroid(asteroid)
pushMatrix()
pushStyle()
translate(asteroid.pos.x, asteroid.pos.y)
ellipse(0,0,2*killDist(asteroid))
scale(asteroid.scale)
strokeWidth(1/asteroid.scale)
for i,l in ipairs(asteroid.shape) do
line(l.x, l.y, l.z, l.w)
end
popStyle()
popMatrix()
end

function moveAsteroid(asteroid)
local step = Ratio*vec2(Vel,0):rotate(asteroid.angle)
local pos = asteroid.pos + step
asteroid.pos = vec2(keepInBounds(pos.x, WIDTH), keepInBounds(pos.y, HEIGHT))
end

function keepInBounds(value, bound)
return (value+bound)%bound
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

Missiles = {}

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

Missile = class()

local MissileVelocity = 2.0

function Missile:init(ship)
function die()
self:die()
end
self.pos = ship.pos
Missiles[self] = self
tween(3, self, {}, tween.easing.linear, die)
end

function Missile:die()
Missiles[self] = nil
end

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

function Missile:move()
self.pos = self.pos + Ratio*self.vel
self.pos = vec2(keepInBounds(self.pos.x, WIDTH), keepInBounds(self.pos.y, HEIGHT))
end``````

## The Ending Code

Here’s what we finished with, commit: Created Universe:

``````--# 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()
createShip()
end

function draw()
Ratio = DeltaTime/0.0083333
--displayMode(FULLSCREEN_NO_BUTTONS)
checkButtons()
pushStyle()
background(40, 40, 50)
U:draw()
drawButtons()
drawShip()
moveShip()
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 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(keepInBounds(100,1000)).is(100)
_:expect(keepInBounds(1000,1000)).is(0)
_:expect(keepInBounds(1001,1000)).is(1)
_:expect(keepInBounds(-1,1000)).is(999)
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 = {}

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

function drawShip()
local sx = 10
local sy = 6
pushStyle()
pushMatrix()
translate(Ship.pos.x, Ship.pos.y)
rotate(Ship.ang)
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.ang = Ship.ang + 1 end
if Button.right then Ship.ang = Ship.ang - 1 end
if Button.fire then if not Ship.holdFire then fireMissile() end end
if not Button.fire then Ship.holdFire = false 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()
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 Vel = 1.5

function createAsteroids(asteroids)
for i = 1,4 do
local a = createAsteroid()
asteroids[a] = a
end
end

function createAsteroid()
local a = {}
a.pos = vec2(math.random(WIDTH), math.random(HEIGHT))
a.angle = math.random()*2*math.pi
a.shape = Rocks[math.random(1,4)]
a.scale = 16
return a
end

function drawAsteroids(asteroids)
pushStyle()
stroke(255)
fill(0,0,0, 0)
strokeWidth(2)
rectMode(CENTER)
for i,asteroid in pairs(asteroids) do
drawAsteroid(asteroid)
moveAsteroid(asteroid)
end
popStyle()
end

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

asteroids[a] = nil
end
end

function deathSize()
local i = 0
for k, a in pairs(DeadAsteroids) do
i = i + 1
end
return i
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)
return
end
asteroid.scale = asteroid.scale//2
asteroid.angle = math.random()*2*math.pi
local new = createAsteroid()
new.pos = asteroid.pos
new.scale = asteroid.scale
asteroids[new] = new
Splat(asteroid.pos)
end

function drawAsteroid(asteroid)
pushMatrix()
pushStyle()
translate(asteroid.pos.x, asteroid.pos.y)
ellipse(0,0,2*killDist(asteroid))
scale(asteroid.scale)
strokeWidth(1/asteroid.scale)
for i,l in ipairs(asteroid.shape) do
line(l.x, l.y, l.z, l.w)
end
popStyle()
popMatrix()
end

function moveAsteroid(asteroid)
local step = Ratio*vec2(Vel,0):rotate(asteroid.angle)
local pos = asteroid.pos + step
asteroid.pos = vec2(keepInBounds(pos.x, WIDTH), keepInBounds(pos.y, HEIGHT))
end

function keepInBounds(value, bound)
return (value+bound)%bound
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

Missiles = {}

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

Missile = class()

local MissileVelocity = 2.0

function Missile:init(ship)
function die()
self:die()
end
self.pos = ship.pos
Missiles[self] = self
tween(3, self, {}, tween.easing.linear, die)
end

function Missile:die()
Missiles[self] = nil
end

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

function Missile:move()
self.pos = self.pos + Ratio*self.vel
self.pos = vec2(keepInBounds(self.pos.x, WIDTH), keepInBounds(self.pos.y, HEIGHT))
end

--# Universe
-- Universe
-- RJ 20200523

Universe = class()

function Universe:init()
self.asteroids = {}
end

function Universe:draw()
drawAsteroids(self.asteroids)
end

function Universe:createAsteroids()
createAsteroids(self.asteroids)
end

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