Examples

This section provides complete examples of game-state data models.

Tic Tac Toe

Game state for Tic Tac Toe consists of the board plus whose turn it is.

class TicTacToeGame {
   TicTacToeSquare[][] theBoard;
   TicTacToePlayer whoseTurn;
}
    
enum TicTacToeSquare { Blank, X, O }
    
enum TicTacToePlayer { Player1, Player2 }

Blackjack

We model the deck, the dealer's cards, and the players' cards as Card arrays. The number of players is variable, so we will not use an enum type with a fixed number of players, as we did in Tic Tac Toe. Instead, we will put the players' hands in an array (of Card arrays). Whose hand gets the next card from the deck is represented as an int that will be used to index into the array containing the dealer's hand and the players' hands.

class Blackjack {
   Card[] deck;
   Card[][] dealerPlayerHands;
   int whoGetsNextCard;
}
     
class Card {
   Suit suit;
   Rank rank;
}
     
enum Suit { Spades, Hearts, Clubs, Diamonds }
     
enum Rank { Ace, R2, R3, R4, R5, R6, R7, R8, R9 , R10, Jack, Queen, King }

You may be wondering how this data model works out when the deck and the various hands have variable lengths, and arrays are fixed-length structures. This can be managed but will require some more explanation, which we will do when we get into the code for managing arrays.

Rush Hour

Rush Hour is a puzzle, which consists of a number of 2- and 3-unit long vehicles arranged on a grid. One of the vehicles needs to get to the exit, but the other vehicles are blocking it. To solve the puzzle you need to move the vehicles around so that the vehicle that you are trying to get out can reach the exit.

Rush Hour
class RushHour {
   Vehicle[] vehicles;
   int exitingVehicle;
}
     
class Vehicle {
   Orientation orientation;
   int length; // length = 2 or length = 3
   Position position;
}
     
enum Orientation { Horizontal, Vertical }
     
class Position {
   int row; // 0-5 (6 rows)
   int col; // 0-5 (6 cols)
}

There are only two possible lengths, 2 and 3, so one could argue for an enumerated type for this, like we did for Orientation, but we anticipate that we will computing with length (e.g., calculating if a move would put a vehicle off the grid) so we use int.

Decomposition procedure

Below is a procedure to follow when modeling game state (or the data model for any applications). We'll use a Crossword puzzle as a running example.

  1. Define the “root type” as a class, that is all-encompassing, representing the whole. For our example, this would be class CrosswordPuzzle { }.
  2. Identify the parts of the whole. These will become the fields of the root type. For our example, we ask ourselves, what does a crossword puzzle consist of? There is the rectangle of squares that contain the letters guessed, and there is the set of clues.
  3. For each field, decide whether it’s a class, array, enumerated type, or primitive. We might model the set of squares as an array of char arrays: char[][]. We might model the set of clues as an array of CrosswordClues.
  4. For each class part, identify its parts. (No class parts for our parts.)
  5. For each array part, identify the type of its elements. The set of squares we modeled as a char[][], so that's done. We modeled the set of clues as CrosswordClue[].
  6. Continue until you've reached primitives or enumerated types. In our example, we have not yet, because we still have the CrosswordClue class to define.

So we'll restart the cycle, and this time:

  1. The root type is class CrosswordClue { }.
  2. CrosswordClue consists of the number of the clue, the row and column of the square it starts in, the direction of the clue, and the clue itself.
  3. The number of the clue is int, and the row and column of the square the word starts in are both ints (other choices are possible, but this is the simplest). An enumerated type is a good choice for the direction. We could use boolean since there are only two values, but this leads to code that reads awkwardly. As an enumerated type, we could model it as enum ClueDirection { Down, Across }. The clue itself would be a String.

And we are done. Here is our model:

class CrosswordPuzzle {
   char[][] squares;
   CrosswordClue[] clues;
}
     
class CrosswordClue {
   int clueNumber;
   int squareRow;
   int squareCol;
   ClueDirection clueDirection;
   String clue;
}
     
enum ClueDirection { Down, Across }