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.