Python Asteroids+Invaders on GitHub

I think I’d like to try the player shot. Names are becoming an issue. For once I do the right thing.

Let’s plan a bit. A player shot will start out right above the player’s center, where the little pointy thing sticks up. It has a nominal size of one pixel wide by four high, which at our scale will be four by sixteen if I’m not mistaken. It travels straight up at 4 nominal pixels per interrupt, or 16 at our scale.

I figure that it will be a separate object, and that it will start flying upon creation, and that the player will use the interact_with logic to see whether there is a shot alive or not. Player can only fire when there is no shot already on screen.

At some altitude to be determined the shot explodes. And, if it hits an invader, the invader explodes. Looking at the game in play, I think that the shot does not emit its explosion in that case, allowing the alien all the thrills.

The shot will be attempted if the K key is down and will not be attempted again until it is lifted and hit again. I think we’ll skip using the other keys that I was thinking about. It’ll just make the logic weird.

I guess I should TDD this, though I am reluctant to do so, since I think the logic is actually simpler than the test will be. Maybe I can figure out how to do a better test.

    def test_can_fire_initially(self):
        fleets = Fleets()
        fi = FI(fleets)
        player = InvaderPlayer()
        fleets.append(player)
        player.attempt_firing(fleets)
        assert fi.player_shots

player does not know attempt_firing and FI does not know player_shots. And we need a new class for FI to know about. I create the class, and …

OK, this has to stop right now. PyCharm demands that I implement these methods:

class PlayerShot(Flyer):
    def interact_with(self, other, fleets):
        pass

    def draw(self, screen):
        pass

    def tick(self, delta_time, fleets):
        pass

    def interact_with_asteroid(self, asteroid, fleets):
        pass

    def interact_with_missile(self, missile, fleets):
        pass

    def interact_with_saucer(self, saucer, fleets):
        pass

    def interact_with_ship(self, ship, fleets):
        pass

None of those with_ methods even relate to invaders. This will not do!

We need two new subclasses of Flyer, and for asteroids and invaders objects each to use their own. How shall we do this?

There are lots of asteroids classes and few invaders ones. So let’s rename Flyer to A_Flyer first, then insert the Flyer superclass, and then the I_Flyer subclass.

One test fails, probably one of my special ones. No, just player shot test above. Super. Now to create a superclass of A_Flyer, called Flyer. PyCharm complains about the name A_Flyer, so to placate it, I rename to AsteroidFlyer. Should have done that right off. I was trying to save typing, which really isn’t an issue.

class Flyer(ABC):


class AsteroidFlyer(Flyer):

    @abstractmethod
    def interact_with(self, other, fleets):
        pass
        ...

At this point, all the abstract methods are in AsteroidFlyer. Move the basic ones into Flyer:

class Flyer(ABC):

    @abstractmethod
    def interact_with(self, other, fleets):
        pass

    @abstractmethod
    def draw(self, screen):
        pass

    @abstractmethod
    def tick(self, delta_time, fleets):
        pass

Good so far. Now a new subclass, InvadersFlyer:


class InvadersFlyer(Flyer):

    @abstractmethod
    def interact_with_bumper(self, bumper, fleets):
        pass

    @abstractmethod
    def interact_with_invaderfleet(self, bumper, fleets):
        pass

    @abstractmethod
    def interact_with_invaderplayer(self, bumper, fleets):
        pass

    @abstractmethod
    def interact_with_playershot(self, bumper, fleets):
        pass

Now to make my new classes inherit from InvadersFlyer. Here’s one:

class PlayerShot(InvadersFlyer):
    def interact_with(self, other, fleets):
        other.interact_with_playershot(self, fleets)

    def interact_with_bumper(self, bumper, fleets):
        pass

    def interact_with_invaderfleet(self, bumper, fleets):
        pass

    def interact_with_invaderplayer(self, bumper, fleets):
        pass

    def interact_with_playershot(self, bumper, fleets):
        pass

    def draw(self, screen):
        pass

    def tick(self, delta_time, fleets):
        pass

Now for the others. InvaderPlayer, Bumper, and InvaderFleet.

After only a bit of hassle, we’re back to green except for my new player shot test. I think I’ll make that work and then commit.

    def test_can_fire_initially(self):
        fleets = Fleets()
        fi = FI(fleets)
        player = InvaderPlayer()
        fleets.append(player)
        player.attempt_firing(fleets)
        assert fi.player_shots

We just need this:

class FleetInspector:
    @property
    def player_shots(self):
        return self.select_class(PlayerShot)

class InvadersPlayer(InvadersFlyer):
    def attempt_firing(self, fleets):
        fleets.append(PlayerShot())

The test is green. Commit: New Flyer subclasses, AsteroidsFlyer and InvadersFlyer. InvadersPlayer has initial attempt_firing method, creates a PlayerShot.

Very extensive commit, 23 files. Essentially everything got re-rooted. But it went smoothly.

That might be enough for this session. Let’s sum up.

Summary

Hierarchy improved

The need to change the hierarchy slapped me in the face, with a raft of abstract methods that I absolutely did not need. So I finally went ahead and made the changes and they were simple and almost all automatic. I removed unneeded methods where I saw them, but there was no real harm had I left them in. There might still be a few that I should look for.

Small step

I’m also a bit pleased at the extremely small step toward the player shot. I posited a new method, attempt_firing and just implemented it by tossing an instance of a trivial new class PlayerShot, into the mix. Test runs, next test will drive out more behavior.

Naming is an issue

The naming of things is bugging me. I’ve been prepending Invaders to class names in the invaders game. Probably what I really needed to do was to put each game in its own directory, which might allow me to use obvious names like Missile and Player with impunity. I am not adept enough with Python’s style to know for sure what to do, but certainly unique names will work.

Went well

That aside, this went well and I’m sure we can make this thing fly soon. My rough plan is to follow the standard structure of games in this decentralized design, so that, the player will detect whether there is a shot using the begin_interactions, interact_with_playershot kind of thing. Weird, yes, but easy and independent.

That’ll do, pig.

I am giving myself a hearty “well done, lads” or “that’ll do, pig”. I do love it when a simple plan comes together, and I’m pleased to have made the design a bit better with the new interface superclasses.

See you next time!