I found some better recordings of the Asteroids sounds. And it’s time to trigger new waves.

The sounds are on my old iPad, and moving them over isn’t worth writing about. I’ll see to it that they’re in the next “release”. The new ones have the same names as the old ones, except they end in Hi, because they are higher quality. I’ll use the ones I prefer. And there’s a new sound, which is called “saucerFire” or something like that, a different “pew pew” for the saucers.

There is one sound I’ve not found, which is a loud one that sounds to me like “dive, dive”, that plays when the saucers are on screen. I’ve applied mass Google-fu with no luck. Yet.

OK, new waves. I see these tasks:

  • Detect when there are no asteroids left. Easy, we have a loop over all asteroids, we can put a flag in there.
  • Delay about one or two seconds. Easy at first glance, with a tween, but we’ll have to be careful to trigger the wave once and only once, since the draw cycle will run multiple times before a couple of seconds have gone by.
  • The wave really needs to start at the edges of the screen, to give the player a decent chance, and for consistency with the original game. The last time I did that, I didn’t like how it looked, so I’ll try again.

I reckon I’ll do these tasks in the order shown above. We can release after any of them, though releasing after only the first makes little sense.

So, a little design thinking and then let’s go.

New Wave

The draw loop includes this code:

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

This happens at a propitious time, the very end of the draw loop. Couldn’t be better.

To detect that there all asteroids are gone, we can readily do this:

function Universe:findCollisions()
    local allGone = true
    for i,a in pairs(self.asteroids) do
        allGone = false
        self:checkMissileCollisions(a)
        if self.ship then self:checkShipCollision(a) end
    end
end

To trigger the new wave, we can just call the newWave function, which we broke out a few days ago. And we want to call it just once, not every time the draw cycle discovers no asteroids.

What if we initialize allGone, not to true, but to true when we haven’t called for a newWave, and false when a new wave is pending.

So, roughly:

  • When a new wave is created, turn U.newWaveCreated to true;
  • Init allGone to U.newWaveCreated, so it’ll be true except …
  • When our loop above detects allGone true, it sets U.newWaveCreated to false and starts the tween.

That’s good except that I don’t like the variable names. Rename allGone to needNewWave:

function Universe:findCollisions()
    local needNewWave = true
    for i,a in pairs(self.asteroids) do
        needNewWave = false
        self:checkMissileCollisions(a)
        if self.ship then self:checkShipCollision(a) end
    end
end

You know, this somewhat tricky interlock is a perfect example of where we should write a test. I’m half way through the implementation, though, and I rather hate pausing right here. On the other hand, it seems like the right thing to do, because the logic is tricky.

Put on testing hat.

Testing New Wave

These tests are going to be pretty invasive, because it has to make everything happen at test time that would normally happen over a “long” period of game play. Let me try a sketch:

        _:test("Trigger New Wave", function()
            local u = Universe()
            u:newWave()
            _:expect(u.newWavePending).is(false)
        end)

Right after a new wave, there should not be a new wave pending. (There is no such variable, of course, but the test is helping to drive the design.)

Now if we destroy all the asteroids, and run a draw cycle, we should find newWavePending:

        _:test("Trigger New Wave", function()
            local u = Universe()
            u:newWave()
            _:expect(u.newWavePending).is(false)
            u.asteroids = {}
            u:draw()
            _:expect(u.newWavePending).is(true)
        end)

That seems like good logic. And it looks like newWave is where that flag should get set to false. First I’ll run this test and see it fail. It does. I’m slightly surprised to find that, though the game is in attractMode, running the test fires three missiles. We have those three tests for missiles, and now that the game has sounds I can see and hear the asteroids explode if the test missiles happen to hit an asteroid. Fun.

Anyway:

function Universe:newWave()
    self.newWavePending = false
    for i = 1, self:newWaveSize() do
        local a = Asteroid()
        self.asteroids[a] = a
    end
end

And a corresponding setting in Universe:init to set it to true. I expect I can run this test and have it pass now.

I’m mistaken. The first assert passes, but the second doesn’t pass yet because the logic to set it to pending and do the tween isn’t in Universe yet. Oh, and I need to ensure that it only gets set once.

Wait. What if we let the draw loop increment a new wave request counter? Then at our leisure we can run the new wave and set the counter to zero. And we have to make draw initialize the newWavePending flag to true whenever the count is greater than zero.

Oh I think I like this: The test is making me express the logic clearly in code, helping me make my vague ideas more real. That’s why we write them,to help drive the design. I’ll recast the test:

        _:test("Trigger New Wave", function()
            local u = Universe()
            u:newWave()
            _:expect(u.newWaveRequests).is(0)
            u.asteroids = {}
            u:draw()
            _:expect(u.newWaveRequests > 0).is(true)
            u:newWave()
            _:expect(u.newWaveRequests).is(0)
        end)

I’m a bit sad that I can’t say

  _:expect(u.newWaveRequest).greater(0)

But CodeaUnit doesn’t support that operator. Perhaps I should add it. Later. Right now, let’s just make this test work:

function Universe:newWave()
    self.newWaveRequests = 0
    for i = 1, self:newWaveSize() do
        local a = Asteroid()
        self.asteroids[a] = a
    end
end

And I initialize it to 1 in init:

function Universe:init()
    self.processorRatio = 1.0
    self.score = 0
    self.rotationStep = math.rad(1.5) -- degrees
    self.missileVelocity = vec2(MissileSpeed,0)
    self.frame64 = 0
    self.newWaveRequests = 1
    self:defineSounds()
    self.button = {}
    self.asteroids = {}
    self.missiles = {}
    self.explosions = {}
    self.attractMode = true
    self:newWave()
end

The test doesn’t run yet, because the find logic isn’t right. We have this:

function Universe:findCollisions()
    local needNewWave = true
    for i,a in pairs(self.asteroids) do
        needNewWave = false
        self:checkMissileCollisions(a)
        if self.ship then self:checkShipCollision(a) end
    end
end

We initialize the needNewWave to true … I guess that’s OK. We don’t need any tricky logic now. First to make the main effect run. We are happy to tick the counter, but we will only trigger the timer when the counter coming in is zero.

(I’m starting to think this will work with just booleans, but I’m not letting that distract me from this path. If I’m right, though, this will be an interesting result.)

function Universe:findCollisions()
    local needNewWave = true
    for i,a in pairs(self.asteroids) do
        needNewWave = false
        self:checkMissileCollisions(a)
        if self.ship then self:checkShipCollision(a) end
    end
    if needNewWave == true then
        if self.newWaveRequests > 0 then
            -- do the tween
        end
        self.newWaveRequests = self.newWaveRequests + 1
    end
end

I expect my test to run now. I’m mistaken, the second expect fails:

        _:test("Trigger New Wave", function()
            local u = Universe()
            u:newWave()
            _:expect(u.newWaveRequests).is(0)
            u.asteroids = {}
            u:draw()
            _:expect(u.newWaveRequests > 0).is(true)
            u:newWave()
            _:expect(u.newWaveRequests).is(0)
        end)

Hm what am I missing?

I’ve spent at least ten minutes fiddling with this. What is happening is clear: after I set u.asteroids to an empty collection, the loops in findCollisions is finding asteroids.

I’ve checked that my local universe isn’t the one that the system is running … except, wait, if that were true, why would the missiles fire into the game field and kill things. That’s a clue.

Since I’m sure I know exactly how to build this feature now, I’m inclined to abandon the test and just do it. But there’s something funky going on here and I want to know what it is. What about those firing tests? Ah. They don’t use a local universe. I think I’d best fix them so that they do, even though that’s a bit of a change of direction given current confusion.

Ah, no. They have to be in the running universe because Ships register automatically in the current universe. We should make a note that that’s a bit of a bug.

One more idea,. I’ll make the running universe not run at all, by commenting out its draw call in Main,.

And my test still fails, finding asteroids where there should be none. What the heck am I missing? I should scrap this implementation and do over, it would save time in the long run. The summing up section is going to have some finger-wagging today. Let’s change the test to call only findCollisions, not draw, and see what happens:

        _:test("Trigger New Wave", function()
            local u = Universe()
            u:newWave()
            _:expect(u.newWaveRequests).is(0)
            u.asteroids = {}
            _:expect(asteroidCount(u)).is(0)
            u:findCollisions()
            _:expect(asteroidCount(u)).is(0)
            _:expect(u.newWaveRequests > 0).is(true)
            u:newWave()
            _:expect(u.newWaveRequests).is(0)
        end)

The test passes! So there’s something going on in draw that has messed me up.

Interesting. This test:

        _:test("Trigger New Wave", function()
            local u = Universe()
            _:expect(u.newWaveRequests).is(0)
            u.asteroids = {}
            _:expect(asteroidCount(u)).is(0)
            u:draw()
            _:expect(asteroidCount(u)).is(0)
            _:expect(u.needNewWave).is(true)
            _:expect(u.newWaveRequests > 0).is(true)
            u:newWave()
            _:expect(u.newWaveRequests).is(0)
        end)

fails when that u:draw() is there, and not when it’s u:findCollisions(). The asteroidCount is zero as expected, but both needNewWave is false and newWaveRequests is 0. What’s most interesting is that needNewWave is nil. This is maddening.

I know very well that I should revert and start over. But I’m weak and I want to understand this. It’s acting like findCollisions, doesn’t get called via u:draw(). Let’s test that:

        _:test("Trigger New Wave", function()
            local u = Universe()
            _:expect(u.newWaveRequests).is(0)
            u.asteroids = {}
            _:expect(asteroidCount(u)).is(0)
            u:draw()
            _:expect(findWasCalled).is(true) -- <---
            _:expect(asteroidCount(u)).is(0)
            _:expect(u.needNewWave).is(true)
            _:expect(u.newWaveRequests > 0).is(true)
            u:newWave()
            _:expect(u.newWaveRequests).is(0)
        end)

And …

function Universe:findCollisions()
    findWasCalled = true -- <---
    self.needNewWave = true
    for i,a in pairs(self.asteroids) do
        self.needNewWave = false
        self:checkMissileCollisions(a)
        if self.ship then self:checkShipCollision(a) end
    end
    if self.needNewWave == true then
        if self.newWaveRequests > 0 then
            -- do the tween
        end
        self.newWaveRequests = self.newWaveRequests + 1
    end
end

I see it! Look at the bottom of this:

function Universe:draw()
    self.frame64 = (self.frame64+1)%64
    self:checkBeat()
    --displayMode(FULLSCREEN_NO_BUTTONS)
    pushStyle()
    background(40, 40, 50)
    self.processorRatio = DeltaTime/0.0083333
    self:drawAsteroids()
    self:drawExplosions()
    checkButtons()
    drawButtons()
    if self.ship then self.ship:draw() end
    if self.ship then self.ship:move() end
    self:drawMissiles()
    drawSplats()
    U:drawScore()
    popStyle()
    U:findCollisions()
end

This method is referring to the global U, not to self. This means that it’s running findCollisions all right, but in the mirror universe. Wow, that’s weird. That would bite us in real life if we ever wanted to swap U around, so I’m glad I finally realized what was going on. Had I not decided to do this test, we’d have left this in. Maybe we’d have been OK, but it’s a potentially serious bug.

The good news is, even had I started over, this was going to make any test like the one we have fail. So I’ll fix that bug and clean up the test after being sure it runs.

I note in doing that that various other objects refer to U to move themselves and to access status and so on.

What we are seeing here is one of the big problems with a “god object”. It’s difficult to test if everyone knows the secret name of god. We’ll think about that later, right now, we have a feature to finish.

        _:test("Trigger New Wave", function()
            local u = Universe()
            _:expect(u.newWaveRequests).is(0)
            u.asteroids = {}
            _:expect(asteroidCount(u)).is(0)
            u:draw()
            _:expect(asteroidCount(u)).is(0)
            _:expect(u.needNewWave).is(true)
            _:expect(u.newWaveRequests > 0).is(true)
            u:newWave()
            _:expect(u.newWaveRequests).is(0)
        end)

This finally runs. Notice that in order to test it, I had to promote the needNewWave variable into Universe, which is a thing that happens when a test needs to look that deeply into things. I did that when I was confused, so I’ll remove that and demote that flag back to local:

        _:test("Trigger New Wave", function()
            local u = Universe()
            _:expect(u.newWaveRequests).is(0)
            u.asteroids = {}
            _:expect(asteroidCount(u)).is(0)
            u:draw()
            _:expect(asteroidCount(u)).is(0)
            _:expect(u.newWaveRequests > 0).is(true)
            u:newWave()
            _:expect(u.newWaveRequests).is(0)
        end)

This test runs, on this code:

function Universe:findCollisions()
    local needNewWave = true
    for i,a in pairs(self.asteroids) do
        needNewWave = false
        self:checkMissileCollisions(a)
        if self.ship then self:checkShipCollision(a) end
    end
    if needNewWave == true then
        if self.newWaveRequests > 0 then
            -- do the tween
        end
        self.newWaveRequests = self.newWaveRequests + 1
    end
end

Now to actually trigger the new wave. The code above anticipates doing it with a tween. It might be better, though, to trigger the new wave more explicitly as part of our regular cycle. How might we do that?

We now have a reliable value in Universe, newWaveRequests, which is greater than zero when we need a new wave.

What if we had a universe value called timeOfNextWave which was zero when there was no need for a new wave, and to some future time when there was need? Then we’d have another famous constant, timeBetweenWaves, which would be, oh, 2, for two seconds. Then rather than fire a tween in findCollisions, we would set timeOfNextWave to now + timeBetweenWaves` only if it isn’t already set.

That’s better. Let’s change the test to specify that:

        _:test("Trigger New Wave", function()
            local u = Universe()
            _:expect(u.timeOfNextWave).is(0)
            u.asteroids = {}
            _:expect(asteroidCount(u)).is(0)
            u:draw()
            _:expect(u.timeOfNextWave).is(ElapsedTime + 2, .05)
            u:newWave()
            _:expect(u.timeOfNextWave).is(0)
        end)

That should test everything but the code that checks the timer. We’ll worry about that in a moment. Let’s make this work:

function Universe:findCollisions()
    local needNewWave = true
    for i,a in pairs(self.asteroids) do
        needNewWave = false
        self:checkMissileCollisions(a)
        if self.ship then self:checkShipCollision(a) end
    end
    if needNewWave == true then
        if self.timeOfNextWave == 0 then
            self.timeOfNextWave = ElapsedTime + self.timeBetweenWaves
        end
    end
end

WIth init changed to set up the time between as 2, and `newWave:

function Universe:newWave()
    self.timeOfNextWave = 0
    for i = 1, self:newWaveSize() do
        local a = Asteroid()
        self.asteroids[a] = a
    end
end

Test passes. Now where should we trigger a new wave if needed?

Seems to me that the beginning of U:draw is right. “Obviously” the code will look like:

  if self.timeOfNextWave > 0 and ElapsedTime > self.timeOfNextWave then
    self:newWave()
  end

Suppose we were on a roll with testing, and we wanted to test that? We don’t want to write a test that waits for a few days for a timer to elapse. Instead, we want to spin time forward in the universe, more or less at will.

Let’s say that U:draw will be changed to accept the time of the current drawing as a parameter, typically ElapsedTime, and that it’ll save that in a u variable, and never use ElapsedTime directly, only its own time.

There’s only one call to draw outside the tests, so that should be relatively uninvasive. I’ll spare you the details of replacing ElapsedTime everywhere with self.currentTime. I did have to change startGame to expect the time as well, since that initializes the beat timer.

That all went smoothly enough. Tests all run.

(Full disclosure: the “random” test runs 1000 random number calls and checks the distribution. Once in a while you get unlucky and it fails. I’ll set it to ignore, as it has served its purpose.

OK, now we just need to trigger the new wave at the top of draw.

Because I can, I’m going to test it. Here’s our new wave test so far:

        _:test("Trigger New Wave", function()
            local u = Universe()
            _:expect(u.timeOfNextWave).is(0)
            u.asteroids = {}
            u:draw(ElapsedTime)
            _:expect(u.timeOfNextWave).is(u.currentTime + 2, .05)
            u:newWave()
            _:expect(u.timeOfNextWave).is(0)
        end)

“All” we need to do is set time forward, and instead of calling newWave, call draw. If draw does its job, the timeOfNextWave will go to zero, because draw will call newWave, which clears that value. I’ll make the change and watch it fail:

        _:test("Trigger New Wave", function()
            local u = Universe()
            _:expect(u.timeOfNextWave).is(0)
            u.asteroids = {}
            u:draw(ElapsedTime)
            _:expect(u.timeOfNextWave).is(u.currentTime + 2, .05)
            u:draw(u.currentTime + 2)
            _:expect(u.timeOfNextWave).is(0)
        end)

So in draw:

function Universe:draw(currentTime)
    self.currentTime = currentTime
    if self.currentTime >= self.timeOfNextWave then
        self:newWave()
    end
    self.frame64 = (self.frame64+1)%64
    self:checkBeat()
    --displayMode(FULLSCREEN_NO_BUTTONS)
    pushStyle()
    background(40, 40, 50)
    self.processorRatio = DeltaTime/0.0083333
    self:drawAsteroids()
    self:drawExplosions()
    checkButtons()
    drawButtons()
    if self.ship then self.ship:draw() end
    if self.ship then self.ship:move() end
    self:drawMissiles()
    drawSplats()
    self:drawScore()
    popStyle()
    self:findCollisions()
end

Now the test should run … and this happens:

lots of asteroids

because I forgot to check for timeOfNextWave non-zero, so we keep adding waves. Arguably timeOfTextWave should be set to the infinite future, but for now, no:

function Universe:draw(currentTime)
    self.currentTime = currentTime
    if self timeOfNextWave > 0 and self.currentTime >= self.timeOfNextWave then
        self:newWave()
    end
...

The test runs, and the game generates a new wave after a couple of seconds. One bug: the beat timer doesn’t go back to slow. That needs to be initialized in newGame:

function Universe:newWave()
    self.beatDelay = 1 -- second
    self.timeOfNextWave = 0
    for i = 1, self:newWaveSize() do
        local a = Asteroid()
        self.asteroids[a] = a
    end
end

And it works. The game now goes to new waves then all the asteroids are gone, after a two second delay. The ship continues as it is during that interval.

This is as intended. Time to commit: “new waves work”.

This has taken long enough that I’ll defer shipping the new sounds until the next version. Time to sum up for today.

Summing Up

Why do I always sum up at the end of these sessions? I’m obviously tired and it’s well past lunch time.

I do it, not just for you, dear reader, but for myself. I find it valuable to reflect back on what has happened, to see what lessons I might learn for next time.

It might be analogous to sharpening one’s tools after working with them, instead of putting them down dull and maybe sharpening them later. Or doing the dishes right after using them, instead of letting them pile up.

I’m not saying that I do either of those things, but I do like to reflect on what has happened.

First of all, despite the confusion, I’m really glad I wrote that test and stuck with it. I think there’s a decent chance that I could have just made the new waves work without the test, but there are a lot of places in the system that were impacted.

Just for fun, I’ll include Working Copy’s diff of the day’s work. There were three files change, 37 insertions, 12 deletions. That’s a fair amount of change, mostly in Universe of course.

And the test helped me come up with better ways of doing the change. My ideas were good, but not as concrete as they needed to be, and saying in the test, first this happens then this should be the case, then that, then this … helped me a lot.

This is why we do TDD-style microtests … they help us get things right and they help us get things right sooner.

Did the tests help me get it right sooner today? It’s hard to be sure. Certainly I stayed stuck for along while, because of the Universe code referring to its own global SIngleton instance. I might have managed not to get into trouble with that, since it only impacts the testing. But it’s wrong to have it coded that way, so the confusion was probably worth finding the bug.

And the god class aspect, and the singleton aspect, are troubling. I’m not sure what would be better, but it’s not perfect.

Beyond that, not much comes to mind. I’ll see what I’ve thought of by tomorrow.

See you then!

Zipped Code


Here’s the diff

new waves work

Asteroids c4bde77

 From c4bde778845fbca208645376714a201e83e25f54 Mon Sep 17 00:00:00 2001
From: Ron Jeffries <ronjeffries@acm.org>
Date: Mon, 1 Jun 2020 12:48:15 -0400
Subject: [PATCH] new waves work

---
 Main.lua          |  4 ++--
 TestAsteroids.lua | 13 ++++++++++++-
 Universe.lua      | 32 +++++++++++++++++++++++---------
 3 files changed, 37 insertions(+), 12 deletions(-)

diff --git a/Main.lua b/Main.lua
index dbaf943..4d52a37 100644
--- a/Main.lua
+++ b/Main.lua
@@ -8,7 +8,7 @@ function setup()
 end
 
 function draw()
-    U:draw()
+    U:draw(ElapsedTime)
     if U.attractMode then
         pushStyle()
         fontSize(50)
@@ -19,7 +19,7 @@ function draw()
 end
 
 function touched(touch)
-    if U.attractMode and touch.state == ENDED then U:startGame() end
+    if U.attractMode and touch.state == ENDED then U:startGame(ElapsedTime) end
     if touch.state == ENDED or touch.state == CANCELLED then
         Touches[touch.id] = nil
     else
diff --git a/TestAsteroids.lua b/TestAsteroids.lua
index a47cf5c..1f19551 100644
--- a/TestAsteroids.lua
+++ b/TestAsteroids.lua
@@ -18,7 +18,7 @@ function testAsteroids()
             _:expect( 2+1 ).is(3)
         end)
         
-        _:test("Random", function()
+        _:ignore("Random", function()
             local min = 100
             local max = 0
             for i = 0,1000 do
@@ -99,6 +99,17 @@ function testAsteroids()
             _:expect(u:newWaveSize()).is(11)
             _:expect(u:newWaveSize()).is(11)
         end)
+        
+        _:test("Trigger New Wave", function()
+            local u = Universe()
+            _:expect(u.timeOfNextWave).is(0)
+            u.asteroids = {}
+            u:draw(ElapsedTime)
+            _:expect(u.timeOfNextWave).is(u.currentTime + 2, .05)
+            u:draw(u.currentTime + 2)
+            _:expect(u.timeOfNextWave).is(0)
+        end)
 
     end)
 end
+
diff --git a/Universe.lua b/Universe.lua
index 1b38e6d..137e960 100644
--- a/Universe.lua
+++ b/Universe.lua
@@ -11,6 +11,7 @@ function Universe:init()
     self.rotationStep = math.rad(1.5) -- degrees
     self.missileVelocity = vec2(MissileSpeed,0)
     self.frame64 = 0
+    self.timeBetweenWaves = 2
     self:defineSounds()
     self.button = {}
     self.asteroids = {}
@@ -34,21 +35,25 @@ function Universe:defineSounds()
     self.sounds.thrust = asset.thrust
 end
 
-function Universe:startGame()
+function Universe:startGame(currentTime)
+    self.currentTime = currentTime
     self.attractMode = false
     createButtons()
     self.ship = Ship()
     self.asteroids = {}
     self.waveSize = nil
-    self.beatDelay = 1 -- second
-    self.lastBeatTime = ElapsedTime
+    self.lastBeatTime = self.currentTime
     self:newWave()
 end
 
-function Universe:draw()
+function Universe:draw(currentTime)
+    self.currentTime = currentTime
+    if self.timeOfNextWave > 0 and self.currentTime >= self.timeOfNextWave then
+        self:newWave()
+    end
     self.frame64 = (self.frame64+1)%64
     self:checkBeat()
-    displayMode(FULLSCREEN_NO_BUTTONS)
+    --displayMode(FULLSCREEN_NO_BUTTONS)
     pushStyle()
     background(40, 40, 50)
     self.processorRatio = DeltaTime/0.0083333
@@ -60,16 +65,16 @@ function Universe:draw()
     if self.ship then self.ship:move() end
     self:drawMissiles()
     drawSplats()
-    U:drawScore()
+    self:drawScore()
     popStyle()
-    U:findCollisions()
+    self:findCollisions()
 end
 
 function Universe:checkBeat()
     if self.attractMode then return end
     self:updateBeatDelay()
-    if ElapsedTime - self.lastBeatTime > self.beatDelay then
-        self.lastBeatTime = ElapsedTime
+    if self.currentTime - self.lastBeatTime > self.beatDelay then
+        self.lastBeatTime = self.currentTime
         self:playBeat()
     end
 end
@@ -90,6 +95,8 @@ function Universe:playBeat()
 end
 
 function Universe:newWave()
+    self.beatDelay = 1 -- second
+    self.timeOfNextWave = 0
     for i = 1, self:newWaveSize() do
         local a = Asteroid()
         self.asteroids[a] = a
@@ -97,10 +104,17 @@ function Universe:newWave()
 end
 
 function Universe:findCollisions()
+    local needNewWave = true
     for i,a in pairs(self.asteroids) do
+        needNewWave = false
         self:checkMissileCollisions(a)
         if self.ship then self:checkShipCollision(a) end
     end
+    if needNewWave == true then
+        if self.timeOfNextWave == 0 then
+            self.timeOfNextWave = self.currentTime + self.timeBetweenWaves
+        end
+    end
 end
 
 function Universe:checkShipCollision(asteroid)
--
Working Copy 4.2.4