Author: paulrwebster

  • Zobrist Hash Keys/Transposition Table

    A transposition table is a cache of the board positions previously evaluated. These positions can be reached in various ways, known as transpositions. The transpositions are represented by a hash table. If a board positions is already represented in the hash table, the position does not need to be re-examined, the evaluation is taken from the hash table.
    The hash table used is a hash table of Zobrist Keys.
    Firstly we populate an array named hashkeys with 781 random numbers (generated by std::mt19927_64 which is a Mersenne twister)
    A Zobrist key is generated for the current board position by XOR into the Zobrist key the hashkeys that we generated for 0 to 780 as follows:
    12 pieces *64 squares – 0 to 767

    void Hashing::generateHashKeys() 
    {
        twister.seed(5489u); //standard seed
        for (int i = 0; i < 781; i++)
        {
            hashkeys[i] = twister();
        }
    }
    
    • 1 side black – 768
    • 4 castling rights – 769 to 772
    • 8 en passant file – 773 to 780
      I have set up a struct transpositionTable:

    typedef struct 
    {
       unsigned long long key;
       int depth;				
       int flags;				
       int value;				 
       int best;				
    }  transpositionTable;
    

    An instance is created with the class: Hashing::transpositionTable hash_table[HASHSIZE];
    key is the full Zobrist key.
    depth is the depth of the search.
    flags are: hashfEXACT 0, hashfALPHA 1, hashfBETA 2
    If hashfEXACT, the evaluation.
    If flag hashfALPHA, then at most the evaluation.
    If hashfBETA, at least the evaluation
    best is the best move encountered the last time the position was searched.

    The following functions help populate the transposition table.

    void Hashing::setZobristPiece(int square, Defs::Pieces piece) //populate piece on square
    {
    if (piece > Defs::Pieces::empty && piece <= Defs::Pieces::enpassant ) 
      { int file = (square & 7); int rank = (square >> 4);
        int sq64 = (rank * 8) + (file + 1);
        zobristKey ^= hashkeys[(sq64 * static_cast(piece)) - 1];
      }
    }
    void Hashing::setZobristEp(int square) //populate en passant
    {
       int file = square & 7;
       zobristKey ^= hashkeys[773 + file];
    }
    void Hashing::setZobristSide() //populate black
    {
       zobristKey ^= 768; //set if black. Reverse if white
    }
    void Hashing::setZobristCastle(int castleflag) //populate castling flag
    {
       if (castleflag & 1) { zobristKey ^= 769; }
       if (castleflag & 2) { zobristKey ^= 770; }
       if (castleflag & 4) { zobristKey ^= 771; }
       if (castleflag & 8) { zobristKey ^= 772; }
    }

    The RecordHash function populates the transpositionTable structure with a position.
    void Hashing::RecordHash(int depth, int val, int hashf, int bestMove)
    The key for the array is Zobrist Key modulo the defined size of the hash table that I have set at hex 400000. Any clashes will overwrite. This is done to prevent the use of too much memory.

    define HASHSIZE 0x400000 //int 4,194,304

    The ProbeHash function – int Hashing::ProbeHash(int depth, int alpha, int beta)
    It gets a pointer to the transposition table for a key


    transpositionTable* transpoTable = &hash_table[zobristKey % HASHSIZE];

    If key exactly matches the Zobrist key, depth is at least the search depth:
    If the flag is hashfEXACT, value (the evaluation) is returned.
    If the flag is hashfALPHA and value is <= alpha, the alpha passed to the function is returned. If the flag is hashfBETA and value is >= beta, the beta passed to the function is returned.

    Negamax Function

    Probe the hash table to see if the current board position has already been recorded, and if so, and a value is found, return that value.

    If at depth zero, evaluate the board, record the hash and its value, return the value

    For each child, if value >= beta, call RecordHash with hashfBETA flag and return beta.

    If value > alpha, set alpha to value, set hashFlag to hashfEXACT and record to the hash table.

    When called:

    Below is pseudocode of how this fits into a negamax function

    
    int negamax( int alpha, int beta, int depth ) {
        int hashFlag = hashfALPHA; // define hash flag
        int hashVal = Hash.ProbeHash(depth, alpha, beta);
        if (hashVal != VALUNKNOWN)
        {
    	// if the move has already been searched (hence has a value)
    	// we just return the score for this move without searching it
    	return hashVal;
       }
       if( depth == 0 ) return quiescence( alpha, beta );
       for ( all moves)  {
          score = -alphaBeta( -beta, -alpha, depth - 1 );
          if( val >= beta )
            // store hash entry with the score equal to beta
            Hash.RecordHash(depth, beta, hashfBETA);
             return beta;   //  fail hard beta-cutoff
          if( val > alpha )
             alpha = val; // alpha acts like max in MiniMax
               // switch hash flag from storing score for fail-low node
               // to the one storing score for PV node
               hashFlag = hashfEXACT;
       }
            // store hash entry with the score equal to alpha
            Hash.RecordHash(depth, alpha, hashFlag);
            return alpha;
    }
  • 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;
    }
    
  • Invaders

    A game written in WebGL using the Unity Game Engine.

    Use arrow keys to move gun. Use spacebar to fire.

  • Golf Courses for the computer games PGA Tour 2K21 and The Golf Club 2019

    I have designed two local golf courses to be played on the above games. The terrain for the courses is accurate having been extracted from the DEFRA aerial Lidar surveys.

    The fairway, tee, green and bunker locations have been extracted from the satellite imagery/mapping on Open Street Maps.

    Cuckfield Golf Centre
    Burgess Hill Golf Centre

    Courses created using Chad’s Tools

  • 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

  • Asteroids

    A Javascript/HTML5 game.

    Use arrows to move and rotate. Use Space key to fire gun.

  • Tetris

    A game written in Javascript/HTML5.

    Use arrow keys to rotate and move blocks. Use spacebar to drop.