The Source Code

The full and working source code is available here.

Some Remarks

Even though the code is pretty short and we discussed the basics of pygame in the previous article, I stumbled across a few things that I thought would be interesting to share.

No signum Function in Python

First, it may sound surprising, but Python doesn't have a signum function out of the box. I ended up writing the following code:

def sign(num):
    if num < 0:
        return -1
    elif num == 0:
        return 0
    else:
        return 1

Expressive

Second, the Python language is so expressive. Consider the part in the game when we have to check whether the snake leaves the screen. Normally, we should write something like that:

def snake_is_on_screen(self):
    return self.head.x >= 0 and self.head.x <= SCREEN_WDITH - UNIT_LENGTH and self.head.y >= 0 and self.head.y <= SCREEN_HEIGHT - UNIT_SIZE

Instead, we can write:

def snake_is_on_screen(self):
    return (0 <= self.head.x <= SCREEN_WIDTH - UNIT_SIZE) and (0 <= self.head.y <= SCREEN_HEIGHT - UNIT_SIZE)

It's not a question what version is more readable.

Readability vs Being Short

Also, even in this simple project there were many places where I could simplfy and shorten things. However, those "adjustments" would make the code very hard to understand for others. For example, the Pos class' purpose is to hold the x and y position.

Basically that class was not needed. It would've been better to store the coordinates in a list -- that would let me make the move_function a one-liner as folows.

DOWN = 1
UP = -3
RIGHT = 2
LEFT = -4

def move_head(self, direction):
    self.body[0][abs(direction) % 2] += UNIT_SIZE * sign(direction)

Wow, what's that? Without comments and deep diving into the code it's not possible to understand what's going on. The abs(direction) % 2 part first determines which coordinate x (0) or y (1) to increase or decrease. It'll be increased if the direction is greater than zero. Otherwise, it'll be decreased, that's taken care of by the sign function. Of course, this idea also required properly selected values for the direction variable, see the constans above.

So a one-liner solution at the cost of the readbility. Even though this code looks cool, it's not worth it at all.

Instead of that, I wound up writing this:

def move_head(self, direction):
    if direction == UP:
        self.head.y += UNIT_SIZE
    if directon == DOWN:
        self.head.y -= UNIT_SIZE
    if direction == LEFT:
        self.head.x -= UNIT_SIZE
    if direction == RIGHT:
        self.head.x += UNIT_SIZE

This code, unlike the previous solution, immediately reveals its intention.

As a rule of thumb, be always clear about your intention, even if you end up writing more code. It's worth the effort.