The baseGame class defines a Game object that is the heart of the game engine. It controls the flow of the game through the opening negotiations to connect the radios, through the exchange of alternating moves, and then ultimately terminating the game and offering you the option to restart.

You MUST create a derived game class because it contains pure virtual functions that you must define. In our implementations we chose to make all of our game specific variables and functions globally accessible rather than make them data items or methods of the Game class.

The code for the baseGame object is in the file TwoPlayerGame_base_game.h and TwoPlayerGame_base_game.cpp.

Here is the definition of baseGame:

enum gameState_t {
  OFFERING_GAME, SEEKING_GAME, MY_TURN, OPPONENTS_TURN,  GAME_OVER
};

class baseGame {
  public:
    uint16_t currentMoveNum; 
    baseMove* Move;          
    baseResults* Results;    
    baseRadio* Radio;
    uint8_t myPlayerNum;      //For initializing my radio
    uint8_t otherPlayerNum;   //Destination of our transmissions
    baseGame(baseMove* move_ptr, baseResults* results_ptr, 
             baseRadio* radio_ptr, bool isPlayer_1);
    virtual void setup(void); 
    virtual void loopContents(void);
  protected:
    virtual void initialize(void){gameState=OFFERING_GAME;};
    virtual bool coinFlip(void)=0;
    virtual void processGameOver(void)=0;
    virtual void fatalError(const char* s)=0;
    virtual void processFlip(bool coin) {};
    virtual void foundGame(void) {};
    gameState_t gameState;    //The internal state of the game engine
  private:
    //Internal routines that handle each of the various states of the engine
    void offeringGame(void);
    void seekingGame(void);
    void doMyTurn(void);
    void doOpponentsTurn(void);
    void gameOver(void); 
};

Here are the details of the baseGame class...

  • uint16_t currentMoveNum; -- The number of the current move.
  • baseMove* Move; -- Pointer to a move object that will be transmitted between devices. Is used for both sending our move and receiving our opponents move. 
  • baseResults* Results; -- Pointer to a results object that will be transmitted between devices. Is used for both sending our results and receiving results from our opponent. 
  • baseRadio* Radio; -- Pointer to a radio object that will do the transmitting and receiving of data.
  • uint8_t myPlayerNum; -- My player number either 1 or 2 computed by constructor based on isPlayer_1 parameter.
  • baseGame(baseMove* move_ptr, baseResults* results_ptr, baseRadio* radio_ptr, bool isPlayer_1); -- Constructor.
  • virtual void setup(void); -- Call this method ONLY once in your main setup() function.
  • virtual void loopContents(void); -- Call this method inside your main loop() function.
  • virtual void initialize(void); -- This method is called any time a new game starts.
  • bool coinFlip(void); -- You MUST implement this pure virtual method in your derived game class. It determines who goes first.
  • void processGameOver(void); -- You MUST implement this method in your derived game class. It will be called at the end of every game. You get to decide what to do.
  • void fatalError (const char* s); -- You MUST implement this method in your derived game class. It would only be called in the event of an unrecoverable error such as failure to receive acknowledgment of a data packet or some other programming logic error.
  • void processFlip(bool coin){}; -- Used by accepting player to process an incoming COIN_FLIP_PACKET. The base method does nothing.
  • void foundGame(void) {}; -- Used by accepting player to print a message that a game has been found and we are waiting on the offering player to do the coin toss.
  • gameState_t gameState; -- The internal state of the game engine. See the gameState_t definition above for legal values.

The remaining methods are all internal methods for processing the various game states.

We suggest you take a look at the implementation of each of these methods in the Tic-Tac-Toe and Battleship games. A couple of extra notes to consider.

When you are the accepting player and you are waiting on the other player to perform the coin toss, you don't know that your game has been accepted. So the offering player will send a FOUND_GAME_PACKET to let you know that you have found a game. When you get that packet, the game engine calls foundGame() so that you can display a message saying that you have found a game and are waiting on the coin toss. Similarly if you lose the coin toss, you have to wait for your opponent to make their first move. Therefore the results of the coin toss are sent to you in a COIN_FLIP_PACKET and the game engine calls processFlip(coin) so that you can print a message explaining that you lost the toss.

In both demonstration games we implemented coinFlip() as a random coin toss but you could implement any system you want as long as it returns true for the offering player winning and false for the accepting player winning. For example you might implement a rock/paper/scissors decision.

Note that your setup() and initialize() derived methods MUST call the base methods somewhere in their processing. See the example games for details.

We invite you to take a close look at the implementation of loopContents(). The base method code is shown below.

void baseGame::loopContents(void) {
  switch(gameState) {
    case OFFERING_GAME:  offeringGame();  break;
    case SEEKING_GAME:   seekingGame();   break;
    case MY_TURN:        doMyTurn();      break;
    case OPPONENTS_TURN: doOpponentsTurn(); break;
    case GAME_OVER:      gameOver();      break;
  }
}

As you can see it simply hands off control to the proper routine depending on the gameState. But under some circumstances you might want to do something before or after the game engine enters a particular state. Take a look at the BShip::loopContents() method we implemented. With some of the details removed, it looks like this.

void BShip_Game::loopContents(void) {
  gameState_t saveState=gameState;  //save the gameState for postprocessing
  //This is processing we do before the game engine does a particular state
  switch(gameState) {
    case OFFERING_GAME: 
      //Do some stuff before we enter the offering state.
      break;
    case SEEKING_GAME:
      //Do some stuff before we enter the streaking state.
      break;
    case MY_TURN:      
      //Do some stuff before each of my turns.
      break;
    case OPPONENTS_TURN: 
      //Do some stuff before each of my opponent's turns.
      break;
  }
  
  //You MUST call this to let the game engine do its thing
  baseGame::loopContents(); 
  
  //This is processing we do after the game engine does a particular state
  switch(saveState) {
    case OFFERING_GAME: 
      //Do some stuff after offering of the game.
      break;
    case OPPONENTS_TURN:
      //Do some stuff after the opponents turn.
      break;
  }
}

Note that we had to save the state of the game because after we call baseGame::loopContents() the variable gameState is no longer the same. The switch statement after we call baseGame::loopContents() uses saveState and not gameState.

You might not need to implement all of the options before or after your loopContents() method. The Tic-Tac-Toe game doesn't do any after-loop processing and neither example uses the MY_TURN option for pre-processing or post-processing. We just wanted to show you that you have the option to do this if necessary.

This guide was first published on Jul 07, 2020. It was last updated on Mar 08, 2024.

This page (The baseGame object class) was last updated on Mar 08, 2024.

Text editor powered by tinymce.