More SCons configure checks

Current Repository Revision: 35

Code highlight key
Grey: Code unchanged
Red: Code removed
Green: Code added
Blue: Code not shown

The first SCons configure style checks were added in the previous post. These simply checked for the existence of header files. This blog entry will add to the header checks by adding checks for functions.

As well as the addition of the configure checks, code will be added to handle the players, keeping track of who’s turn it is to go next and who should start the next game.

To enable any game which uses squares with three possible states to use the Board class (e.g. Reversi, Draughts (Checkers), etc. or even ones using two states (filled/empty) such as solitaire) the Board class can be changed to use any number of squares. As the size of the Board isn’t known before hand, an std::vector will be used.

The choice to use a std::vector is a simple one. To do it without STL containers would require pointers (managed by the MereIdea code) to allocate the appropriate amount of memory. This management is simple, but it’s always better not to do it if there’s an alternative as it avoids memory errors that can be hard to debug.

When using an STL container, std::vector should always be considered first, as it is the most efficient in terms of space and speed of access (it is essentially a management wrapper around a C-style array). It is perfect for this purpose, so there is no need to look further.

The code of the Board class is changed to use the std::vector in place of the nine element array:

/*!
   \file board.h

   \brief Maintain a board for a game of noughts and crosses
   \brief Maintain a board for a game using squares and up to →
three states

   \clog
...

#ifndef MI_TICTAC_CORE_BOARD_H_
#define MI_TICTAC_CORE_BOARD_H_

#include <vector>

#include <tictac/core/piece.h>
...
    //! Construct an empty board
    /*!
        \param n_squares The number of squares on the board. These →
will be
                         numbered 1 to n_squares.
        \post The board will be initialised with nine n_squares →
undefined squares
    */
    Board(void);
    Board(const unsigned int n_squares);
...
    void setSquare(const unsigned int num,
                   const Piece pce);

    //! Clear a single square on the board
    /*!
        \param num The number of the square to set
        \pre num contains a number in the range 1-n inclusive →
(where n is
                 the number of squares specified on contruction).
        \exception SquareOutOfRange thrown if num is not in the →
range 1-n
        \sa content(), setSquare()
    */
    void clearSquare(const unsigned int num);

    //! Clear the board so that all squares are undefined
...
  private:
    //! The squares for the noughts and crosses board
    Square board_m[9];
    std::vector<Square> board_m;
  };
}

The changes to the Board class are straightforward. The constructor has an argument added for the number of squares needed for the board. There is no need for a default constructor as the board always has to have some size, and there is no sensible default size. There is no need to restrict the board to square numbers, either, as it isn’t known how the Board will be used.

The second change is the addition of the clearSquare() method. This is now necessary as a use of the Board could include the removal of pieces.

Finally, the actual internal data type for the Board is changed from and array of Square to a std::vector of Square.

These changes don’t require much of a change in the board.cxx file (as both the array and std::vector are accessed with the square brackets operator), so the code isn’t shown here. The board_test.cxx was also altered to correct for the changes and add new tests for the clearSquare() method.

As the Board has been changed to have board sizes other than 3×3, it can be used for more than Noughts and Crosses (or tic-tac-toe). Therefore, it no longer makes sense to call the library tictac. The name was, therefore, changed to bdgme, for board game. This required changing all the namespaces from mi_tictac to mi_bdgme, changing the TictacException to BdGmeException, changing any comments containing tictac to bdgme, and changing the SConscript file in mi_test_scons/src to include the directory bdgme instead of tictac.

Finally, svn is used to move the tictac directory to bdgme:

svn mv tictac bdgme

Directory name changes should be done using svn otherwise the repository and working copies become out of sync and it is possible to end up with both tictac and bdgme directories active in the latest revision if you’re not careful!

The next step was to add a Player class. This class simply holds the name of a player and the Piece they are using. The class is declared as follows:

class Player
{
public:
  Player(const std::string &name = "anonymous",
         const Piece pce = "UNDEFINED_PIECE");
  ~Player(void);

  void setName(const std::string &name);
  void setPiece(const Piece pce);

  const std::string &name(void) const;
  const Piece &piece(void) const; 

private:
  std::string name_m;
  Piece piece_m;
};

The definition of the Player class is straightforward, and so is not shown. Some tests were also added in the file src/bdgme/core/tests/player_test.cxx.

To handle the number of players in the game, who’s turn it is next, and who’s turn it is to start the next game, a Players class is created. This is declared as follows:

class Players
{
public:
  Players(const Player &p);
  Players(const std::vector<Player> &vp);
  ~Players(void);

  void addPlayer(const Player &p);

  const Player &startPlay(bool rnd=false);
  const Player &currentPlayer(void) const;
  const Player &nextPlayer(void);

  unsigned int nPlayers(void);

private:
  std::vector<Player> players_m;
  unsigned int current_player_m;
  unsigned int next_start_m;
};

There is no default constructor for the Players class as we always need at least one player in any game! The player added through the single Player constructor becomes the first player, and other players remain in the order they are added through the addPlayer() method. By default the startPlay() method starts the next game from the next player in the std::vector. This behaviour can be changed by specifying true as the rnd argument of the startPlay() method. The player to start is then picked randomly from the number of players in the class. This could mean the same player being picked a number of times in a row. The standard C built-in rand() function is used. As their names suggest, currentPlayer() returns the current player taking their turn, nextPlayer() moves the current_player_m index by one (to point at the next Player in the std::vector) and returns that player, and nPlayers() returns the number of players held by the class. A set of tests were added in the file src/bdgme/core/players_test.cxx.

The definition of the constructors and the startPlay() functions are as follows:

...
mi_bdgme::Players::Players(const Player &p) :
current_player_m(0),
next_start_m(0)
{
  players_m.push_back(p);

  // Seed the random number generator based on time
  srand((unsigned int)time(0));
}
...
mi_bdgme::Players::Players(const std::vector<Player> &vp) :
players_m(vp),
current_player_m(0),
next_start_m(0)
{
  // Seed the random number generator based on time
  srand((unsigned int)time(0));
}
...
const mi_bdgme::Player &mi_bdgme::Players::startPlay(bool rnd
                                                     /*=false*/)
{
  unsigned int st;
  if (rnd)
  {
    st = rand() % players_m.size();
    next_start_m=st+1;
  }
  else
  {
    st = next_start_m++;
  }

  next_start_m = (next_start_m==players_m.size()) ? 0 : next_start_m;
  current_player_m = st;

  return players_m[st];
}
...

The adjusted Board and new Players classes include three new standard headers – vector, cstdlib and ctime. SCons configure style checks were added for these headers in src/bdgme/core/SConfig:

...
if not conf.CheckCXXHeader('vector'):
  print "ERROR: Vector header not present."
  print "ERROR: Please ensure the Standard Library is installed and it"
  print "ERROR: contains <vector>."
  Exit(1)

# Check for C library headers
if not conf.CheckCXXHeader('cstdlib'):
  print "ERROR: The C standard library header not present."
  print "ERROR: Please ensure your C library is properly installed and"
  print "ERROR: contains <cstdlib>."
  Exit(1)

if not conf.CheckCXXHeader('ctime'):
  print "ERROR: The C time header not present."
  print "ERROR: Please ensure your C library is properly installed and"
  print "ERROR: contains <ctime>."
  Exit(1)
...

The Players class also includes three standard functions – srand, time and rand. Configure checks can be added to the SConfig file for the existence of these functions using the CheckFunc() SCons function:

...
# Check for functions
if not conf.CheckFunc('srand'):
  print "ERROR: Function srand is not present."
  print "ERROR: Please ensure your C library is properly installed and"
  print "ERROR: contains srand()."
  Exit(1)

if not conf.CheckFunc('time'):
  print "ERROR: Function time is not present."
  print "ERROR: Please ensure your C library is properly installed and"
  print "ERROR: contains time()."
  Exit(1)

if not conf.CheckFunc('rand'):
  print "ERROR: Function rand is not present."
  print "ERROR: Please ensure your C library is properly installed and"
  print "ERROR: contains rand()."
  Exit(1)

# Finish up the configure environment
loc_env = conf.Finish()

Now running SCons without the -Q switch will show the new set of checks:

[mi_test_scons]$ scons
scons: Reading SConscript files ...
Checking for C++ header file string... yes
Checking for C++ header file exception... yes
Checking for C++ header file sstream... yes
Checking for C++ header file vector... yes
Checking for C++ header file cstdlib... yes
Checking for C++ header file ctime... yes
Checking for C function srand()... yes
Checking for C function time()... yes
Checking for C function rand()... yes
Checking for C++ header file iostream... yes
Checking for C++ header file string... yes
Checking for C++ header file vector... yes
Checking for C++ header file cstdlib... yes
Checking for C function srand()... yes
scons: done reading SConscript files.
scons: Building targets ...

There are repeated checks in this list. As SCons caches checks, it is hoped that these are not repeated (i.e. recompiled).

They exist because I like to have the checks local to the code (in this case in core and in core/tests). This is to make it easier to keep track of which checks are necessary. This is especially true in the case where, for example, a particular header file is removed from all files within a particular directory. The header check can be removed in the local SConfig file, but if other directories contain files including that header the necessary checks will remain. Having a global file would require searching of all directories to check if the header check is needed.


The changes detailed in this post have been added to the example code repository. The current revision is 35.

Revision 33 included the changes to the Board class to enable any number of squares to be specified. It also changed the library name from tictac to bdgme.

Revision 34 added the Player and Players classes. It also changed the TictacException class to BdGmeException. This revision also added the extra header checks and the function checks detailed in this post.

Revision 35 Simply added a newline to the end of the Players class and added a check for the time function missed in the earlier revision.

So far only simple function checks have been added. In the next post a further function check will be created that will fail to find the function, and will use a replacement function which is locally written. Also some custom checks will be added.

Leave a Reply

You must be logged in to post a comment.