Python Asteroids+Invaders on GitHub

I have an idea for a step toward attract mode. I plan to try part of it.

I was thinking about attract mode, in that early morning not quite awake mode that I have, and I was thinking that the attract mode would have a driver object, because that’s how we do things around here. And Driver would want to know things like where the shields are. So I was thinking that I could do some math and figure out the values of X where the player can fire without hitting a shield. Then I thought of finding out with code, and then of having the Driver find out dynamically.

I envisioned the Player starting on the far left, moving left until X doesn’t change, discovering its minimum X value, emphasis on discovering. Then it would traverse right, and it would look up to see if there was a shield above its firing point.

All this was more fuzzy than I’m presenting here, so that’s pretty darn fuzzy. Quickly, the idea of ray-casting came to mind. Suppose that the Driver/Player had the ability to cast a vertical ray, from the firing point of the Player, and get back information about what was hit by the ray. We could scoot across and quickly map out the X coordinates where the ray hits a shield. That would give us key information about the space.

And in the game, the Driver could cast a ray all the time and get back “real time info” about whether there was an alien above. (Some kind of advanced ray might tell us whether there was a falling bomb above. We’re not fanatics here about how this ray casting might work.)

So that’s more than enough idea to let us try something. This will definitely be an experiment, but parts of it seem to me to be likely to make sense, so I’ll try to use test-driving as much as I can, and to keep things reasonable, so that we can inch forward rather than ditch the idea. But we’ll be prepared to ditch the idea if we have to.

I interrupt myself …

I had been thinking about moving the Player around to get the information I need. But clearly we don’t really have to do that. It makes a kind of sense to give the Player only local information, because of this fantasy I have about it learning how to play the game well. But the fundamental notion of the “look up what do you see” ray-cast isn’t tied to the Player and shouldn’t be.

Let’s see about a RayCaster object. Let’s learn how it works by writing a test for it.

class TestAttractMode:
    def test_hookup(self):
        assert 2+2 == 4

    def test_raycaster_exists(self):
        Raycaster()

class Raycaster:
    def __init__(self):
        pass

I should write a macro or something to set up a quick test and class. It might encourage me to use tests more often. It could just create a single blurch1 into a test file as a starting point, and I could move the class out later. I work that way often anyway. I’ll make a card for that.

How to test this thing? Well, lets start with the assertion first, that’s a good approach when we don’t know how something is going to work. Often we can figure out what we want to ask it. I conclude that my first test asserts no result, so with that in mind, here’s my first wild guess:

    def test_empty_scan(self):
        caster = Raycaster()
        x = 15
        result = caster.cast(x)
        assert not result

I’ve figured out that the raycaster only needs to be given the X coordinate, because we’re just going to look up from some x position. And I’m not sure what the result is, but I was thinking that it would be nothing. I don’t think that will stand, I already have a better idea, but let’s make this work.

class Raycaster:
    def __init__(self):
        pass

    def cast(self, x):
        return None

I think we really want a result object that we can inspect and think about. What will we likely want?

I think we’ll generally want to know what we saw, nothing, a shield, an invader, a bomb, a saucer (good luck with that one). And we’ll want to know at least its Y coordinate, perhaps more. So do we need a RaycastResultType?

I think that’ll be useful for now. Should we create a superclass AbstractRaycastResult and subclasses? That might pay off, though it seems a bit heavy duty. Might be premature. Let’s just do the type and a single class for now.

I revise the test with current thinking, and it is enough to tell me that I don’t like where we’re going:

    def test_empty_scan(self):
        caster = Raycaster()
        x = 15
        result = caster.cast(x)
        assert isinstance(result, CastResult)
        assert result.type == CastResultType.NOTHING

How about this:

    def test_empty_scan(self):
        caster = Raycaster()
        x = 15
        result = caster.cast(x)
        assert isinstance(result, EmptyCastResult)

This implies the existence of a CastResult, possibly abstract, and used the Qualified Class Name notion to assert that there is a class EmptyCastResult.

I’ll put that in with the Raycaster.

class CastResult:
    pass


class EmptyCastResult(CastResult):
    pass


class Raycaster:
    def __init__(self):
        pass

    def cast(self, x):
        return EmptyCastResult()

Green. Let’s start committing this stuff. It’s in separate files and therefore harmless even if we later give up. I’d rather have the save points, and I do not want a branch, no never. Commit: initial raycasting.

Now we need to start getting serious. How is this thing going to work?

I can think of at least two ways: Inspect the contents of Fleets, or somehow use the interaction logic to detect things.

The first idea is rough and tumble, since we don’t usually go probing around in the object collections: our style is to interact with things.

The first way is straightforward but a bit nasty. What about the interaction approach?

If we were to do that, the instead of actually casting a ray, we could imagine that our object, the Driver or whatever it is, has an X position that is known to it, and it gets interactions with shield, invader, missile, and it ascertains whether a raycast from current X would see that thing. Hmm.

The update cycle is update, interactions, tick, draw. So our “raycast” object would know, by the end of the cycle, what the situation is above it. And then on the next update, we’d have information about what’s above, and could use it to make a decision.

Change of Direction!

That makes me want to belay the Raycaster and sketch some kind of more passive information collection based on interactions.

This is leading me down a different path. Currently the InvaderPlayer object is tightly coupled to the pygame keys, for example:

class InvaderPlayer:
    def check_motion(self, keys):
        if keys[pygame.K_f]:
            self.move(self.step)
        elif keys[pygame.K_d]:
            self.move(-self.step)

We’ll have to figure this out anyway, so let’s think about it now. How will our Driver control the Player? It might just have it and send the abstract messages to it, like move above. But we’ll want to disconnect the keys, since otherwise you can play the game in attract mode.

We could probably separate out the key disconnection from how we control via the Driver. Some kind of cover objects for the keys could provide a set of keys that never go down. And the Driver could still just send abstract messages to the Player. Or, we could have it work by acting like keys … which seems like a hassle.

Let’s assume that the Driver has a pointer to the Player and work from there. We’ll ignore the keys during attract mode issue, figuring that we can resolve it later.

So yes, belay the raycasting and do a new InvadersFlyer, the Driver. I am momentarily slightly irritated about this change of direction, but no real harm done.

Could we repurpose the Raycaster? Meh. Sunk cost fallacy. Just do a new thing. I’ll keep my test file, it’s named test_attract_mode.py anyway.

    def test_driver(self):
        player = InvaderPlayer()
        driver = Driver(player)

class Driver:
    def __init__(self, player):
        self.player = player

This time I’m keeping the class in the test file for a while, rather than over in prod, like the Raycaster is.

I think the idea will be that the Driver is open to interactions from shields, invader shots, and invader fleet. (There are no invader interactions,, at least not yet.) I’m guessing that we’ll recruit some help from InvaderFleet and InvaderGroup on that.

Now will the Driver send messages to the Player, telling it what to do? Or will the Player somehow know to ask the Driver?

Should we perhaps just have the Driver be a kind of player that looks like a player but makes its own decisions and draws like a Player?

That seems fruitful to me. Let’s assume that.

So the Driver needs to be an InvaderFlyer with all that implies. We’ll start on that, but I plan to wrap up soon. I’ve been at this long enough.

class Driver(Spritely, InvadersFlyer):
    def interact_with(self, other, fleets):
        other.interact_with_driver(self, fleets)

    def __init__(self):
        self._sprite = Sprite.player()
        self.step = 4
        half_width = self._sprite.width / 2
        self.left = 64 + half_width
        self.right = 960 - half_width
        self.position = Vector2(self.left, u.INVADER_PLAYER_Y)

class TestAttractMode:
    def test_driver(self):
        driver = Driver()

I’m going to toss one of these things into the invader attract mode coin. When I do that, we get what appears to be a Player at the bottom left, just as we should. Chalk this up as a win. Here’s what it looks like: note that what looks like a Player at lower left is actually the Driver!

game over with driver at bottom

I need a break. Let’s wrap this up. Commit: initial Driver.

Summary

Experience with the raycasting idea clarified my thinking enough to tell me that I’d prefer to use the interaction logic, leading to a new object, Driver. Focusing on its nearly trivial code as a Flyer drove me to making it a Spritely object, borrowing part of the Player init, and gives me an object with a trivial starting test but that can display itself and interact.

It has no interactions, but they are being sent to it: it’s just that they default to ignored.

I think this is an interesting start. I felt a little frustrated about the Raycaster being apparently useless … but it was doing it and thinking about it in context that told me there was a better way. Was it a step in the wrong direction? Or sort of in the right direction? Don’t know, don’t care. It was a small step and then we took another and we’re in a better position now.

One thought comes to me as I clean up the article a bit: We were on a path that was going to lead to some kind of structured CastResult, probably a class hierarchy. With the Driver being a Flyer, we won’t need those results: Driver will accumulate whatever information it needs in the form that it needs and then apply it. I am happier about abandoning the ray casting.

And I want a break.

See you next time!



  1. Is that the correct spelling, or is it “blerch”? I’m not really sure …