Welcome to our series of articles on PHP Design Patterns Games! In this article, we’ll be exploring another popular design pattern, the Observer Pattern. This behavioral pattern is used to establish relationships between objects, so that when one object changes, all of its dependents are notified and updated automatically. We’ll show you how to implement the Observer Pattern to create a game where players can subscribe to different events and receive notifications when those events occur. By the end of this article, you’ll have a solid understanding of how to use the Observer Pattern to create flexible and modular code.

Observer Pattern

The Observer pattern is a behavioral design pattern that allows an object, called the subject, to notify other objects, called observers, when its state changes. The observer objects are then notified and can update their state accordingly.

In a game, the Observer pattern can be used to notify various objects about changes to the game state. For example, we can have a Game class that represents the state of the game and multiple objects, such as Player objects, that need to be updated when the game state changes.

Let’s implement the Observer pattern in PHP using the game example. Here’s an example code:

// Define the Subject interface
interface Subject {
    public function registerObserver(Observer $observer);
    public function removeObserver(Observer $observer);
    public function notifyObservers();
}

// Define the Observer interface
interface Observer {
    public function update();
}

// Define the Game class
class Game implements Subject {
    private $observers = array();
    private $state;

    public function registerObserver(Observer $observer) {
        $this->observers[] = $observer;
    }

    public function removeObserver(Observer $observer) {
        $key = array_search($observer, $this->observers, true);
        if ($key !== false) {
            unset($this->observers[$key]);
        }
    }

    public function notifyObservers() {
        foreach ($this->observers as $observer) {
            $observer->update();
        }
    }

    public function setState($state) {
        $this->state = $state;
        $this->notifyObservers();
    }

    public function getState() {
        return $this->state;
    }
}

// Define the Player class, which will act as the observer
class Player implements Observer {
    private $name;
    private $game;

    public function __construct($name, $game) {
        $this->name = $name;
        $this->game = $game;
        $game->registerObserver($this);
    }

    public function update() {
        echo "{$this->name}: The game state has changed to {$this->game->getState()}\n";
    }
}

// Create a new Game object and two Player objects
$game = new Game();
$player1 = new Player('Player 1', $game);
$player2 = new Player('Player 2', $game);

// Change the game state and see the Player objects get notified
$game->setState('running');
$game->setState('paused');

In this example, we define the Subject interface, which defines the methods for registering, removing, and notifying observers. We also define the Observer interface, which defines the update method that will be called when the subject’s state changes.

We then define the Game class, which implements the Subject interface. It has a private $observers array that holds all the observer objects, a $state variable that represents the state of the game, and methods for registering, removing, and notifying observers. The setState method is used to update the game state and notify all observers of the change.

Next, we define the Player class, which implements the Observer interface. It has a $name variable that represents the name of the player and a $game variable that represents the Game object that it is observing. The constructor registers the Player object as an observer of the Game object and the update method is called when the game state changes.

Finally, we create a new Game object and two Player objects. We change the game state using the setState method and see the Player objects get notified of the change.

This example demonstrates how the Observer pattern can be used to notify multiple objects of changes to a game state. By implementing the Observer pattern, we can decouple the game logic from the player objects and other observers, making our code more modular and easier to maintain.

In a real-world scenario, the Observer pattern can be used in various applications, such as weather forecasting systems, stock market monitoring tools, and more.

Let’s defeat the Observer Pattern in a game

As we have seen the observer pattern is a behavioral pattern that defines a one-to-many dependency between objects. In this pattern, when one object changes state, all its dependent objects are notified and updated automatically.

Consider a game where a player can collect points by defeating enemies. Now, we want to display the current score of the player in real-time to multiple views in the game UI. We can use the Observer pattern to achieve this.

First, we need to define the Subject interface, which will be implemented by the object whose state changes. In our game, the Player object will implement the Subject interface.

interface Subject {
    public function attach(Observer $observer);
    public function detach(Observer $observer);
    public function notify();
}

The Subject interface defines three methods: attach(), detach(), and notify().

  • attach() is used to register an observer that wants to receive notifications when the subject’s state changes.
  • detach() is used to unregister an observer that no longer wants to receive notifications.
  • notify() is used to notify all registered observers when the subject’s state changes.

Next, we need to define the Observer interface, which will be implemented by the objects that need to be notified when the subject’s state changes. In our game, the UI views that display the player’s score will implement the Observer interface.

interface Observer {
    public function update(Subject $subject);
}

The Observer interface defines a single method update(), which is called by the subject to notify the observer when its state changes.

Now, let’s implement the Player class which will be the subject.

class Player implements Subject {
    private $score;
    private $observers = [];

    public function setScore($score) {
        $this->score = $score;
        $this->notify();
    }

    public function getScore() {
        return $this->score;
    }

    public function attach(Observer $observer) {
        $this->observers[] = $observer;
    }

    public function detach(Observer $observer) {
        $index = array_search($observer, $this->observers);
        if ($index !== false) {
            unset($this->observers[$index]);
        }
    }

    public function notify() {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }
}

The Player class has a private $score property and an array $observers to keep track of all the observers. The setScore() method is used to update the player’s score and then call the notify() method to notify all the observers. The getScore() method is used to get the player’s current score.

The attach(), detach(), and notify() methods are used to register, unregister, and notify observers respectively.

Now, let’s implement the UI views that will be the observers.

In our game, we have two UI views: the board view and the score view. Both of these views should observe the game object for changes in the state of the game.

First, let’s create the BoardView class. This class will be responsible for rendering the game board and updating it when the game state changes. Here’s an example implementation:

class BoardView implements SplObserver {
    private $game;

    public function __construct(Game $game) {
        $this->game = $game;
        $game->attach($this);
    }

    public function update(SplSubject $subject) {
        $board = $this->game->getBoard();
        // Render the board
    }

    // Other board rendering methods...
}

The BoardView implements the SplObserver interface, which requires it to implement the update() method. This method is called by the game object whenever the game state changes. In the update() method, we get the current board state from the game object and render it.

We also need to register the BoardView as an observer of the game object in its constructor. We do this by calling the attach() method of the game object, which takes the observer object as an argument.

Next, let’s create the ScoreView class. This class will be responsible for rendering the current score of the game and updating it when the game state changes. Here’s an example implementation:

class ScoreView implements SplObserver {
    private $game;

    public function __construct(Game $game) {
        $this->game = $game;
        $game->attach($this);
    }

    public function update(SplSubject $subject) {
        $score = $this->game->getScore();
        // Render the score
    }

    // Other score rendering methods...
}

The ScoreView class is similar to the BoardView class. It also implements the SplObserver interface and registers itself as an observer of the game object in its constructor.

In the update() method, we get the current score from the game object and render it.

Now that we have our UI views implemented as observers, we need to update the game object to notify its observers whenever the game state changes. We can do this by calling the notify() method of the SplSubject interface in the setState() method of the Game class:

class Game implements SplSubject {
    private $observers = [];
    private $board;
    private $score;

    public function attach(SplObserver $observer) {
        $this->observers[] = $observer;
    }

    public function detach(SplObserver $observer) {
        $key = array_search($observer, $this->observers, true);
        if ($key !== false) {
            unset($this->observers[$key]);
        }
    }

    public function notify() {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    public function setState($board, $score) {
        $this->board = $board;
        $this->score = $score;
        $this->notify();
    }

    public function getBoard() {
        return $this->board;
    }

    public function getScore() {
        return $this->score;
    }

    // Other game methods...
}

In the notify() method, we iterate over all the registered observers and call their update() method with the game object as an argument.

In the setState() method, we update the game state and call the notify() method to notify all observers of the change.

With these changes, our game object will now notify its observers whenever there is a change in the state of the game. This includes when a player makes a move, when the game is won or lost, or when the game is reset. The observers will be updated with the latest information about the game state and can take appropriate actions based on the updated information.

In conclusion, the Observer pattern is a powerful tool for designing systems where objects need to be notified of changes in state. By separating the observer logic from the subject, we can create more flexible and maintainable code. In the next article of our PHP Design Patterns Games series, we will explore another design pattern that can be used to create more modular and scalable games.

LEAVE A REPLY

Please enter your comment!
Please enter your name here