var pgn = "";
var fen = "";
var pngUrl = "";
var APIKEY1 = "";
var APIKEY2 = "";
var solution = "";
var mateValue = 1;
var extractedMoves = {
result1: null,
result2: null
};
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 changeMateValue(){
mateValue = jQuery("input#myRange2").val();
mateValue = parseInt(mateValue);//change from string to float
}
function handleApiUpdate(event){
event.preventDefault();
document.getElementById("APIKEY1").value = sessionStorage.getItem("APIKEY1");
document.getElementById("APIKEY2").value = sessionStorage.getItem("APIKEY2");
const lockModal = bootstrap.Modal.getInstance(document.getElementById('lockModal'));
lockModal.show();
sessionStorage.setItem('modalClosed', false);
}
function handleApi(event){
event.preventDefault();
// close the modal
sessionStorage.setItem("APIKEY1", event.target.elements["APIKEY1"].value);
sessionStorage.setItem("APIKEY2", event.target.elements["APIKEY2"].value);
APIKEY1=event.target.elements["APIKEY1"].value;
APIKEY2=event.target.elements["APIKEY2"].value;
const lockModal = bootstrap.Modal.getInstance(document.getElementById('lockModal'));
lockModal.hide();
sessionStorage.setItem('modalClosed', true);
document.getElementById("updateApiBtn").style.display = "block";
}
function convert2FEN(pgn){
const chess = new Chess();
chess.load_pgn(pgn);
const fen = chess.fen();
return fen;
}
function initLockModal() {
sessionStorage.clear();
sessionStorage.setItem('modalClosed', false);
const lockModal = new bootstrap.Modal(document.getElementById('lockModal'));
// Check if sessionStorage has a flag
if (sessionStorage.getItem('modalClosed')) {
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=mateIn1&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[0];
if (!pgn || !solution) {
throw new Error("Puzzle has no PGN or SOLUTION");
}
return pgn;
}
async function fetchPuzzleImage() {
try {
document.getElementById("result1").innerHTML = `<div class="d-flex justify-content-center"><div class="spinner-border text-warning" role="status"><span class="visually-hidden">Loading...</span></div></div>`;
document.getElementById("result2").innerHTML = `<div class="d-flex justify-content-center"><div class="spinner-border text-warning" role="status"><span class="visually-hidden">Loading...</span></div></div>`;
document.querySelector("main p:nth-of-type(2)").innerHTML = `<div class="spinner-grow" style="width: 3rem; height: 3rem;" role="status"><span class="visually-hidden">Loading...</span></div>`;
// 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/>
<img src="${pngUrl}" style="max-width:100%;border:1px solid #ccc;border-radius:8px;height:60%;width:60%;" /><br/>
EXPECTED SOLUTION:<strong>${solution}</strong>
`;
}, 2000);
await send2APIs();
} catch (err) {
console.error(err);
document.querySelector("main p:nth-of-type(2)").innerHTML = "Error: " + err.message;
}
}
async function callAIAPI(apiOptions, targetElementId) {
try {
var url = "";
if(targetElementId === "result1"){
url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent";
console.log(`Calling gemini with ${APIKEY2}`);
}
else{
url = "https://api.groq.com/openai/v1/responses";
console.log(`calling groq with ${APIKEY1}`);
}
const response = await fetch(url, apiOptions);
const data = await response.json();
var finalText = "";
console.log(data);
if(targetElementId === "result1"){
finalText = data.candidates[0].content.parts[0].text;
}else{
finalText = data.output[1].content[0].text;
}
// Insert into the page
document.getElementById(targetElementId).innerHTML = finalText;
// Extract move inside brackets and store in global object
const match = finalText.match(/\[([^\]]+)\]/);
extractedMoves[targetElementId] = match ? match[1] : null;
} catch (error) {
console.error(error);
document.getElementById(targetElementId).innerHTML = "Error fetching response";
}
}
function showAlert(message, type = "success", duration = 10000) {
const container = document.getElementById("alert-container");
const alertId = "alert-" + Date.now();
container.innerHTML = `
<div class="alert alert-${type} alert-dismissible fade show mt-3" id="${alertId}" role="alert">
<strong>${message}</strong>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
`;
setTimeout(() => {
const alertEl = document.getElementById(alertId);
if (alertEl) {
// Trigger Bootstrap fade-out
alertEl.classList.remove("show");
alertEl.classList.add("hide");
// remove element after fade completes (150ms default Bootstrap fade)
setTimeout(() => alertEl.remove(), 150);
}
}, duration);
}
async function send2APIs(){
const input = `Mate in: ${mateValue}, give the solution for the given FEN string: ${fen},give the moves required for eg [f4d4] and give a brief 10 word explaination for your decision. Do not use markdown, just text will do.`;
console.log(input);
const API1Options = {//POST to API
method: 'POST',
headers: {
'x-goog-api-key': `${APIKEY2}`,//checking API key
'Content-Type': 'application/json'
},
body: JSON.stringify({//what to send
contents: [{
parts: [{"text": input}],
}],
generationConfig: {
temperature: tmp,
},
}),
};
const API2Options = {//POST to API
method: 'POST',
headers: {
'Authorization': `Bearer ${APIKEY1}`,//checking API key
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: "openai/gpt-oss-120b",
temperature: tmp,
input: input,
})
};
try{
await callAIAPI(API1Options, "result1");
await callAIAPI(API2Options, "result2");
const move1 = extractedMoves.result1;
const move2 = extractedMoves.result2;
if (move1 === solution && move2 === solution) {
showAlert("Both AIs are correct!", "success");
} else if (move1 !== solution && move2 !== solution) {
showAlert("Both AIs are incorrect!", "danger");
} else if(move1 === solution) {
showAlert("Gemini AI is correct!", "warning");
} else if(move2 === solution) {
showAlert("Groq AI is correct!", "warning");
}
// const response1 = await fetch("https://api.groq.com/openai/v1/responses", API1Options);
// const data1 = await response1.json();
// const finalTextApi1 = data1.output[1].content[0].text;
// document.getElementById("result1").innerHTML = finalTextApi1;
// const response2 = await fetch("https://api.groq.com/openai/v1/responses", API2Options);
// const data2 = await response2.json();
// const finalTextApi2 = data2.output[1].content[0].text;
// //Insert into the div
// document.getElementById("result2").innerHTML = finalTextApi2;
// console.log(data1);
// console.log(data2);
}
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>
:root{
--bg-100: #f5f7fb;
--bg-200: #e8eefb;
--accent-500: #1f6feb;
--accent-600: #1258d6;
--muted-500: #6b7280;
--card-border: #e6eaf4;
--success: #16a34a;
--danger: #dc2626;
--glass: rgba(255,255,255,0.65);
--radius-lg: 12px;
}
html, body { height: 100%; }
body {
margin: 0;
padding: 0.5rem;
display: flex;
flex-direction: column;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
color: #0f172a;
background: linear-gradient(180deg, var(--bg-100) 0%, #eef6ff 100%);
-webkit-font-smoothing:antialiased;
line-height: 1.45;
}
/* A flex container that holds BOTH header and button */
.top-bar {
display: flex;
align-items: center;
flex-direction: row;
justify-content: center; /* centers the header block */
position: relative;
gap: 10px;
background: linear-gradient(90deg, rgba(255,255,255,0.6), rgba(255,255,255,0.35));
border-radius: var(--radius-lg);
box-shadow: 0 6px 18px rgba(16,24,40,0.06);
}
/* Your original header styling */
.page-header {
// border: 2px solid #000;
padding: 1rem;
margin: 0;
display: inline-block;
color: #0b1220;
letter-spacing: -0.2px;
font-size: 1.25rem;
}
/* update API button (primary accent) */
#updateApiBtn {
padding: 0.5rem 0.9rem;
border-radius: 10px;
border: none;
font-weight: 600;
}
#updateApiBtn:hover { transform: translateY(-1px); }
.wrap {
display: grid;
grid-template-columns: 220px 1fr 220px;
gap: 1rem;
padding: 1rem;
flex: 1 1 auto;
align-items: start;
}
aside {
background: linear-gradient(180deg, var(--glass), rgba(255,255,255,0.85));
border: 1px solid var(--card-border);
padding: 1rem;
border-radius: 8px;
min-height: 200px;
box-shadow: 0 8px 22px rgba(13, 20, 40, 0.04);
}
aside h2, main h2 {
margin-top: 0;
margin-bottom: 0.5rem;
font-size: 0.95rem;
color: var(--muted-500);
text-align: center;
}
#result1, #result2 {
min-height: 110px;
padding: 0.75rem;
border-radius: 10px;
background: linear-gradient(180deg, rgba(255,255,255,0.6), rgba(250,251,255,0.4));
border: 1px dashed rgba(20,35,65,0.04);
color: #0b1220;
font-size: 0.95rem;
overflow-wrap: anywhere;
}
main {
background: linear-gradient(180deg, var(--glass), rgba(255,255,255,0.85));
border: 1px solid var(--card-border);
padding: 1rem;
border-radius: 8px;
min-height: 200px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 8px 22px rgba(13, 20, 40, 0.04);
}
/* main image & puzzle area */
main p:nth-of-type(2) img {
border-radius: 10px;
box-shadow: 0 8px 18px rgba(16,24,40,0.06);
}
main p:nth-of-type(2) {
min-height: 160px;
display:flex;
align-items:center;
justify-content:center;
flex-direction:column;
}
// .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 */
.bottom-bar {
position: sticky;
bottom: 0;
left: 0;
background: #ffffffcc;
border-top: 1px solid var(--card-border);
padding: 0.75rem 1rem;
display: flex;
gap: 0.5rem;
align-items: center;
justify-content: center;
backdrop-filter: blur(6px);
}
/* send button */
#sendMessage {
background: linear-gradient(180deg,var(--accent-500),var(--accent-600));
border: none;
color: #fff;
padding: 0.6rem 1rem;
border-radius: 10px;
font-weight: 700;
cursor: pointer;
box-shadow: 0 8px 18px rgba(31,111,235,0.14);
}
#sendMessage:hover { transform: translateY(-1px); }
/* modal tweaks */
.modal-content {
border-radius: 12px;
border: 1px solid var(--card-border);
background: linear-gradient(180deg, #fff, #fbfdff);
}
/* spinner sizing */
.spinner-border, .spinner-grow {
color: var(--accent-500);
}
/* responsive */
@media (max-width: 980px) {
.wrap { grid-template-columns: 1fr; }
.top-bar { flex-direction: column; gap: 0.5rem; }
#updateApiBtn { width: 100%; }
input[type="range"] { width: 160px; }
}
.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 Keys</h5>
</div>
<div class="modal-body">
<form onSubmit=handleApi(event) style="display:flex; flex-direction: column; align-items: center; gap: 1rem;">
<input type="text" id="APIKEY1" name="APIKEY1" placeholder="Groq Api Key" required=true onChange="APIKEY1=this.value;"/>
<input type="text" id="APIKEY2" name="APIKEY2" placeholder="Gemini Api Key" required=true onChange="APIKEY2=this.value;"/>
<button type="submit" class="btn btn-primary">Done</button>
</form>
</div>
</div>
</div>
</div>
<div class="top-bar">
<header class="page-header">
<h1>Chess Move Comparison</h1>
</header>
<button class="btn btn-warning" style="display:none;" id="updateApiBtn" onClick=handleApiUpdate(event)>Update Api Key</button>
</div>
<div id="alert-container"></div>
<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;">Gemini AI</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;">Groq AI</h2>
<div id="result2"></div>
</aside>
</div>
<form class="bottom-bar">
<div class="bottom-bar-inner">
<p><b>Maybe change the <br>temperature to <br> see what happens :)</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>
`);
// }