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):
/*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); } }
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);
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>