Python Asteroids on GitHub

I think we should break out the thumper logic into a Flyer and simplify Fleets accordingly.

Fleets makes the increasingly scary “thumper” sound with this code:

class Fleets:
def __init__(self, asteroids=(), missiles=(), saucers=(), saucer_missiles=(), ships=()):
...
self.thumper = Thumper(self.beat1, self.beat2)

def tick(self, delta_time):
if self._asteroids and self._ships:
self.thumper.tick(delta_time)
else:
self.thumper.reset()
self.flyers.tick(delta_time, self)

@staticmethod
def beat1():
player.play("beat1")

@staticmethod
def beat2():
player.play("beat2")

The Thumper object is just this:

class Thumper:
def __init__(self, b1, b2):
self._long_interval = 30/60
self._short_interval = 8/60
self._decrement_interval = 127/60
self.b1 = b1
self.b2 = b2
self.reset()

# noinspection PyAttributeOutsideInit
def reset(self):
self._interval = self._long_interval
self._decrement_time = 0
self._execute_time = 0

# noinspection PyAttributeOutsideInit
def tick(self, delta_time):
self._execute_time += delta_time
if self._execute_time >= self._interval:
self._execute_time = 0
self.b1()
self.b1, self.b2 = self.b2, self.b1
self._decrement_time += delta_time
if self._decrement_time >= self._decrement_interval:
self._decrement_time = 0
self._interval = self._interval - 1/60
if self._interval < 8/60:
self._interval = 8/60

What the thing does, and I don’t like the names much here, is that hmm it needs to use that _short_interval member, not the literal 8/60. As I was saying …

1. The value _interval starts at _long_interval and over time gets decremented down to _short_interval;
2. Every _interval seconds, it plays one of the beats and swaps them so that next time it will play the other. Bump BUMP Bump BUMP.
3. Every _decrement_interval seconds, it shortens the _interval by a sixtieth of a second, making the beats come closer and closer together.

Let’s rename the variables and use them where they need it.

OK, I went wild with names and extracts. Here it is now:

class Thumper:
def __init__(self, b1, b2):
self._longest_time_between_beats = 30 / 60
self._shortest_time_between_beats = 8 / 60
self._delay_before_shortening_beat_time = 127 / 60
self._amount_to_shorten_beat_time = 1 / 60
self._time_between_beats = 0
self._time_since_last_beat = 0
self._time_since_last_decrement = 0
self.b1 = b1
self.b2 = b2
self.reset()

def reset(self):
self._time_between_beats = self._longest_time_between_beats
self._time_since_last_decrement = 0
self._time_since_last_beat = 0

def tick(self, delta_time):
if self.it_is_time_to_beat(delta_time):
self.play_and_reset_beat()
if self.it_is_time_to_speed_up_beats(delta_time):
self.speed_up_beats()

def it_is_time_to_beat(self, delta_time):
self._time_since_last_beat += delta_time
return self._time_since_last_beat >= self._time_between_beats

def play_and_reset_beat(self):
self._time_since_last_beat = 0
self.b1()
self.b1, self.b2 = self.b2, self.b1

def it_is_time_to_speed_up_beats(self, delta_time):
self._time_since_last_decrement += delta_time
return self._time_since_last_decrement >= self._delay_before_shortening_beat_time

def speed_up_beats(self):
self._time_since_last_decrement = 0
self._time_between_beats =
self._time_between_beats - self._amount_to_shorten_beat_time
if self._time_between_beats < self._shortest_time_between_beats:
self._time_between_beats = self._shortest_time_between_beats

Let’s simplify that last bit.

def speed_up_beats(self):
self._time_since_last_decrement = 0
self._time_between_beats = max(
self._time_between_beats - self._amount_to_shorten_beat_time,
self._shortest_time_between_beats)

OK, that’s about the best I can do. Commit: refactor Thumper to death.

Now let’s move the beats inside.

class Thumper:
def __init__(self, b1=None, b2=None):
self._longest_time_between_beats = 30 / 60
self._shortest_time_between_beats = 8 / 60
self._delay_before_shortening_beat_time = 127 / 60
self._amount_to_shorten_beat_time = 1 / 60
self._time_between_beats = 0
self._time_since_last_beat = 0
self._time_since_last_decrement = 0
self.b1 = b1 if b1 else self.beat1
self.b2 = b2 if b2 else self.beat2
self.reset()

@staticmethod
def beat1():
player.play("beat1")

@staticmethod
def beat2():
player.play("beat2")

That works just fine. Commit: Thumper defaults to play beat1 and beat2.

I think I’ll stop here, as the day has become random.

## Summary

We’ve taken a few steps toward making Thumper more clear and more self-contained, while keeping its tests running. We can turn it into a Flyer real soon now.