//=== EXPLANATIONS =============================================================
//This project has enabled the creation of a platform for mini chess games based
//on learning by observing moves played by artificial intelligences,
//attempting to reproduce them, and analyzing games played by these same AIs.
//==============================================================================
//===== RULES ==================================================================
//The rules have been implemented following the chessboardjs.com documentation:
//https://www.chessboardjs.com/
//I only modified the onDrop function to create its own rules according
//to the mini game being played.
//==============================================================================
var whiteSquareGrey = '#a9a9a9';
var blackSquareGrey = '#696969';
function removeGreySquares (name) {
$('#' + name + ' .square-55d63').css('background', '');
}
function greySquare (square, name) {
var $square = $('#' + name + ' .square-' + square);
var background = whiteSquareGrey;
if ($square.hasClass('black-3c85d')) {
background = blackSquareGrey;
}
$square.css('background', background);
}
function onDragStart (source, piece, game) {
// do not pick up pieces if the game is over
if (game.game_over()) return false;
// or if it's not that side's turn
if ((game.turn() === 'w' && piece.search(/^b/) !== -1) ||
(game.turn() === 'b' && piece.search(/^w/) !== -1)) {
return false;
}
}
function onDrop (source, target, board, name, game) {
if (game.mode == 'menu') {
moveMenu(source, target, board, name, game);
}
else if (game.mode == 'puzzle') {
if (typeof board.currentIndexPuzzle !== "undefined" && board.sequencePuzzle) {
movePuzzle(source, target, board, name, game);
}
}
else if (game.mode == 'analysis' || game.mode == 'navigation') {
return 'snapback';
}
else {
removeGreySquares(name);
// see if the move is legal
var userMove = game.move({
from: source,
to: target,
promotion: 'q' // NOTE: always promote to a queen for example simplicity
});
// illegal move
if (userMove === null) return 'snapback';
}
}
function onMouseoverSquare (square, piece, name, game) {
if (game.game_over()) return;
// get list of possible moves for this square
var moves = game.moves({
square: square,
verbose: true
});
// exit if there are no moves available for this square
if (moves.length === 0) return;
// highlight the square they moused over
greySquare(square, name);
// highlight the possible squares for this piece
for (var i = 0; i < moves.length; i++) {
greySquare(moves[i].to, name);
}
}
function onMouseoutSquare (square, piece, name) {
removeGreySquares(name);
}
function onSnapEnd (board, game) {
board.position(game.fen());
}
//==============================================================================
//===== CHESSBOARD IMPORT ======================================================
//This function allows you to create a chessboard and a game linked to the board,
//using the Chessboard and Chess js libraries.
function initBoard(name) {
let game = new Chess();
game.mode = 'menu';
let board = Chessboard(name, {
draggable: true,
position: 'start',
onDragStart: function (source, piece, position, orientation) {
return onDragStart(source, piece, game);
},
onDrop: function (source, target) {
return onDrop(source, target, board, name, game);
},
onMouseoutSquare: function (square, piece) {
return onMouseoutSquare(square, piece, name);
},
onMouseoverSquare: function (square, piece) {
return onMouseoverSquare(square, piece, name, game);
},
onSnapEnd: function () {
return onSnapEnd(board, game);
},
pieceTheme: '/uploads/korentin/{piece}.png',
});
return [board, game];
}
//==============================================================================
//===== AI API CALLS ===========================================================
//In this section, we create API calls to both versions of Stockfish to predict the
//next best move for a given FEN.
//The keywords “async” and “await” are used to pause the code until the function
//call is completed.
//I've used an example of how to use these keywords here:
//https://javascript.info/async-await
//==============================================================================
function generatePromotionMove(fen) {
const chess = new Chess(fen);
const moves = chess.moves({ verbose: true });
const promotionMove = moves.find(move => move.promotion && move.promotion === 'q');
if (promotionMove) {
return promotionMove.san;
}
return null;
}
async function getBestMoveStockfishv1(fen, depth=13) {
const apiurl = "https://stockfish.online/api/stockfish.php";
const params = new URLSearchParams({
fen: fen,
depth: depth,
mode: "bestmove"
});
try {
const response = await fetch(`${apiurl}?${params.toString()}`, {
method: 'GET',
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
});
if (!response.ok) {
throw new Error(`API Error: ${response.status} - ${response.statusText}`);
}
const json = await response.json();
const text = json.data;
const bestMove = text.split(' ')[1];
const chess = new Chess(fen);
chess.move({ from: bestMove.slice(0, 2), to: bestMove.slice(2, 4) });
const sanMove = chess.history({ verbose: true })[0].san;
return sanMove;
}
catch (error) {
//if there is an error, we check if we can make a promotion move
const promotionMove = generatePromotionMove(fen);
if (promotionMove) {
return promotionMove;
}
if (game.mode == 'menu') { document.getElementById("text-bar").textContent = "An error has occurred during the API call...try to restart the game!"; }
if (game.mode == 'puzzle') { document.getElementById("text-bar").textContent = "An error has occurred during the API call...try to load a new puzzle!"; }
if (game.mode == 'analysis') { document.getElementById("text-bar").textContent = "An error has occurred during the API call...try to restart the game!"; }
}
}
async function getBestMoveStockfishv2(fen, depth=13) {
const apiurl = "https://stockfish.online/api/s/v2.php";
const params = new URLSearchParams({
fen: fen,
depth: depth,
});
try {
const response = await fetch(`${apiurl}?${params.toString()}`, {
method: 'GET',
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
});
if (!response.ok) {
throw new Error(`API Error: ${response.status} - ${response.statusText}`);
}
const json = await response.json();
const bestMove = json.bestmove.split(' ')[1];
const chess = new Chess(fen);
chess.move({ from: bestMove.slice(0, 2), to: bestMove.slice(2, 4) });
const sanMove = chess.history({ verbose: true })[0].san;
return sanMove;
}
catch (error) {
//if there is an error, we check if we can make a promotion move
const promotionMove = generatePromotionMove(fen);
if (promotionMove) {
return promotionMove;
}
if (game.mode == 'menu') { document.getElementById("text-bar").textContent = "An error has occurred during the API call...try to restart the game!"; }
if (game.mode == 'puzzle') { document.getElementById("text-bar").textContent = "An error has occurred during the API call...try to load a new puzzle!"; }
if (game.mode == 'analysis') { document.getElementById("text-bar").textContent = "An error has occurred during the API call...try to restart the game!"; }
}
}
//==============================================================================
//===== MENU ===================================================================
//In the menu, you can play a game against the AI, use buttons in the
//information panel to restart the game, browse through old moves...
//Buttons can also be used to access mini-games.
//The main functions in this section are :
//- initMenu(), which initializes the board and information panel buttons
//- moveMenu(), which is activated when a chessboard piece is dropped at a valid
//position (onDrop function). When the user has played, the function calls the
//v2 API to determine the best move.
//The other functions in this section are simply tools called in these 2
//previous methods.
//==============================================================================
function pause(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function testGameOver(game) {
if (game.game_over()) {
if (game.in_draw()) {
document.getElementById("text-menu").textContent = "The game is a draw...";
document.getElementById("text-bar").textContent = "The game is a draw...";
}
else {
if (game.turn() == 'b') {
document.getElementById("text-menu").textContent = "Checkmate! White has won the game...";
document.getElementById("text-bar").textContent = "Checkmate! White has won the game...";
}
else if (game.turn() == 'w') {
document.getElementById("text-menu").textContent = "Checkmate! Black has won the game...";
document.getElementById("text-bar").textContent = "Checkmate! Black has won the game...";
}
}
}
}
function initMenu(board, game) {
document.getElementById("text-bar").textContent = "Waiting for user action...";
board.currentIndexMenu = 0;
board.arrowIndexMenu = 0;
board.sequenceMenu = [];
game.mode = 'menu';
$('#restart-menu').off('click').on('click', function() {
[board, game] = initBoard('chess-board');
initMenu(board, game);
document.getElementById("text-menu").textContent = "The game hasn't started yet...";
document.getElementById("text-bar").textContent = "Waiting for user action...";
});
$('#orientation-menu').off('click').on('click', async function() {
board.flip();
if ((board.orientation() == 'white' && game.turn() == 'b') || (board.orientation() == 'black' && game.turn() == 'w')) {
game.mode = 'navigation';
document.getElementById("text-bar").textContent = "The game is on... (AI turn)";
if (game.fen() == 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1') {
game.mode = 'navigation';
document.getElementById("text-menu").textContent = "The game is on...";
document.getElementById("text-bar").textContent = "The game is on... (AI turn)";
}
const stockfishMove = await getBestMoveStockfishv2(game.fen());
game.move(stockfishMove);
board.position(game.fen());
(board.sequenceMenu).push(stockfishMove);
board.currentIndexMenu++;
board.arrowIndexMenu++;
game.mode = 'menu';
document.getElementById("text-bar").textContent = "The game is on... (User turn)";
testGameOver(game);
}
});
function updateMenuPosition() {
game.load('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
for (let i = 0; i < board.arrowIndexMenu; i++) {
game.move(board.sequenceMenu[i]);
}
board.position(game.fen());
}
$('#start-menu').off('click').on('click', function() {
board.arrowIndexMenu = 0;
updateMenuPosition();
});
$('#pre-move-menu').off('click').on('click', function() {
if (board.currentIndexMenu > 0 && board.arrowIndexMenu > 0) {
board.arrowIndexMenu--;
updateMenuPosition();
}
});
$('#next-move-menu').off('click').on('click', function() {
if (board.arrowIndexMenu < board.currentIndexMenu) {
board.arrowIndexMenu++;
updateMenuPosition();
}
});
$('#current-menu').off('click').on('click', function() {
board.arrowIndexMenu = board.currentIndexMenu;
updateMenuPosition();
});
}
async function moveMenu(source, target, board, name, game) {
removeGreySquares(name);
const userMove = game.move({
from: source,
to: target,
promotion: 'q'
});
if (userMove === null) return 'snapback';
else {
(board.sequenceMenu).push(userMove.san);
board.currentIndexMenu++;
board.arrowIndexMenu++;
game.mode = 'navigation';
document.getElementById("text-bar").textContent = "The game is on... (AI turn)";
if ((board.sequenceMenu).length > 0) {
textMenu = document.getElementById("text-menu");
if (textMenu.textContent !== "The game is on...") {
textMenu.textContent = "The game is on...";
game.mode = 'navigation';
document.getElementById("text-bar").textContent = "The game is on... (AI turn)";
}
}
testGameOver(game);
const stockfishMove = await getBestMoveStockfishv2(game.fen());
game.move(stockfishMove);
board.position(game.fen());
(board.sequenceMenu).push(stockfishMove);
board.currentIndexMenu++;
board.arrowIndexMenu++;
game.mode = 'menu';
document.getElementById("text-bar").textContent = "The game is on... (User turn)";
testGameOver(game);
}
}
//==============================================================================
//===== PUZZLE GAME ============================================================
//In the Puzzle Game, the loadPuzzle() function uses the chess.com API to access
//random puzzles. For each puzzle, we extract the FEN (to obtain the starting
//position) and the PGN (to obtain the sequence of moves to play to complete
//the puzzle).
//We then use initPuzzle() to create the chessboard, import the position and
//activate all the panel buttons. In particular, we activate the “Solution”
//and “Next Move” buttons to visualize on the chessboard the moves the user
//should have made to advance in the puzzle.
//We then call these 2 methods in a third: startPuzzle() and add a movePuzzle()
//function, which is activated again when a piece is validly moved on the
//chessboard. If the puzzle is completed (or the “Next Puzzle” button pressed),
//we call the StartPuzzle() method again to obtain a new puzzle and create a loop.
//The functions at the beginning of this section are used to calculate the
//accuracy values which are then displayed on the panel.
//I should also point out that puzzle PGNs vary considerably and I've tried to
//create a robust extraction method as you can see in loadPuzzle(),
//but some PGNs still escape the algorithm and in this case it's necessary
//to move on to the next puzzle with the button.
//==============================================================================
let userScore = 0;
let userNumerator = 0;
let userDenominator = 0;
let userAccuracy = 0;
function userUpdateAccuracy(value) {
userDenominator++;
if (value) {
userNumerator++;
}
userAccuracy = Math.round((userNumerator / userDenominator) * 100);
}
let stockfishv1Numerator = 0;
let stockfishv1Denominator = 0;
let stockfishv2Numerator = 0;
let stockfishv2Denominator = 0;
let stockfishv1Accuracy = 0;
let stockfishv2Accuracy = 0;
function stockfishv1UpdateAccuracy(value) {
stockfishv1Denominator++;
if (value) {
stockfishv1Numerator++;
}
stockfishv1Accuracy = Math.round((stockfishv1Numerator / stockfishv1Denominator) * 100);
}
function stockfishv2UpdateAccuracy(value) {
stockfishv2Denominator++;
if (value) {
stockfishv2Numerator++;
}
stockfishv2Accuracy = Math.round((stockfishv2Numerator / stockfishv2Denominator) * 100);
}
async function stockfishUpdate(board, game) {
// Stockfish v1
const stockfishv1Move = await getBestMoveStockfishv1(game.fen());
if (stockfishv1Move === board.sequencePuzzle[board.currentIndexPuzzle]) {
stockfishv1UpdateAccuracy(true);
}
else {
if (stockfishv1Move) { stockfishv1UpdateAccuracy(false); }
}
// Stockfish v2
const stockfishv2Move = await getBestMoveStockfishv2(game.fen());
if (stockfishv2Move === board.sequencePuzzle[board.currentIndexPuzzle]) {
stockfishv2UpdateAccuracy(true);
}
else {
if (stockfishv2Move) { stockfishv2UpdateAccuracy(false); }
}
}
let currentStartFEN;
async function loadPuzzle() {
document.getElementById("text-bar").textContent = "Loading puzzle...";
let startFEN;
let data;
do {
const response = await fetch("https://api.chess.com/pub/puzzle/random");
if (!response.ok) {
throw new Error(`Error while retrieving the puzzle: ${response.statusText}`);
}
data = await response.json();
if (!data || !data.fen || !data.pgn) {
throw new Error("Invalid or incomplete puzzle data...");
}
startFEN = data.fen;
} while (startFEN === currentStartFEN);
currentStartFEN = startFEN;
const chess = new Chess();
const loaded = chess.load(startFEN);
if (!loaded) {
console.error("Error loading position with FEN...");
return;
}
let pgn = data.pgn;
pgn = pgn.substring(pgn.lastIndexOf(']') + 1);
pgn = pgn.replace(/\{[^}]*\}/g, '');
pgn = pgn.replace(/\([^)]*\}/g, '');
pgn = pgn.split("\n").join(" ");
const moveSequence = pgn
.replace(/\d+\./g, '')
.replace(/\.\./g, '')
.split(/\s+/)
.map(move => move.replace(/\r/g, ''))
.filter(move => move !== "" && move !== "*");
moveSequence.forEach(move => chess.move(move));
const endFEN = chess.fen();
document.getElementById("text-bar").textContent = "Puzzle loaded!";
return [startFEN, endFEN, moveSequence];
}
function initPuzzle(board, game, startFEN, moveSequence) {
game.load(startFEN);
board.position(game.fen());
board.currentIndexPuzzle = 0;
board.arrowIndexPuzzle = 0;
board.sequencePuzzle = moveSequence;
game.endPuzzle = false;
stockfishUpdate(board, game);
$('#back-button-puzzle').off('click').on('click', function() {
$('#info-panel-puzzle').css('display', 'none');
[board, game] = initBoard('chess-board');
initMenu(board, game);
});
$('#solution-button').off('click').on('click', async function() {
$('#solution-button').prop('disabled', true);
$('#next-move-button').prop('disabled', true);
$('#next-puzzle-button').prop('disabled', true);
if (!(game.endPuzzle)) {
document.getElementById("text-bar").textContent = "Explanation of the solution...";
userUpdateAccuracy(false);
document.getElementById("user-accuracy").textContent = userAccuracy + "%";
document.getElementById("stockfishv1-accuracy").textContent = stockfishv1Accuracy + "%";
document.getElementById("stockfishv2-accuracy").textContent = stockfishv2Accuracy + "%";
}
for (let i=board.currentIndexPuzzle; i<board.sequencePuzzle.length; i++) {
game.move(board.sequencePuzzle[board.currentIndexPuzzle]);
board.position(game.fen());
board.currentIndexPuzzle++;
board.arrowIndexPuzzle++;
await pause(500);
}
game.endPuzzle = true;
document.getElementById("text-bar").textContent = "Puzzle completed!";
$('#solution-button').prop('disabled', false);
$('#next-move-button').prop('disabled', false);
$('#next-puzzle-button').prop('disabled', false);
});
$('#next-move-button').off('click').on('click', async function() {
$('#solution-button').prop('disabled', true);
$('#next-move-button').prop('disabled', true);
$('#next-puzzle-button').prop('disabled', true);
if (!(game.endPuzzle)) {
document.getElementById("text-bar").textContent = "Explanation of next move...";
userUpdateAccuracy(false);
document.getElementById("user-accuracy").textContent = userAccuracy + "%";
document.getElementById("stockfishv1-accuracy").textContent = stockfishv1Accuracy + "%";
document.getElementById("stockfishv2-accuracy").textContent = stockfishv2Accuracy + "%";
for(let i=0; i<2; i++) {
game.move(board.sequencePuzzle[board.currentIndexPuzzle]);
board.position(game.fen());
board.currentIndexPuzzle++;
board.arrowIndexPuzzle++;
if (board.currentIndexPuzzle === board.sequencePuzzle.length) {
game.endPuzzle = true;
document.getElementById("text-bar").textContent = "Puzzle completed!";
}
await pause(500);
}
if (!game.endPuzzle) {
document.getElementById("text-bar").textContent = "User turn now!";
stockfishUpdate(board, game);
}
}
$('#solution-button').prop('disabled', false);
$('#next-move-button').prop('disabled', false);
$('#next-puzzle-button').prop('disabled', false);
})
$('#next-puzzle-button').off('click').on('click', function() {
$('#solution-button').prop('disabled', true);
$('#next-move-button').prop('disabled', true);
$('#next-puzzle-button').prop('disabled', true);
if (!(game.endPuzzle)) {
userUpdateAccuracy(false);
document.getElementById("user-accuracy").textContent = userAccuracy + "%";
document.getElementById("stockfishv1-accuracy").textContent = stockfishv1Accuracy + "%";
document.getElementById("stockfishv2-accuracy").textContent = stockfishv2Accuracy + "%";
game.endPuzzle = true;
}
startPuzzle();
$('#solution-button').prop('disabled', false);
$('#next-move-button').prop('disabled', false);
$('#next-puzzle-button').prop('disabled', false);
});
function updatePuzzlePosition() {
game.load(startFEN);
for (let i = 0; i < board.arrowIndexPuzzle; i++) {
game.move(board.sequencePuzzle[i]);
}
board.position(game.fen());
}
$('#start-puzzle').off('click').on('click', function() {
game.mode = 'navigation';
board.arrowIndexPuzzle = 0;
updatePuzzlePosition();
});
$('#pre-move-puzzle').off('click').on('click', function() {
if (board.currentIndexPuzzle > 0 && board.arrowIndexPuzzle > 0) {
game.mode = 'navigation';
board.arrowIndexPuzzle--;
updatePuzzlePosition();
}
});
$('#next-move-puzzle').off('click').on('click', function() {
if (board.arrowIndexPuzzle < board.currentIndexPuzzle) {
game.mode = 'navigation';
board.arrowIndexPuzzle++;
if (board.arrowIndexPuzzle == board.currentIndexPuzzle) { game.mode = 'puzzle'; }
updatePuzzlePosition();
}
});
$('#current-puzzle').off('click').on('click', function() {
game.mode = 'puzzle';
board.arrowIndexPuzzle = board.currentIndexPuzzle;
updatePuzzlePosition();
});
return [game, board.currentIndexPuzzle];
}
let replay = false;
function movePuzzle(source, target, board, name, game) {
removeGreySquares(name);
const userMove = game.move({
from: source,
to: target,
promotion: 'q'
});
if (!userMove) {
return 'snapback';
}
else if (userMove.san === board.sequencePuzzle[board.currentIndexPuzzle]) {
document.getElementById("text-bar").textContent = "Well done! What's next?";
if (!replay) { userUpdateAccuracy(true); }
replay = false;
document.getElementById("user-accuracy").textContent = userAccuracy + "%";
document.getElementById("stockfishv1-accuracy").textContent = stockfishv1Accuracy + "%";
document.getElementById("stockfishv2-accuracy").textContent = stockfishv2Accuracy + "%";
board.currentIndexPuzzle++;
board.arrowIndexPuzzle++;
board.position(game.fen());
if (board.currentIndexPuzzle === board.sequencePuzzle.length) {
userScore++;
document.getElementById("score").textContent = userScore;
game.endPuzzle = true;
document.getElementById("text-bar").textContent = "Puzzle completed!";
startPuzzle();
return 0;
}
else {
game.move(board.sequencePuzzle[board.currentIndexPuzzle]);
board.position(game.fen());
board.currentIndexPuzzle++;
board.arrowIndexPuzzle++;
}
}
else {
if (!replay) {
userUpdateAccuracy(false);
replay = true;
document.getElementById("user-accuracy").textContent = userAccuracy + "%";
document.getElementById("stockfishv1-accuracy").textContent = stockfishv1Accuracy + "%";
document.getElementById("stockfishv2-accuracy").textContent = stockfishv2Accuracy + "%";
}
document.getElementById("text-bar").textContent = "It's not the right move! Here's a hint..." + board.sequencePuzzle[board.currentIndexPuzzle];
game.undo();
board.position(game.fen());
return 'snapback';
}
stockfishUpdate(board, game);
}
async function startPuzzle(board, game) {
[board, game] = initBoard('chess-board');
game.mode = 'puzzle';
document.getElementById("text-bar").textContent = "Puzzles game!";
[startFEN, endFEN, moveSequence] = await loadPuzzle();
[game, currentIndexPuzzle] = initPuzzle(board, game, startFEN, moveSequence);
}
//==============================================================================
//===== ANALYSIS GAME ==========================================================
//The Analyse Game structure is similar to the 2 previous structures.
//An initAnalysis() function imports the board, allows settings to be modified
//and activates buttons.
//Then, a loopAnalysis() function advances the game if you're in “play” mode,
//and stops it if you're in “pause” mode.
//There is no move function, as the pieces cannot be moved, and the two previous
//functions are called again in a startAnalysis() function. This function is
//then called in several places, notably to restart the game.
//There's also the generateRandomFEN() tool for generating valid,
//random FEN positions. This allows you to diversify the mini-game!
//==============================================================================
function generateRandomFEN() {
const chess = new Chess();
const movesCount = Math.floor(Math.random() * 50) + 1;
for (let i = 0; i < movesCount; i++) {
const possibleMoves = chess.moves();
if (possibleMoves.length === 0) {
break;
}
const randomMove = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
chess.move(randomMove);
}
return chess.fen();
}
let analysisFEN;
function initAnalysis(board, game) {
document.getElementById("text-bar").textContent = "Analysis game! You can change the settings before starting the game by pressing the START button!";
board.currentIndexAnalysis = 0;
board.arrowIndexAnalysis = 0;
board.sequenceAnalysis = [];
game.playAnalysis = false;
analysisFEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
$('#back-button-analysis').off('click').on('click', function() {
$('#info-panel-analysis').css('display', 'none');
[board, game] = initBoard('chess-board');
initMenu(board, game);
});
$('#white').on('change', function() {
const whiteChoice = $(this).val();
const blackSelect = $('#black');
blackSelect.empty();
if (whiteChoice === "v1") {
blackSelect.append('<option value="v2">Stockfish v2</option>');
} else if (whiteChoice === "v2") {
blackSelect.append('<option value="v1">Stockfish v1</option>');
}
});
$('#black').on('change', function() {
const blackChoice = $(this).val();
const whiteSelect = $('#white');
whiteSelect.empty();
if (blackChoice === "v1") {
whiteSelect.append('<option value="v2">Stockfish v2</option>');
} else if (blackChoice === "v2") {
whiteSelect.append('<option value="v1">Stockfish v1</option>');
}
});
$('#play-analysis').off('click').on('click', function() {
document.getElementById("text-bar").textContent = "The game is in progress...";
board.currentIndexAnalysis = board.arrowIndexAnalysis;
board.sequenceAnalysis = (board.sequenceAnalysis).slice(0, board.currentIndexAnalysis);
game.playAnalysis = true;
$('#start-analysis').prop('disabled', true);
$('#pre-move-analysis').prop('disabled', true);
$('#next-move-analysis').prop('disabled', true);
$('#current-analysis').prop('disabled', true);
loopAnalysis(board, game);
});
$('#pause-analysis').off('click').on('click', function() {
document.getElementById("text-bar").textContent = "The game is currently paused...";
$('#start-analysis').prop('disabled', false);
$('#pre-move-analysis').prop('disabled', false);
$('#next-move-analysis').prop('disabled', false);
$('#current-analysis').prop('disabled', false);
game.playAnalysis = false;
});
$('#restart-analysis').off('click').on('click', function() {
startAnalysis(board, game);
});
$('#orientation-analysis').off('click').on('click', function() {
board.flip();
});
$('#random-analysis').off('click').on('click', function() {
game.playAnalysis = false;
board.currentIndexAnalysis = 0;
board.arrowIndexAnalysis = 0;
board.sequenceAnalysis = [];
document.getElementById("text-bar").textContent = "The random valid position has been loaded...press START!";
analysisFEN = generateRandomFEN();
game.load(analysisFEN);
board.position(game.fen());
});
function updateAnalysisPosition() {
game.load(analysisFEN);
for (let i = 0; i < board.arrowIndexAnalysis; i++) {
game.move(board.sequenceAnalysis[i]);
}
board.position(game.fen());
}
$('#start-analysis').off('click').on('click', function() {
board.arrowIndexAnalysis = 0;
updateAnalysisPosition();
});
$('#pre-move-analysis').off('click').on('click', function() {
if (board.currentIndexAnalysis > 0 && board.arrowIndexAnalysis > 0) {
board.arrowIndexAnalysis--;
updateAnalysisPosition();
}
});
$('#next-move-analysis').off('click').on('click', function() {
if (board.arrowIndexAnalysis < board.currentIndexAnalysis) {
board.arrowIndexAnalysis++;
updateAnalysisPosition();
}
});
$('#current-analysis').off('click').on('click', function() {
board.arrowIndexAnalysis = board.currentIndexAnalysis;
updateAnalysisPosition();
});
}
async function loopAnalysis(board, game) {
depth = Number(document.getElementById('depth').value);
speed = document.getElementById('speed').value;
if (speed == 'fast') { speed = 0; }
else if (speed == 'medium') { speed = 3; }
else if (speed == 'slow') { speed = 6; }
while (game.mode == 'analysis' && !game.game_over() && game.playAnalysis) {
if (document.getElementById('white').value == "v1" && document.getElementById('black').value == "v2") {
if (game.turn() == 'w') {
await pause(speed*500);
if (game.playAnalysis) {
const whiteMove = await getBestMoveStockfishv1(game.fen(), depth);
game.move(whiteMove);
await board.position(game.fen());
(board.sequenceAnalysis).push(whiteMove);
board.currentIndexAnalysis++;
board.arrowIndexAnalysis++;
}
}
else if (game.turn() == 'b') {
await pause(speed*500);
if (game.playAnalysis) {
const blackMove = await getBestMoveStockfishv2(game.fen(), depth);
game.move(blackMove);
await board.position(game.fen());
(board.sequenceAnalysis).push(blackMove);
board.currentIndexAnalysis++;
board.arrowIndexAnalysis++;
}
}
}
else if (document.getElementById('white').value == "v2" && document.getElementById('black').value == "v1") {
if (game.turn() == 'w') {
await pause(speed*500);
if (game.playAnalysis) {
const whiteMove = await getBestMoveStockfishv2(game.fen(), depth);
game.move(whiteMove);
await board.position(game.fen());
(board.sequenceAnalysis).push(whiteMove);
board.currentIndexAnalysis++;
board.arrowIndexAnalysis++;
}
}
else if (game.turn() == 'b') {
await pause(speed*500);
if (game.playAnalysis) {
const blackMove = await getBestMoveStockfishv1(game.fen(), depth);
game.move(blackMove);
await board.position(game.fen());
(board.sequenceAnalysis).push(blackMove);
board.currentIndexAnalysis++;
board.arrowIndexAnalysis++;
}
}
}
}
if (game.game_over()) {
if (game.in_draw()) {
document.getElementById("text-bar").textContent = "The game is a draw...";
}
else {
if (game.turn() == 'b') {
document.getElementById("text-bar").textContent = "Checkmate! White has won the game...";
}
else if (game.turn() == 'w') {
document.getElementById("text-bar").textContent = "Checkmate! Black has won the game...";
}
}
}
}
function startAnalysis(board, game) {
[board, game] = initBoard('chess-board');
game.mode = 'analysis';
initAnalysis(board, game);
}
//==============================================================================
//===== MAIN ===================================================================
$(document).ready(function() {
//===== CSS PART ===============================================================
//The CSS part was the hardest for me to code, as I was a total beginner in html
//and css.
//I relied entirely on the following documentation to create my wrappers and divs.
//This one in particular for the positions of the elements in my divs:
//https://developer.mozilla.org/en-US/docs/Web/CSS/position
//This one in particular for DOM Document/Elements:
//https://www.w3schools.com/js/js_htmldom.asp
//Finally, I found a lot of inspiration in the other Ancient-Brain projects
//for the creation of buttons and other interactive elements.
//==============================================================================
$('<link/>', {
rel: 'stylesheet',
type: 'text/css',
href: '/uploads/korentin/chessboard.css'
}).appendTo('head');
$('<style>')
.prop('type', 'text/css')
.html(`
body {
background-color: #d1b28f !important;
margin: 0;
padding: 0;
}
`).appendTo('head');
$('#ab-wrapper').append('<div id="main-wrapper" style="display: flex; flex-direction: column; align-items: flex-start; position: absolute; z-index: 20; top: 45%; left: 50%; transform: translate(-50%, -50%);"></div>');
$('#main-wrapper').append('<div id="top-wrapper" style="display: flex; justify-content: center;"></div>');
//===== CHESSBOARD =========================================================
$('#top-wrapper').append('<div id="chess-wrapper" style="width: 402px; margin: 20px 10px;"></div>');
$('#chess-wrapper').append('<div id="chess-board" style="width:100%; margin:0 auto;"></div>');
//==========================================================================
//===== INFO PANEL =========================================================
$('#top-wrapper').append('<div id="info-panel" style="width: 402px; height: 402px; background-color: #f0d9b5; color: black; margin: 20px 10px; padding: 20px; box-sizing: border-box; overflow: hidden; position: relative; display: block; flex-direction: column; border: 2px solid #404040;"></div>');
// Title
$('#info-panel').append(`
<div class="panel-header" style="background-color: #b58863; color: white; padding: 15px; display: flex; justify-content: space-between; align-items: center; width: calc(100% - (2 * 15px)); position: absolute; top: 0; left: 0;">
<h2 style="margin: 0; font-size: 1.5em; flex-grow: 1; text-align: center;">CHESS API GAME</h2>
</div>
`);
// Mini-Games Section
$('#info-panel').append(`
<div class="mini-game-section" style="display: flex; flex-direction: column; align-items: center; margin-top: 50;">
<h2 style="margin: 0; font-size: 1.2em; text-align: center; color: #404040;">MINI-GAMES</h2>
<div style="display: flex; justify-content: space-between; width: 100%; margin-top: 15px;">
<button id="puzzle-button" style="width: 45%; padding: 8px; font-size: 1em; color: white; background-color: #404040; cursor: pointer; border-radius: 5px; border: 2px solid #404040;">Puzzles</button>
<button id="analysis-button" style="width: 45%; padding: 8px; font-size: 1em; color: white; background-color: #404040; cursor: pointer; border-radius: 5px; border: 2px solid #404040;">Analysis</button>
</div>
<hr style="width: 80%; border: 1px solid #404040; margin-top: 20px;"/>
</div>
`);
// Computer Game Section
$('#info-panel').append(`
<div class="computer-game-section" style="display: flex; flex-direction: column; text-align: left; margin-top: 5px;">
<h2 style="margin: 0; font-size: 1.2em; text-align: center; color: #404040;">COMPUTER GAME</h2>
<div style="display: flex; justify-content: space-between; width: 100%; margin-top: 15px;">
<button id="restart-menu" style="width: 45%; padding: 8px; font-size: 1em; color: white; background-color: #b58863; cursor: pointer; border-radius: 5px; border: 2px solid #b58863;">Restart</button>
<button id="orientation-menu" style="width: 45%; padding: 8px; font-size: 1em; color: white; background-color: #b58863; cursor: pointer; border-radius: 5px; border: 2px solid #b58863;">Orientation</button>
</div>
<div style="margin-top: 16px; padding: 10px; border: 2px solid #404040; border-radius: 5px; background-color: white; color: #404040;">
<span id="text-menu" style="font-weight: normal;">The game hasn't started yet...</span>
</div>
</div>
`);
// Navigation
$('#info-panel').append(`
<div class="navigation-section" style="background-color: #b58863; display: flex; justify-content: space-around; padding: 10px; position: absolute; bottom: 20px; left: 20px; right: 20px; border-radius: 5px;">
<button id="start-menu" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">⇐</button>
<button id="pre-move-menu" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">←</button>
<button id="next-move-menu" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">→</button>
<button id="current-menu" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">⇒</button>
</div>
`);
//==========================================================================
//===== INFORMATION BAR ====================================================
$('#main-wrapper').append('<div id="info-bar" style="width: calc(100% - 20px); height: 40px; background-color: white; margin: 0 10px; padding: 5px; box-sizing: border-box; overflow: hidden; position: relative; display: block; flex-direction: column; border: 2px solid #404040;"></div>');
$('#info-bar').append(`
<div style="width: 100%; background-color: white; color: #404040; display: flex; align-items: center; height: 100%;">
<span id="text-bar" style="font-weight: normal; padding: 0 5px">Waiting for user action...</span>
</div>
`);
//==========================================================================
//===== INFO PANEL PUZZLE ==================================================
$('#info-panel').append('<div id="info-panel-puzzle" style="width: 100%; height: 100%; background-color: #f0d9b5; padding: 20px; box-sizing: border-box; overflow: hidden; position: absolute; top: 0; left: 0; display: none;"></div>');
// Title
$('#info-panel-puzzle').append(`
<div class="panel-header" style="background-color: #b58863; color: white; padding: 15px; display: flex; flex-direction: column; justify-content: space-between; align-items: center; width: calc(100% - (2 * 15px)); position: absolute; top: 0; left: 0;">
<button id="back-button-puzzle" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em; position: absolute; left:20px; top:50%; transform:translateY(-50%);">←</button>
<h2 style="margin: 0; font-size: 1.5em; flex-grow: 1; text-align: center;">PUZZLES</h2>
</div>
`);
// Score & Accuracy
$('#info-panel-puzzle').append(`
<div class="score-section" style="margin-top: 50px; font-size: 1.2em; display: flex; flex-direction: column; align-items: center;">
<p style="margin: 5px 0; color: #404040; font-weight: bold;">SCORE : <span id="score" style="font-weight: bold;">0</span></p>
<p style="margin: 5px 0; color: #404040;">My accuracy : <span id="user-accuracy" style="font-weight: bold;">0%</span></p>
<p style="margin: 5px 0; color: #404040;">Stockfish v1 API accuracy : <span id="stockfishv1-accuracy" style="font-weight: bold;">0%</span></p>
<p style="margin: 5px 0; color: #404040;">Stockfish v2 API accuracy : <span id="stockfishv2-accuracy" style="font-weight: bold;">0%</span></p>
</div>
`);
// Solution Section
$('#info-panel-puzzle').append(`
<div class="solution-section" style="display: flex; justify-content: space-between; margin-top: 15;">
<button id="solution-button" style="width: 45%; padding: 8px; font-size: 1em; color: white; background-color: #404040; cursor: pointer; border-radius: 5px; border: 2px solid #404040;">Solution</button>
<button id="next-move-button" style="width: 45%; padding: 8px; font-size: 1em; color: white; background-color: #404040; cursor: pointer; border-radius: 5px; border: 2px solid #404040;">Next Move</button>
</div>
`);
// Next Puzzle
$('#info-panel-puzzle').append(`
<div class="next-section" style="display: flex; justify-content: center; align-items: center; margin-top: 20px;">
<button id="next-puzzle-button" style="width: 45%; padding: 8px; font-size: 1em; color: white; background-color: #404040; cursor: pointer; border-radius: 5px; border: 2px solid #404040;">Next Puzzle</button>
</div>
`);
// Navigation
$('#info-panel-puzzle').append(`
<div class="navigation-section" style="background-color: #b58863; display: flex; justify-content: space-around; padding: 10px; position: absolute; bottom: 20px; left: 20px; right: 20px; border-radius: 5px;">
<button id="start-puzzle" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">⇐</button>
<button id="pre-move-puzzle" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">←</button>
<button id="next-move-puzzle" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">→</button>
<button id="current-puzzle" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">⇒</button>
</div>
`);
//==========================================================================
//===== INFO PANEL ANALYSIS ================================================
$('#info-panel').append('<div id="info-panel-analysis" style="width: 100%; height: 100%; background-color: #f0d9b5; padding: 20px; box-sizing: border-box; overflow: hidden; position: absolute; top: 0; left: 0; display: none;"></div>');
// Title
$('#info-panel-analysis').append(`
<div class="panel-header" style="background-color: #b58863; color: white; padding: 15px; display: flex; flex-direction: column; justify-content: space-between; align-items: center; width: calc(100% - (2 * 15px)); position: absolute; top: 0; left: 0;">
<button id="back-button-analysis" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em; position: absolute; left:20px; top:50%; transform:translateY(-50%);">←</button>
<h2 style="margin: 0; font-size: 1.5em; flex-grow: 1; text-align: center;">ANALYSIS</h2>
</div>
`);
// Parameters Section
$('#info-panel-analysis').append(`
<div class="parameters-section" style="display: flex; flex-direction: column; align-items: center; justify-content: center; margin-top: 50px; padding: 0 20px;">
<h2 style="margin: 0; font-size: 1.2em; text-align: center; color: #404040;">SETTINGS</h2>
<div style="display: flex; flex-direction: column; align-items: flex-start; width: 100%; margin-top: 10px;">
<!-- White Selection -->
<div style="display: flex; justify-content: space-between; margin-bottom: 10px; text-align: left; width: 100%;">
<label for="white" style="flex: 1; font-size: 1em; color: #404040;">White:</label>
<select id="white" style="flex: 2; padding: 5px; color: #404040;">
<option value="v1" style="color: #404040;">Stockfish v1</option>
<option value="v2" style="color: #404040;">Stockfish v2</option>
</select>
</div>
<!-- Black Selection -->
<div style="display: flex; justify-content: space-between; margin-bottom: 10px; text-align: left; width: 100%;">
<label for="black" style="flex: 1; font-size: 1em; color: #404040;">Black:</label>
<select id="black" style="flex: 2; padding: 5px; color: #404040;">
<option value="v2" style="color: #404040;">Stockfish v2</option>
<option value="v1" style="color: #404040;">Stockfish v1</option>
</select>
</div>
<!-- Depth Selection -->
<div style="display: flex; justify-content: space-between; margin-bottom: 10px; text-align: left; width: 100%;">
<label for="depth" style="flex: 1; font-size: 1em; color: #404040;">Depth:</label>
<select id="depth" style="flex: 2; padding: 5px; color: #404040;">
<option value="1" style="color: #404040;">1</option>
<option value="2" style="color: #404040;">2</option>
<option value="3" style="color: #404040;">3</option>
<option value="4" style="color: #404040;">4</option>
<option value="5" style="color: #404040;">5</option>
<option value="6" style="color: #404040;">6</option>
<option value="7" style="color: #404040;">7</option>
<option value="8" style="color: #404040;">8</option>
<option value="9" style="color: #404040;">9</option>
<option value="10" style="color: #404040;">10</option>
<option value="11" style="color: #404040;">11</option>
<option value="12" style="color: #404040;">12</option>
<option value="13" style="color: #404040;">13</option>
</select>
</div>
<!-- Speed Selection -->
<div style="display: flex; justify-content: space-between; margin-bottom: 10px; text-align: left; width: 100%;">
<label for="speed" style="flex: 1; font-size: 1em; color: #404040;">Speed:</label>
<select id="speed" style="flex: 2; padding: 5px; color: #404040;">
<option value="fast" style="color: #404040;">Fast</option>
<option value="medium" style="color: #404040;">Medium</option>
<option value="slow" style="color: #404040;">Slow</option>
</select>
</div>
</div>
`);
//Game Parameters
$('#info-panel-analysis').append(`
<div class="navigation-section" style="background-color: #404040; display: flex; justify-content: space-around; padding: 5px; position: absolute; bottom: 90px; left: 75px; right: 75px; border-radius: 5px;">
<button id="play-analysis" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">▶</button>
<button id="pause-analysis" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">❚❚</button>
<button id="restart-analysis" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">↺</button>
<button id="random-analysis" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">﹖</button>
<button id="orientation-analysis" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">◨</button>
</div>
`);
// Navigation
$('#info-panel-analysis').append(`
<div class="navigation-section" style="background-color: #b58863; display: flex; justify-content: space-around; padding: 10px; position: absolute; bottom: 20px; left: 20px; right: 20px; border-radius: 5px;">
<button id="start-analysis" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">⇐</button>
<button id="pre-move-analysis" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">←</button>
<button id="next-move-analysis" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">→</button>
<button id="current-analysis" style="background-color: transparent; color: white; border: none; cursor: pointer; font-size: 1.5em;">⇒</button>
</div>
`);
//==============================================================================
//===== CODE PART ==============================================================
//The main() is quite simple: we import our two libraries, initialize our board
//and menu, and start the mini-games if the buttons are clicked!
//==============================================================================
$.getScript("/uploads/korentin/chess.js", function() {
$.getScript ( "/uploads/korentin/chessboard.js", function(){
//===== MENU =======================================================
[board, game] = initBoard('chess-board');
initMenu(board, game);
//==================================================================
//===== PUZZLE GAME ================================================
$('#puzzle-button').on('click', function() {
$('#info-panel-puzzle').css('display', 'block');
startPuzzle(board, game);
});
//==================================================================
//===== ANALYSIS GAME ==============================================
$('#analysis-button').on('click', function() {
$('#info-panel-analysis').css('display', 'block');
document.getElementById("text-bar").textContent = "Analysis game!";
startAnalysis(board, game);
});
//==================================================================
});
});
//==========================================================================
});
//==============================================================================