/*
* Please note!
* All of this work is mine EXCEPT for the fenToBoard function
* The source is provided above the function declaration at line ~136
* Otherwise, everything else is original to me and my partner
*
* Authors -----------¬
* - Rhodney Paraiso
* - Evan Mackey
*
* You may use a sample input for a FEN position, located below
* r1bqkbnr/pp2pppp/2np4/8/3NP3/2N5/PPP2PPP/R1BQKB1R b KQkq - 2 5
*/
document.write(`
<html>
<header>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&display=swap" rel="stylesheet">
</header>
</html>
<body>
<div>
<div class="title-title">
<h1>
Welcome to the Chess App
</h1>
</div>
<div class="title-title">
<p1>
This is an Ancient Brain world that will analyse your chess FEN position and output the best moves to make from our API'S
</p1>
</div>
<div class="title-title">
<p1>
Please be sure to enter the Hugging Face API Key first!
</p1>
</div>
<div class="input-container">
<input type="text" id="hf_api_key" placeholder="Enter HF API Key">
<button onclick="setKey()">Enter Key</button>
</div>
<div class="input-container">
<input type="text" id="fen_input" placeholder="Enter FEN position">
<button onclick="processFen()">Show Board</button>
</div>
<br>
<div id="output-title" style="width: 100vw; text-align: center"></div>
<div id="output"></div>
<div id="output-title"></div>
<div id="output-bestmove"></div>
</div>
</body>
<style>
* {
font-family: "Geist Mono", monospace;
font-optical-sizing: auto;
font-style: normal;
}
.title-title {
display: flex;
justify-content: center;
}
.input-container {
display: flex;
justify-content: center;
gap: 10px;
margin: 20px;
}
#output {
display: flex;
justify-content: center;
margin-top: 20px;
white-space: pre;
font-family: monospace;
}
#output-title {
display: flex;
justify-content: center;
margin-top: 20px;
white-space: pre;
font-family: monospace;
}
#output-bestmove {
display: flex;
justify-content: center;
margin-top: 20px;
white-space: pre;
font-family: monospace;
}
</style>
`);
// VARIABLES
let api_key = "";
async function setKey() {
const hf_api_key = document.getElementById('hf_api_key').value;
api_key = hf_api_key;
console.log("HF API Key set to:", api_key);
}
async function processFen() {
const fen_input = document.getElementById('fen_input').value;
const board = fenToBoard(fen_input);
// TESTING
const STOCKFISH_RESULT = await stockfishAPI(fen_input); // Stockfish API
const top_moves = await getTopFiveMoves(fen_input); // To get the top 5 moves (for ranking)
const LICHESS_API = await lichessAPI(fen_input); // Lichess API
const CHESSAPI_RESULT = await chessAPI({ fen: fen_input }); // Chess.com API
const GITHUB_RESULT = await githubAPI(fen_input); // Github API
const CHINESECN_RESULT = await chineseDBAPI(fen_input); // Github API
const HF_RESULT = await huggingFaceAPI(fen_input); // Hugging Face API
console.log(HF_RESULT)
console.log("RESULT IS BELOW");
console.log(CHINESECN_RESULT);
const formattedBoard = board.map(row => row.join(' ')).join('\n'); // Formatting the board
let output = document.getElementById('output');
let output_message = ""; // Constructing the output message
output.innerHTML = `${formattedBoard}\n`;
output_message = output_message + "Here are the best moves provided by our chess engines\n\n";
output_message = output_message + `\nStockfish API: ${STOCKFISH_RESULT} Score: (${top_moves[STOCKFISH_RESULT] || "Not a good move"})\n`;
output_message = output_message + `\nChess API: ${CHESSAPI_RESULT.move} Score: (${top_moves[CHESSAPI_RESULT.move] || "Not a good move"})\n`;
output_message = output_message + `\nLichess API: ${LICHESS_API} Score: (${top_moves[LICHESS_API] || "Not a good move"})\n`;
output_message = output_message + `\nGitHub API: ${GITHUB_RESULT?.message} Score: (${top_moves[GITHUB_RESULT] || "Not a good move"})\n`;
output_message = output_message + `\nChineseCN API: ${CHINESECN_RESULT.best_move} Score: (${top_moves[CHINESECN_RESULT.best_move] || "Not a good move"})\n`;
output_message = output_message + `\nHugging Face API: (Check the console!)\n`;
output = document.getElementById('output-bestmove');
output.innerHTML = output_message;
}
// Stockfish
// Inspired from https://stackoverflow.com/questions/66451525/how-to-convert-fen-id-onto-a-chess-board
// Function => Convert FEN to Board in text
function fenToBoard(fen) {
const board = []
const rows = fen.split('/')
for (const row of rows) {
const brow = []
for (const c of row) {
if (c === ' ') break
if ('12345678'.includes(c)) {
brow.push(...Array(parseInt(c)).fill('--'))
} else if (c === 'p') {
brow.push('bp')
} else if (c === 'P') {
brow.push('wp')
} else if (c > 'Z') {
brow.push('b' + c.toUpperCase())
} else {
brow.push('w' + c)
}
}
board.push(brow)
}
return board
}
/// Now using Stockfish API
// Creditt => https://lichess.org/api
const stockfishAPI = async (fenPosition) => {
const options = {
method: 'GET',
headers: { 'Accept': 'application/json' }
};
const encodedFen = encodeURIComponent(fenPosition);
const url = `https://lichess.org/api/cloud-eval?fen=${encodedFen}`;
try {
const response = await fetch(url, options);
const data = await response.json();
const best_move = data.pvs[0].moves.split(' ')[0];
return best_move;
} catch (err) {
console.error('Error:', err);
return 'Error analyzing position';
}
};
// Credit => https://docs.github.com/en/rest/activity/events?apiVersion=2022-11-28
async function githubAPI() {
try {
const response = await fetch("https://api.github.com/events", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
return await response.json();
} catch (error) {
console.error("Error querying GitHub API:", error);
return { message: 'Error fetching data' };
}
}
// Just getting the top 5 moves from Lichess API
// Creditt => https://lichess.org/api
async function getTopFiveMoves(fen) {
const url = `https://lichess.org/api/cloud-eval?fen=${encodeURIComponent(fen)}`;
try {
const response = await fetch(url);
const data = await response.json();
const main_lines = data?.pvs?.[0]?.moves?.split(' ') || [];
const move_score_dict = {};
main_lines.slice(0, 5).forEach((move, index) => {
const baseScore = data?.pvs?.[0]?.cp || 0;
const scoreAdjustment = index * 5;
const adjustedScore = (baseScore - scoreAdjustment) / 100;
move_score_dict[move] = adjustedScore.toFixed(2);
});
console.log(fen);
return move_score_dict;
} catch (error) {
return { error: 'Error fetching moves' };
}
}
// Credit => https://explorer.lichess.ovh
async function lichessAPI(fen) {
const url = 'https://explorer.lichess.ovh/masters';
try {
const response = await fetch(`${url}?fen=${encodeURIComponent(fen)}`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
if (!response.ok) {
return 'API response not ok';
}
const data = await response.json();
const best_move = data?.moves?.[0]?.uci || 'No move available';
return best_move;
} catch (error) {
console.log('An error occurred:', error);
return 'Error fetching move';
}
}
// Credit => https://chess-api.com/
async function chessAPI(data = {}) {
const response = await fetch("https://chess-api.com/v1", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data),
});
return response.json();
}
// Using a Chinese chess database API
// Credit => https://www.chessdb.cn/cloudbookc_api.html
const chineseDBAPI = async (fenPosition) => {
const options = {
method: 'GET',
headers: {
'Accept': 'application/json'
}
};
const encodedFen = encodeURIComponent(fenPosition);
const url = `https://www.chessdb.cn/cdb.php?action=queryall&board=${encodedFen}&json=1`;
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data && data.moves && data.moves.length > 0) {
const best_move = data.moves[0].uci;
const score = data.moves[0].score;
return { best_move, score };
} else {
console.log('No moves found in analysis');
return null;
}
} catch (error) {
console.log('Analysis request:', error.message);
return null;
}
};
// Credit => https://huggingface.co/openai-community/gpt2
async function huggingFaceAPI(fen) {
const url = 'https://api-inference.huggingface.co/models/gpt2';
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${api_key}`
},
body: JSON.stringify({
inputs: `Hugging face output ------ \nAnalyze this chess position: ${fen}. What's the best move?`,
})
});
const data = await response.json();
return data[0].generated_text;
} catch (error) {
return 'Analysis unavailable';
}
}