0

I'll try to describe the scenario using a Turn-based game (such as card game or somethin'), where Game is the server, and the actual player is a client. (hence using Client-Server architecture), I'm going to discuss only the server side of Game.

Many players can be on a single game (multi-player) and the game is split to different game phases, where each game phase describes the game logic at the current moment, i.e. at the first phase, players can only pick a card from their hand or somethin' and at the second phase each player can attack an enemy.

The trick is that players choose the set of game phases before the game (they are building the game flow as they like) and can choose only a subset of phases, for example, if there are total of 10 phases to choose from, players can choose only 3.

Now, because each phase of the game defines different set of actions the player can do, I declared a different API for each phase, denoted as "GamePhase[num]API". (where [num] is the number of the phase)

The First problem that arises is that all my game API is now scattered over many classes and interfaces, isn't it bad?

Another problem is that "Player" class can't interact with "Game" class via API as it is changing from game to game, so I was thinking of using the Observer pattern -> Player would implement Observable and emit events in form of generic message events based on what the client sent via the connection, but then each "GamePhase" class has to listen to different 'events' and also has to parse those messages which violates SRP as I know the communication format is going to change as the game grows, and "GamePhase" shouldn't know how the Player is communicating and what format (Should I create a mediator or somethin'?)

Should I make "Game" class implement all those GamePhase[num]API which results in huge amount of functions, even if in most scenarios it won't use them all and only one API is 'active' at the same time based of the game flow and phase? Or my approach to scatter the API is good?

Classes (for concrete Example, in javascript/typescript):

  • Game: Manage game flow and game data/environment information, Using the State Machine pattern with GamePhase.
  • GamePhase: Manages the current game phase with respect to game logic.
  • Player: The player that is in the game, the actual player is connected remotely via WebSocket or simple TCP Connection where in the other end sits a client program, "Game" class interacts with "Player" class as it informs the player about the state of the game and related stuff, for that reason "Player" class implements ClientAPI.

My game class is as follows:

class Game
{   
    private gamePhases: GamePhase[];
    private currentGamePhase: GamePhase;
    // ~
    // some other private fields such as GameData.
    // ~

    constructor(gamePhases: GamePhase[]) {
        this.gamePhases = gamePhases;
        this.currentGamePhase = this.gamePhases[0]; // assumes not empty.
    }

    // ~ 
    // Switching between Game Phases like a State Machine.
    // Switch to a new game phase when the previous one has finished
    // based on game flow graph or somethin'...
    // ~ 
}

Game Phases classes:

class GamePhase1 implements GamePhase, GamePhase1API
{
    // ~ 
    // Private fields, holding GameData here, such as players...
    // ~ 

    startPhase(): void {
        // start game/phase logic.
        //! p1.defend()
        //! do something with game environment (GameData).
        // ...
    }

    // Players Action! as part of GamePhase1API.
    pickACard(/* card object */): void {
        // do something with that card...
    }
}

interface GamePhase1API
{
    pickACard(/* card object */): void;
}

class GamePhase2 implements GamePhase, GamePhase2API
{
    // ~ 
    // Private fields, holding GameData here, such as players...
    // ~ 

    startPhase(): void {
        // start game/phase logic.
        //! p1.defend()
        //! p2.attack()
        // ...
    }

    // Players Action! as part of GamePhase2API.
    hitAnEnemy(/* enemy object */): void {
        // do something with that enemy...
    }
}

interface GamePhase2API
{
    hitAnEnemy(/* enemy object */): void;
}

interface GamePhase
{
    startPhase(): void;
}

Player class:


class Player implements ClientAPI
{
    // ~ 
    // Private field of Connection. (Socket.io or somethin')...
    // ~
    
    attack(): void {
        // send through a connection 
        // that its the player's turn to attack.
    }

    defend(): void {
        // send through a connection 
        // that its the player's turn to defend.
    }

    // *** How does the player interact with the game? (or game phases). *** //
}

interface ClientAPI
{ 
    attack(): void;
    defend(): void;
}

Link to the above code on Typescript Playground

How should "Player" communicate with "GamePhase" or "Game" classes in order to keep SOLID principles and clean code?

I hope that the example I gave isn't complicated to understand.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.