// Cloned by Killian Daly on 23 Nov 2024 from World "Deep Flannel" by Killian Daly
// Please leave this clone trail here.
// Cloned by Killian Daly on 25 May 2024 from World "One Cube World (P5)" by Starter user
// Please leave this clone trail here.
// Test cases from Alisson Gichette
const boardSize = 600;
const tileSize = boardSize / 8;
let canvasWidth, canvasHeight;
let chess = null;
let pieceImages = {};
// Create dictionary of pieces and corresponding images to display
// Wikimedia Commons. (2022). Wikimedia.org. https://commons.wikimedia.org/wiki/Category:PNG_chess_pieces/Standard_transparent#
const pieceImageUrls = {
w: {
p: "https://upload.wikimedia.org/wikipedia/commons/0/04/Chess_plt60.png",
r: "https://upload.wikimedia.org/wikipedia/commons/5/5c/Chess_rlt60.png",
n: "https://upload.wikimedia.org/wikipedia/commons/2/28/Chess_nlt60.png",
b: "https://upload.wikimedia.org/wikipedia/commons/9/9b/Chess_blt60.png",
q: "https://upload.wikimedia.org/wikipedia/commons/4/49/Chess_qlt60.png",
k: "https://upload.wikimedia.org/wikipedia/commons/3/3b/Chess_klt60.png",
},
b: {
p: "https://upload.wikimedia.org/wikipedia/commons/c/cd/Chess_pdt60.png",
r: "https://upload.wikimedia.org/wikipedia/commons/a/a0/Chess_rdt60.png",
n: "https://upload.wikimedia.org/wikipedia/commons/f/f1/Chess_ndt60.png",
b: "https://upload.wikimedia.org/wikipedia/commons/8/81/Chess_bdt60.png",
q: "https://upload.wikimedia.org/wikipedia/commons/a/af/Chess_qdt60.png",
k: "https://upload.wikimedia.org/wikipedia/commons/e/e3/Chess_kdt60.png",
},
};
// chess.js a chess library for chess move generation/validation, piece placement/movement and check/checkmate/draw detection
// Jeff Hlywa
// https://github.com/jhlywa/chess.js/tree/master
// code sourced from https://www.npmjs.com/package/chess.js/v/0.13.0?activeTab=readme
$.getScript("uploads/dalyk34/chess-min.js", function () {
// Create chess object to use chess.js functionality
chess = new Chess();
});
function preload() {
// Audio for moves from http://images.chesscomfiles.com/chess-themes/sounds/_MP3_/default/notify.mp3
moveSound = new Audio("uploads/dalyk34/notify.mp3");
moveSound.volume = 0.2;
// Music from Valve Corporation for non-commercial use in line with copyright policy
const MUSICFILE = 'https://ia801404.us.archive.org/33/items/fight-songs-the-music-of-team-fortress-2-flac/14.%20Valve%20Studio%20Orchestra%20-%20Archimedes.mp3';
AB.backgroundMusic ( MUSICFILE );
// Load piece images
for (let color in pieceImageUrls) {
pieceImages[color] = {};
for (let type in pieceImageUrls[color]) {
pieceImages[color][type] = loadImage(pieceImageUrls[color][type]);
}
}
}
function setup() {
canvasWidth = windowWidth;
canvasHeight = windowHeight;
createCanvas(canvasWidth, canvasHeight);
// Chess Stockfish 16 gives a broken move when castling, so we disable castling by moving the pieces at start
chess.load(chess.fen().replace(/ [KQkq]+ /, " - "));
drawChessboard();
drawPieces();
makeWhiteMove();
}
function draw() {
background("LightBlue");
drawChessboard();
drawPieces();
drawWinnerBox();
}
function drawChessboard() {
const startX = (canvasWidth - boardSize) / 2;
const startY = (canvasHeight - boardSize) / 2;
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
// Draw chessboard squares
fill((row + col) % 2 === 0 ? 240 : 181, (row + col) % 2 === 0 ? 217 : 136, (row + col) % 2 === 0 ? 181 : 68);
noStroke();
rect(startX + col * tileSize, startY + row * tileSize, tileSize, tileSize);
}
}
}
function drawPieces() {
if (!chess) return;
const board = chess.board();
const startX = (canvasWidth - boardSize) / 2;
const startY = (canvasHeight - boardSize) / 2;
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
const piece = board[row][col];
if (piece) {
const img = pieceImages[piece.color][piece.type];
if (img) {
image(img, startX + col * tileSize, startY + row * tileSize, tileSize, tileSize);
}
}
}
}
}
function updatePiecesFromChess() {
const board = chess.board();
pieces = board.map((row) =>
row.map((square) => (square ? square.type.toUpperCase() : " "))
);
}
function redrawBoard() {
background("beige");
drawChessboard();
drawPieces();
}
function drawWinnerBox(whiteWin) {
const boxWidth = 260;
const boxHeight = 100;
const padding = 20;
fill(181, 136, 68);
rect(padding, padding, boxWidth, boxHeight, 10);
// Black for stockfish 17
fill(0);
textSize(boxHeight / 4);
textAlign(LEFT, CENTER);
textStyle(BOLD);
const textX = padding + 10;
const textY = padding + boxHeight / 2;
if (chess.in_checkmate()) {
text(`Winner: Stockfish ${chess.turn() === "w" ? "17" : "16"}`, textX, textY);
} else if (chess.in_stalemate() || chess.in_draw()) {
text("No winner!", textX, textY);
} else {
text("Stockfish 17 vs.", textX, textY - 10);
// WHite text for white player stockfish 16
fill(255)
text("Stockfish 16", textX, textY + 20);
}
// We check a boolean value because Stockfish 16 doesn't give its final winning move
if (whiteWin) {
text(`Winner: Stockfish 16"}`, textX, textY);
}
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
// Play sound whenever a move is made
function playMoveSound() {
if (moveSound) {
// Reset to the start of the sound
moveSound.currentTime = 0;
moveSound.play();
}
}
// Get move from Chess StockFish 16 API @ https://rapidapi.com/cinnamon17/api/chess-stockfish-16-api
async function makeWhiteMove() {
// End game if checkmate, stalemate, or draw
if (checkGameState()) return;
const form = new FormData();
// Get current board state in fen format for White's move
form.append("fen", chess.fen());
const settings = {
async: true,
crossDomain: true,
url: "https://chess-stockfish-16-api.p.rapidapi.com/chess/api",
method: "POST",
headers: {
"x-rapidapi-key": "291a0dfb38mshf850128e2ea20d3p1a81cejsn8348250cd9d2", // API key
"x-rapidapi-host": "chess-stockfish-16-api.p.rapidapi.com",
},
processData: false,
contentType: false,
mimeType: "multipart/form-data",
data: form,
};
$.ajax(settings).done(function (response) {
const data = JSON.parse(response);
// Stockfish 16 does not give winning moves, for some reason, but that means bestmove (none) is a winning move
if (data.result === "bestmove (none)" && data.message === "There is no available moves from this position") {
console.log("Checkmate! White wins");
// Let draw winner box know the winning move happened
drawWinnerBox(true);
return;
}
const bestMove = data.bestmove;
// Apply move and trigger response if move is valid
if (bestMove) {
applyMove(bestMove);
}
});
// Provide a turn delay so we don't hit API rate limits
await turnDelay();
makeBlackMove();
}
// Get move from Chess-API @ https://chess-api.com/ powered by Stockfish 17
async function makeBlackMove() {
// End game if checkmate, stalemate, or draw
if (checkGameState()) return;
const fenData = {
// Stockfish 17 recognises a different boardstate format than chess.js, so we process it
fen: await preprocessFenForBlack(chess.fen()),
// Max settings
variants: 5,
depth: 18,
maxThinkingTime: 100,
};
try {
const response = await fetch("https://chess-api.com/v1", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(fenData),
});
// Parse the response as JSON
const data = await response.json();
const bestMove = data.move;
// Apply move and trigger response if move is valid
if (bestMove) {
applyMove(bestMove);
}
} catch (error) {
console.error("Error in Black's move:", error);
}
// Provide a turn delay so we don't hit API rate limits or become desynced
await turnDelay();
makeWhiteMove();
}
// Function to preprocess FEN using the correct turn from chess.js
async function preprocessFenForBlack(fen) {
let parts = fen.split(" ");
// Modify the turn to 'b' for black's turn, and update the halfmove/fullmove counters if needed
parts[1] = 'b'; // Force black's turn (if needed)
return parts.join(" ");
}
// We can slice the string e5e6 and make sense of it as e5 to e6 for example
function applyMove(bestMove) {
const from = bestMove.slice(0, 2);
const to = bestMove.slice(2, 4);
const move = { from, to };
// Stockfish 16 can't handle pawn promotion choice so they're always promoted to queen
const piece = chess.get(from);
if (piece && piece.type === "p" && (to[1] === "1" || to[1] === "8")) {
// Promote to queen
move.promotion = "q";
}
const result = chess.move(move);
if (result) {
console.log(move);
updatePiecesFromChess();
redrawBoard();
playMoveSound();
}
}
// A turn delay to avoid hitting API rate limits
async function turnDelay() {
await new Promise((resolve) => { setTimeout(resolve, 2000); });
}
// Checking whether game has ended using chess.js library
function checkGameState() {
if (chess.in_checkmate()) {
console.log(`Checkmate! ${chess.turn() === "w" ? "Black" : "White"} wins.`);
drawWinnerBox(false);
return true;
} else if (chess.in_stalemate() || chess.in_draw()) {
console.log("It's a draw.");
drawWinnerBox(false);
return true;
}
return false;
}