Discover Python and Patterns (12): Command pattern

The implementation of the Game Loop pattern in the previous post is not good: I propose to use the Command pattern to get a better one. This pattern allows separating input handling and game updating.

This post is part of the Discover Python and Patterns series

The Command pattern

The main idea behind this pattern is to separate the origin of the modifications from their implementation. For instance, when the player presses an arrow key, the controlled character is not moved. We first memorize that the player wants to move the character in a direction. Then, in a second time, the character is moved according to what we memorized.

It is quite a natural pattern. For instance, when you command a meal in a restaurant, the waiter notes your order on a piece of paper. Then, he goes to the kitchen and gives it to the cooker. The cooker is free to prepare the meals in the best order. For instance, if several customers are asking for the same meal, he can group the preparations.

In software design, this is very similar. The customer is the user (or the player in games) asking for the execution of a task. These orders, or commands, are stored in variables. Then, the code lines that execute tasks read the commands and act accordingly.

As for all patterns, there are many ways to implement it. For our game example, I’ll use class attributes to store the commands. We could also use more advanced representation as a class for each kind of command.

Split user input processing and game state updating

Let’s use the Command pattern to get a better Game Loop pattern.

I first create two new attributes in the constructor to store the move direction the player is asking for:

def __init__(self):
    ... the first lines are as before ...
    
    self.moveCommandX = 0
    self.moveCommandY = 0  

Then, in the processInput() method, I initialize these two attributes with a zero value: it means that, by default, there is no movement. In the event processing loop, and more specifically in the processing of arrow keys, I no more update the x and y attributes (the location of my rectangle = my game state). I now store the shift I want to apply to this location:

def processInput(self):
    self.moveCommandX = 0
    self.moveCommandY = 0        
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            self.running = False
            break
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                self.running = False
                break
            elif event.key == pygame.K_RIGHT:
                self.moveCommandX = 8
            elif event.key == pygame.K_LEFT:
                self.moveCommandX = -8
            elif event.key == pygame.K_DOWN:
                self.moveCommandY = 8
            elif event.key == pygame.K_UP:
                self.moveCommandY = -8

The update() method now has a content: it updates the location of the rectangle (x and y attributes) according to the values of the move command attributes:

def update(self):
    self.x += self.moveCommandX
    self.y += self.moveCommandY

Why is it better?

If you run the program with these changes, it works as before. So you could ask yourself: why should I do this? I was working fine before!

We are following a golden rule in software design: divide and conquer. It is impossible to solve complex problems directly, even for the most clever developer. There is always a level of complexity that becomes intractable. The solution is then to split a complex problem into simpler problems. If these sub-problems are still too complex, we split again. And we repeat, until getting problems simple enough to be solved.

It is what the Game Loop pattern proposes to do for the very complex problem of game development. It splits it into three main sub-problems: input handling, game update, and rendering. For this to work, the splitting must be effective.

In the previous implementation, we didn’t separate input handling and game updating. Sooner or later, we would have faced too complex problems. Thanks to the improvement using the Command pattern, we are better protected from these future issues.

If you are still not convinced, let’s consider the case of controls. In our game example, we use the keyboard arrows. But what about using a gamepad, for instance? With the first implementation, we would have to duplicate the update of the x and y attributes for each control. In our example, this is very simple, but it is easy to imagine more complex cases where we have to deal with collisions and so on. With the new implementation, we only have to create commands for each control (an easy task), and only need a single implementation of the game updating (a more complex task).

The game state

Now that I’ve introduced the “divide and conquer” golden rule, we’ll try to use it as much as possible. For instance, in our current implementation of the game, the game state and the user interface (=input handling and rendering) are mixed.

I propose to create a new class GameState that handles the game state (storage and update):

class GameState():
    def __init__(self):
        self.x = 120
        self.y = 120
        
    def update(self,moveCommandX,moveCommandY):
        self.x += moveCommandX
        self.y += moveCommandY

In the __init__() constructor, I create the x and y attributes.

In the update() method, I update the coordinates using the move command values.

In the constructor of my main class, I replace the creation of the x and y attributes:

def __init__(self):
    ...
    self.x = 120
    self.y = 120
    ...

with the creation of a new GameState instance:

def __init__(self):
    ...
    self.gameState = GameState()
    ...

For the update() method, I call the update() method of the GameState class:

def update(self):
    self.gameState.update(self.moveCommandX,self.moveCommandY)

And finally, in the render() method, I use the attributes in the game state instead of the ones in the main class. For instance, self.x becomes self.gameState.x.

That way, all problems related to game data storage and updating is delegated to the GameState class. The main class no more has to worry about that, and it only has to know the name of the attributes and methods it can use.

Final code

import os
import pygame

os.environ['SDL_VIDEO_CENTERED'] = '1'

class GameState():
    def __init__(self):
        self.x = 120
        self.y = 120
        
    def update(self,moveCommandX,moveCommandY):
        self.x += moveCommandX
        self.y += moveCommandY

class UserInterface():
    def __init__(self):
        pygame.init()
        self.window = pygame.display.set_mode((640,480))
        pygame.display.set_caption("Discover Python & Patterns - https://www.patternsgameprog.com")
        pygame.display.set_icon(pygame.image.load("icon.png"))
        self.clock = pygame.time.Clock()
        self.gameState = GameState()
        self.running = True
        self.moveCommandX = 0
        self.moveCommandY = 0          

    def processInput(self):
        self.moveCommandX = 0
        self.moveCommandY = 0
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False
                break
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    self.running = False
                    break
                elif event.key == pygame.K_RIGHT:
                    self.moveCommandX = 8
                elif event.key == pygame.K_LEFT:
                    self.moveCommandX = -8
                elif event.key == pygame.K_DOWN:
                    self.moveCommandY = 8
                elif event.key == pygame.K_UP:
                    self.moveCommandY = -8
                    
    def update(self):
        self.gameState.update(self.moveCommandX,self.moveCommandY)
        
    def render(self):
        self.window.fill((0,0,0))
        x = self.gameState.x
        y = self.gameState.y
        pygame.draw.rect(self.window,(0,0,255),(x,y,400,240))
        pygame.display.update()    
        
    def run(self):
        while self.running:
            self.processInput()
            self.update()
            self.render()
            self.clock.tick(60)
    
userInterface = UserInterface()
userInterface.run()

pygame.quit()

In the next post, we’ll start to use sprites to render our game!

This entry was posted in Tutorial and tagged , , . Bookmark the permalink.

Leave a Reply