Category: Uncategorised

  • 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;
    }
  • 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