// Cloned by Conor Mc Quillan on 18 Nov 2024 from World "Chess" by Mathias Bazin
// Please leave this clone trail here.
// Using Lichess cloud evaluation API: https://lichess.org/api
// Using Stockfish API (via Lichess Cloud Eval)
// Credits:
// Using Lichess Cloud Evaluation API: https://lichess.org/api
// Note: Lichess API provides multiple evaluation options. Here, we specifically obtain moves from the Stockfish engine through Lichess.
// I compare two responses from the Lichess API to simulate different AI models (e.g., Stockfish at different evaluation depths).
// - Lichess Cloud Evaluation API: https://lichess.org/api (i used to evaluate board positions with Stockfish at different depths).
// - Comparison is based on Lichess Stockfish evaluations at two different settings.
// - Tutorials: General tutorial on using Fetch API in JavaScript: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
// - Chessboard concepts learned from Chess.js library documentation: https://github.com/jhlywa/chess.js
// - CSS gradient background inspiration: https://www.w3schools.com/css/css3_gradients.asp
// Conor's Code: Added setup control flag and GPT move cache
let setupCalled = false; // need to Prevent duplicate setup calls
let gptMoveCache = {}; // now need to Move cache
const fallbackMoves = {
'8/8/8/8/3k4/3P4/4K3/8 w - - 0 1': 'e2d2', // Endgame position
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1': 'e2e4', // Starting position
'8/8/8/8/4k3/3P4/4K3/8 b - - 0 1': 'd4e3', // Black to move in endgame
};
// Original Code: Declaring variables for chess game, square size, and offsets
let chess; // Chess object
let scl = 40;//swqaure size
let xoff = 250;// x
let yoff = 300;// y
// Original Code: Images for chess pieces
let bk, bq, bn, br, bb, bp, wk, wq, wn, wr, wb, wp; // peice images
// Original Code: Setting runReady to false
AB.runReady = false;
// Conor's Code/ original: Modified preload function
// Loading images for all the pieces
function preload() {
bk = loadImage("/uploads/mathias/bk.png");
bq = loadImage("/uploads/mathias/bq.png");
bn = loadImage("/uploads/mathias/bn.png");
br = loadImage("/uploads/mathias/br.png");
bb = loadImage("/uploads/mathias/bb.png");
bp = loadImage("/uploads/mathias/bp.png");
wk = loadImage("/uploads/mathias/wk.png");
wq = loadImage("/uploads/mathias/wq.png");
wr = loadImage("/uploads/mathias/wr.png");
wn = loadImage("/uploads/mathias/wn.png");
wb = loadImage("/uploads/mathias/wb.png");
wp = loadImage("/uploads/mathias/wp.png");
}
// Conor's Code / original: Function to wait for p5.js to load
// Added to handle asynchronous loading of p5.js library
function waitForP5(callback, interval = 100) {
const intervalId = setInterval(() => {
if (typeof createCanvas !== "undefined") {
clearInterval(intervalId);
callback();
} else {
console.log("Waiting for p5.js to be ready...");
}
}, interval);
}
// Conor's Code: Called waitForP5 to initiate setup
waitForP5(setup);
// Conor's Code: New setup function to initialize the board and create canvas other origina code didnt suit
function setup() {
if (setupCalled) return;
setupCalled = true;
console.log("setup called!");
if (typeof createCanvas !== "undefined") {
createCanvas(800, 800); // Create canvas for chessboard
} else {
console.error("p5.js is not ready, cannot create canvas.");
return;
}
// Conor's Custom Chess Object
// Simplified chess representation without using chess.js library
chess = {
board: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
fen: function () {
return this.board;
},
makeMove: function (move) {
console.log("Making move:", move);
this.board = this.board.replace(' w ', ' b '); // Alternate turns
},
};
// Conor's Code: Draw grid and initialize board with FEN input
drawGrid();
drawBoard(chess.fen());
addFenInput(); // Add the FEN input field
addButtons(); // Add the "Get AI Moves" button
}
// Conor's Code: New draw function for p5.js
// Clears and redraws the board every frame
function draw() {
background(255); // clear the canvas
drawGrid();
drawBoard(chess.fen());
}
// Conor's Code: Adding FEN input field for users to update the board
function addFenInput() {
// Create a container for FEN input
let fenContainer = document.createElement("div");
fenContainer.style.position = "absolute";
fenContainer.style.top = "20px";
fenContainer.style.left = "50%";
fenContainer.style.transform = "translateX(-50%)";
fenContainer.style.zIndex = "9999";
fenContainer.style.textAlign = "center";
// Create a label for FEN input
let fenLabel = document.createElement("label");
fenLabel.innerHTML = "Enter FEN: ";
fenLabel.style.fontSize = "16px";
fenContainer.appendChild(fenLabel);
// Create an input box for FEN
let fenInput = document.createElement("input");
fenInput.type = "text";
fenInput.id = "fen-input";
fenInput.style.padding = "5px";
fenInput.style.fontSize = "14px";
fenInput.style.marginRight = "10px";
fenContainer.appendChild(fenInput);
// Create a button to submit FEN
let fenButton = document.createElement("button");
fenButton.innerHTML = "Update Board";
fenButton.style.padding = "5px 10px";
fenButton.style.fontSize = "14px";
fenContainer.appendChild(fenButton);
document.body.appendChild(fenContainer);
// Add an event listener to update the board based on the FEN input
fenButton.addEventListener("click", function () {
const fen = fenInput.value.trim();
if (fen) {
console.log("Updating board with FEN:", fen);
chess.board = fen; // first Update the chess object with the new FEN
drawBoard(fen); // now Redraw the board with the new FEN
} else {
console.error("FEN input is empty!");
}
});
}
// Conor's Code: Adding a button function to help to fetch AI moves
function addButtons() {
console.log("addButtons called!");
let moveButton = document.createElement("button");
moveButton.innerHTML = "Get AI Moves";
moveButton.style.position = "absolute";
moveButton.style.bottom = "20px";
moveButton.style.left = "50%";
moveButton.style.transform = "translateX(-50%)";
moveButton.style.padding = "10px 20px";
moveButton.style.fontSize = "16px";
moveButton.style.zIndex = "9999";
document.body.appendChild(moveButton);
moveButton.addEventListener("click", async function () {
console.log("Button clicked!");
if (chess) {
const fen = chess.fen(); // Get the current chessboard position in FEN
console.log("Current FEN:", fen);
// Fetch moves trying to use fallback logic
const stockfishMove = await getMoveWithFallback(getMoveFromStockfish, fen);
console.log("Stockfish suggested move:", stockfishMove);
const lichessMove = await getMoveWithFallback(getMoveFromLichess, fen);
console.log("Lichess suggested move:", lichessMove);
//now redraw the board
drawBoard(chess.fen());
// now display both moves
displayComparison(stockfishMove, lichessMove);
} else {
console.error("Chess instance not available when button clicked.");
}
});
}
// oringinal code
function drawGrid() {
strokeWeight(2); // adding thicker lines here
for (let i = 0; i < 8; i++) {
for (let j = 0; j < 8; j++) {
let isLightSquare = (i + j) % 2 === 0;
fill(isLightSquare ? "#f0d9b5" : "#b58863"); // Light and dark squares so it looks better visually kinda
rect(xoff + j * scl, yoff + i * scl, scl, scl);
}
}
}
//Oringinal code
function drawBoard(board) {
let square = 0;
for (let cur of board) {
const x = xoff + scl * (square % 8);
const y = yoff + scl * Math.floor(square / 8);
// Draw pieces original code
switch (cur) {
case "r":
image(br, x, y);
square++;
break;
case "n":
image(bn, x, y);
square++;
break;
case "b":
image(bb, x, y);
square++;
break;
case "q":
image(bq, x, y);
square++;
break;
case "k":
image(bk, x, y);
square++;
break;
case "p":
image(bp, x, y);
square++;
break;
case "R":
image(wr, x, y);
square++;
break;
case "N":
image(wn, x, y);
square++;
break;
case "B":
image(wb, x, y);
square++;
break;
case "Q":
image(wq, x, y);
square++;
break;
case "K":
image(wk, x, y);
square++;
break;
case "P":
image(wp, x, y);
square++;
break
case "/":
break;
case " ":
return;
case "1": case "2": case "3": case "4":
case "5": case "6": case "7": case "8":
square += +cur;
break;
default:
console.log("Error in drawBoard");
}
}
}
// Original function with modifications
async function getMoveFromStockfish(fen) {
const apiUrl = "https://lichess.org/api/cloud-eval";
try {
const response = await fetch(`${apiUrl}?fen=${encodeURIComponent(fen)}`, { method: "GET" });
if (!response.ok) {
console.error(`Stockfish API Error: ${response.status} - ${response.statusText}`);
return "No move found";
}
const data = await response.json();
console.log("Stockfish API response:", data);
return data.pvs && data.pvs[0] ? data.pvs[0].moves.split(" ")[0] : "No move found";
} catch (error) {
console.error("Error with Stockfish API:", error);
return "Error";
}
}
// Conor's Code: This Fetch move from Lichess using cloud evaluation API
async function getMoveFromLichess(fen) {
const apiUrl = "https://lichess.org/api/cloud-eval";
try {
console.log(`Sending request to Lichess API for FEN: ${fen}`);
// Perform GET request without API key (Conor's modification)
const response = await fetch(`${apiUrl}?fen=${encodeURIComponent(fen)}`, { method: "GET" });
// Handle unsuccessful response
if (!response.ok) {
console.error(`Lichess API Error: ${response.status} - ${response.statusText}`);
if (response.status === 404) {
console.warn("Position evaluation not found in Lichess cloud eval (404). Falling back to alternatives...");
}
return "No move found";
}
// Parse response (Conor's Code)
const data = await response.json();
console.log("Lichess API response:", data);
return data.pvs && data.pvs[0] ? data.pvs[0].moves.split(" ")[0] : "No move found";
} catch (error) {
console.error("Error with Lichess API:", error);
return "Error";
}
}
// Conor's Code: Fallback function to handle move fetching from APIs
async function getMoveWithFallback(apiCall, fen) {
let move = await apiCall(fen);
if (move === "No move found" || move === "Error") {
console.warn(`Falling back for FEN: ${fen}`);
// If Lichess fails, use fallback moves if available
move = fallbackMoves[fen] || "No legal moves available";
}
return move;
}
// Conor's Code: Function to display comparison between Stockfish and Lichess moves
function displayComparison(stockfishMove, lichessMove) {
let resultsSection = document.getElementById("results-section");
if (!resultsSection) {
resultsSection = document.createElement("div");
resultsSection.id = "results-section";
resultsSection.style.border = "1px solid #000"; // fix bug
resultsSection.style.padding = "10px";
resultsSection.style.marginTop = "20px";
document.body.appendChild(resultsSection);
}
// Clear previous results
resultsSection.innerHTML = "";
// Add Stockfish and Lichess results
const stockfishDiv = document.createElement("div");
stockfishDiv.innerHTML = `<strong>Stockfish Move:</strong> ${stockfishMove}`;
resultsSection.appendChild(stockfishDiv);
const lichessDiv = document.createElement("div");
lichessDiv.innerHTML = `<strong>Lichess Move:</strong> ${lichessMove}`;
resultsSection.appendChild(lichessDiv);
// Now compare the two moves
if (stockfishMove === lichessMove) {
const agreementDiv = document.createElement("div");
agreementDiv.innerHTML = `<strong>Result:</strong> Both engines agree on the move!`;
agreementDiv.style.color = "Gree";
resultsSection.appendChild(agreementDiv);
} else {
const disagreementDiv = document.createElement("div");
disagreementDiv.innerHTML = `<strong>Result:</strong> Engines suggest different moves.`;
disagreementDiv.style.color = "red";
resultsSection.appendChild(disagreementDiv);
}
}
// Original function with modifications
function displayStockfishMove(stockfishMove) {
let resultsSection = document.getElementById("results-section");
if (!resultsSection) {
resultsSection = document.createElement("div");
resultsSection.id = "results-section";
resultsSection.style.border = "1px solid #000";
resultsSection.style.padding = "10px";
resultsSection.style.marginTop = "20px";
document.body.appendChild(resultsSection);
}
const stockfishMoveDiv = document.createElement("div");
stockfishMoveDiv.innerHTML = `<strong>Stockfish Move:</strong> ${stockfishMove}`;
resultsSection.appendChild(stockfishMoveDiv);
}
// Original function for world initialization with new modifications
function World() {
this.newRun = function () {
console.log("World initialized!");
};
}
// now adding some css for fun and just to make it look nice :) (Conors Code)
const pageStyle = document.createElement("style");
pageStyle.innerHTML = `
body {
margin: 0;
padding: 0;
background: linear-gradient(to bottom, #1e3c72, #2a5298); /* Dark blue gradient */
color: white;
font-family: 'Roboto', sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh; /* Full height */
overflow: hidden; /* Prevent scrollbars */
}
canvas {
display: block;
margin: 0 auto; /* Center horizontally */
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); /* Add shadow to chessboard */
border-radius: 10px; /* Rounded corners */
}
#fen-input {
border: 2px solid #FFD700; /* Gold border */
border-radius: 5px;
padding: 10px;
width: 300px;
font-size: 14px;
margin-bottom: 10px;
}
button {
background-color: #FFD700; /* Gold button */
color: black;
border: none;
border-radius: 5px;
padding: 10px 20px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
margin: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
button:hover {
background-color: #daa520; /* Darker gold on hover */
}
#results-section {
font-family: 'Courier New', Courier, monospace;
color: #333;
background-color: rgba(255, 255, 255, 0.9); /* Slightly transparent white */
border: 1px solid #FFD700;
padding: 15px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
margin-top: 20px;
text-align: center;
}
`;
document.head.appendChild(pageStyle);