var pgn = "";
var fen = "";
var pngUrl = "";
var APIKEY1 = "";
var APIKEY2 = "";
var solution = "";
var mateValue = 1;
var tmp = 0.7;
function changeTemp(){//slider to change GPTs temperature
tmp = jQuery("input#myRange").val();
tmp = parseFloat(tmp);//change from string to float
}
function checkKeys(){
console.log(APIKEY1, APIKEY2)
if (APIKEY1 && APIKEY2){
start();
}
else{
$("#start").html ( "<b> Need API KEYS!" );
setTimeout(function() {//wait 1 second then reload page.
location.reload();
}, 1000);
}
}
function handleAPIKEY1(){
APIKEY2 = jQuery("input#apikey1").val();
}
function handleAPIKEY2(){
APIKEY1 = jQuery("input#apikey2").val();
}
function changeMateValue(){
mateValue = jQuery("input#myRange2").val();
mateValue = parseInt(mateValue);//change from string to float
}
function handleApi(event){
event.preventDefault();
// close the modal
const lockModal = bootstrap.Modal.getInstance(document.getElementById('lockModal'));
lockModal.hide();
sessionStorage.setItem("APIKEY1", APIKEY1);
}
function convert2FEN(pgn){
const chess = new Chess();
chess.load_pgn(pgn);
const fen = chess.fen();
return fen;
}
function initLockModal() {
const lockModal = new bootstrap.Modal(document.getElementById('lockModal'));
// Check if sessionStorage has a flag
if (sessionStorage.getItem('modalClosed') === "true") {
lockModal.show(); // show modal on page load
}
// Close button behavior
document.getElementById('closeModalBtn').addEventListener('click', () => {
lockModal.hide();
sessionStorage.setItem('modalClosed', 'true');
});
}
async function fenToPngUrl(fen) {
const encoded = encodeURIComponent(fen);
return `https://fen2png.com/api/?fen=${encoded}&raw=true`;
}
async function getRandomPuzzlePGN() {
const response = await fetch(`https://lichess.org/api/puzzle/next?angle=mateIn${mateValue}&difficulty=normal`, {headers: {}}
);
if (!response.ok) {
throw new Error("Invalid puzzle ID or Lichess error");
}
const data = await response.json();
console.log(data)
const pgn = data.game?.pgn || data.pgn;
solution = data.puzzle.solution;
if (!pgn || !solution) {
throw new Error("Puzzle has no PGN or SOLUTION");
}
return pgn;
}
async function fetchPuzzleImage() {
try {
document.getElementById("result1").innerHTML = "Loading answer...";
document.getElementById("result2").innerHTML = "Loading answer...";
document.querySelector("main p:nth-of-type(2)").innerHTML = "Loading puzzle...";
// 1. Get puzzle PGN
pgn = await getRandomPuzzlePGN();
console.log("PGN:", pgn);
// 2. Convert PGN → FEN
fen = convert2FEN(pgn);
console.log("FEN:", fen);
// 3. Convert FEN → PNG URL
pngUrl = await fenToPngUrl(fen);
console.log("Image URL:", pngUrl);
console.log("SOLUTION:", solution);
// 4. Display puzzle image
setTimeout(function() {
document.querySelector("main p:nth-of-type(2)").innerHTML = `
Mate in: <strong>${mateValue}</strong><br><br>
<img src="${pngUrl}" style="max-width:100%;border:1px solid #ccc;border-radius:8px;height:80%;width:80%;" />
<br><br><strong>EXPECTED SOLUTION:</strong> ${solution}
`;
}, 2000);
send2APIs();
} catch (err) {
console.error(err);
document.querySelector("main p:nth-of-type(2)").innerHTML = "Error: " + err.message;
}
}
async function callAIAPI(apiOptions, targetElementId) {
var finalText = "";
if (targetElementId === "result1"){
try {
const response = await fetch("https://api.groq.com/openai/v1/responses", apiOptions);
const data = await response.json();
finalText = data.output[1].content[0].text;
console.log("Groq Data Aquired!");
console.log(data);
// Insert into the page
document.getElementById(targetElementId).innerHTML = finalText;
} catch (error) {
console.error("Groq API Error:", error);
document.getElementById(targetElementId).innerHTML = "Error fetching response";
}
}
if (targetElementId === "result2"){
try{
const response = await fetch("https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent", apiOptions);
const data = await response.json();
console.log("Gemini Data Aquired!");
console.log(data);
finalText = data.candidates[0].content.parts[0].text;
document.getElementById(targetElementId).innerHTML = finalText;
}catch (error) {
console.error("Gemini API Error:", error);
document.getElementById(targetElementId).innerHTML = "Error fetching response";
}
}
}
async function send2APIs(){
const input = `Mate in: ${mateValue}, give the solution for the given FEN string: ${fen},give the moves required in an array showing all moves including mate. Also give a brief 20 word explaination for your decision. Do not use markdown, just text will do.`;
console.log(input);
const API1Options = {//POST to API
method: 'POST',
headers: {
'Authorization': `Bearer ${APIKEY1}`,//checking API key
'Content-Type': 'application/json'
},
body: JSON.stringify({//what to send
model: "openai/gpt-oss-120b",
temperature: tmp,
input: input,
})
};
const API2Options = {
method: "POST",
headers: {
'x-goog-api-key': `${APIKEY2}`,//checking API key
"Content-Type": "application/json"
},
body: JSON.stringify({
contents: [
{
role: "user", parts: [{
text: input
}] }
],
generationConfig: {
temperature: tmp,
}
})
}
try{
await callAIAPI(API1Options, "result1");
await callAIAPI(API2Options, "result2");
}
catch(err){
console.error(err);
}
}
function start(){
document.write(`
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
html, body { height: 100%; }
body {
padding: 0.5rem;
display: flex;
flex-direction: column;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
color: #111;
background: #f7f7f8;
line-height: 1.4;
}
button {
padding: 0.6rem 1rem;
border-radius: 6px;
border: 1px solid #1f6feb;
background: #1f6feb;
color: white;
font-weight: 600;
cursor: pointer;
font-size: 1rem;
}
header {
display: flex;
align-items: center;
margin-left: auto;
margin-right: auto;
padding: 1rem;
background: white;
border: 1px solid #e6e6e9;
}
.wrap {
display: grid;
grid-template-columns: 220px 1fr 220px;
gap: 1rem;
padding: 1rem;
flex: 1 1 auto;
align-items: start;
}
aside {
background: white;
border: 5px solid #e6e6e9;
padding: 1rem;
border-radius: 8px;
min-height: 200px;
}
set{
background: black;
}
main {
background: white;
border: 5px solid #e6e6e9;
padding: 1rem;
border-radius: 8px;
min-height: 200px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
.bottom-bar {
position: sticky;
bottom: 0;
background: #fff;
border-top: 1px solid #e6e6e9;
padding: 0.75rem 1rem;
display: flex;
gap: 0.5rem;
align-items: center;
justify-content: center;
}
.bottom-bar-inner {
width: 100%;
max-width: 1100px;
display: flex;
gap: 0.5rem;
align-items: center;
justify-content: center;
}
.bottom-bar button {
padding: 0.6rem 1rem;
border-radius: 6px;
border: 1px solid #1f6feb;
background: #1f6feb;
color: white;
font-weight: 600;
cursor: pointer;
font-size: 1rem;
}
.bottom-bar button:active { transform: translateY(1px); }
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.3/chess.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
window.addEventListener('DOMContentLoaded', ${initLockModal});
</script>
<!-- Modal -->
<div class="modal fade" id="lockModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1">
<div class="modal-dialog modal-dialog-centered" style="width:fit-content;">
<div class="modal-content">
<div class="modal-header" style="display:flex; align-items: center; justify-content:center;">
<h5 class="modal-title">Enter Api Key</h5>
</div>
<div class="modal-body" style="display:flex; flex-direction: column; align-items: center; gap: 1rem;">
<input type="password" placeholder="Api Key" onChange="APIKEY1=this.value;" onkeydown="if(event.key === 'Enter'){ APIKEY1=this.value;handleApi(event); }"/>
<button type="button" class="btn btn-primary" id="closeModalBtn" onClick=handleApi(event)>Done</button>
</div>
</div>
</div>
</div>
<header>
<h1 style="font-size:2.1rem;">Chess Move Comparison</h1>
</header>
<div class="wrap">
<aside class="left" aria-label="Left column" style="max-height: 400px; overflow-y: auto;">
<h2 style="font-size:0.95rem; margin-bottom:0.5rem; text-align:center;">API 1 (ChatGPT)</h2>
<div id="result1"></div>
</aside>
<main role="main" aria-label="Main content">
<h2 style="font-size:0.95rem; text-align:center; margin-bottom:0.5rem;">Chess Puzzle</h2>
<p>
<p style="margin-top:1rem;"></p>
<!-- Add more content to demonstrate scrolling -->
</main>
<aside class="right" aria-label="Right column" style="max-height: 400px; overflow-y: auto;">
<h2 style="font-size:0.95rem; margin-bottom:0.5rem; text-align:center;">API 2 (Gemini)</h2>
<div id="result2"></div>
</aside>
</div>
<form class="bottom-bar">
<div class="bottom-bar-inner">
<p><b>Temperature Control</b></p>
<input style="accent-color: #1f6feb;" type="range" min="0" max="1" value="0.7" step="0.1" class="slider" id="myRange" onChange="rangeValue.innerText = this.value; changeTemp();"> , <p id="rangeValue">0.7</p>
</div>
<div class="bottom-bar-inner">
<button id="sendMessage" type="button" onClick=fetchPuzzleImage()>Send</button>
</div>
<div class="bottom-bar-inner">
<p><b>Mate in N (Choose Between 1 and 5)</b></p>
<input style="accent-color: #1f6feb;" type="range" min="1" max="5" value="1" step="1" class="slider" id="myRange2" onChange="rangeValue2.innerText = this.value; changeMateValue();"> , <p id="rangeValue2">1</p>
</div>
</form>
</body>
`);
}
document.write(
` <style>
#default {
border: 5px solid #e6e6e9;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 10px;
}
input.apikey1{
background:"red"
}
</style>
<div>
<h1 style="text-align:center;padding:20px;"> AI API Chess Move Comparison</h1>
</div>
<div id=default>
Enter your API KEY: <br><br>
<input NAME="apikey1" id="apikey1" oninput=handleAPIKEY1() VALUE='' placeholder="Gemini API KEY"><br>
<input NAME="apikey2" id="apikey2" oninput=handleAPIKEY2() VALUE='' placeholder="Groq API KEY"><br>
<button id="set" type="button" onclick='checkKeys();'>SET KEY TO START!
`);