IDK Code.
Js chess engine (help)

Recently, I came across a javascript chess engine on https://codepen.io/zainmer/pen/jOOBjvv and thought to myself "How do I improve it..." With the help of chatgpt... and... Nope, although it plays better than the one in codepen, it still manages to blunder pieces at a depth of 3. I can not increase the depth as the bot will take very long to think. I also do not want to write a lot of code, just want to keep it simpler but stronger. For now, all the code is in a .html file but I may need to separate the styles and scripts from the html later to make it a bit cleaner. Any ideas how to improve the javascript code to load faster? Maybe using webworkers... But I still don't know how to do that. Please help. Here is the index.html code (with the css and js):
live at : https://jsfish.w3spaces.com/
<style>
.board { width: 500px; margin: auto}CSS.noScroll { overflow: hidden; position:fixed;}CSS.mobileNavOverlay { position: fixed; width: 100%; height: 100%; background: rgba(0, 0, 0, .7); z-index: 2; top: 0; left: 0;}.mobileNavOverlay { position: fixed; width: 100%; height: 100%; background: rgba(0, 0, 0, .7); z-index: 2; top: 0; left: 0;}.noScroll { overflow: hidden; position:fixed;}
.info { width: 400px; margin: auto;}
.move-history { max-height: 100px; overflow-y: scroll;}
/* clearfix */.clearfix-7da63 { clear: both;}
/* board */.board-b72b1 { border: 2px solid #404040; -moz-box-sizing: content-box; box-sizing: content-box;}
/* square */.square-55d63 { float: left; position: relative;
/* disable any native browser highlighting */ -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;}
/* white square */.white-1e1d7 { background-color: #f0d9b5; color: #b58863;}
/* black square */.black-3c85d { background-color: #b58863; color: #f0d9b5;}
/* highlighted square */.highlight1-32417, .highlight2-9c5d2 { -webkit-box-shadow: inset 0 0 3px 3px yellow; -moz-box-shadow: inset 0 0 3px 3px yellow; box-shadow: inset 0 0 3px 3px yellow;}
/* notation */.notation-322f9 { cursor: default; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; position: absolute;}.alpha-d2270 { bottom: 1px; right: 3px;}.numeric-fc462 { top: 2px; left: 2px;}
select{height: 30px;width: 30px;border-radius: 100%;transition: 0.3s ease;}
select:hover{
transform: scale(1.1);background-color: black;color: white;
}
</style>
<!-- Used to get images --><base href="http://chessboardjs.com/" /><h3 class="board">JSfish Chess Bot</h3><div id="board" class="board"></div><br><div class="info"> Search depth: <select id="search-depth"> <option value="1">1</option> <option value="2">2</option> <option value="3" selected>3</option> <option value="4">4</option>
</select>
<br> <span>Positions evaluated: <span id="position-count"></span></span> <br> <span>Time: <span id="time"></span></span> <br> <span>Positions/s: <span id="positions-per-s"></span> </span> <br> <br> <div id="move-history" class="move-history"> </div></div><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.js"></script><script src="https://rawcdn.githack.com/zm2231/APCSA-Assignemnts/6b4cacbd8039a38ef66ed74921503aa872bb48cc/newChess.js"></script><script src="https://rawcdn.githack.com/zm2231/APCSA-Assignemnts/643fa5bb62c8d425d18a0dd13b04169e46c8bd6e/chessboard.js"></script><script></script>
<script>var board, game = new Chess();
/*The "AI" part starts here */// Create a new div element for the evaluation scores// Create a new div element for the evaluation scoresconst evaluationDiv = createElement("div");evaluationDiv.style.position = "fixed";evaluationDiv.style.top = "0";evaluationDiv.style.left = "0";evaluationDiv.style.width = "320px";evaluationDiv.style.height = "100vh";evaluationDiv.style.padding = "10px";evaluationDiv.style.overflowY = "scroll";body(evaluationDiv);
const minimaxRoot = function (depth, game, isMaximisingPlayer, callback) { const newGameMoves = game.ugly_moves(); let bestMove = -Infinity; let bestMoveFound; const totalIterations = newGameMoves.length; let iterationsSoFar = 0;
for (let i = 0; i < totalIterations; i++) { const newGameMove = newGameMoves[i]; game.ugly_move(newGameMove); const value = minimax(depth - 1, game, -Infinity, Infinity, !isMaximisingPlayer, callback); game.undo(); if (value >= bestMove) { bestMove = value; bestMoveFound = newGameMove; } iterationsSoFar++; if (iterationsSoFar === Math.floor(totalIterations / 2)) { // Append the current evaluation score to the evaluation div after half the iterations const moveText = createElement("p"); moveText.textContent = "Evaluation: " + bestMove; evaluationDiv(moveText); } }
return bestMoveFound;};const minimax = function(depth, game, alpha, beta, isMaximisingPlayer, callback) { positionCount++; if (depth === 0) { return -evaluateBoard(game.board()); }
const newGameMoves = game.ugly_moves(); let bestMove;
if (isMaximisingPlayer) { bestMove = -Infinity; for (let i = 0; i < newGameMoves.length; i++) { game.ugly_move(newGameMoves[i]); bestMove = Math.max(bestMove, minimax(depth - 1, game, alpha, beta, !isMaximisingPlayer, callback)); game.undo(); alpha = Math.max(alpha, bestMove); if (beta <= alpha) { break; } } } else { bestMove = Infinity; for (let i = 0; i < newGameMoves.length; i++) { game.ugly_move(newGameMoves[i]); bestMove = Math.min(bestMove, minimax(depth - 1, game, alpha, beta, !isMaximisingPlayer, callback)); game.undo(); beta = Math.min(beta, bestMove); if (beta <= alpha) { break; } } } return bestMove;};
var evaluateBoard = function (board) { var totalEvaluation = 0; for (var i = 0; i < 8; i++) { for (var j = 0; j < 8; j++) { totalEvaluation = totalEvaluation + getPieceValue(board[i][j], i ,j); } } return totalEvaluation;};
var reverseArray = function(array) { return array.slice().reverse();};
var pawnEvalWhite = [ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 10.0, 3.0, 0.0, 0.0, 0.0], [0.0, 0.0, 20.0, 4.0, 4.0, 0.0, 0.0, 0.0], [10.0, 5.0, 0.0, 5.0, 5.0, 0.0, 5.0, 10.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] ];
var pawnEvalBlack = reverseArray(pawnEvalWhite);
var knightEval = [ [0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 5.0, 0.0, 0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 7.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] ];
var bishopEvalWhite = [ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0], [0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]];
var bishopEvalBlack = reverseArray(bishopEvalWhite);
var rookEvalWhite = [ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 6.0, 8.0, 8.0, 6.0, 0.0, 0.0]];
var rookEvalBlack = reverseArray(rookEvalWhite);
var evalQueen = [
[0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [-1.0, 0.0, 0.0, -5.0, -5.0, -4.0, 0.0, 0.0], [0.0, 0.0, 0.0, -5.0, -5.0, -4.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]];
var kingEvalWhite = [
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]];
var kingEvalBlack = reverseArray(kingEvalWhite);
var getPieceValue = function (piece, x, y) { if (piece === null) { return 0; } var getAbsoluteValue = function (piece, isWhite, x ,y) { if (piece.type === 'p') { return 160 + ( isWhite ? pawnEvalWhite[y][x] : pawnEvalBlack[y][x] ); } else if (piece.type === 'r') { return 500 + ( isWhite ? rookEvalWhite[y][x] : rookEvalBlack[y][x] ); } else if (piece.type === 'n') { return 290 + knightEval[y][x]; } else if (piece.type === 'b') { return 350 + ( isWhite ? bishopEvalWhite[y][x] : bishopEvalBlack[y][x] ); } else if (piece.type === 'q') { return 900 + evalQueen[y][x]; } else if (piece.type === 'k') { return 0 + ( isWhite ? kingEvalWhite[y][x] : kingEvalBlack[y][x] ); } throw "Unknown piece type: " + piece.type; };
var absoluteValue = getAbsoluteValue(piece, piece.color === 'w', x ,y); return piece.color === 'w' ? absoluteValue : -absoluteValue;};
/* board visualization and games state handling */
var onDragStart = function (source, piece, position, orientation) { if (game.in_checkmate() === true || game.in_draw() === true || piece.search(/^b/) !== -1) { return false; }};
var makeBestMove = function () { var bestMove = getBestMove(game); game.ugly_move(bestMove); board.position(game.fen()); renderMoveHistory(game.history()); if (game.game_over()) { alert('Game over'); }};
var positionCount;var getBestMove = function (game) { if (game.game_over()) { alert('Game over'); }
positionCount = 0; var depth = parseInt($('#search-depth').find('elected').text());
var d = new Date().getTime(); var bestMove = minimaxRoot(depth, game, true); var d2 = new Date().getTime(); var moveTime = (d2 - d); var positionsPerS = ( positionCount * 1000 / moveTime);
$('#position-count').text(positionCount); $('#time').text(moveTime/1000 + 's'); $('#positions-per-s').text(positionsPerS); return bestMove;};
var renderMoveHistory = function (moves) { var historyElement = $('#move-history').empty(); historyElement.empty(); for (var i = 0; i < moves.length; i = i + 2) { historyElement.append('<span>' + moves[i] + ' ' + ( moves[i + 1] ? moves[i + 1] : ' ') + '</span><br>') } historyElement.scrollTop(historyElement[0].scrollHeight);
};
var onDrop = function (source, target) {
var move = game.move({ from: source, to: target, promotion: 'q' });
removeGreySquares(); if (move === null) { return 'snapback'; }
renderMoveHistory(game.history()); setTimeout(makeBestMove, 250);};
var onSnapEnd = function () { board.position(game.fen());};
var onMouseoverSquare = function(square, piece) { var moves = game.moves({ square: square, verbose: true });
if (moves.length === 0) return;
greySquare(square);
for (var i = 0; i < moves.length; i++) { greySquare(moves[i].to); }};
var onMouseoutSquare = function(square, piece) { removeGreySquares();};
var removeGreySquares = function() { $('#board .square-55d63').css('background', '');};
var greySquare = function(square) { var squareEl = $('#board .square-' + square);
var background = '#a9a9a9'; if (squareEl.hasClass('black-3c85d') === true) { background = '#696969'; }
squareEl.css('background', background);};
var cfg = { draggable: true, position: 'start', onDragStart: onDragStart, onDrop: onDrop, onMouseoutSquare: onMouseoutSquare, onMouseoverSquare: onMouseoverSquare, onSnapEnd: onSnapEnd};
board = ChessBoard('board', cfg);</script>
First you need more html just script will not work

Or just use a Python script I wrote
import chess
import random
from time import perf_counter
class TranspositionTable:
"""
A transposition table for storing positions that have already been evaluated.
This prevents redundant calculations during search.
"""
def __init__(self, size=2**20): # 1 million entries
self.table = {}
self.size = size
def store(self, key, depth, flag, score, best_move):
"""Stores a position's data in the transposition table."""
if len(self.table) >= self.size:
# Simple eviction policy: overwrite oldest entries
# For a real engine, more advanced policies (e.g., LRU) are better.
self.table.pop(next(iter(self.table)))
self.table[key] = (depth, flag, score, best_move)
def retrieve(self, key):
"""Retrieves data for a given position if it exists."""
return self.table.get(key)
class AdvancedChessEngine:
def __init__(self):
self.tt = TranspositionTable()
self.opening_book = {}
self.nodes = 0
self.start_time = 0
self.time_limit = 5.0 # seconds per move
self.killer_moves = {} # Heuristic for move ordering
self.history_moves = {} # Heuristic for move ordering
# Load an opening book from a text file (e.g., a list of FENs or moves)
self.load_opening_book("opening_book.txt")
def load_opening_book(self, filename):
"""Loads a list of moves from a file for an opening book."""
try:
with open(filename, 'r') as f:
for line in f:
fen, move_str = line.strip().split()
if fen not in self.opening_book:
self.opening_book[fen] = []
self.opening_book[fen].append(chess.Move.from_uci(move_str))
except FileNotFoundError:
print("Opening book not found. Starting from scratch.")
def search_move(self, board: chess.Board):
"""
Performs an iterative deepening search to find the best move.
This approach incrementally increases the search depth.
"""
self.nodes = 0
self.start_time = perf_counter()
best_move = None
# Check opening book
fen = board.fen()
if fen in self.opening_book:
return random.choice(self.opening_book[fen])
for depth in range(1, 10): # Iterative deepening loop
if perf_counter() - self.start_time > self.time_limit:
break
score, move = self.alphabeta(board, depth, -float('inf'), float('inf'))
if move:
best_move = move
return best_move
def alphabeta(self, board: chess.Board, depth, alpha, beta):
"""
Alpha-Beta pruning with several advanced enhancements.
"""
self.nodes += 1
# Transposition table lookup
tt_key = chess.polyglot.zobrist_hash(board)
tt_entry = self.tt.retrieve(tt_key)
if tt_entry and tt_entry[0] >= depth:
flag, score = tt_entry[1], tt_entry[2]
if flag == "exact":
return score, tt_entry[3]
elif flag == "lowerbound" and score > alpha:
alpha = score
elif flag == "upperbound" and score < beta:
beta = score
if alpha >= beta:
return score, tt_entry[3]
# Base case for recursion
if depth == 0 or board.is_game_over():
return self.evaluate_board(board), None
# Quiescence search (prevents horizon effect)
if depth <= 2:
return self.quiescence_search(board, alpha, beta), None
# Move ordering heuristics
moves = sorted(list(board.legal_moves), key=lambda m: self.score_move(board, m), reverse=True)
best_move = None
for move in moves:
board.push(move)
score, _ = self.alphabeta(board, depth - 1, -beta, -alpha)
board.pop()
score = -score
if score > alpha:
alpha = score
best_move = move
self.tt.store(tt_key, depth, "exact", score, best_move)
if alpha >= beta:
self.tt.store(tt_key, depth, "lowerbound", score, best_move)
return alpha, best_move
self.tt.store(tt_key, depth, "upperbound", alpha, best_move)
return alpha, best_move
def quiescence_search(self, board, alpha, beta):
"""
Searches only "noisy" moves (captures and promotions) at the end of the search.
"""
self.nodes += 1
stand_pat = self.evaluate_board(board)
if stand_pat >= beta:
return beta
alpha = max(alpha, stand_pat)
for move in self.get_noisy_moves(board):
board.push(move)
score = -self.quiescence_search(board, -beta, -alpha)
board.pop()
if score >= beta:
return beta
alpha = max(alpha, score)
return alpha
def score_move(self, board, move):
"""
Move ordering heuristic. Scores moves based on desirability.
This is a simplified example; a real engine would use more complex scores.
"""
# Prioritize captures using Most Valuable Victim - Least Valuable Aggressor
if board.is_capture(move):
victim = board.piece_at(move.to_square).piece_type
aggressor = board.piece_at(move.from_square).piece_type
# Material value mapping
piece_values = {chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3, chess.ROOK: 5, chess.QUEEN: 9, chess.KING: 10}
return 10 * piece_values.get(victim, 0) - piece_values.get(aggressor, 0)
# Consider killer moves
if move in self.killer_moves.get(board.ply(), []):
return 900
return 0
def evaluate_board(self, board: chess.Board):
"""
Advanced material and position evaluation.
Scores are in centipawns.
"""
if board.is_checkmate():
return -float('inf') if board.turn == chess.WHITE else float('inf')
if board.is_stalemate() or board.is_insufficient_material() or board.is_fivefold_repetition():
return 0
score = 0
piece_values = {
chess.PAWN: 100, chess.KNIGHT: 320, chess.BISHOP: 330,
chess.ROOK: 500, chess.QUEEN: 900
}
# Material evaluation
for piece_type in piece_values:
score += len(board.pieces(piece_type, chess.WHITE)) * piece_values[piece_type]
score -= len(board.pieces(piece_type, chess.BLACK)) * piece_values[piece_type]
# Piece-Square Tables (advanced evaluation)
# These are simple examples. Real tables are much more detailed.
pawn_table_white = [
0, 0, 0, 0, 0, 0, 0, 0,
5, 10, 10,-20,-20, 10, 10, 5,
5, -5,-10, 0, 0,-10, -5, 5,
0, 0, 0, 20, 20, 0, 0, 0,
5, 5, 10, 25, 25, 10, 5, 5,
10, 10, 20, 30, 30, 20, 10, 10,
50, 50, 50, 50, 50, 50, 50, 50,
0, 0, 0, 0, 0, 0, 0, 0
]
pawn_table_black = pawn_table_white[::-1]
for i in range(64):
if board.piece_at(i) and board.piece_at(i).piece_type == chess.PAWN:
if board.piece_at(i).color == chess.WHITE:
score += pawn_table_white[i]
else:
score -= pawn_table_black[i]
return score if board.turn == chess.WHITE else -score
def get_noisy_moves(self, board):
"""Returns only capture and promotion moves."""
moves = []
for move in board.legal_moves:
if board.is_capture(move) or move.promotion:
moves.append(move)
return moves
# Example usage for interacting with a UCI-like interface
if __name__ == "__main__":
engine = AdvancedChessEngine()
board = chess.Board()
print("UCI-like command line interface. Type 'uci' to begin.")
while True:
try:
command = input().strip()
if command == "uci":
print("id name Advanced Python Engine")
print("id author [Your Name]")
print("uciok")
elif command.startswith("isready"):
print("readyok")
elif command.startswith("ucinewgame"):
engine = AdvancedChessEngine()
elif command.startswith("position"):
parts = command.split()
if "fen" in parts:
fen_index = parts.index("fen") + 1
fen = " ".join(parts[fen_index:])
board = chess.Board(fen)
else:
board = chess.Board()
if "moves" in parts:
moves_index = parts.index("moves") + 1
for move_str in parts[moves_index:]:
move = chess.Move.from_uci(move_str)
board.push(move)
elif command.startswith("go"):
best_move = engine.search_move(board)
if best_move:
print(f"bestmove {best_move.uci()}")
elif command.startswith("quit"):
break
except Exception as e:
print(f"Error: {e}")
Recently, I came across a javascript chess engine on https://codepen.io/zainmer/pen/jOOBjvv and thought to myself "How do I improve it..." With the help of chatgpt... and... Nope, although it plays better than the one in codepen, it still manages to blunder pieces at a depth of 3. I can not increase the depth as the bot will take very long to think. I also do not want to write a lot of code, just want to keep it simpler but stronger. For now, all the code is in a .html file but I may need to separate the styles and scripts from the html later to make it a bit cleaner. Any ideas how to improve the javascript code to load faster? Maybe using webworkers... But I still don't know how to do that. Please help. Here is the index.html code (with the css and js):
live at : https://jsfish.w3spaces.com/
<style>
.board {
width: 500px;
margin: auto
}
CSS
.noScroll {
overflow: hidden;
position:fixed;
}
CSS
.mobileNavOverlay {
position: fixed;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, .7);
z-index: 2;
top: 0;
left: 0;
}
.mobileNavOverlay {
position: fixed;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, .7);
z-index: 2;
top: 0;
left: 0;
}
.noScroll {
overflow: hidden;
position:fixed;
}
.info {
width: 400px;
margin: auto;
}
.move-history {
max-height: 100px;
overflow-y: scroll;
}
/* clearfix */
.clearfix-7da63 {
clear: both;
}
/* board */
.board-b72b1 {
border: 2px solid #404040;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* square */
.square-55d63 {
float: left;
position: relative;
/* disable any native browser highlighting */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* white square */
.white-1e1d7 {
background-color: #f0d9b5;
color: #b58863;
}
/* black square */
.black-3c85d {
background-color: #b58863;
color: #f0d9b5;
}
/* highlighted square */
.highlight1-32417, .highlight2-9c5d2 {
-webkit-box-shadow: inset 0 0 3px 3px yellow;
-moz-box-shadow: inset 0 0 3px 3px yellow;
box-shadow: inset 0 0 3px 3px yellow;
}
/* notation */
.notation-322f9 {
cursor: default;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
position: absolute;
}
.alpha-d2270 {
bottom: 1px;
right: 3px;
}
.numeric-fc462 {
top: 2px;
left: 2px;
}
select{
height: 30px;
width: 30px;
border-radius: 100%;
transition: 0.3s ease;
}
select:hover{
transform: scale(1.1);
background-color: black;
color: white;
}
</style>
<!-- Used to get images -->
<base href="http://chessboardjs.com/" />
<h3 class="board">
JSfish Chess Bot
</h3>
<div id="board" class="board"></div>
<br>
<div class="info">
Search depth:
<select id="search-depth">
<option value="1">1</option>
<option value="2">2</option>
<option value="3" selected>3</option>
<option value="4">4</option>
</select>
<br>
<span>Positions evaluated: <span id="position-count"></span></span>
<br>
<span>Time: <span id="time"></span></span>
<br>
<span>Positions/s: <span id="positions-per-s"></span> </span>
<br>
<br>
<div id="move-history" class="move-history">
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.js"></script>
<script src="https://rawcdn.githack.com/zm2231/APCSA-Assignemnts/6b4cacbd8039a38ef66ed74921503aa872bb48cc/newChess.js"></script>
<script src="https://rawcdn.githack.com/zm2231/APCSA-Assignemnts/643fa5bb62c8d425d18a0dd13b04169e46c8bd6e/chessboard.js"></script>
<script></script>
<script>
var board,
game = new Chess();
/*The "AI" part starts here */
// Create a new div element for the evaluation scores
// Create a new div element for the evaluation scores
const evaluationDiv = createElement("div");
evaluationDiv.style.position = "fixed";
evaluationDiv.style.top = "0";
evaluationDiv.style.left = "0";
evaluationDiv.style.width = "320px";
evaluationDiv.style.height = "100vh";
evaluationDiv.style.padding = "10px";
evaluationDiv.style.overflowY = "scroll";
body(evaluationDiv);
const minimaxRoot = function (depth, game, isMaximisingPlayer, callback) {
const newGameMoves = game.ugly_moves();
let bestMove = -Infinity;
let bestMoveFound;
const totalIterations = newGameMoves.length;
let iterationsSoFar = 0;
for (let i = 0; i < totalIterations; i++) {
const newGameMove = newGameMoves[i];
game.ugly_move(newGameMove);
const value = minimax(depth - 1, game, -Infinity, Infinity, !isMaximisingPlayer, callback);
game.undo();
if (value >= bestMove) {
bestMove = value;
bestMoveFound = newGameMove;
}
iterationsSoFar++;
if (iterationsSoFar === Math.floor(totalIterations / 2)) {
// Append the current evaluation score to the evaluation div after half the iterations
const moveText = createElement("p");
moveText.textContent = "Evaluation: " + bestMove;
evaluationDiv(moveText);
}
}
return bestMoveFound;
};
const minimax = function(depth, game, alpha, beta, isMaximisingPlayer, callback) {
positionCount++;
if (depth === 0) {
return -evaluateBoard(game.board());
}
const newGameMoves = game.ugly_moves();
let bestMove;
if (isMaximisingPlayer) {
bestMove = -Infinity;
for (let i = 0; i < newGameMoves.length; i++) {
game.ugly_move(newGameMoves[i]);
bestMove = Math.max(bestMove, minimax(depth - 1, game, alpha, beta, !isMaximisingPlayer, callback));
game.undo();
alpha = Math.max(alpha, bestMove);
if (beta <= alpha) {
break;
}
}
} else {
bestMove = Infinity;
for (let i = 0; i < newGameMoves.length; i++) {
game.ugly_move(newGameMoves[i]);
bestMove = Math.min(bestMove, minimax(depth - 1, game, alpha, beta, !isMaximisingPlayer, callback));
game.undo();
beta = Math.min(beta, bestMove);
if (beta <= alpha) {
break;
}
}
}
return bestMove;
};
var evaluateBoard = function (board) {
var totalEvaluation = 0;
for (var i = 0; i < 8; i++) {
for (var j = 0; j < 8; j++) {
totalEvaluation = totalEvaluation + getPieceValue(board[i][j], i ,j);
}
}
return totalEvaluation;
};
var reverseArray = function(array) {
return array.slice().reverse();
};
var pawnEvalWhite =
[
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 10.0, 3.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 20.0, 4.0, 4.0, 0.0, 0.0, 0.0],
[10.0, 5.0, 0.0, 5.0, 5.0, 0.0, 5.0, 10.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
];
var pawnEvalBlack = reverseArray(pawnEvalWhite);
var knightEval =
[
[0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 5.0, 0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 7.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
];
var bishopEvalWhite = [
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0],
[0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
];
var bishopEvalBlack = reverseArray(bishopEvalWhite);
var rookEvalWhite = [
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 6.0, 8.0, 8.0, 6.0, 0.0, 0.0]
];
var rookEvalBlack = reverseArray(rookEvalWhite);
var evalQueen = [
[0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[-1.0, 0.0, 0.0, -5.0, -5.0, -4.0, 0.0, 0.0],
[0.0, 0.0, 0.0, -5.0, -5.0, -4.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
];
var kingEvalWhite = [
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
];
var kingEvalBlack = reverseArray(kingEvalWhite);
var getPieceValue = function (piece, x, y) {
if (piece === null) {
return 0;
}
var getAbsoluteValue = function (piece, isWhite, x ,y) {
if (piece.type === 'p') {
return 160 + ( isWhite ? pawnEvalWhite[y][x] : pawnEvalBlack[y][x] );
} else if (piece.type === 'r') {
return 500 + ( isWhite ? rookEvalWhite[y][x] : rookEvalBlack[y][x] );
} else if (piece.type === 'n') {
return 290 + knightEval[y][x];
} else if (piece.type === 'b') {
return 350 + ( isWhite ? bishopEvalWhite[y][x] : bishopEvalBlack[y][x] );
} else if (piece.type === 'q') {
return 900 + evalQueen[y][x];
} else if (piece.type === 'k') {
return 0 + ( isWhite ? kingEvalWhite[y][x] : kingEvalBlack[y][x] );
}
throw "Unknown piece type: " + piece.type;
};
var absoluteValue = getAbsoluteValue(piece, piece.color === 'w', x ,y);
return piece.color === 'w' ? absoluteValue : -absoluteValue;
};
/* board visualization and games state handling */
var onDragStart = function (source, piece, position, orientation) {
if (game.in_checkmate() === true || game.in_draw() === true ||
piece.search(/^b/) !== -1) {
return false;
}
};
var makeBestMove = function () {
var bestMove = getBestMove(game);
game.ugly_move(bestMove);
board.position(game.fen());
renderMoveHistory(game.history());
if (game.game_over()) {
alert('Game over');
}
};
var positionCount;
var getBestMove = function (game) {
if (game.game_over()) {
alert('Game over');
}
positionCount = 0;
var depth = parseInt($('#search-depth').find('
var d = new Date().getTime();
var bestMove = minimaxRoot(depth, game, true);
var d2 = new Date().getTime();
var moveTime = (d2 - d);
var positionsPerS = ( positionCount * 1000 / moveTime);
$('#position-count').text(positionCount);
$('#time').text(moveTime/1000 + 's');
$('#positions-per-s').text(positionsPerS);
return bestMove;
};
var renderMoveHistory = function (moves) {
var historyElement = $('#move-history').empty();
historyElement.empty();
for (var i = 0; i < moves.length; i = i + 2) {
historyElement.append('<span>' + moves[i] + ' ' + ( moves[i + 1] ? moves[i + 1] : ' ') + '</span><br>')
}
historyElement.scrollTop(historyElement[0].scrollHeight);
};
var onDrop = function (source, target) {
var move = game.move({
from: source,
to: target,
promotion: 'q'
});
removeGreySquares();
if (move === null) {
return 'snapback';
}
renderMoveHistory(game.history());
setTimeout(makeBestMove, 250);
};
var onSnapEnd = function () {
board.position(game.fen());
};
var onMouseoverSquare = function(square, piece) {
var moves = game.moves({
square: square,
verbose: true
});
if (moves.length === 0) return;
greySquare(square);
for (var i = 0; i < moves.length; i++) {
greySquare(moves[i].to);
}
};
var onMouseoutSquare = function(square, piece) {
removeGreySquares();
};
var removeGreySquares = function() {
$('#board .square-55d63').css('background', '');
};
var greySquare = function(square) {
var squareEl = $('#board .square-' + square);
var background = '#a9a9a9';
if (squareEl.hasClass('black-3c85d') === true) {
background = '#696969';
}
squareEl.css('background', background);
};
var cfg = {
draggable: true,
position: 'start',
onDragStart: onDragStart,
onDrop: onDrop,
onMouseoutSquare: onMouseoutSquare,
onMouseoverSquare: onMouseoverSquare,
onSnapEnd: onSnapEnd
};
board = ChessBoard('board', cfg);
</script>