This document is a description of the design of mchess and mchess2

Piece representation

The chess board consists of 64 squares. Each square may be empty or contain a white or black piece. Each piece is given an integer value (this is not related to the worth of the piece in number of pawns).

This enumeration is called e_piece and is defined in the file CMove.h:

typedef enum {
    EM = 0,   // Empty
    WP = 1,   // White Pawn
    WN = 2,   // White Knight
    WB = 3,   // White Bishop
    WR = 4,   // White Rook
    WQ = 5,   // White Queen
    WK = 6,   // White King
    BP = -1,  // Black Pawn
    BN = -2,  // Black Knight
    BB = -3,  // Black Bishop
    BR = -4,  // Black Rook
    BQ = -5,  // Black Queen
    BK = -6,  // Black King
    IV = 99   // INVALID
} e_piece;
The above is merely an enumeration, and the actual numerical values are arbitrary. However, I do in the following rely on the fact that:

Additionally, I have a special value for an invalid piece. This has several uses, the foremost will be explained in the following.

Square representation

An important function in a chess engine is to be able to generate a list of all legal moves in a given position. This function is used all the time, and therefore must be as fast as possible.

When writing such a function, one must first consider how to represent the board configuration, i.e. where each piece is placed, or equivalently, which piece is on a given square.

I've chosen to represent the board as a simple one-dimensional array of integers. Each index in the array corresponds to a particular square, and each value in the array is the corresponding piece, according to the enumeration above.

When generating a list of legal moves, it is necessary to check if a piece is about to move outside the edge of the board. For instance, a rook on H1 can not move to the right.

This is dealt with in a very smart way: The board is enlarged, so that it is surrounded by two layers of invalid squares. This gives a total of 12 rows of ten squares each, i.e. 120 elements in the array.

110 111 112 113 114 115 116 117 118 119
100 101 102 103 104 105 106 107 108 109
90 A8 B8 C8 D8 E8 F8 G8 H8 99
80 A7 B7 C7 D7 E7 F7 G7 H7 89
70 A6 B6 C6 D6 E6 F6 G6 H6 79
60 A5 B5 C5 D5 E5 F5 G5 H5 69
50 A4 B4 C4 D4 E4 F4 G4 H4 59
40 A3 B3 C3 D3 E3 F3 G3 H3 49
30 A2 B2 C2 D2 E2 F2 G2 H2 39
20 A1 B1 C1 D1 E1 F1 G1 H1 29
10 11 12 13 14 15 16 17 18 19
0 1 2 3 4 5 6 7 8 9

When testing whether a move is legal, it is enough to test if the target square is empty or contains an enemy piece. It is not even necessary to exlicitly test for invalid squares.

An enumeration is used to define the index of each square. This enumeration is defined in the file CSquare.h. This is purely for code readability, i.e. if I refer to a square as E2 it is more readable, than using the integer 35.

enum // Squares
{
    A8 = 91, B8, C8, D8, E8, F8, G8, H8,
    A7 = 81, B7, C7, D7, E7, F7, G7, H7,
    A6 = 71, B6, C6, D6, E6, F6, G6, H6,
    A5 = 61, B5, C5, D5, E5, F5, G5, H5,
    A4 = 51, B4, C4, D4, E4, F4, G4, H4,
    A3 = 41, B3, C3, D3, E3, F3, G3, H3,
    A2 = 31, B2, C2, D2, E2, F2, G2, H2,
    A1 = 21, B1, C1, D1, E1, F1, G1, H1,
};

The CSquare class

It does become necessary to determine the row and/or column number of a given square, for instance when checking for pawn promotion. Therefore I've chosen to write a class CSquare to handle this. The following is the essential ingredients:
class CSquare 
{
    public: 
        int row() const {return (m_sq/10) - 1;} // returns 1 - 8
        int col() const {return (m_sq%10);}     // returns 1 - 8
        operator int() const { return m_sq; }   // Implicit conversion to integer

    private:
        uint8_t  m_sq; // Internal representation, 0 - 119.
}; // end of class CSquare
The conversion operator int() allows the CSquare class to be used directly as an array index.

Move representation

The next to consider is how to represent a move. Obviously, we need an originating square, and a target square. However, this is not quite enough. When a pawn reaches the back rank, it may promote to any other piece, and this must be given as well.

The CMove class

The class CMove is used to represent a single chess move. The essential ingredients in this class are:
class CMove
{
    public:
        CSquare     From(void) const {return m_from;}
        CSquare     To(void) const {return m_to;}

    private:
        CSquare     m_from;
        CSquare     m_to;
        int8_t      m_promoted; // This handles pawn promotion.
}; // end of CMove

Note the member variable m_promoted. This is only used during pawn promotion, where it contains the piece that the pawn promotes to. In case of an ordinary move, the variable is not used, and should be set to zero (empty).

Enhancements

This class contains two additional private member variables:
        int8_t      m_piece;
        int8_t      m_captured;
The first, m_piece, contains the current piece being moved, the second variable, m_captured, contains the piece being captured, if any. They are both used only for printing the current move to the user.

The CMoveList class

When generating a list of all the legal moves in a chess position, it becomes necessary to manipulate lists of moves. I've found it convenient to write a separate class just for this:
class CMoveList
{
    public:
        void clear()
        {
            m_moveList.clear();
        }

        void push_back(const CMove& move)
        {
            m_moveList.push_back(move);
        }

        unsigned int size() const
        {
            return m_moveList.size();
        }

        const CMove & operator [] (unsigned int ix) const { return m_moveList[ix]; }

    private:
        std::vector m_moveList;

}; // end of CMoveList

The purpose of this class is just to encapsulate the semantics of an ordinary array. Here I've chosen to implement the array using the std::vector container class.

When populating the list, the functions clear() and push_back(move) are used. When examining the list, the functions size() and operator [] are used.

Board representation

The CBoard class

This class is defined in the file CBoard.h and contains all the information defining the current position on the board. Of course, this includes the above mentioned array. Additionally, it includes an integer value that specifies whether it is the white or the black pieces to move.
class CBoard
{
    public:
        void newGame();
        void find_legal_moves(CMoveList &moves) const;
        void make_move(const CMove &move);
        int  get_value();

    private:
        std::vector   m_board;
        int m_side_to_move;
}; // end of class CBoard

The function newGame() resets the board to the starting position, the function make_move() updates the board position with the supplied move, and finally find_legal_moves() generates a list of all legal moves in the current board position.

Enhancements

To improve performance, I've added another member variable that keeps track of the current material balance.
        int m_material;
This is kind of like a cache. Instead of calculating the material balance after each move, it is much faster to check for captures and promotion, and adjust the current cache incrementally.

Searching for the best move

The AI class

This class contains an implementation of the alpha-beta pruning algorithm. This is a complicated function, and is best described in wikipedia and the chess programming site. The class interface is very simple, however:
class AI
{
    public:
        AI(CBoard& board) : m_board(board) { }

        CMove find_best_move();

    private:
        int search(int alpha, int beta, int level);

        CBoard&         m_board;
}; // end of class AI
Note that the m_board variable is a reference. This is to avoid making copies of the entire CBoard class.

Final remarks

These are the basic ingredients necessary to make a working chess engine. There only remains to write a main() function that instantiates a CBoard and a AI object. The you must read the user input and call ai.find_best_move().