OK, let’s fare off on our own here and see about drawing a ship. As I am new to Python, this will entail learning how to create a list or array or one of those things.

In Kotlin, the ship was defined like this:

private val shipPoints = listOf(
    Vector2(-3.0, -2.0), Vector2(-3.0, 2.0), Vector2(-5.0, 4.0),
    Vector2(7.0, 0.0), Vector2(-5.0, -4.0), Vector2(-3.0, -2.0)
)

private val shipFlare = listOf(
    Vector2(-3.0,-2.0), Vector2(-7.0,0.0), Vector2(-3.0, 2.0)
)

I’ll create a new Python file and just get to it. I start with this much:

class Ship():
    def __init__(self, position):
        self.position = position
        self.points = [vector2(-3.0, -2.0), vector2(-3.0, 2.0), vector2(-5.0, 4.0),
                       vector2(7.0, 0.0), vector2(-5.0, -4.0), vector2(-3.0, -2.0)]
        self.flare = [vector2(-3.0, -2.0), vector2(-7.0, 0.0), vector2(-3.0, 2.0)]

Now, what pygame has for my drawing purpose appears to be draw.lines, which takes a list of points. I guess I can just create a draw function and draw directly on the screen. But I think that’s not really in the proper spirit. I think we should have a surface, draw to that, and then blit to the screen at the right position.

Also I think we really will want the thing bigger than 14 pixels, but we’ll worry about that when we get it drawn at all. And I need my points offset so that top left is (0,0). Let’s have a fill surface method.

class Ship():
    def __init__(self, position):
        self.position = position
        self.points = [vector2(-3.0, -2.0), vector2(-3.0, 2.0), vector2(-5.0, 4.0),
                       vector2(7.0, 0.0), vector2(-5.0, -4.0), vector2(-3.0, -2.0)]
        self.flare = [vector2(-3.0, -2.0), vector2(-7.0, 0.0), vector2(-3.0, 2.0)]
        self.fill_surface()

    def fill_surface(self):
        shape = map(lambda point: point + vector2(7,4), self.points)

I’d really better write a test for my very first map and lambda.

    def test_map_lambda(self):
        points = [vector2(-3.0, -2.0), vector2(-3.0, 2.0), vector2(-5.0, 4.0),
                  vector2(7.0, 0.0), vector2(-5.0, -4.0), vector2(-3.0, -2.0)]
        new_points = map(lambda point: point + vector2(7,4), points)
        for point in new_points:
            self.assertGreaterEqual(point.x, 0)
            self.assertLessEqual(point.x, 14)
            self.assertGreaterEqual(point.y, 0)
            self.assertLessEqual(point.y, 9)

This runs, and I did print the results to be sure. They range from 0 thru 14 in x, 0 thru 8 in y. I guess this means that I need a surface 9 by 15 to draw into.

Or … I could just draw on the screen to begin with and then worry about a surface. Let’s do that.

In fact let’s map the points right away as well.

I have confused myself with this:

    pygame.draw.circle(screen, "red", player_pos, 40)
    ship.draw(screen)


class Ship():
    def __init__(self, position):
        self.position = position
        self.raw_points = [vector2(-3.0, -2.0), vector2(-3.0, 2.0), vector2(-5.0, 4.0),
                      vector2(7.0, 0.0), vector2(-5.0, -4.0), vector2(-3.0, -2.0)]
        self.raw_flare = [vector2(-3.0, -2.0), vector2(-7.0, 0.0), vector2(-3.0, 2.0)]

    def adjust(self, point):
        return point + vector2(7, 4) + self.position

    def draw(self, screen):
        ship_points = map(self.adjust, self.raw_points)
        pygame.draw.lines(screen, "white", False, ship_points)

The program fails saying:

Traceback (most recent call last):
  File "/Users/ron/PycharmProjects/firstGo/main.py", line 27, in <module>
    ship.draw(screen)
  File "/Users/ron/PycharmProjects/firstGo/ship.py", line 18, in draw
    pygame.draw.lines(screen, "white", False, ship_points)
TypeError: points argument must be a sequence of number pairs

I guess that map returns an iterator, not a list. I do this:

    def draw(self, screen):
        ship_points = list(map(self.adjust, self.raw_points))
        pygame.draw.lines(screen, "white", False, ship_points)

Thanks, stackoverflow. That draws and works just about as well as could be expected.

purple screen, red dot, ship very small and offset

We have a tiny ship, no surprise there, and its top left corner does appear to be about at the center of the circle. So far so good. Our tests are green, so we’ll commit this on the grounds that it is progress. Commit: drawing tiny ship nearly at screen center.

That may be enough to satisfy me, since these few lines of code, given that they are the first lines of Python I’ve actually written, have taken about an hour of my time. Let’s reflect.

Reflection

I’ve learned that map returns an iterator and that an iterator isn’t a list even if it is an iterator on a list. I’m not entirely solid in understanding there, so I’ll read up on Python collections a bit.

I’ve learned, nonetheless, how to adjust my list of points to a different position and I could certainly adjust it to anywhere on the screen by doing the map on the fly. But that does seem a bit costly. Surely we can do better. Even though our ship is a collection of lines, I think we should probably draw it to a surface once and for all and then display it.

I’m not completely out of energy yet, so let’s try it.

A bit more fumbling and I get this:

class Ship():
    def __init__(self, position):
        self.position = position
        self.raw_points = [vector2(-3.0, -2.0), vector2(-3.0, 2.0), vector2(-5.0, 4.0),
                           vector2(7.0, 0.0), vector2(-5.0, -4.0), vector2(-3.0, -2.0)]
        self.raw_flare = [vector2(-3.0, -2.0), vector2(-7.0, 0.0), vector2(-3.0, 2.0)]
        self.surface = pygame.Surface((15, 9))
        self.paint(self.surface)

    def adjust(self, point):
        return point + vector2(7, 4)

    def draw(self, screen):
        screen.blit(self.surface, self.position)

    def paint(self, surface):
        ship_points = list(map(self.adjust, self.raw_points))
        pygame.draw.lines(surface, "white", False, ship_points)

We create our surface just once and paint it during the init. We blit it to the ship’s position in draw. This nearly works:

ship is on black background

The ship’s background is black, which in the fullness of time will be just right. There is a way to define transparency on a surface, and I’d like to know what it is, but for now it’s not important. Observing Bryan’s really interesting program, it seems that transparency isn’t as convenient as it might be. I might never find out, given what I’m planning at the moment.

Let’s do one more thing. Let’s make the ship four times larger than it is now.

    def __init__(self, position):
        self.position = position
        self.raw_points = [vector2(-3.0, -2.0), vector2(-3.0, 2.0), vector2(-5.0, 4.0),
                           vector2(7.0, 0.0), vector2(-5.0, -4.0), vector2(-3.0, -2.0)]
        self.raw_flare = [vector2(-3.0, -2.0), vector2(-7.0, 0.0), vector2(-3.0, 2.0)]
        self.surface = pygame.Surface((60, 36))
        self.paint(self.surface)

    def adjust(self, point):
        return point*4 + vector2(28, 16)

big ship still black background

Perfect! Well, in a manner of speaking. Commit: Big ship on black via blit.

Summary

OK, that was ragged but not bad for my first time typing Python in anger. And I didn’t even get angry. So there. What have we learned?

We have a small grasp on creating a screen, on drawing some lines onto a separate Surface, and on blitting that surface to the screen. We have not handled positioning the ship so that it is centered around its position, but that should be a matter of adding a constant somewhere. And we’ll want to give it thicker lines, which we can do either by drawing with thicker lines or by scaling up the surface after we build it. We’ll try both ways.

My early take on PyGame is that it’s rather old-school, kind of rudimentary compared to what’s out there these days. I don’t know that there’s anything better, but I’ll look around.

We’re on our way. I think we’ll continue to work in this program, treating it as a spike, until we get a better handle on basic Python and PyGame. Right now, my main focus will be on building up some chops on both.

It’s a good day. Not a good day to die, mind you, but a good day.