Snake - Part 5: First-class quality

Resisting marxism

In object oriented programming, everything revolves around classes.

A class defines a basic set of methods and variables, which every object created from that class will feature.
This is especially useful for refactoring code which has a great set of similar variables for slightly different uses. Though you might just use it to make your code more readable.

In our case, we could use classes for the snake itself, or the apples. But first, we will put all the window control code into one class, allowing us to cleanly define the game in a separate file.

Defining a class

A basic class definition might look like this:

class classname():
    def __init__(self,attribute):
        self.attribute = attribute
    def set_attribute(self,attribute):
        self.attribute = attribute

Here we have defined a class, classname, which is comprised of two methods:

  1. __init__
  2. set_attribute

__init__ is a special method, which is executed once, at object creation.
set_attribute you can call anytime you want to change attribute.

You might have noticed the self-keyword there.
self is used as a self-reference to the class itself, so self.attribute actually means classname.attribute.
Variables defined as self.variable can be used througout the whole class.
Functions defined inside a class always take self as the first attribute, however when calling these functions, you won’t need to specify it.

To refactor our code into a new class I made the following changes:

  • move the code initializing the window into the __init__-method
  • move the code running the window into the run-function
  • rewrite all functions and some variables to use self
  • add in some empty functions we can redefine later

The following code can be executed by creating a new instance of the Game class, specifying the window and playing field dimensions.
Game(600,600,10)

import pygame

class Game():
    def __init__(self, window_width, window_height, playing_field_size):
        self._is_running = True
        pygame.init()
        self._display_size = (window_width,window_height)
        self._playing_field_size = playing_field_size
        self.on_init()
        self.run()

    def draw_cell(self,x,y,color,border_color=(0,0,0)):
        border_width_as_divisor = 10
        rect = pygame.Rect(x*self._cell_size,y*self._cell_size,self._cell_size,self._cell_size)
        pygame.draw.rect(self._display_surf,color,rect)
        pygame.draw.rect(self._display_surf,border_color,rect,self._cell_size//border_width_as_divisor)

    def smaller_side(self,tuple):
        a,b=tuple
        if(a<=b):
            return a
        else:
            return b
        
    def run(self):
        while(self._is_running):
            self._display_surf = pygame.display.set_mode(self._display_size, pygame.HWSURFACE | pygame.DOUBLEBUF | pygame.RESIZABLE)
            self._cell_size = self.smaller_side(self._display_surf.get_size())//self._playing_field_size

            for x in range(0,self._playing_field_size):
                for y in range(0,self._playing_field_size):
                    self.draw_cell(x,y,(150,150,150))

            self.draw()
            self.render()

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self._is_running = False
                if event.type == pygame.VIDEORESIZE:
                    self._display_size = [event.w,event.h]
                self.on_event(event)
                
        pygame.quit()

    def on_init(self):
        pass
    def on_event(self, event):
        pass
    def draw(self):
        pass
    def render(self):
        pygame.display.update()