Python Asteroids on GitHub

I’ve implemented the SurfaceManager to allow callers to specify extra room for fat lines. That was not a good idea.

Since we’re drawing lines with width 3, lines that are right on the edge of an object’s drawing can come out looking too thin, unless we make room for the part of the line that is outside the nominal coordinates. I’ve provided for this by allowing a parameter, room_for_fat_line in the surface creation:

class SurfaceManager:
    @staticmethod
    def create_desired_surface(points_to_draw, object_size, raw_points_span, room_for_fat_line):
        raw_points_offset = raw_points_span / 2
        scale_factor = object_size.x / raw_points_span.x
        expanded_size = object_size + room_for_fat_line
        surface = SurfaceMaker.create_scaled_surface(
            expanded_size, raw_points_offset, scale_factor, points_to_draw)
        return surface

What I realized a few moments ago, and noted in the preceding article, is that we could just always expand the nominal space by 2 pixels width and height, and remove the parameter, simplifying everyone’s code and reducing the thought burden. For example, asteroids get created like this:

class SurfaceManager:
    @staticmethod
    def asteroid_surface(object_size: Vector2):
        points_to_draw = SurfaceMaker.get_next_shape()
        raw_points_span = SurfaceMaker.span(points_to_draw)
        room_for_fat_line = Vector2(2, 2)
        return SurfaceMaker.create_desired_surface(points_to_draw, object_size, raw_points_span, room_for_fat_line)

When we’re done, we’ll have one less line of code in each of the four methods like this one.

We’ll begin by assigning the value directly in the create:

    @staticmethod
    def create_desired_surface(points_to_draw, object_size, raw_points_span, room_for_fat_line):
        room_for_fat_line = Vector2(2, 2)
        raw_points_offset = raw_points_span / 2
        scale_factor = object_size.x / raw_points_span.x
        expanded_size = object_size + room_for_fat_line
        surface = SurfaceMaker.create_scaled_surface(
            expanded_size, raw_points_offset, scale_factor, points_to_draw)
        return surface

This works. Commit: All surfaces are expanded by 2 pixels in width and height. Input parameter ignored.

Now change signature:

    @staticmethod
    def create_desired_surface(points_to_draw, object_size, raw_points_span):
        room_for_fat_line = Vector2(2, 2)
        raw_points_offset = raw_points_span / 2
        scale_factor = object_size.x / raw_points_span.x
        expanded_size = object_size + room_for_fat_line
        surface = SurfaceMaker.create_scaled_surface(
            expanded_size, raw_points_offset, scale_factor, points_to_draw)
        return surface

Commit: Remove unused parameter from create_desired_surface.

Now we can readily find the lines we don’t need and remove them. There are four. I’ll do them all at once, though I could commit four more times if I were a fanatic.

    @staticmethod
    def accelerating_surface(object_size: Vector2):
        points_to_draw = raw_ship_points + raw_flare_points
        raw_points_span = SurfaceMaker.span(points_to_draw)
        return SurfaceMaker.create_desired_surface(points_to_draw, object_size, raw_points_span)

    @staticmethod
    def asteroid_surface(object_size: Vector2):
        points_to_draw = SurfaceMaker.get_next_shape()
        raw_points_span = SurfaceMaker.span(points_to_draw)
        return SurfaceMaker.create_desired_surface(points_to_draw, object_size, raw_points_span)

    @staticmethod
    def saucer_surface(object_size: Vector2):
        points_to_draw = raw_saucer_points
        raw_points_span = SurfaceMaker.span(points_to_draw)
        return SurfaceMaker.create_desired_surface(points_to_draw, object_size, raw_points_span)

    @staticmethod
    def ship_surface(object_size: Vector2):
        points_to_draw = raw_ship_points
        points_to_span = raw_ship_points + raw_flare_points
        raw_points_span = SurfaceMaker.span(points_to_span)
        return SurfaceMaker.create_desired_surface(points_to_draw, object_size, raw_points_span)

Commit: remove useless settings of room_for_fat_line.

So that’s better.

Summary

Sometimes we think something needs to be variable, but in fact it doesn’t. A bit of over-engineering, you might call it. We spot the issue, we fix it up, we simplify the system just a bit. Future changes are generally easier. We go a bit faster.

It would be very nice if we could automatically generate the surface span, which is a function of the lines that are to be drawn. Unfortunately, that won’t work for the ship, because it has two surfaces, and one of them does not extend as far as the other, so they won’t line up unless we set the span the same in both cases.

There might be some deep way to improve that. We talked about that a bit last time, perhaps some kind of small object holding all the drawing information. It wouldn’t make the problem go away: I think the problem is built into the way Pygame works. But it could bury it in an object so that we don’t have to think about it, at least not most of the time.

For now, the world is a better place. I’m happy to have recognized a simpler way to do things.

See you next time!