First Element: The Board

This is the second in an (N) part series of articles based upon Seven Essential Elements of a Tile Based Rendering Engine. In particular, this post covers the first element, which is called, for lack of any more appropriate term, the board.

I use the generic term of board, which invokes thoughts of games such as chess, checkers, reversi, Monopoly, Risk, Stratego, and so on. In tile based games, we are always dealing with some sort of grid of squares, rhombuses, triangles, or triangles. Many games, such as Risk or Monopoly, vary from the grid structure common to the other games, and this post isn’t specifically about those sorts of boards, even though many of the priciples do still apply.

Many times, people who play games are not really aware of the nature of games. The game itself is abstract, and exists wholly within the mind of the participants. The board, tokens, play money, and whatever else are simply ways to keep tabs on the state of the game, but they are not the game itself.

Case Study: Chess

In a game of chess, there are 64 squares, and each player has 16 pieces, of 6 different types. We might then abstract it in the following manner:

A chess board consists of a grid of 8 columns and 8 rows of squares.

Each square can contain one of 13 states: empty, white king, white queen, white rook, white knight, white bishop, white pawn, black king, black queen, black rook, black knight, black bishop, black pawn.

Beyond the board itself, the state of a game of chess contains one extra piece of information, which is the active player. At any time, there is one and only one active player, and he remains active until he has moved a piece according to the rules or the game is over, so the potential states are white to move, black to move, white wins, black wins, and draw.

Representing a chess board can be as easy as having a two dimensional array with an enumeration value for the pieces in each square, plus the enumeration value for the current player or winner of the game.

We could have a very simplistic (and naive) representation of a chess game in code, like so:

public enum PieceState
{
WhiteKing,
WhiteQueen,
WhiteRook,
WhiteKnight,
WhiteBishop,
WhitePawn,
Empty,
BlackPawn,
BlackBishop,
BlackKnight,
BlackRook,
BlackQueen,
BlackKing
}
public enum PlayerState
{
WhiteToMove,
BlackToMove,
WhiteWins,
BlackWins,
Stalemate
}
public class BoardState
{
public const int Columns = 8;
public const int Rows = 8;
public PieceState[,] Pieces = new PieceState[Columns, Rows];
}
public class GameState
{
public BoardState BoardState = new BoardState();
public PlayerState PlayerState = PlayerState.WhiteToMove;
}

While I would say that this is not a good way to represent a chessboard, it is enough for our purposes at the moment, in that it is barely adequate to keep track of a game of chess within a data structure.

But we shall now think about the other more visual aspects of the game as it will be displayed to the player. No, I’m not talking about actual rendering code, as rendering code is forbidden in this portion of the game.  I’m speaking instead of describing what it looks like in code. While the game code here doesn’t do the actual rendering, the values stored here do impact what the renderer does when it does render.

When I think of chess games I have played in the past, they are played often with the mouse, although they don’t need to be. The workflow of moving a piece is:

  1. Select a piece either with the mouse or by moving a cursor with the arrow keys.
  2. Once selected, the piece is highlighted in some manner in order to indicate that it is the piece about to be moved.
  3. Also, highlight the valid squares to which the selected piece may be moved and capture pieces.
  4. Somehow annotate squares that put the opponents king in check.
  5. Somehow highlight squares that have potential captures for the moved piece should it land in that location.
  6. Once the player selects a destination, the piece is moved to the new location, and captured piece is removed.

This is quite a workflow, and it means that there are a number of things that need to be added to the description of a square.

Both items 1 and 6 talk about “selecting” a square. In many games of this nature, there is typically one square over which the mouse is hovering, and it is shown differently.Furthermore, item 2 indicates that in addition to having the mouse hover, we need to declare one of the squares as the selected square to initiate the original location of a move.

This means that we need to somehow mark which square the mouse is hovering over and selected. Since the mouse only hovers over one at a time, this can either be a property of the square itself, or of the board, pointing to the column and row. The same goes for selecting a square.  The latter of these is the easier, so let us modify BoardState:

public class BoardState

{

public const int Columns = 8;

public const int Rows = 8;

public PieceState[,] Pieces = new PieceState[Columns, Rows];

public int HoverColumn = -1;

public int HoverRow = -1;

public int SelectedColumn = -1;

public int SelectedRow = -1;

}

At this point, I’m going to talk about the common pattern of having a pair of variables that contain both a Column and a Row value. In real code, I would wrap these in some sort of other structure, and almost every framework ever has some sort of structure already defined for this. For the purposes of this article, we’re keeping it simple and approachable.

For the remaining issues 3,4, and 5, there needs to be some manner of highlighting a square. Here are the possible values for the highlight of a square.

  • Not highlighted. The selected piece cannot move here. This is the default value most of the time for squares. Call this one “None”.
  • Highlighted. The selected piece can move here, and no other conditions apply. Call this one “Highlighted”.
  • Puts the king in check. The selected piece, when moved here, will put the opposing King in check. Call this one “Checks”.
  • Captures an opposing piece. The selected piece, when moved here, will capture the piece already present. Call this one “Captures”.
  • May be captured. The selected piece, when moved here, is in danger from other pieces. Call this one “Danger”.

The first two, not highlighted and highlighted, are completely exclusive, but the rest of the criteria might apply or not apply simultaneously, and the last three only exist when highlighting is in force.

So, our total list of possible states and combination of states:

  • None
  • Highlighted
  • Highlighted+Checks
  • Highlighted+Captures
  • Highlighted+Checks+Captures
  • Highlighted+Danger
  • Highlighted+Checks+Danger
  • Highlighted+Captures+Danger
  • Highlighted+Checks+Captures+Danger

This is a grand total of nine states. We might accomplish this with an enum, or we might accomplish this with several bool values for each square, with the highlighted property being the overarching quality.

A final property that we might assign to a board is the color of the square. This is entirely aesthetic, but it becomes important when drawing the board.  This we might have as an enum with the values White and Black.

And we have now come to a decision point for our BoardState. Currently, the state of each square is just an enum value. We can either decide to go ahead and add arrays for the various other states of the board (five of them), or we can encapsulate the state of a square into a structure of its own, which is probably the better idea in the long run.

This change makes BoardState and GameState look like the following:

public enum SquareColor
{
White,
Black
}
public struct BoardSquare
{
public PieceState Piece;
public bool Highlight;
public bool Checks;
public bool Captures;
public bool Danger;
public SquareColor Color;
}
public class BoardState
{
public const int Columns = 8;
public const int Rows = 8;
public BoardSquare[,] Pieces = new BoardSquare[Columns, Rows];
public int HoverColumn = -1;
public int HoverRow = -1;
public int SelectedColumn = -1;
public int SelectedRow = -1;
}
public class GameState
{
public BoardState BoardState = new BoardState();
public PlayerState PlayerState = PlayerState.WhiteToMove;
}

So, as you can see, the representation of the game data is getting more detailed, and at this point we’ll consider this case study concluded, and we can now talk about some general concepts that we have discovered in the course of doing it.

Levels of Detail

From the chess case study, we can see that, at the very least, there are three levels of detail in any game that deals with a board or board like world of any kind.  These levels are:

  1. The entire game, which encapsulates not only the board, but also other necessary details of the players of the game, and other overall game mechanic issues.
  2. The entire board, which encapsulates all of the constituent board squares as well as board-level concerns, like the hovering and selected cells.
  3. The board square, which has all of the information needed in order to consider the square as a separate entity.

This is not to say that the levels of detail do not ever bleed across, but it is a good idea to try and keep things stratified in this manner. So, let us think about the representation of a board (and this is abstract, so not any particular game) and its cells, and see what we come up with.

Cell

The smallest atomic unit is the cell itself. In our chess case study, it maps to the BoardSquare structure. The purpose of the Cell class is to describe all of the details within a single square. Later on, when we discuss walkers, we will add relationships to nearby cells, which makes a lot of things simpler, but for now we just consider those details pertinent to the cell.

Another good choice to add to the Cell class is some sort of description of the column and row to which it belongs. This is not always necessary, but is often convenient to know, especially for rendering purposes.  An adequate, although by no means complete, example of what a base class for Cell looks like is as follows:

public class Cell<InfoType>
where InfoType:new()
{
private int column;
private int row;
private InfoType info;
public int Column
{
get
{
return column;
}
}
public int Row
{
get
{
return row;
}
}
private Cell()
{
}
public Cell(int theColumn, int theRow)
{
column = theColumn;
row = theRow;
info = new InfoType();
}
}
The really nice thing here is that we’ve got a generic type where all we need to do is plug in an InfoType, and we’ve got any sort of cell that we want, and we’ve got the pertinent Column and Row information alongside the cell.

BoardColumn

I like to be able to reference my cells with board[column][row], but I also like to control the manner in which things can be accessed. For example, I like to be able to get references to the cells, but not to be able to replace them(especially important when it contains neighbor information). Or sometimes I do like to be able to replace them, when I’m using structs.  In either case, in order to simulate being a two dimensional jagged array, I need an intermediate level of a column of cells with the this[] property.  This is a C# specific thing to do, although it also works out in languages in C++.  In other languages, a board would have to be created directly with getter and setter functions.

A column really doesn’t have to do much than to pretend to be an array of cells, and allow access to how many rows are in the column.

public class BoardColumn<InfoType>
where InfoType: new()
{
private int column;
private Cell<InfoType>[] rows;
public int Column
{
get
{
return column;
}
}
public int Rows
{
get
{
return rows.Length;
}
}
public Cell<InfoType> this[int row]
{
get
{
if (row < 0 || row >= rows.Length)
{
return null;
}
else
{
return rows[row];
}
}
}
private BoardColumn()
{
}
public BoardColumn(int theColumn, int theRows)
{
column = theColumn;
rows = new Cell<InfoType>[theRows];
for (int row = 0; row < theRows; ++row)
{
rows[row] = new Cell<InfoType>(theColumn, row);
}
}
}

Board

Like the column, the board itself doesn’t have to do much. It is basically an array of columns. It also has to keep track of the number of rows and columns.

The code is rather similar to BoardColumn, as you can see here:

public class Board<InfoType>
where InfoType: new()
{
private BoardColumn<InfoType>[] columns;
public int Columns
{
get
{
return columns.Length;
}
}
public int Rows
{
get
{
return columns[0].Rows;
}
}
public BoardColumn<InfoType> this[int column]
{
get
{
if (column < 0 || column >= Columns) return null;
return columns[column];
}
}
private Board()
{
}
public Board(int theColumns, int theRows)
{
columns = new BoardColumn<InfoType>[theColumns];
for (int column = 0; column < Columns; ++column)
{
columns[column] = new BoardColumn<InfoType>(column, theRows);
}
}
}
Believe it or not, with a few modifications to Cell later on, the classes for Board and BoardColumn are done, and will not need to be modified (except that there will eventually need more type parameters later on).

And that’s element one, the board!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: