chess.com puzzle hack

good luck ;)

Ten skrypt nie powinien być instalowany bezpośrednio. Jest to biblioteka dla innych skyptów do włączenia dyrektywą meta // @require https://update.greasyfork.org/scripts/555273/1692024/chesscom%20puzzle%20hack.js

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// https://github.com/Strryke/betafish/blob/f281791317d766cb5deaf453a221da632b67df1c/js/betafish.js
// Modified 14.2.2023 to expose the best move function.
// Modified 23.2.2023 to change the generic name and comment out searchcontroller logging
// Modified 27.2.2023 to diable additional logging

const betafishEngine = function() {
  /****************************\
   ============================
   
    Board Constants
   ============================              
  \****************************/

  const START_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
  const PceChar = ".PNBRQKpnbrqk";
  const SideChar = "wb-";
  const RankChar = "12345678";
  const FileChar = "abcdefgh";
  const MFLAGEP = 0x40000;
  const MFLAGPS = 0x80000;
  const MFLAGCA = 0x1000000;
  const MFLAGCAP = 0x7c000;
  const MFLAGPROM = 0xf00000;
  const NOMOVE = 0;

  // prettier-ignore
  const CastlePerm = [
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 13, 15, 15, 15, 12, 15, 15, 14, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 7, 15, 15, 15, 3, 15, 15, 11, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15
  ];

  //prettier-ignore
  {
    var PIECES = { EMPTY: 0, wP: 1, wN: 2, wB: 3, wR: 4, wQ: 5, wK: 6, bP: 7, bN: 8, bB: 9, bR: 10, bQ: 11, bK: 12 };
    var BRD_SQ_NUM = 120;
    var FILES = {
      FILE_A: 0, FILE_B: 1, FILE_C: 2, FILE_D: 3,
      FILE_E: 4, FILE_F: 5, FILE_G: 6, FILE_H: 7, FILE_NONE: 8
    };
    var RANKS = {
      RANK_1: 0, RANK_2: 1, RANK_3: 2, RANK_4: 3,
      RANK_5: 4, RANK_6: 5, RANK_7: 6, RANK_8: 7, RANK_NONE: 8
    };
    var COLOURS = { WHITE: 0, BLACK: 1, BOTH: 2 };
    var CASTLEBIT = { whiteKing: 1, whiteQueen: 2, blackKing: 4, blackQueen: 8 };
    var SQUARES = {
      A1: 21, B1: 22, C1: 23, D1: 24, E1: 25, F1: 26, G1: 27, H1: 28,
      A8: 91, B8: 92, C8: 93, D8: 94, E8: 95, F8: 96, G8: 97, H8: 98,
      NO_SQ: 99, OFFBOARD: 100
    };
    var MAXGAMEMOVES = 2048;
    var MAXPOSITIONMOVES = 256;
    var MAXDEPTH = 64;
    var INFINITE = 30000;
    var MATE = 29000;
    var PVENTRIES = 10000;

    var FilesBrd = new Array(BRD_SQ_NUM);
    var RanksBrd = new Array(BRD_SQ_NUM);
  }

  // prettier-ignore
  {
    var PieceVal = [0, 100, 325, 325, 550, 1000, 50000, 100, 325, 325, 550, 1000, 50000];
    var PieceCol = [COLOURS.BOTH, COLOURS.WHITE, COLOURS.WHITE, COLOURS.WHITE, COLOURS.WHITE, COLOURS.WHITE, COLOURS.WHITE,
    COLOURS.BLACK, COLOURS.BLACK, COLOURS.BLACK, COLOURS.BLACK, COLOURS.BLACK, COLOURS.BLACK];

    var PiecePawn = [0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0];
    var PieceKnight = [0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0];
    var PieceKing = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1];
    var PieceRookQueen = [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0];
    var PieceBishopQueen = [0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0];
    var PieceSlides = [0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0];
    var Kings = [PIECES.wK, PIECES.bK];


    var KnDir = [-8, -19, -21, -12, 8, 19, 21, 12];
    var RkDir = [-1, -10, 1, 10];
    var BiDir = [-9, -11, 11, 9];
    var KiDir = [-1, -10, 1, 10, -9, -11, 11, 9];

    var DirNum = [0, 0, 8, 4, 4, 8, 8, 0, 8, 4, 4, 8, 8];
    var PceDir = [0, 0, KnDir, BiDir, RkDir, KiDir, KiDir, 0, KnDir, BiDir, RkDir, KiDir, KiDir];
    var LoopNonSlidePce = [PIECES.wN, PIECES.wK, 0, PIECES.bN, PIECES.bK, 0];
    var LoopNonSlideIndex = [0, 3];
    var LoopSlidePce = [PIECES.wB, PIECES.wR, PIECES.wQ, 0, PIECES.bB, PIECES.bR, PIECES.bQ, 0];
    var LoopSlideIndex = [0, 4];

    var PieceKeys = new Array(14 * 120);
    var SideKey;
    var CastleKeys = new Array(16);

    var Sq120ToSq64 = new Array(BRD_SQ_NUM);
    var Sq64ToSq120 = new Array(64);
    var Mirror64 = [
      56, 57, 58, 59, 60, 61, 62, 63, 48, 49, 50, 51, 52, 53, 54, 55, 40, 41, 42,
      43, 44, 45, 46, 47, 32, 33, 34, 35, 36, 37, 38, 39, 24, 25, 26, 27, 28, 29,
      30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2,
      3, 4, 5, 6, 7,
    ];
  }

  // init

  function InitFilesRanksBrd() {
    var index = 0;
    var file = FILES.FILE_A;
    var rank = RANKS.RANK_1;
    var sq = SQUARES.A1;

    for (index = 0; index < BRD_SQ_NUM; ++index) {
      FilesBrd[index] = SQUARES.OFFBOARD;
      RanksBrd[index] = SQUARES.OFFBOARD;
    }

    for (rank = RANKS.RANK_1; rank <= RANKS.RANK_8; ++rank) {
      for (file = FILES.FILE_A; file <= FILES.FILE_H; ++file) {
        sq = fileRanktoSquare(file, rank);
        FilesBrd[sq] = file;
        RanksBrd[sq] = rank;
      }
    }
  }

  function InitHashKeys() {
    for (index = 0; index < 14 * 120; ++index) {
      PieceKeys[index] = getRand32();
    }

    SideKey = getRand32();

    for (index = 0; index < 16; ++index) {
      CastleKeys[index] = getRand32();
    }
  }

  function InitSq120To64() {
    var file = FILES.FILE_A;
    var rank = RANKS.RANK_1;
    var sq = SQUARES.A1;
    var sq64 = 0;

    for (index = 0; index < BRD_SQ_NUM; ++index) {
      Sq120ToSq64[index] = 65;
    }

    for (index = 0; index < 64; ++index) {
      Sq64ToSq120[index] = 120;
    }

    for (rank = RANKS.RANK_1; rank <= RANKS.RANK_8; ++rank) {
      for (file = FILES.FILE_A; file <= FILES.FILE_H; ++file) {
        sq = fileRanktoSquare(file, rank);
        Sq64ToSq120[sq64] = sq;
        Sq120ToSq64[sq] = sq64;
        sq64++;
      }
    }
  }

  function InitBoardVars() {
    var index = 0;
    for (index = 0; index < MAXGAMEMOVES; ++index) {
      GameBoard.history.push({
        move: NOMOVE,
        castlePerm: 0,
        enPas: 0,
        fiftyMove: 0,
        posKey: 0,
      });
    }

    for (index = 0; index < PVENTRIES; ++index) {
      GameBoard.PvTable.push({
        move: NOMOVE,
        posKey: 0,
      });
    }
  }

  function init() {
    InitFilesRanksBrd();
    InitHashKeys();
    InitSq120To64();
    InitBoardVars();
    InitMvvLva();
    ParseFen(START_FEN);
  }

  /****************************\
   ============================
   
    Board Helper Functions
   ============================              
  \****************************/

  function getRand32() {
    return (
      (Math.floor(Math.random() * 255 + 1) << 23) |
      (Math.floor(Math.random() * 255 + 1) << 16) |
      (Math.floor(Math.random() * 255 + 1) << 8) |
      Math.floor(Math.random() * 255 + 1)
    );
  }

  function fileRanktoSquare(f, r) {
    return 21 + f + r * 10;
  }

  function isSqOffBoard(sq) {
    return FilesBrd[sq] == SQUARES.OFFBOARD;
  }

  function hashPiece(pce, sq) {
    GameBoard.posKey ^= PieceKeys[pce * 120 + sq];
  }

  function hashCastle() {
    GameBoard.posKey ^= CastleKeys[GameBoard.castlePerm];
  }
  function hashSide() {
    GameBoard.posKey ^= SideKey;
  }
  function hashEnPas() {
    GameBoard.posKey ^= PieceKeys[GameBoard.enPas];
  }

  function sq120to64(sq120) {
    return Sq120ToSq64[sq120];
  }

  function sq64to120(sq64) {
    return Sq64ToSq120[sq64];
  }

  function getPieceIndex(pce, pceNum) {
    return pce * 10 + pceNum;
  }

  function mirror64(sq) {
    return Mirror64[sq];
  }

  /*
  0000 0000 0000 0000 0000 0111 1111 -> From 0x7F
  0000 0000 0000 0011 1111 1000 0000 -> To >> 7, 0x7F
  0000 0000 0011 1100 0000 0000 0000 -> Captured >> 14, 0xF
  0000 0000 0100 0000 0000 0000 0000 -> EP 0x40000
  0000 0000 1000 0000 0000 0000 0000 -> Pawn Start 0x80000
  0000 1111 0000 0000 0000 0000 0000 -> Promoted Piece >> 20, 0xF
  0001 0000 0000 0000 0000 0000 0000 -> Castle 0x1000000
  */

  function fromSQ(m) {
    return m & 0x7f;
  }
  function toSQ(m) {
    return (m >> 7) & 0x7f;
  }
  function CAPTURED(m) {
    return (m >> 14) & 0xf;
  }
  function PROMOTED(m) {
    return (m >> 20) & 0xf;
  }

  /****************************\
   ============================
   
    Gameboard
   ============================              
  \****************************/

  function getPieceIndex(pce, pceNum) {
    return pce * 10 + pceNum;
  }

  let GameBoard = {};

  GameBoard.pieces = new Array(BRD_SQ_NUM);
  GameBoard.side = COLOURS.WHITE;
  GameBoard.fiftyMove = 0;
  GameBoard.hisPly = 0;
  GameBoard.history = [];
  GameBoard.ply = 0;
  GameBoard.enPas = 0;
  GameBoard.castlePerm = 0;
  GameBoard.material = new Array(2); // WHITE,BLACK material of pieces
  GameBoard.pceNum = new Array(13); // indexed by Pce
  GameBoard.pList = new Array(14 * 10);
  GameBoard.posKey = 0;
  GameBoard.moveList = new Array(MAXDEPTH * MAXPOSITIONMOVES);
  GameBoard.moveScores = new Array(MAXDEPTH * MAXPOSITIONMOVES);
  GameBoard.moveListStart = new Array(MAXDEPTH);
  GameBoard.PvTable = [];
  GameBoard.PvArray = new Array(MAXDEPTH);
  GameBoard.searchHistory = new Array(14 * BRD_SQ_NUM);
  GameBoard.searchKillers = new Array(3 * MAXDEPTH);
  GameBoard.GameOver = false;

  function PrintBoard() {
    var sq, file, rank, piece;

    console.log("\nGame Board:\n");
    for (rank = RANKS.RANK_8; rank >= RANKS.RANK_1; rank--) {
      var line = RankChar[rank] + "  ";
      for (file = FILES.FILE_A; file <= FILES.FILE_H; file++) {
        sq = fileRanktoSquare(file, rank);
        piece = GameBoard.pieces[sq];
        line += " " + PceChar[piece] + " ";
      }
      console.log(line);
    }

    console.log("");
    var line = "   ";
    for (file = FILES.FILE_A; file <= FILES.FILE_H; file++) {
      line += " " + FileChar[file] + " ";
    }

    console.log(line);
    console.log("side:" + SideChar[GameBoard.side]);
    console.log("enPas:" + GameBoard.enPas);
    line = "";

    if (GameBoard.castlePerm & CASTLEBIT.whiteKing) line += "K";
    if (GameBoard.castlePerm & CASTLEBIT.whiteQueen) line += "Q";
    if (GameBoard.castlePerm & CASTLEBIT.blackKing) line += "k";
    if (GameBoard.castlePerm & CASTLEBIT.blackQueen) line += "q";
    console.log("castle:" + line);
    console.log("key:" + GameBoard.posKey.toString(16));
  }

  function GenerateFEN() {
    var fenStr = "";
    var rank, file, sq, piece;
    var emptyCount = 0;

    for (rank = RANKS.RANK_8; rank >= RANKS.RANK_1; rank--) {
      emptyCount = 0;
      for (file = FILES.FILE_A; file <= FILES.FILE_H; file++) {
        sq = fileRanktoSquare(file, rank);
        piece = GameBoard.pieces[sq];
        if (piece == PIECES.EMPTY) {
          emptyCount++;
        } else {
          if (emptyCount != 0) {
            fenStr += emptyCount.toString();
          }
          emptyCount = 0;
          fenStr += PceChar[piece];
        }
      }
      if (emptyCount != 0) {
        fenStr += emptyCount.toString();
      }

      if (rank != RANKS.RANK_1) {
        fenStr += "/";
      } else {
        fenStr += " ";
      }
    }

    fenStr += SideChar[GameBoard.side] + " ";

    if (GameBoard.castlePerm == 0) {
      fenStr += "- ";
    } else {
      if (GameBoard.castlePerm & CASTLEBIT.whiteKing) fenStr += "K";
      if (GameBoard.castlePerm & CASTLEBIT.whiteQueen) fenStr += "Q";
      if (GameBoard.castlePerm & CASTLEBIT.blackKing) fenStr += "k";
      if (GameBoard.castlePerm & CASTLEBIT.blackQueen) fenStr += "q";
    }

    if (GameBoard.enPas == SQUARES.NO_SQ) {
      fenStr += " -";
    }

    fenStr += " ";
    fenStr += GameBoard.fiftyMove;
    fenStr += " ";
    var tempHalfMove = GameBoard.hisPly;
    if (GameBoard.side == COLOURS.BLACK) {
      tempHalfMove--;
    }
    fenStr += tempHalfMove / 2;

    return fenStr;
  }

  function ParseMove(from, to) {
    GenerateMoves();

    var Move = NOMOVE;
    var PromPce = PIECES.EMPTY;
    var found = false;

    for (
      index = GameBoard.moveListStart[GameBoard.ply];
      index < GameBoard.moveListStart[GameBoard.ply + 1];
      ++index
    ) {
      Move = GameBoard.moveList[index];
      if (fromSQ(Move) == from && toSQ(Move) == to) {
        PromPce = PROMOTED(Move);
        if (PromPce != PIECES.EMPTY) {
          if (
            (PromPce == PIECES.wQ && GameBoard.side == COLOURS.WHITE) ||
            (PromPce == PIECES.bQ && GameBoard.side == COLOURS.BLACK)
          ) {
            found = true;
            break;
          }
          continue;
        }
        found = true;
        break;
      }
    }

    if (found != false) {
      if (MakeMove(Move) == false) {
        return NOMOVE;
      }
      TakeMove();
      return Move;
    }

    return NOMOVE;
  }

  function GeneratePosKey() {
    var sq = 0;
    var finalKey = 0;
    var piece = PIECES.EMPTY;

    for (sq = 0; sq < BRD_SQ_NUM; ++sq) {
      piece = GameBoard.pieces[sq];
      if (piece != PIECES.EMPTY && piece != SQUARES.OFFBOARD) {
        finalKey ^= PieceKeys[piece * 120 + sq];
      }
    }

    if (GameBoard.side == COLOURS.WHITE) {
      finalKey ^= SideKey;
    }

    if (GameBoard.enPas != SQUARES.NO_SQ) {
      finalKey ^= PieceKeys[GameBoard.enPas];
    }

    finalKey ^= CastleKeys[GameBoard.castlePerm];

    return finalKey;
  }

  function OppositePrSq(move) {
    // takes in a move and returns the reverse of prsq
    // b1 > 22
    // c3 > 43
    // e2 > 35
    // e3 > 45

    const file = {
      a: 1,
      b: 2,
      c: 3,
      d: 4,
      e: 5,
      f: 6,
      g: 7,
      h: 8,
    };

    let to120 = file[move[0]] + 10 * (parseInt(move[1]) + 1);

    return to120;
  }

  function UpdateListsMaterial() {
    var piece, sq, index, colour;

    for (index = 0; index < 14 * 120; ++index) {
      GameBoard.pList[index] = PIECES.EMPTY;
    }

    for (index = 0; index < 2; ++index) {
      GameBoard.material[index] = 0;
    }

    for (index = 0; index < 13; ++index) {
      GameBoard.pceNum[index] = 0;
    }

    for (index = 0; index < 64; ++index) {
      sq = sq64to120(index);
      piece = GameBoard.pieces[sq];
      if (piece != PIECES.EMPTY) {
        colour = PieceCol[piece];

        GameBoard.material[colour] += PieceVal[piece];

        GameBoard.pList[getPieceIndex(piece, GameBoard.pceNum[piece])] = sq;
        GameBoard.pceNum[piece]++;
      }
    }
  }

  function ResetBoard() {
    var index = 0;

    for (index = 0; index < BRD_SQ_NUM; ++index) {
      GameBoard.pieces[index] = SQUARES.OFFBOARD;
    }

    for (index = 0; index < 64; ++index) {
      GameBoard.pieces[sq64to120(index)] = PIECES.EMPTY;
    }

    GameBoard.side = COLOURS.BOTH;
    GameBoard.enPas = SQUARES.NO_SQ;
    GameBoard.fiftyMove = 0;
    GameBoard.ply = 0;
    GameBoard.hisPly = 0;
    GameBoard.castlePerm = 0;
    GameBoard.posKey = 0;
    GameBoard.moveListStart[GameBoard.ply] = 0;
    GameBoard.GameOver = false;
  }

  //rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1

  function ParseFen(fen) {
    ResetBoard();

    var rank = RANKS.RANK_8;
    var file = FILES.FILE_A;
    var piece = 0;
    var count = 0;
    var i = 0;
    var sq120 = 0;
    var fenCnt = 0; // fen[fenCnt]

    // prettier-ignore
    while ((rank >= RANKS.RANK_1) && fenCnt < fen.length) {
      count = 1;
      switch (fen[fenCnt]) {
        case 'p': piece = PIECES.bP; break;
        case 'r': piece = PIECES.bR; break;
        case 'n': piece = PIECES.bN; break;
        case 'b': piece = PIECES.bB; break;
        case 'k': piece = PIECES.bK; break;
        case 'q': piece = PIECES.bQ; break;
        case 'P': piece = PIECES.wP; break;
        case 'R': piece = PIECES.wR; break;
        case 'N': piece = PIECES.wN; break;
        case 'B': piece = PIECES.wB; break;
        case 'K': piece = PIECES.wK; break;
        case 'Q': piece = PIECES.wQ; break;

        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
          piece = PIECES.EMPTY;
          count = fen[fenCnt].charCodeAt() - '0'.charCodeAt();
          break;

        case '/':
        case ' ':
          rank--;
          file = FILES.FILE_A;
          fenCnt++;
          continue;
        default:
          console.log("FEN error");
          return;

      }

      for (i = 0; i < count; i++) {
        sq120 = fileRanktoSquare(file, rank);
        GameBoard.pieces[sq120] = piece;
        file++;
      }
      fenCnt++;
    } // while loop end

    //rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
    GameBoard.side = fen[fenCnt] == "w" ? COLOURS.WHITE : COLOURS.BLACK;
    fenCnt += 2;

    for (i = 0; i < 4; i++) {
      if (fen[fenCnt] == " ") {
        break;
      }
      switch (fen[fenCnt]) {
        case "K":
          GameBoard.castlePerm |= CASTLEBIT.whiteKing;
          break;
        case "Q":
          GameBoard.castlePerm |= CASTLEBIT.whiteQueen;
          break;
        case "k":
          GameBoard.castlePerm |= CASTLEBIT.blackKing;
          break;
        case "q":
          GameBoard.castlePerm |= CASTLEBIT.blackQueen;
          break;
        default:
          break;
      }
      fenCnt++;
    }
    fenCnt++;

    if (fen[fenCnt] != "-") {
      file = fen[fenCnt].charCodeAt() - "a".charCodeAt();
      rank = fen[fenCnt + 1].charCodeAt() - "1".charCodeAt();
      // console.log(
      //     "fen[fenCnt]:" + fen[fenCnt] + " File:" + file + " Rank:" + rank
      // );
      GameBoard.enPas = fileRanktoSquare(file, rank);
    }

    GameBoard.posKey = GeneratePosKey();
    UpdateListsMaterial();
  }

  function SqAttacked(sq, side) {
    var pce;
    var t_sq;
    var index;

    if (side == COLOURS.WHITE) {
      if (
        GameBoard.pieces[sq - 11] == PIECES.wP ||
        GameBoard.pieces[sq - 9] == PIECES.wP
      ) {
        return true;
      }
    } else {
      if (
        GameBoard.pieces[sq + 11] == PIECES.bP ||
        GameBoard.pieces[sq + 9] == PIECES.bP
      ) {
        return true;
      }
    }

    for (index = 0; index < 8; index++) {
      pce = GameBoard.pieces[sq + KnDir[index]];
      if (
        pce != SQUARES.OFFBOARD &&
        PieceCol[pce] == side &&
        PieceKnight[pce] == true
      ) {
        return true;
      }
    }

    for (index = 0; index < 4; ++index) {
      dir = RkDir[index];
      t_sq = sq + dir;
      pce = GameBoard.pieces[t_sq];
      while (pce != SQUARES.OFFBOARD) {
        if (pce != PIECES.EMPTY) {
          if (PieceRookQueen[pce] == true && PieceCol[pce] == side) {
            return true;
          }
          break;
        }
        t_sq += dir;
        pce = GameBoard.pieces[t_sq];
      }
    }

    for (index = 0; index < 4; ++index) {
      dir = BiDir[index];
      t_sq = sq + dir;
      pce = GameBoard.pieces[t_sq];
      while (pce != SQUARES.OFFBOARD) {
        if (pce != PIECES.EMPTY) {
          if (PieceBishopQueen[pce] == true && PieceCol[pce] == side) {
            return true;
          }
          break;
        }
        t_sq += dir;
        pce = GameBoard.pieces[t_sq];
      }
    }

    for (index = 0; index < 8; index++) {
      pce = GameBoard.pieces[sq + KiDir[index]];
      if (
        pce != SQUARES.OFFBOARD &&
        PieceCol[pce] == side &&
        PieceKing[pce] == true
      ) {
        return true;
      }
    }

    return false;
  }

  /****************************\
   ============================
   
    Make Move
   ============================              
  \****************************/

  function ClearPiece(sq) {
    var pce = GameBoard.pieces[sq];
    var col = PieceCol[pce];
    var index;
    var t_pceNum = -1;

    hashPiece(pce, sq);

    GameBoard.pieces[sq] = PIECES.EMPTY;
    GameBoard.material[col] -= PieceVal[pce];

    for (index = 0; index < GameBoard.pceNum[pce]; ++index) {
      if (GameBoard.pList[getPieceIndex(pce, index)] == sq) {
        t_pceNum = index;
        break;
      }
    }

    GameBoard.pceNum[pce]--;
    GameBoard.pList[getPieceIndex(pce, t_pceNum)] =
      GameBoard.pList[getPieceIndex(pce, GameBoard.pceNum[pce])];
  }

  function AddPiece(sq, pce) {
    var col = PieceCol[pce];

    hashPiece(pce, sq);

    GameBoard.pieces[sq] = pce;
    GameBoard.material[col] += PieceVal[pce];
    GameBoard.pList[getPieceIndex(pce, GameBoard.pceNum[pce])] = sq;
    GameBoard.pceNum[pce]++;
  }

  function MovePiece(from, to) {
    var index = 0;
    var pce = GameBoard.pieces[from];

    hashPiece(pce, from);
    GameBoard.pieces[from] = PIECES.EMPTY;

    hashPiece(pce, to);
    GameBoard.pieces[to] = pce;

    for (index = 0; index < GameBoard.pceNum[pce]; ++index) {
      if (GameBoard.pList[getPieceIndex(pce, index)] == from) {
        GameBoard.pList[getPieceIndex(pce, index)] = to;
        break;
      }
    }
  }

  function MakeMove(move) {
    var from = fromSQ(move);
    var to = toSQ(move);
    var side = GameBoard.side;

    GameBoard.history[GameBoard.hisPly].posKey = GameBoard.posKey;

    if ((move & MFLAGEP) != 0) {
      if (side == COLOURS.WHITE) {
        ClearPiece(to - 10);
      } else {
        ClearPiece(to + 10);
      }
    } else if ((move & MFLAGCA) != 0) {
      switch (to) {
        case SQUARES.C1:
          MovePiece(SQUARES.A1, SQUARES.D1);
          break;
        case SQUARES.C8:
          MovePiece(SQUARES.A8, SQUARES.D8);
          break;
        case SQUARES.G1:
          MovePiece(SQUARES.H1, SQUARES.F1);
          break;
        case SQUARES.G8:
          MovePiece(SQUARES.H8, SQUARES.F8);
          break;
        default:
          break;
      }
    }

    if (GameBoard.enPas != SQUARES.NO_SQ) hashEnPas();
    hashCastle();

    GameBoard.history[GameBoard.hisPly].move = move;
    GameBoard.history[GameBoard.hisPly].fiftyMove = GameBoard.fiftyMove;
    GameBoard.history[GameBoard.hisPly].enPas = GameBoard.enPas;
    GameBoard.history[GameBoard.hisPly].castlePerm = GameBoard.castlePerm;

    GameBoard.castlePerm &= CastlePerm[from];
    GameBoard.castlePerm &= CastlePerm[to];
    GameBoard.enPas = SQUARES.NO_SQ;

    hashCastle();

    var captured = CAPTURED(move);
    GameBoard.fiftyMove++;

    if (captured != PIECES.EMPTY) {
      ClearPiece(to);
      GameBoard.fiftyMove = 0;
    }

    GameBoard.hisPly++;
    GameBoard.ply++;

    if (PiecePawn[GameBoard.pieces[from]] == true) {
      GameBoard.fiftyMove = 0;
      if ((move & MFLAGPS) != 0) {
        if (side == COLOURS.WHITE) {
          GameBoard.enPas = from + 10;
        } else {
          GameBoard.enPas = from - 10;
        }
        hashEnPas();
      }
    }

    MovePiece(from, to);

    var prPce = PROMOTED(move);
    if (prPce != PIECES.EMPTY) {
      ClearPiece(to);
      AddPiece(to, prPce);
    }

    GameBoard.side ^= 1;
    hashSide();

    if (
      SqAttacked(GameBoard.pList[getPieceIndex(Kings[side], 0)], GameBoard.side)
    ) {
      TakeMove();
      return false;
    }

    return true;
  }

  function TakeMove() {
    GameBoard.hisPly--;
    GameBoard.ply--;

    var move = GameBoard.history[GameBoard.hisPly].move;
    var from = fromSQ(move);
    var to = toSQ(move);

    if (GameBoard.enPas != SQUARES.NO_SQ) hashEnPas();
    hashCastle();

    GameBoard.castlePerm = GameBoard.history[GameBoard.hisPly].castlePerm;
    GameBoard.fiftyMove = GameBoard.history[GameBoard.hisPly].fiftyMove;
    GameBoard.enPas = GameBoard.history[GameBoard.hisPly].enPas;

    if (GameBoard.enPas != SQUARES.NO_SQ) hashEnPas();
    hashCastle();

    GameBoard.side ^= 1;
    hashSide();

    if ((MFLAGEP & move) != 0) {
      if (GameBoard.side == COLOURS.WHITE) {
        AddPiece(to - 10, PIECES.bP);
      } else {
        AddPiece(to + 10, PIECES.wP);
      }
    } else if ((MFLAGCA & move) != 0) {
      switch (to) {
        case SQUARES.C1:
          MovePiece(SQUARES.D1, SQUARES.A1);
          break;
        case SQUARES.C8:
          MovePiece(SQUARES.D8, SQUARES.A8);
          break;
        case SQUARES.G1:
          MovePiece(SQUARES.F1, SQUARES.H1);
          break;
        case SQUARES.G8:
          MovePiece(SQUARES.F8, SQUARES.H8);
          break;
        default:
          break;
      }
    }

    MovePiece(to, from);

    var captured = CAPTURED(move);
    if (captured != PIECES.EMPTY) {
      AddPiece(to, captured);
    }

    if (PROMOTED(move) != PIECES.EMPTY) {
      ClearPiece(from);
      AddPiece(
        from,
        PieceCol[PROMOTED(move)] == COLOURS.WHITE ? PIECES.wP : PIECES.bP
      );
    }
  }

  function ThreeFoldRep() {
    var i = 0,
      r = 0;

    for (i = 0; i < GameBoard.hisPly; ++i) {
      if (GameBoard.history[i].posKey == GameBoard.posKey) {
        r++;
      }
    }
    return r;
  }

  function DrawMaterial() {
    if (GameBoard.pceNum[PIECES.wP] != 0 || GameBoard.pceNum[PIECES.bP] != 0)
      return false;
    if (
      GameBoard.pceNum[PIECES.wQ] != 0 ||
      GameBoard.pceNum[PIECES.bQ] != 0 ||
      GameBoard.pceNum[PIECES.wR] != 0 ||
      GameBoard.pceNum[PIECES.bR] != 0
    )
      return false;
    if (GameBoard.pceNum[PIECES.wB] > 1 || GameBoard.pceNum[PIECES.bB] > 1) {
      return false;
    }
    if (GameBoard.pceNum[PIECES.wN] > 1 || GameBoard.pceNum[PIECES.bN] > 1) {
      return false;
    }

    if (GameBoard.pceNum[PIECES.wN] != 0 && GameBoard.pceNum[PIECES.wB] != 0) {
      return false;
    }
    if (GameBoard.pceNum[PIECES.bN] != 0 && GameBoard.pceNum[PIECES.bB] != 0) {
      return false;
    }

    return true;
  }

  /****************************\
   ============================
   
    Move Gen
   ============================              
  \****************************/

  var MvvLvaValue = [
    0, 100, 200, 300, 400, 500, 600, 100, 200, 300, 400, 500, 600,
  ];
  var MvvLvaScores = new Array(14 * 14);

  function InitMvvLva() {
    var Attacker;
    var Victim;

    for (Attacker = PIECES.wP; Attacker <= PIECES.bK; ++Attacker) {
      for (Victim = PIECES.wP; Victim <= PIECES.bK; ++Victim) {
        MvvLvaScores[Victim * 14 + Attacker] =
          MvvLvaValue[Victim] + 6 - MvvLvaValue[Attacker] / 100;
      }
    }
  }

  function MoveExists(move) {
    GenerateMoves();

    var index;
    var moveFound = NOMOVE;
    for (
      index = GameBoard.moveListStart[GameBoard.ply];
      index < GameBoard.moveListStart[GameBoard.ply + 1];
      ++index
    ) {
      moveFound = GameBoard.moveList[index];
      if (MakeMove(moveFound) == false) {
        continue;
      }
      TakeMove();
      if (move == moveFound) {
        return true;
      }
    }
    return false;
  }

  function MOVE(from, to, captured, promoted, flag) {
    return from | (to << 7) | (captured << 14) | (promoted << 20) | flag;
  }

  function AddCaptureMove(move) {
    GameBoard.moveList[GameBoard.moveListStart[GameBoard.ply + 1]] = move;
    GameBoard.moveScores[GameBoard.moveListStart[GameBoard.ply + 1]++] =
      MvvLvaScores[CAPTURED(move) * 14 + GameBoard.pieces[fromSQ(move)]] +
      1000000;
  }

  function AddQuietMove(move) {
    GameBoard.moveList[GameBoard.moveListStart[GameBoard.ply + 1]] = move;
    GameBoard.moveScores[GameBoard.moveListStart[GameBoard.ply + 1]] = 0;

    if (move == GameBoard.searchKillers[GameBoard.ply]) {
      GameBoard.moveScores[GameBoard.moveListStart[GameBoard.ply + 1]] = 900000;
    } else if (move == GameBoard.searchKillers[GameBoard.ply + MAXDEPTH]) {
      GameBoard.moveScores[GameBoard.moveListStart[GameBoard.ply + 1]] = 800000;
    } else {
      GameBoard.moveScores[GameBoard.moveListStart[GameBoard.ply + 1]] =
        GameBoard.searchHistory[
        GameBoard.pieces[fromSQ(move)] * BRD_SQ_NUM + toSQ(move)
        ];
    }

    GameBoard.moveListStart[GameBoard.ply + 1]++;
  }

  function AddEnPassantMove(move) {
    GameBoard.moveList[GameBoard.moveListStart[GameBoard.ply + 1]] = move;
    GameBoard.moveScores[GameBoard.moveListStart[GameBoard.ply + 1]++] =
      105 + 1000000;
  }

  function AddWhitePawnCaptureMove(from, to, cap) {
    if (RanksBrd[from] == RANKS.RANK_7) {
      AddCaptureMove(MOVE(from, to, cap, PIECES.wQ, 0));
      AddCaptureMove(MOVE(from, to, cap, PIECES.wR, 0));
      AddCaptureMove(MOVE(from, to, cap, PIECES.wB, 0));
      AddCaptureMove(MOVE(from, to, cap, PIECES.wN, 0));
    } else {
      AddCaptureMove(MOVE(from, to, cap, PIECES.EMPTY, 0));
    }
  }

  function AddBlackPawnCaptureMove(from, to, cap) {
    if (RanksBrd[from] == RANKS.RANK_2) {
      AddCaptureMove(MOVE(from, to, cap, PIECES.bQ, 0));
      AddCaptureMove(MOVE(from, to, cap, PIECES.bR, 0));
      AddCaptureMove(MOVE(from, to, cap, PIECES.bB, 0));
      AddCaptureMove(MOVE(from, to, cap, PIECES.bN, 0));
    } else {
      AddCaptureMove(MOVE(from, to, cap, PIECES.EMPTY, 0));
    }
  }

  function AddWhitePawnQuietMove(from, to) {
    if (RanksBrd[from] == RANKS.RANK_7) {
      AddQuietMove(MOVE(from, to, PIECES.EMPTY, PIECES.wQ, 0));
      AddQuietMove(MOVE(from, to, PIECES.EMPTY, PIECES.wR, 0));
      AddQuietMove(MOVE(from, to, PIECES.EMPTY, PIECES.wB, 0));
      AddQuietMove(MOVE(from, to, PIECES.EMPTY, PIECES.wN, 0));
    } else {
      AddQuietMove(MOVE(from, to, PIECES.EMPTY, PIECES.EMPTY, 0));
    }
  }

  function AddBlackPawnQuietMove(from, to) {
    if (RanksBrd[from] == RANKS.RANK_2) {
      AddQuietMove(MOVE(from, to, PIECES.EMPTY, PIECES.bQ, 0));
      AddQuietMove(MOVE(from, to, PIECES.EMPTY, PIECES.bR, 0));
      AddQuietMove(MOVE(from, to, PIECES.EMPTY, PIECES.bB, 0));
      AddQuietMove(MOVE(from, to, PIECES.EMPTY, PIECES.bN, 0));
    } else {
      AddQuietMove(MOVE(from, to, PIECES.EMPTY, PIECES.EMPTY, 0));
    }
  }

  function GenerateMoves() {
    GameBoard.moveListStart[GameBoard.ply + 1] =
      GameBoard.moveListStart[GameBoard.ply];

    var pceType;
    var pceNum;
    var sq;
    var pceIndex;
    var pce;
    var t_sq;
    var dir;

    if (GameBoard.side == COLOURS.WHITE) {
      pceType = PIECES.wP;
      for (pceNum = 0; pceNum < GameBoard.pceNum[pceType]; ++pceNum) {
        sq = GameBoard.pList[getPieceIndex(pceType, pceNum)];
        if (GameBoard.pieces[sq + 10] == PIECES.EMPTY) {
          AddWhitePawnQuietMove(sq, sq + 10);
          if (
            RanksBrd[sq] == RANKS.RANK_2 &&
            GameBoard.pieces[sq + 20] == PIECES.EMPTY
          ) {
            AddQuietMove(
              MOVE(sq, sq + 20, PIECES.EMPTY, PIECES.EMPTY, MFLAGPS)
            );
          }
        }

        if (
          isSqOffBoard(sq + 9) == false &&
          PieceCol[GameBoard.pieces[sq + 9]] == COLOURS.BLACK
        ) {
          AddWhitePawnCaptureMove(sq, sq + 9, GameBoard.pieces[sq + 9]);
        }

        if (
          isSqOffBoard(sq + 11) == false &&
          PieceCol[GameBoard.pieces[sq + 11]] == COLOURS.BLACK
        ) {
          AddWhitePawnCaptureMove(sq, sq + 11, GameBoard.pieces[sq + 11]);
        }

        if (GameBoard.enPas != SQUARES.NO_SQ) {
          if (sq + 9 == GameBoard.enPas) {
            AddEnPassantMove(
              MOVE(sq, sq + 9, PIECES.EMPTY, PIECES.EMPTY, MFLAGEP)
            );
          }

          if (sq + 11 == GameBoard.enPas) {
            AddEnPassantMove(
              MOVE(sq, sq + 11, PIECES.EMPTY, PIECES.EMPTY, MFLAGEP)
            );
          }
        }
      }

      if (GameBoard.castlePerm & CASTLEBIT.whiteKing) {
        if (
          GameBoard.pieces[SQUARES.F1] == PIECES.EMPTY &&
          GameBoard.pieces[SQUARES.G1] == PIECES.EMPTY
        ) {
          if (
            SqAttacked(SQUARES.F1, COLOURS.BLACK) == false &&
            SqAttacked(SQUARES.E1, COLOURS.BLACK) == false
          ) {
            AddQuietMove(
              MOVE(SQUARES.E1, SQUARES.G1, PIECES.EMPTY, PIECES.EMPTY, MFLAGCA)
            );
          }
        }
      }

      if (GameBoard.castlePerm & CASTLEBIT.whiteQueen) {
        if (
          GameBoard.pieces[SQUARES.D1] == PIECES.EMPTY &&
          GameBoard.pieces[SQUARES.C1] == PIECES.EMPTY &&
          GameBoard.pieces[SQUARES.B1] == PIECES.EMPTY
        ) {
          if (
            SqAttacked(SQUARES.D1, COLOURS.BLACK) == false &&
            SqAttacked(SQUARES.E1, COLOURS.BLACK) == false
          ) {
            AddQuietMove(
              MOVE(SQUARES.E1, SQUARES.C1, PIECES.EMPTY, PIECES.EMPTY, MFLAGCA)
            );
          }
        }
      }
    } else {
      pceType = PIECES.bP;

      for (pceNum = 0; pceNum < GameBoard.pceNum[pceType]; ++pceNum) {
        sq = GameBoard.pList[getPieceIndex(pceType, pceNum)];
        if (GameBoard.pieces[sq - 10] == PIECES.EMPTY) {
          AddBlackPawnQuietMove(sq, sq - 10);
          if (
            RanksBrd[sq] == RANKS.RANK_7 &&
            GameBoard.pieces[sq - 20] == PIECES.EMPTY
          ) {
            AddQuietMove(
              MOVE(sq, sq - 20, PIECES.EMPTY, PIECES.EMPTY, MFLAGPS)
            );
          }
        }

        if (
          isSqOffBoard(sq - 9) == false &&
          PieceCol[GameBoard.pieces[sq - 9]] == COLOURS.WHITE
        ) {
          AddBlackPawnCaptureMove(sq, sq - 9, GameBoard.pieces[sq - 9]);
        }

        if (
          isSqOffBoard(sq - 11) == false &&
          PieceCol[GameBoard.pieces[sq - 11]] == COLOURS.WHITE
        ) {
          AddBlackPawnCaptureMove(sq, sq - 11, GameBoard.pieces[sq - 11]);
        }

        if (GameBoard.enPas != SQUARES.NO_SQ) {
          if (sq - 9 == GameBoard.enPas) {
            AddEnPassantMove(
              MOVE(sq, sq - 9, PIECES.EMPTY, PIECES.EMPTY, MFLAGEP)
            );
          }

          if (sq - 11 == GameBoard.enPas) {
            AddEnPassantMove(
              MOVE(sq, sq - 11, PIECES.EMPTY, PIECES.EMPTY, MFLAGEP)
            );
          }
        }
      }
      if (GameBoard.castlePerm & CASTLEBIT.blackKing) {
        if (
          GameBoard.pieces[SQUARES.F8] == PIECES.EMPTY &&
          GameBoard.pieces[SQUARES.G8] == PIECES.EMPTY
        ) {
          if (
            SqAttacked(SQUARES.F8, COLOURS.WHITE) == false &&
            SqAttacked(SQUARES.E8, COLOURS.WHITE) == false
          ) {
            AddQuietMove(
              MOVE(SQUARES.E8, SQUARES.G8, PIECES.EMPTY, PIECES.EMPTY, MFLAGCA)
            );
          }
        }
      }

      if (GameBoard.castlePerm & CASTLEBIT.blackQueen) {
        if (
          GameBoard.pieces[SQUARES.D8] == PIECES.EMPTY &&
          GameBoard.pieces[SQUARES.C8] == PIECES.EMPTY &&
          GameBoard.pieces[SQUARES.B8] == PIECES.EMPTY
        ) {
          if (
            SqAttacked(SQUARES.D8, COLOURS.WHITE) == false &&
            SqAttacked(SQUARES.E8, COLOURS.WHITE) == false
          ) {
            AddQuietMove(
              MOVE(SQUARES.E8, SQUARES.C8, PIECES.EMPTY, PIECES.EMPTY, MFLAGCA)
            );
          }
        }
      }
    }

    pceIndex = LoopNonSlideIndex[GameBoard.side];
    pce = LoopNonSlidePce[pceIndex++];

    while (pce != 0) {
      for (pceNum = 0; pceNum < GameBoard.pceNum[pce]; ++pceNum) {
        sq = GameBoard.pList[getPieceIndex(pce, pceNum)];

        for (index = 0; index < DirNum[pce]; index++) {
          dir = PceDir[pce][index];
          t_sq = sq + dir;

          if (isSqOffBoard(t_sq) == true) {
            continue;
          }

          if (GameBoard.pieces[t_sq] != PIECES.EMPTY) {
            if (PieceCol[GameBoard.pieces[t_sq]] != GameBoard.side) {
              AddCaptureMove(
                MOVE(sq, t_sq, GameBoard.pieces[t_sq], PIECES.EMPTY, 0)
              );
            }
          } else {
            AddQuietMove(MOVE(sq, t_sq, PIECES.EMPTY, PIECES.EMPTY, 0));
          }
        }
      }
      pce = LoopNonSlidePce[pceIndex++];
    }

    pceIndex = LoopSlideIndex[GameBoard.side];
    pce = LoopSlidePce[pceIndex++];

    while (pce != 0) {
      for (pceNum = 0; pceNum < GameBoard.pceNum[pce]; ++pceNum) {
        sq = GameBoard.pList[getPieceIndex(pce, pceNum)];

        for (index = 0; index < DirNum[pce]; index++) {
          dir = PceDir[pce][index];
          t_sq = sq + dir;

          while (isSqOffBoard(t_sq) == false) {
            if (GameBoard.pieces[t_sq] != PIECES.EMPTY) {
              if (PieceCol[GameBoard.pieces[t_sq]] != GameBoard.side) {
                AddCaptureMove(
                  MOVE(sq, t_sq, GameBoard.pieces[t_sq], PIECES.EMPTY, 0)
                );
              }
              break;
            }
            AddQuietMove(MOVE(sq, t_sq, PIECES.EMPTY, PIECES.EMPTY, 0));
            t_sq += dir;
          }
        }
      }
      pce = LoopSlidePce[pceIndex++];
    }
  }

  function GenerateCaptures() {
    GameBoard.moveListStart[GameBoard.ply + 1] =
      GameBoard.moveListStart[GameBoard.ply];

    var pceType;
    var pceNum;
    var sq;
    var pceIndex;
    var pce;
    var t_sq;
    var dir;

    if (GameBoard.side == COLOURS.WHITE) {
      pceType = PIECES.wP;

      for (pceNum = 0; pceNum < GameBoard.pceNum[pceType]; ++pceNum) {
        sq = GameBoard.pList[getPieceIndex(pceType, pceNum)];

        if (
          isSqOffBoard(sq + 9) == false &&
          PieceCol[GameBoard.pieces[sq + 9]] == COLOURS.BLACK
        ) {
          AddWhitePawnCaptureMove(sq, sq + 9, GameBoard.pieces[sq + 9]);
        }

        if (
          isSqOffBoard(sq + 11) == false &&
          PieceCol[GameBoard.pieces[sq + 11]] == COLOURS.BLACK
        ) {
          AddWhitePawnCaptureMove(sq, sq + 11, GameBoard.pieces[sq + 11]);
        }

        if (GameBoard.enPas != SQUARES.NO_SQ) {
          if (sq + 9 == GameBoard.enPas) {
            AddEnPassantMove(
              MOVE(sq, sq + 9, PIECES.EMPTY, PIECES.EMPTY, MFLAGEP)
            );
          }

          if (sq + 11 == GameBoard.enPas) {
            AddEnPassantMove(
              MOVE(sq, sq + 11, PIECES.EMPTY, PIECES.EMPTY, MFLAGEP)
            );
          }
        }
      }
    } else {
      pceType = PIECES.bP;

      for (pceNum = 0; pceNum < GameBoard.pceNum[pceType]; ++pceNum) {
        sq = GameBoard.pList[getPieceIndex(pceType, pceNum)];

        if (
          isSqOffBoard(sq - 9) == false &&
          PieceCol[GameBoard.pieces[sq - 9]] == COLOURS.WHITE
        ) {
          AddBlackPawnCaptureMove(sq, sq - 9, GameBoard.pieces[sq - 9]);
        }

        if (
          isSqOffBoard(sq - 11) == false &&
          PieceCol[GameBoard.pieces[sq - 11]] == COLOURS.WHITE
        ) {
          AddBlackPawnCaptureMove(sq, sq - 11, GameBoard.pieces[sq - 11]);
        }

        if (GameBoard.enPas != SQUARES.NO_SQ) {
          if (sq - 9 == GameBoard.enPas) {
            AddEnPassantMove(
              MOVE(sq, sq - 9, PIECES.EMPTY, PIECES.EMPTY, MFLAGEP)
            );
          }

          if (sq - 11 == GameBoard.enPas) {
            AddEnPassantMove(
              MOVE(sq, sq - 11, PIECES.EMPTY, PIECES.EMPTY, MFLAGEP)
            );
          }
        }
      }
    }

    pceIndex = LoopNonSlideIndex[GameBoard.side];
    pce = LoopNonSlidePce[pceIndex++];

    while (pce != 0) {
      for (pceNum = 0; pceNum < GameBoard.pceNum[pce]; ++pceNum) {
        sq = GameBoard.pList[getPieceIndex(pce, pceNum)];

        for (index = 0; index < DirNum[pce]; index++) {
          dir = PceDir[pce][index];
          t_sq = sq + dir;

          if (isSqOffBoard(t_sq) == true) {
            continue;
          }

          if (GameBoard.pieces[t_sq] != PIECES.EMPTY) {
            if (PieceCol[GameBoard.pieces[t_sq]] != GameBoard.side) {
              AddCaptureMove(
                MOVE(sq, t_sq, GameBoard.pieces[t_sq], PIECES.EMPTY, 0)
              );
            }
          }
        }
      }
      pce = LoopNonSlidePce[pceIndex++];
    }

    pceIndex = LoopSlideIndex[GameBoard.side];
    pce = LoopSlidePce[pceIndex++];

    while (pce != 0) {
      for (pceNum = 0; pceNum < GameBoard.pceNum[pce]; ++pceNum) {
        sq = GameBoard.pList[getPieceIndex(pce, pceNum)];

        for (index = 0; index < DirNum[pce]; index++) {
          dir = PceDir[pce][index];
          t_sq = sq + dir;

          while (isSqOffBoard(t_sq) == false) {
            if (GameBoard.pieces[t_sq] != PIECES.EMPTY) {
              if (PieceCol[GameBoard.pieces[t_sq]] != GameBoard.side) {
                AddCaptureMove(
                  MOVE(sq, t_sq, GameBoard.pieces[t_sq], PIECES.EMPTY, 0)
                );
              }
              break;
            }
            t_sq += dir;
          }
        }
      }
      pce = LoopSlidePce[pceIndex++];
    }
  }

  /****************************\
   ============================
   
    Evaluation
   ============================              
  \****************************/

  // prettier-ignore
  {

    var mg_value = {
      wP: 82, bP: 82,
      wN: 337, bN: 337,
      wB: 365, bB: 365,
      wR: 477, bR: 477,
      wQ: 1025, bQ: 1025,
      wK: 50000, bK: 50000,
    }

    var eg_value = {
      wP: 94, bP: 94,
      wN: 281, bN: 281,
      wB: 297, bB: 297,
      wR: 512, bR: 512,
      wQ: 936, bQ: 936,
      wK: 50000, bK: 50000,
    }

    mg_pawn_table = [
      0, 0, 0, 0, 0, 0, 0, 0,
      98, 134, 61, 95, 68, 126, 34, -11,
      -6, 7, 26, 31, 65, 56, 25, -20,
      -14, 13, 6, 21, 23, 12, 17, -23,
      -27, -2, -5, 12, 17, 6, 10, -25,
      -26, -4, -4, -10, 3, 3, 33, -12,
      -35, -1, -20, -23, -15, 24, 38, -22,
      0, 0, 0, 0, 0, 0, 0, 0,
    ];

    eg_pawn_table = [
      0, 0, 0, 0, 0, 0, 0, 0,
      178, 173, 158, 134, 147, 132, 165, 187,
      94, 100, 85, 67, 56, 53, 82, 84,
      32, 24, 13, 5, -2, 4, 17, 17,
      13, 9, -3, -7, -7, -8, 3, -1,
      4, 7, -6, 1, 0, -5, -1, -8,
      13, 8, 8, 10, 13, 0, 2, -7,
      0, 0, 0, 0, 0, 0, 0, 0,
    ];

    mg_knight_table = [
      -167, -89, -34, -49, 61, -97, -15, -107,
      -73, -41, 72, 36, 23, 62, 7, -17,
      -47, 60, 37, 65, 84, 129, 73, 44,
      -9, 17, 19, 53, 37, 69, 18, 22,
      -13, 4, 16, 13, 28, 19, 21, -8,
      -23, -9, 12, 10, 19, 17, 25, -16,
      -29, -53, -12, -3, -1, 18, -14, -19,
      -105, -21, -58, -33, -17, -28, -19, -23,
    ];

    eg_knight_table = [
      -58, -38, -13, -28, -31, -27, -63, -99,
      -25, -8, -25, -2, -9, -25, -24, -52,
      -24, -20, 10, 9, -1, -9, -19, -41,
      -17, 3, 22, 22, 22, 11, 8, -18,
      -18, -6, 16, 25, 16, 17, 4, -18,
      -23, -3, -1, 15, 10, -3, -20, -22,
      -42, -20, -10, -5, -2, -20, -23, -44,
      -29, -51, -23, -15, -22, -18, -50, -64,
    ];

    mg_bishop_table = [
      -29, 4, -82, -37, -25, -42, 7, -8,
      -26, 16, -18, -13, 30, 59, 18, -47,
      -16, 37, 43, 40, 35, 50, 37, -2,
      -4, 5, 19, 50, 37, 37, 7, -2,
      -6, 13, 13, 26, 34, 12, 10, 4,
      0, 15, 15, 15, 14, 27, 18, 10,
      4, 15, 16, 0, 7, 21, 33, 1,
      -33, -3, -14, -21, -13, -12, -39, -21,
    ];

    eg_bishop_table = [
      -14, -21, -11, -8, -7, -9, -17, -24,
      -8, -4, 7, -12, -3, -13, -4, -14,
      2, -8, 0, -1, -2, 6, 0, 4,
      -3, 9, 12, 9, 14, 10, 3, 2,
      -6, 3, 13, 19, 7, 10, -3, -9,
      -12, -3, 8, 10, 13, 3, -7, -15,
      -14, -18, -7, -1, 4, -9, -15, -27,
      -23, -9, -23, -5, -9, -16, -5, -17,
    ];

    mg_rook_table = [
      32, 42, 32, 51, 63, 9, 31, 43,
      27, 32, 58, 62, 80, 67, 26, 44,
      -5, 19, 26, 36, 17, 45, 61, 16,
      -24, -11, 7, 26, 24, 35, -8, -20,
      -36, -26, -12, -1, 9, -7, 6, -23,
      -45, -25, -16, -17, 3, 0, -5, -33,
      -44, -16, -20, -9, -1, 11, -6, -71,
      -19, -13, 1, 17, 16, 7, -37, -26,
    ];

    eg_rook_table = [
      13, 10, 18, 15, 12, 12, 8, 5,
      11, 13, 13, 11, -3, 3, 8, 3,
      7, 7, 7, 5, 4, -3, -5, -3,
      4, 3, 13, 1, 2, 1, -1, 2,
      3, 5, 8, 4, -5, -6, -8, -11,
      -4, 0, -5, -1, -7, -12, -8, -16,
      -6, -6, 0, 2, -9, -9, -11, -3,
      -9, 2, 3, -1, -5, -13, 4, -20,
    ];

    mg_queen_table = [
      -28, 0, 29, 12, 59, 44, 43, 45,
      -24, -39, -5, 1, -16, 57, 28, 54,
      -13, -17, 7, 8, 29, 56, 47, 57,
      -27, -27, -16, -16, -1, 17, -2, 1,
      -9, -26, -9, -10, -2, -4, 3, -3,
      -14, 2, -11, -2, -5, 2, 14, 5,
      -35, -8, 11, 2, 8, 15, -3, 1,
      -1, -18, -9, 10, -15, -25, -31, -50,
    ];

    eg_queen_table = [
      -9, 22, 22, 27, 27, 19, 10, 20,
      -17, 20, 32, 41, 58, 25, 30, 0,
      -20, 6, 9, 49, 47, 35, 19, 9,
      3, 22, 24, 45, 57, 40, 57, 36,
      -18, 28, 19, 47, 31, 34, 39, 23,
      -16, -27, 15, 6, 9, 17, 10, 5,
      -22, -23, -30, -16, -16, -23, -36, -32,
      -33, -28, -22, -43, -5, -32, -20, -41,
    ];

    mg_king_table = [
      -65, 23, 16, -15, -56, -34, 2, 13,
      29, -1, -20, -7, -8, -4, -38, -29,
      -9, 24, 2, -16, -20, 6, 22, -22,
      -17, -20, -12, -27, -30, -25, -14, -36,
      -49, -1, -27, -39, -46, -44, -33, -51,
      -14, -14, -22, -46, -44, -30, -15, -27,
      1, 7, -8, -64, -43, -16, 9, 8,
      -15, 36, 12, -54, 8, -28, 24, 14,
    ];

    eg_king_table = [
      -74, -35, -18, -18, -11, 15, 4, -17,
      -12, 17, 14, 17, 17, 38, 23, 11,
      10, 17, 23, 15, 20, 45, 44, 13,
      -8, 22, 24, 27, 26, 33, 26, 3,
      -18, -4, 21, 24, 27, 23, 9, -11,
      -19, -3, 11, 21, 23, 16, 7, -9,
      -27, -11, 4, 13, 14, 4, -5, -17,
      -53, -34, -21, -11, -28, -14, -24, -43
    ];
  }

  const BishopPair = 40;

  var mg_pesto_table = {
    wP: mg_pawn_table,
    bP: mg_pawn_table,
    wN: mg_knight_table,
    bN: mg_knight_table,
    wB: mg_bishop_table,
    bB: mg_bishop_table,
    wR: mg_rook_table,
    bR: mg_rook_table,
    wQ: mg_queen_table,
    bQ: mg_queen_table,
    wK: mg_king_table,
    bK: mg_king_table,
  };

  var eg_pesto_table = {
    wP: eg_pawn_table,
    bP: eg_pawn_table,
    wN: eg_knight_table,
    bN: eg_knight_table,
    wB: eg_bishop_table,
    bB: eg_bishop_table,
    wR: eg_rook_table,
    bR: eg_rook_table,
    wQ: eg_queen_table,
    bQ: eg_queen_table,
    wK: eg_king_table,
    bK: eg_king_table,
  };

  // wP - bP - wN - bN - wB - bB - wR - bR - wQ - bQ - wK - bK
  var gamephaseInc = {
    wP: 0,
    bP: 0,
    wN: 1,
    bN: 1,
    wB: 1,
    bB: 1,
    wR: 2,
    bR: 2,
    wQ: 4,
    bQ: 4,
    wK: 0,
    bK: 0,
  };

  // initTables()

  function EvalPosition() {
    let gamePhase = 0;
    // var score =
    //   GameBoard.material[COLOURS.WHITE] - GameBoard.material[COLOURS.BLACK];

    let mg_score = 0;
    let eg_score = 0;

    for (pce in PIECES) {
      for (pceNum = 0; pceNum < GameBoard.pceNum[PIECES[pce]]; pceNum++) {
        sq = GameBoard.pList[getPieceIndex(PIECES[pce], pceNum)];
        if (pce[0] == "w") {
          // score += table[pce][sq120to64(sq)];
          mg_score +=
            mg_value[pce] + mg_pesto_table[pce][mirror64(sq120to64(sq))];
          eg_score +=
            eg_value[pce] + eg_pesto_table[pce][mirror64(sq120to64(sq))];
          gamePhase += gamephaseInc[pce];
        } else {
          // score -= table[pce][(mirror64(sq120to64(sq)))];
          mg_score -= mg_value[pce] + mg_pesto_table[pce][sq120to64(sq)];
          eg_score -= eg_value[pce] + eg_pesto_table[pce][sq120to64(sq)];
          gamePhase += gamephaseInc[pce];
        }
      }
    }

    mg_phase = gamePhase;
    eg_phase = 24 - gamePhase;

    score = (mg_score * mg_phase + eg_score * eg_phase) / 24;

    if (GameBoard.side == COLOURS.WHITE) {
      return score;
    } else {
      return -score;
    }
  }

  /****************************\
   ============================
   
    Search
   ============================              
  \****************************/

  function GetPvLine(depth) {
    var move = ProbePvTable();
    var count = 0;

    while (move != NOMOVE && count < depth) {
      if (MoveExists(move) == true) {
        MakeMove(move);
        GameBoard.PvArray[count++] = move;
      } else {
        break;
      }
      move = ProbePvTable();
    }

    while (GameBoard.ply > 0) {
      TakeMove();
    }

    return count;
  }

  function ProbePvTable() {
    var index = GameBoard.posKey % PVENTRIES;

    if (GameBoard.PvTable[index].posKey == GameBoard.posKey) {
      return GameBoard.PvTable[index].move;
    }

    return NOMOVE;
  }

  function StorePvMove(move) {
    var index = GameBoard.posKey % PVENTRIES;
    GameBoard.PvTable[index].posKey = GameBoard.posKey;
    GameBoard.PvTable[index].move = move;
  }

  var SearchController = {};

  SearchController.nodes;
  SearchController.fh;
  SearchController.fhf;
  SearchController.depth;
  SearchController.time = 1000;
  SearchController.start;
  SearchController.stop;
  SearchController.best;
  SearchController.thinking;
  SearchController.endgame;

  function PickNextMove(MoveNum) {
    var index = 0;
    var bestScore = -1;
    var bestNum = MoveNum;

    for (
      index = MoveNum;
      index < GameBoard.moveListStart[GameBoard.ply + 1];
      ++index
    ) {
      if (GameBoard.moveScores[index] > bestScore) {
        bestScore = GameBoard.moveScores[index];
        bestNum = index;
      }
    }

    if (bestNum != MoveNum) {
      var temp = 0;
      temp = GameBoard.moveScores[MoveNum];
      GameBoard.moveScores[MoveNum] = GameBoard.moveScores[bestNum];
      GameBoard.moveScores[bestNum] = temp;

      temp = GameBoard.moveList[MoveNum];
      GameBoard.moveList[MoveNum] = GameBoard.moveList[bestNum];
      GameBoard.moveList[bestNum] = temp;
    }
  }

  function ClearPvTable() {
    for (index = 0; index < PVENTRIES; index++) {
      GameBoard.PvTable[index].move = NOMOVE;
      GameBoard.PvTable[index].posKey = 0;
    }
  }

  function CheckUp() {
    if (performance.now() - SearchController.start > SearchController.time) {
      SearchController.stop = true;
    }
  }

  function IsRepetition() {
    var index = 0;

    for (
      index = GameBoard.hisPly - GameBoard.fiftyMove;
      index < GameBoard.hisPly - 1;
      ++index
    ) {
      if (GameBoard.posKey == GameBoard.history[index].posKey) {
        return true;
      }
    }

    return false;
  }

  function Quiescence(alpha, beta) {
    if ((SearchController.nodes & 2047) == 0) {
      CheckUp();
    }

    SearchController.nodes++;

    if ((IsRepetition() || GameBoard.fiftyMove >= 100) && GameBoard.ply != 0) {
      return 0;
    }

    if (GameBoard.ply > MAXDEPTH - 1) {
      return EvalPosition();
    }

    var Score = EvalPosition();

    if (Score >= beta) {
      return beta;
    }

    if (Score > alpha) {
      alpha = Score;
    }

    GenerateCaptures();

    var MoveNum = 0;
    var Legal = 0;
    var OldAlpha = alpha;
    var BestMove = NOMOVE;
    var Move = NOMOVE;

    for (
      MoveNum = GameBoard.moveListStart[GameBoard.ply];
      MoveNum < GameBoard.moveListStart[GameBoard.ply + 1];
      ++MoveNum
    ) {
      PickNextMove(MoveNum);

      Move = GameBoard.moveList[MoveNum];

      if (MakeMove(Move) == false) {
        continue;
      }
      Legal++;
      Score = -Quiescence(-beta, -alpha);

      TakeMove();

      if (SearchController.stop == true) {
        return 0;
      }

      if (Score > alpha) {
        if (Score >= beta) {
          if (Legal == 1) {
            SearchController.fhf++;
          }
          SearchController.fh++;
          return beta;
        }
        alpha = Score;
        BestMove = Move;
      }
    }

    if (alpha != OldAlpha) {
      StorePvMove(BestMove);
    }

    return alpha;
  }

  function AlphaBeta(alpha, beta, depth) {
    if (depth <= 0) {
      return Quiescence(alpha, beta);
    }

    if ((SearchController.nodes & 2047) == 0) {
      CheckUp();
    }

    SearchController.nodes++;

    if ((IsRepetition() || GameBoard.fiftyMove >= 100) && GameBoard.ply != 0) {
      return 0;
    }

    if (GameBoard.ply > MAXDEPTH - 1) {
      return EvalPosition();
    }

    var InCheck = SqAttacked(
      GameBoard.pList[getPieceIndex(Kings[GameBoard.side], 0)],
      GameBoard.side ^ 1
    );
    if (InCheck == true) {
      depth++;
    }

    var Score = -INFINITE;

    GenerateMoves();

    var MoveNum = 0;
    var Legal = 0;
    var OldAlpha = alpha;
    var BestMove = NOMOVE;
    var Move = NOMOVE;

    var PvMove = ProbePvTable();
    if (PvMove != NOMOVE) {
      for (
        MoveNum = GameBoard.moveListStart[GameBoard.ply];
        MoveNum < GameBoard.moveListStart[GameBoard.ply + 1];
        ++MoveNum
      ) {
        if (GameBoard.moveList[MoveNum] == PvMove) {
          GameBoard.moveScores[MoveNum] = 2000000;
          break;
        }
      }
    }

    for (
      MoveNum = GameBoard.moveListStart[GameBoard.ply];
      MoveNum < GameBoard.moveListStart[GameBoard.ply + 1];
      ++MoveNum
    ) {
      PickNextMove(MoveNum);

      Move = GameBoard.moveList[MoveNum];

      if (MakeMove(Move) == false) {
        continue;
      }
      Legal++;
      Score = -AlphaBeta(-beta, -alpha, depth - 1);

      TakeMove();

      if (SearchController.stop == true) {
        return 0;
      }

      if (Score > alpha) {
        if (Score >= beta) {
          if (Legal == 1) {
            SearchController.fhf++;
          }
          SearchController.fh++;
          if ((Move & MFLAGCAP) == 0) {
            GameBoard.searchKillers[MAXDEPTH + GameBoard.ply] =
              GameBoard.searchKillers[GameBoard.ply];
            GameBoard.searchKillers[GameBoard.ply] = Move;
          }
          return beta;
        }
        if ((Move & MFLAGCAP) == 0) {
          GameBoard.searchHistory[
            GameBoard.pieces[fromSQ(Move)] * BRD_SQ_NUM + toSQ(Move)
          ] += depth * depth;
        }
        alpha = Score;
        BestMove = Move;
      }
    }

    if (Legal == 0) {
      if (InCheck == true) {
        return -MATE + GameBoard.ply;
      } else {
        return 0;
      }
    }

    if (alpha != OldAlpha) {
      StorePvMove(BestMove);
    }

    return alpha;
  }

  function CheckEndgame() {
    totalMaterial =
      GameBoard.material[COLOURS.WHITE] + GameBoard.material[COLOURS.BLACK];

    if (totalMaterial < 105000) {
      SearchController.endgame = true;
    } else {
      SearchController.endgame = false;
    }
  }

  function ClearForSearch() {
    var index = 0;

    for (index = 0; index < 14 * BRD_SQ_NUM; ++index) {
      GameBoard.searchHistory[index] = 0;
    }

    for (index = 0; index < 3 * MAXDEPTH; ++index) {
      GameBoard.searchKillers[index] = 0;
    }

    ClearPvTable();
    CheckEndgame();

    GameBoard.ply = 0;
    SearchController.nodes = 0;
    SearchController.fh = 0;
    SearchController.fhf = 0;
    SearchController.start = performance.now();
    SearchController.stop = false;
  }

  function SearchPosition() {
    var bestMove = NOMOVE;
    var bestScore = -INFINITE;
    var currentDepth = 0;
    var line;
    var PvNum;
    var c;
    ClearForSearch();

    for (
      currentDepth = 1;
      currentDepth <= SearchController.depth;
      ++currentDepth
    ) {
      bestScore = AlphaBeta(-INFINITE, INFINITE, currentDepth);

      if (SearchController.stop) {
        break;
      }

      bestMove = ProbePvTable();
      line =
        "D:" +
        currentDepth +
        " Best:" +
        PrMove(bestMove) +
        " Score:" +
        bestScore +
        " nodes:" +
        SearchController.nodes;

      PvNum = GetPvLine(currentDepth);
      line += " Pv:";
      for (c = 0; c < PvNum; ++c) {
        line += " " + PrMove(GameBoard.PvArray[c]);
      }
      if (currentDepth != 1) {
        line +=
          " Ordering:" +
          ((SearchController.fhf / SearchController.fh) * 100).toFixed(2) +
          "%";
      }
      // console.log(line);
    }

    SearchController.best = bestMove;
    SearchController.thinking = false;
  }

  function getBestMove() {
    SearchController.depth = MAXDEPTH;
    SearchPosition();
    return SearchController.best;
  }

  /****************************\
   ============================
   
    Perft Test
   ============================              
  \****************************/

  var perft_leafNodes;

  function Perft(depth) {
    if (depth == 0) {
      perft_leafNodes++;
      return;
    }

    GenerateMoves();

    var index;
    var move;

    for (
      index = GameBoard.moveListStart[GameBoard.ply];
      index < GameBoard.moveListStart[GameBoard.ply + 1];
      ++index
    ) {
      move = GameBoard.moveList[index];
      if (MakeMove(move) == false) {
        continue;
      }
      Perft(depth - 1);
      TakeMove();
    }

    return;
  }

  function PerftTest(depth) {
    PrintBoard();
    console.log("Starting Test To Depth:" + depth);
    perft_leafNodes = 0;

    GenerateMoves();

    var index;
    var move;
    var moveNum = 0;
    for (
      index = GameBoard.moveListStart[GameBoard.ply];
      index < GameBoard.moveListStart[GameBoard.ply + 1];
      ++index
    ) {
      move = GameBoard.moveList[index];
      if (MakeMove(move) == false) {
        continue;
      }
      moveNum++;
      var cumnodes = perft_leafNodes;
      Perft(depth - 1);
      TakeMove();
      var oldnodes = perft_leafNodes - cumnodes;
      console.log("move:" + moveNum + " " + PrMove(move) + " " + oldnodes);
    }

    console.log("Test Complete : " + perft_leafNodes + " leaf nodes visited");

    return;
  }

  function PrMove(move) {
    var MvStr;

    var ff = FilesBrd[fromSQ(move)];
    var rf = RanksBrd[fromSQ(move)];
    var ft = FilesBrd[toSQ(move)];
    var rt = RanksBrd[toSQ(move)];

    MvStr = FileChar[ff] + RankChar[rf] + FileChar[ft] + RankChar[rt];

    var promoted = PROMOTED(move);

    if (promoted != PIECES.EMPTY) {
      var pchar = "q";
      if (PieceKnight[promoted] == true) {
        pchar = "n";
      } else if (
        PieceRookQueen[promoted] == true &&
        PieceBishopQueen[promoted] == false
      ) {
        pchar = "r";
      } else if (
        PieceRookQueen[promoted] == false &&
        PieceBishopQueen[promoted] == true
      ) {
        pchar = "b";
      }
      MvStr += pchar;
    }
    return MvStr;
  }

  function getMoveList() {
    GenerateMoves();
    var index;
    var move;
    moves = [];

    for (
      index = GameBoard.moveListStart[GameBoard.ply];
      index < GameBoard.moveListStart[GameBoard.ply + 1];
      ++index
    ) {
      move = GameBoard.moveList[index];
      from = fromSQ(move);
      to = toSQ(move);
      const parsed = ParseMove(from, to);
      if (parsed !== NOMOVE) {
        moves.push(PrMove(move).slice(0, 4));
      }
    }
    return moves;
  }

  // LETS GO INITALISE!!!
  init();

  /****************************\
   ============================
   
    Public API Methods
   ============================              
  \****************************/

  function getMovesAtSquare(square) {
    const allMoves = getMoveList();
    const movesAtSquare = allMoves.filter((move) =>
      move.slice(0, 2).includes(square)
    );
    const possibleMoves = movesAtSquare.map((move) => move.slice(2, 4));
    return possibleMoves;
  }

  function move(from, to) {
    from = OppositePrSq(from);
    to = OppositePrSq(to);
    const parsed = ParseMove(from, to);
    if (parsed == NOMOVE) return false;
    else {
      MakeMove(parsed);
      return true;
    }
  }

  function makeAIMove() {
    if (gameStatus().over) return false;
    let bestMove = getBestMove();
    MakeMove(bestMove);
  }

  function reset() {
    ParseFen(START_FEN);
  }

  function gameStatus() {
    const sideToMove = GameBoard.side == COLOURS.WHITE ? "white" : "black";
    let over = false;

    if (GameBoard.fiftyMove >= 100) over = "Game is a draw by 50 move rule";
    if (ThreeFoldRep() >= 2) over = "Game drawn by threefold repetition";
    if (DrawMaterial()) over = "Game drawn by insufficient material";

    moves = getMoveList();

    let check = SqAttacked(
      GameBoard.pList[getPieceIndex(Kings[GameBoard.side], 0)],
      GameBoard.side ^ 1
    );

    if (moves.length === 0) {
      if (check) {
        over = "Checkmate!";
      } else {
        over = "Game drawn by stalemate";
      }
    }

    return { over: over, sideToMove: sideToMove, check: check };
  }

  function setThinkingTime(time) {
    SearchController.time = time * 1000;
  }

  /****************************\
   ============================
   
    Public API
   ============================              
  \****************************/

  return {
    getFEN: GenerateFEN,
    setFEN: ParseFen,
    getMovesAtSquare: getMovesAtSquare,
    move: move,
    makeAIMove: makeAIMove,
    getBestMove: getBestMove,
    reset: reset,
    gameStatus: gameStatus,
    setThinkingTime: setThinkingTime,
  };
};