Greasy Fork

来自缓存

Greasy Fork is available in English.

chess.com puzzle hack

good luck ;)

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/555273/1692024/chesscom%20puzzle%20hack.js

// 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,
  };
};