Category: Chess Engine

  • MVV/LVA Move Ordering

    When the possible moves are searched by an Alpha Beta algorithm to find the best move to play, more branches of the search tree are pruned if the moves are first ordered into an educated guess of the best order. Move Ordering will substantially reduce the number of branches searched.

    MVV/LVA stands for Most Valuable Victim/Least Valuable Attacker. As it says, we order the potential captures by the most valuable piece captured taken by the least valuable attacker.

    We give each piece, as listed in the Pieces Representation, a value. The precise values used are irrelevant, they merely indicate that one piece is more valuable than another.

    enum class Pieces { empty, P, N, B, R, Q, K, p, n, b, r, q, k, offboard, enpassant };

    int mvvLvaValue[13] = { 0,100,200,300,400,500,600,100,200,300,400,500,600 };

    We create an array to hold our evaluations.

    int mvvLvaScores[14 * 14];

    The function InitMvvLva populates the array with an evaluation  for each combination of Attacker and Victim. The index of the array is [victim * 14 + attacker] giving us space to store all of the combinations of victim and attacker.

    void MoveGen::initMvvLva() //Most Valuable Victim, Least Valuable Attacker
    {
     int attacker = 0;
     int victim = 0;
     for (attacker = static_cast <int>(Defs::Pieces::P); 
            attacker <= static_cast <int>(Defs::Pieces::k);  
       	++attacker)
    for (victim = static_cast <int>(Defs::Pieces::P); 
            victim <= static_cast <int>(Defs::Pieces::k);
            ++victim)
    mvvLvaScores[victim * 14 + attacker] = 
            mvvLvaValue[victim] + 6 
            - static_cast<int>(mvvLvaValue[attacker]) / 100);
    }
         

    So if a pawn takes a queen, the evaluation is 500 +6 – (100/100) = 505

    If a pawn takes a rook, the evaluation is 400 +6 – (100/100) = 405

    If a bishop takes a queen, the evaluation is 506 – (300/100 ) = 503

    If a bishop takes a rook, the evaluation is 400 +6 – (300/100) = 503

    Captures of queens all start 500, captures of bishops all start 400 etc.

    Captures by pawns will all end in 0, captures by bishops will all end in 3  etc.

    We will order by highest scoring captures, which equates to the most valuable victim and the least valuable attacker.

    We hold a field, score, on the Move Representation, to hold the MVV/LVA score, populated when we add moves, calling the getCaptureScore function . 1 million is added to score to allow room for later ordering algorithms. En passant captures, pawn taking pawn, always score 105.

    int MoveGen::getCaptureScore(int from, int to, Gameboard& Board, int ply)
    {
     int victim = static_cast<int>(Board.getPiece(to));
     int captureScore = 0;
     if (victim > 0)
    {
     int attacker = static_cast<int>(Board.getPiece(from));
     //enpassant capture
     if (victim == static_cast<int>(Defs::Pieces::enpassant) 
          && (attacker == static_cast<int>(Defs::Pieces::p) 
          || attacker == static_cast<int>(Defs::Pieces::P))) 
       {captureScore = 1000000 + 105;} //Pawn taked Pawn is 105
     else
       {captureScore = getMvvLvaScores(victim * 14 + attacker) + 1000000;}
    }
    return captureScore;
    }
    
  • Castling Representation

    The castling position is held as 4 bytes in the move representation.

    Castle flag 1111 which is 8 4 2 1.

    int castleFlag = ((move >> 14) & 0x0F);

    BytesDecimalFlagsHow to query
    10008White King Side Castleif(castleFlag & 8)
    01004White Queen Side Castleif(castleFlag & 4)
    00102Black King Side Castleif(castleFlag & 2)
    00011Black Queen Side Castleif(castleFlag & 1)
  • Piece Representation

    The pieces are defined as an enumeration to add to the clarity of the code.

    enum class Pieces { empty, P, N, B, R, Q, K, p, n, b, r, q, k, offboard, enpassant };

    They are converted to integers by casting, for instance:

    int a = static_cast<int>(Pieces::Q) ;

  • Move Representation

    Each potential move is stored in a 32 bit integer

    The move integer is a member of a structure. Members of the structure are :

    • move – a 32 bit integer representing the move.
    • score – an integer used for MVV/LVA move ordering.
    • quiet – a bool that indicates whether the move is a quiet move.
    Byte fromByte ToRepresentsHow to query
    17Square Frommove & 0x7F
    814Square To((move >>7) & 0x7F)
    1518Castling((move >> 14) & 0x0F)
    1922Captured Piece((move >> 18) & 0xF)
    2326Promoted Piece((move >> 22) & 0xF)
    2727En Passant Flag(1u & (move >> 26))

    See also:

    Piece Representation

    Castling Representation

  • Board Representation

    The chess board representation that I have used is known as 0X88. A 128 byte array is used to represent the board.

    0x88 board – decimal representation

    0x88 board – hex representation

    The board is 16 by 8 in size, the size of two 8 by 8 chess boards. The on-board squares are represented by the first 8 columns. The second 8 columns are off board.

    Square a2 is decimal 16, a8 is 112, h8 is 119.

    I prefer to think of the board representation in 8 bit binary where the leftmost 4 bits of the binary number represent the board rank (row) indexed 0 to 7, and the right side 4 bits of the binary number represent the board file (column indexed 0 to 7).

    The second 8 columns of the board which are off board.

    Why is the representation names 0x88?

    You can see, from the binary representation, that if the 8 bit is set, the square is off board to the right. This is the basis of  the 0x08 test, which is explained later. This test can be combined with an off the top or bottom of the board test,, the 0x80 test, which tests whether the square is less than 129 (hex 80) and greater than 0. The tests combines, 0x88 give the name to the board representation.

    Converting

    You can find the index of any square, given its file (0 to 15)  and rank (0 to 7), using the formula:

     index = row index *16 + column index

    so:

    a8  is (7 x 16) + 0 = 112.

    h8 is (7 *16) + 7 = 112 + 7 = 119.

    2 is (1*16) + 0 = 16

    To find the index of a square in hexadecimal:

    The rank is stored in the left side 4 bits of an 8 bit binary number.

    The file is stored in the right side 4 bits of an 8 bit binary number.

    So square C5 will be rank 4 (0100 in binary) file 2 (0010 in binary)

    Put them together to get 0100 0010 which is 42 hex.

    The formula for this would be:

    Index [0x88] = ((rank index <<4) + file index

    So for square C5:

    Rank is 4 or 0000 0100 in binary.

    Rank <<4 (bit shifted 4) is 0100 0000  

    File is 2 or 0000 0010 in binary.

    Index is 0000 0100  + 0100 0000 = 0100 0010 binary or 42 Hex.  

    Also:

    file[0 to 7] = square[0x88] &7 (uses bitwise and operator &)

    rank[0 to 7] = square[0x88] >>4 (uses bitwise right shift >>)

    To find the file of square C5 (42 hex) we calculate 0100 0010 & 0000 0111 which is  0000 0010 or 2.

    To find the file of square C5 (42 hex) we calculate 0100 0010  which is 0000 0100 or 4.

    Testing if we are on the board

    The 0x08 test

    If you go off the board to the right, the 0x08bit is set,

    i.e.

    on board 07 is 0111

    on board 77 is 01110111

    off board 08 and 7E are 8 0000 1000 and 0111 1110. The 8 bit is set.

    To test if position is valid use: if(!index & 0x08)

    The 0x80 test

    128 (squares) is hex 0x080, so we can test if off top or bottom of board with: if(!index & 0 x 80)

    The 0x88 test

    To see if we are off board, combine 0x08 and 0x80 tests using: if(!index & 0x88)

    Finding adjacent squares

    Subtract index of square A from square B

    If 1, square A is one to left of B

    If 16, A is immediately below B

    Coding

    In Piglet, the board is defined in the Gameboard class as an array of pieces.

    Defs::Pieces pieceBoard[128];

    Helper functions have been written, using the logic described above, to get a board index for a chess notation, and vice-versa.

    unsigned int Gameboard::notationToIndex(char file, char rank)

    {
          int f = fileAlgabraicToInt(file);
         int r = rankAlgabraicToInt(rank);
          int index = (r * 16) + f;
          return index;
    }
    string Gameboard::indexToNotation(int index)
    {
          if (index <= 128)
          {
                int f = (index & 7);
                int r = (index >> 4);
                char file = Gameboard::file[f];
                char rank = Gameboard::rank[r];
                string s;
                s.push_back(file);
                s.push_back(rank);
                return s;
          }
          else
          {
                return “-“;
          }
    }