| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Chess Tutor Pro</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.2/chess.js"></script> |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.css"> |
| <script src="https://cdn.jsdelivr.net/npm/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.js"></script> |
| <style> |
| .highlight { |
| background-color: rgba(0, 0, 255, 0.3) !important; |
| box-shadow: inset 0 0 3px 3px rgba(0, 0, 255, 0.3); |
| } |
| .last-move { |
| background-color: rgba(255, 255, 0, 0.4) !important; |
| } |
| .check { |
| background-color: rgba(255, 0, 0, 0.5) !important; |
| } |
| .selected { |
| background-color: rgba(0, 0, 255, 0.3) !important; |
| } |
| .training-highlight { |
| animation: pulse 2s infinite; |
| } |
| @keyframes pulse { |
| 0% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0.7); } |
| 70% { box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); } |
| 100% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0); } |
| } |
| .thinking { |
| position: relative; |
| } |
| .thinking:after { |
| content: ""; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background: rgba(0,0,0,0.3); |
| border-radius: 50%; |
| animation: spin 1s linear infinite; |
| } |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| #board { |
| min-width: 400px; |
| min-height: 400px; |
| box-shadow: 0 5px 15px rgba(0,0,0,0.5); |
| border-radius: 4px; |
| } |
| .chessboard-63f37 { |
| background-color: #f0d9b5; |
| } |
| .square-55d63 { |
| position: relative; |
| } |
| .white-1e1d7 { |
| background-color: #f0d9b5; |
| color: #b58863; |
| } |
| .black-3c85d { |
| background-color: #b58863; |
| color: #f0d9b5; |
| } |
| .board-label { |
| position: absolute; |
| font-size: 12px; |
| font-weight: bold; |
| font-family: Arial, sans-serif; |
| } |
| .board-label.file { |
| bottom: -18px; |
| right: 4px; |
| color: #404040; |
| } |
| .board-label.rank { |
| top: 2px; |
| left: -18px; |
| color: #404040; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100 min-h-screen"> |
| <div class="container mx-auto px-4 py-8"> |
| <header class="text-center mb-8"> |
| <h1 class="text-4xl font-bold text-gray-800 mb-2">Chess Tutor Pro</h1> |
| <p class="text-lg text-gray-600">Learn chess like a professional with AI-powered training</p> |
| </header> |
|
|
| <div class="flex flex-col lg:flex-row gap-8"> |
| |
| <div class="lg:w-2/3"> |
| <div id="board" class="mx-auto" style="width: 500px; height: 500px; max-width: 100%;"> |
| <div id="board-inner" style="width: 100%; height: 100%;"></div> |
| </div> |
| |
| <div class="mt-4 flex justify-center gap-4"> |
| <button id="newGameBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg"> |
| <i class="fas fa-chess-board mr-2"></i>New Game |
| </button> |
| <button id="undoBtn" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg"> |
| <i class="fas fa-undo mr-2"></i>Undo |
| </button> |
| <button id="flipBoardBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg"> |
| <i class="fas fa-sync-alt mr-2"></i>Flip Board |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div class="lg:w-1/3 bg-white rounded-lg shadow-lg p-6"> |
| <div class="mb-6"> |
| <h2 class="text-xl font-semibold mb-2 text-gray-800 border-b pb-2">Game Status</h2> |
| <div id="gameStatus" class="text-lg font-medium text-gray-700">Ready to play!</div> |
| <div id="moveHistory" class="mt-4 h-40 overflow-y-auto bg-gray-50 p-2 rounded"></div> |
| </div> |
|
|
| <div class="mb-6"> |
| <h2 class="text-xl font-semibold mb-2 text-gray-800 border-b pb-2">AI Difficulty</h2> |
| <div class="flex items-center mb-2"> |
| <span class="w-24">Level:</span> |
| <input id="aiLevel" type="range" min="1" max="20" value="5" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> |
| <span id="aiLevelValue" class="ml-2 w-8 text-center">5</span> |
| </div> |
| <div class="text-sm text-gray-500"> |
| <span id="aiElo" class="font-medium">~1500 ELO</span> |
| <span class="ml-2">(Auto-adjusting based on your skill)</span> |
| </div> |
| </div> |
|
|
| <div id="trainingSection" class="hidden"> |
| <h2 class="text-xl font-semibold mb-2 text-gray-800 border-b pb-2">Training Focus</h2> |
| <div id="trainingFocus" class="mb-2 text-gray-700">No active training</div> |
| <div id="trainingTips" class="text-sm text-gray-600 italic"></div> |
| <button id="nextTrainingBtn" class="mt-4 bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg w-full"> |
| <i class="fas fa-arrow-right mr-2"></i>Next Training Exercise |
| </button> |
| </div> |
|
|
| <div id="analysisSection" class="mt-6 hidden"> |
| <h2 class="text-xl font-semibold mb-2 text-gray-800 border-b pb-2">Game Analysis</h2> |
| <div id="mistakesList" class="text-sm"></div> |
| <div id="accuracyScore" class="mt-2 font-medium"></div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="tutorialModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50"> |
| <div class="bg-white rounded-lg p-6 max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto"> |
| <div class="flex justify-between items-center mb-4"> |
| <h2 class="text-2xl font-bold text-gray-800">Welcome to Chess Tutor Pro</h2> |
| <button id="closeTutorialBtn" class="text-gray-500 hover:text-gray-700"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| <div class="space-y-4"> |
| <div> |
| <h3 class="text-lg font-semibold text-gray-700">How It Works</h3> |
| <p class="text-gray-600">This chess tutor adapts to your skill level and provides personalized training based on your games.</p> |
| </div> |
| <div> |
| <h3 class="text-lg font-semibold text-gray-700">Main Features</h3> |
| <ul class="list-disc pl-5 text-gray-600 space-y-1"> |
| <li><span class="font-medium">Adaptive AI</span> - Adjusts difficulty to match your skill</li> |
| <li><span class="font-medium">Personalized Training</span> - After each game, you'll get training exercises based on your mistakes</li> |
| <li><span class="font-medium">Professional Guidance</span> - Learn strategic concepts and tactical patterns</li> |
| <li><span class="font-medium">Game Analysis</span> - Review your mistakes and accuracy</li> |
| </ul> |
| </div> |
| <div> |
| <h3 class="text-lg font-semibold text-gray-700">Getting Started</h3> |
| <p class="text-gray-600">1. Play a game against the AI<br> |
| 2. After the game, review your mistakes<br> |
| 3. Complete the training exercises<br> |
| 4. Improve your chess skills!</p> |
| </div> |
| </div> |
| <button id="startPlayingBtn" class="mt-6 bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg w-full"> |
| Start Playing! |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const stockfish = new Worker('https://cdn.jsdelivr.net/npm/stockfish.js@10.0.2/stockfish.js'); |
| |
| |
| const game = new Chess(); |
| |
| |
| let board = null; |
| let playerColor = 'white'; |
| let aiLevel = 5; |
| let gameHistory = []; |
| let trainingExercises = []; |
| let currentTrainingIndex = 0; |
| let lastMove = null; |
| let gameActive = false; |
| let playerRatingEstimate = 1200; |
| let aiThinking = false; |
| |
| |
| const boardEl = document.getElementById('board'); |
| const gameStatusEl = document.getElementById('gameStatus'); |
| const moveHistoryEl = document.getElementById('moveHistory'); |
| const aiLevelEl = document.getElementById('aiLevel'); |
| const aiLevelValueEl = document.getElementById('aiLevelValue'); |
| const aiEloEl = document.getElementById('aiElo'); |
| const newGameBtn = document.getElementById('newGameBtn'); |
| const undoBtn = document.getElementById('undoBtn'); |
| const flipBoardBtn = document.getElementById('flipBoardBtn'); |
| const trainingSectionEl = document.getElementById('trainingSection'); |
| const trainingFocusEl = document.getElementById('trainingFocus'); |
| const trainingTipsEl = document.getElementById('trainingTips'); |
| const nextTrainingBtn = document.getElementById('nextTrainingBtn'); |
| const analysisSectionEl = document.getElementById('analysisSection'); |
| const mistakesListEl = document.getElementById('mistakesList'); |
| const accuracyScoreEl = document.getElementById('accuracyScore'); |
| const tutorialModalEl = document.getElementById('tutorialModal'); |
| const closeTutorialBtn = document.getElementById('closeTutorialBtn'); |
| const startPlayingBtn = document.getElementById('startPlayingBtn'); |
| |
| |
| function initBoard() { |
| const config = { |
| position: 'start', |
| draggable: true, |
| onDragStart: onDragStart, |
| onDrop: onDrop, |
| onSnapEnd: onSnapEnd, |
| pieceTheme: 'https://images.chesscomfiles.com/chess-themes/pieces/neo/150/{piece}.png', |
| orientation: playerColor, |
| showNotation: true, |
| notationStyle: 'chesscom', |
| notationClass: 'board-label', |
| notationPosition: 'outside' |
| }; |
| |
| board = Chessboard('board-inner', config); |
| } |
| |
| |
| function newGame() { |
| game.reset(); |
| gameHistory = []; |
| lastMove = null; |
| gameActive = true; |
| |
| updateMoveHistory(); |
| updateGameStatus(); |
| |
| |
| adjustAILevel(); |
| |
| board.position(game.fen()); |
| |
| |
| trainingSectionEl.classList.add('hidden'); |
| analysisSectionEl.classList.add('hidden'); |
| |
| |
| if (playerColor === 'white') { |
| gameStatusEl.textContent = 'Your turn (White)'; |
| } else { |
| gameStatusEl.textContent = 'AI is thinking...'; |
| makeAIMove(); |
| } |
| } |
| |
| |
| function adjustAILevel() { |
| |
| aiLevel = Math.min(20, Math.max(1, Math.floor(playerRatingEstimate / 100) - 6)); |
| aiLevelEl.value = aiLevel; |
| aiLevelValueEl.textContent = aiLevel; |
| |
| |
| const eloEstimate = 500 + (aiLevel * 100); |
| aiEloEl.textContent = `~${eloEstimate} ELO`; |
| |
| |
| stockfish.postMessage(`setoption name Skill Level value ${aiLevel}`); |
| } |
| |
| |
| function onDragStart(source, piece, position, orientation) { |
| |
| if (!gameActive || game.turn() !== playerColor[0] || game.isGameOver()) { |
| return false; |
| } |
| |
| |
| if ((game.turn() === 'w' && piece.search(/^b/) !== -1) || |
| (game.turn() === 'b' && piece.search(/^w/) !== -1)) { |
| return false; |
| } |
| |
| |
| highlightPossibleMoves(source); |
| } |
| |
| |
| function highlightPossibleMoves(source) { |
| |
| const squares = boardEl.getElementsByTagName('square'); |
| for (let sq of squares) { |
| sq.classList.remove('highlight'); |
| } |
| |
| |
| const moves = game.moves({ |
| square: source, |
| verbose: true |
| }); |
| |
| for (let move of moves) { |
| const square = boardEl.querySelector(`[data-square="${move.to}"]`); |
| if (square) { |
| square.classList.add('highlight'); |
| } |
| } |
| } |
| |
| |
| function onDrop(source, target) { |
| |
| const squares = boardEl.getElementsByTagName('square'); |
| for (let sq of squares) { |
| sq.classList.remove('highlight'); |
| } |
| |
| |
| const move = game.move({ |
| from: source, |
| to: target, |
| promotion: 'q' |
| }); |
| |
| |
| if (move === null) { |
| return 'snapback'; |
| } |
| |
| lastMove = move; |
| gameHistory.push(move); |
| updateMoveHistory(); |
| updateGameStatus(); |
| |
| |
| if (game.isGameOver()) { |
| gameActive = false; |
| analyzeGame(); |
| return; |
| } |
| |
| |
| gameStatusEl.textContent = 'AI is thinking...'; |
| aiThinking = true; |
| setTimeout(() => { |
| makeAIMove(); |
| }, 500); |
| } |
| |
| |
| function onSnapEnd() { |
| board.position(game.fen()); |
| } |
| |
| |
| function makeAIMove() { |
| if (!gameActive || game.isGameOver()) return; |
| |
| |
| stockfish.postMessage('ucinewgame'); |
| stockfish.postMessage(`position fen ${game.fen()}`); |
| |
| |
| const timeLimit = Math.max(500, 2000 - (aiLevel * 75)); |
| stockfish.postMessage(`go movetime ${timeLimit}`); |
| |
| |
| stockfish.onmessage = function(event) { |
| if (event.data.startsWith('bestmove')) { |
| const bestMove = event.data.split(' ')[1]; |
| |
| if (bestMove && bestMove !== '(none)') { |
| const move = game.move({ |
| from: bestMove.substring(0, 2), |
| to: bestMove.substring(2, 4), |
| promotion: bestMove.length > 4 ? bestMove[4] : 'q' |
| }); |
| |
| if (move) { |
| lastMove = move; |
| gameHistory.push(move); |
| updateMoveHistory(); |
| updateGameStatus(); |
| |
| board.position(game.fen()); |
| |
| |
| highlightLastMove(); |
| |
| |
| if (game.isGameOver()) { |
| gameActive = false; |
| analyzeGame(); |
| } |
| } |
| } |
| |
| aiThinking = false; |
| gameStatusEl.textContent = `Your turn (${playerColor === 'white' ? 'White' : 'Black'})`; |
| } |
| }; |
| } |
| |
| |
| function highlightLastMove() { |
| |
| const squares = boardEl.getElementsByTagName('square'); |
| for (let sq of squares) { |
| sq.classList.remove('last-move'); |
| sq.classList.remove('check'); |
| } |
| |
| if (!lastMove) return; |
| |
| |
| const fromSquare = boardEl.querySelector(`[data-square="${lastMove.from}"]`); |
| const toSquare = boardEl.querySelector(`[data-square="${lastMove.to}"]`); |
| |
| if (fromSquare) fromSquare.classList.add('last-move'); |
| if (toSquare) toSquare.classList.add('last-move'); |
| |
| |
| if (game.in_check()) { |
| const kingColor = game.turn() === 'w' ? 'b' : 'w'; |
| const kingPosition = findKingPosition(kingColor); |
| const kingSquare = boardEl.querySelector(`[data-square="${kingPosition}"]`); |
| if (kingSquare) kingSquare.classList.add('check'); |
| } |
| } |
| |
| |
| function findKingPosition(color) { |
| const fen = game.fen().split(' ')[0]; |
| const rows = fen.split('/'); |
| |
| for (let i = 0; i < rows.length; i++) { |
| let col = 0; |
| for (let j = 0; j < rows[i].length; j++) { |
| const c = rows[i][j]; |
| if (isNaN(c)) { |
| if ((color === 'w' && c === 'K') || (color === 'b' && c === 'k')) { |
| const file = String.fromCharCode(97 + col); |
| const rank = 8 - i; |
| return file + rank; |
| } |
| col++; |
| } else { |
| col += parseInt(c); |
| } |
| } |
| } |
| return ''; |
| } |
| |
| |
| function updateMoveHistory() { |
| moveHistoryEl.innerHTML = ''; |
| |
| |
| for (let i = 0; i < gameHistory.length; i += 2) { |
| const movePair = document.createElement('div'); |
| movePair.className = 'flex mb-1'; |
| |
| const moveNumber = document.createElement('span'); |
| moveNumber.className = 'w-8 text-gray-500'; |
| moveNumber.textContent = `${Math.floor(i / 2) + 1}.`; |
| |
| const whiteMove = document.createElement('span'); |
| whiteMove.className = 'px-2'; |
| whiteMove.textContent = gameHistory[i].san; |
| |
| movePair.appendChild(moveNumber); |
| movePair.appendChild(whiteMove); |
| |
| |
| if (i + 1 < gameHistory.length) { |
| const blackMove = document.createElement('span'); |
| blackMove.className = 'px-2'; |
| blackMove.textContent = gameHistory[i + 1].san; |
| movePair.appendChild(blackMove); |
| } |
| |
| moveHistoryEl.appendChild(movePair); |
| } |
| |
| |
| moveHistoryEl.scrollTop = moveHistoryEl.scrollHeight; |
| } |
| |
| |
| function updateGameStatus() { |
| if (game.isCheckmate()) { |
| gameStatusEl.textContent = `Game over, ${game.turn() === 'w' ? 'black' : 'white'} wins by checkmate!`; |
| } else if (game.isDraw()) { |
| if (game.isStalemate()) { |
| gameStatusEl.textContent = 'Game over, stalemate!'; |
| } else if (game.isThreefoldRepetition()) { |
| gameStatusEl.textContent = 'Game over, draw by threefold repetition!'; |
| } else if (game.isInsufficientMaterial()) { |
| gameStatusEl.textContent = 'Game over, draw by insufficient material!'; |
| } else { |
| gameStatusEl.textContent = 'Game over, draw!'; |
| } |
| } else if (game.isCheck()) { |
| gameStatusEl.textContent = `Check! ${game.turn() === 'w' ? 'White' : 'Black'} to move.`; |
| } else { |
| gameStatusEl.textContent = `${game.turn() === 'w' ? 'White' : 'Black'} to move.`; |
| } |
| } |
| |
| |
| function analyzeGame() { |
| |
| analysisSectionEl.classList.remove('hidden'); |
| mistakesListEl.innerHTML = ''; |
| |
| |
| const totalMoves = gameHistory.length; |
| let mistakes = 0; |
| let blunders = 0; |
| |
| |
| if (totalMoves > 0) { |
| |
| if (totalMoves > 2) { |
| const mistakeMove = gameHistory[Math.floor(totalMoves / 2)]; |
| addMistakeToList(mistakeMove, "Missed tactical opportunity", "You could have won material with a better move here."); |
| mistakes++; |
| } |
| |
| |
| if (totalMoves > 4) { |
| const mistakeMove = gameHistory[Math.floor(totalMoves / 3)]; |
| addMistakeToList(mistakeMove, "Weak pawn structure", "This move weakened your pawn structure. Consider keeping pawns connected."); |
| mistakes++; |
| } |
| |
| |
| if (totalMoves > 6) { |
| const blunderMove = gameHistory[Math.floor(totalMoves / 1.5)]; |
| addMistakeToList(blunderMove, "Hanging piece", "You left a piece undefended, allowing your opponent to capture it for free.", true); |
| blunders++; |
| } |
| } |
| |
| |
| const accuracy = Math.max(0, 100 - (mistakes * 5) - (blunders * 15)); |
| accuracyScoreEl.textContent = `Estimated accuracy: ${accuracy.toFixed(1)}%`; |
| |
| |
| const performanceFactor = accuracy / 100; |
| if (performanceFactor > 0.7) { |
| playerRatingEstimate += 25; |
| } else if (performanceFactor > 0.5) { |
| playerRatingEstimate += 10; |
| } else if (performanceFactor < 0.3) { |
| playerRatingEstimate -= 25; |
| } else { |
| playerRatingEstimate -= 10; |
| } |
| |
| |
| generateTrainingExercises(); |
| } |
| |
| |
| function addMistakeToList(move, title, description, isBlunder = false) { |
| const mistakeItem = document.createElement('div'); |
| mistakeItem.className = 'mb-2 p-2 rounded ' + (isBlunder ? 'bg-red-100' : 'bg-yellow-100'); |
| |
| const moveText = document.createElement('div'); |
| moveText.className = 'font-medium'; |
| moveText.textContent = `${move.san} - ${title}`; |
| |
| const descText = document.createElement('div'); |
| descText.className = 'text-sm'; |
| descText.textContent = description; |
| |
| mistakeItem.appendChild(moveText); |
| mistakeItem.appendChild(descText); |
| mistakesListEl.appendChild(mistakeItem); |
| |
| return mistakeItem; |
| } |
| |
| |
| function generateTrainingExercises() { |
| trainingExercises = []; |
| |
| |
| if (gameHistory.length > 0) { |
| |
| trainingExercises.push({ |
| type: 'tactics', |
| title: 'Fork Opportunity', |
| description: 'Practice recognizing fork opportunities where a single piece attacks two or more opponent pieces simultaneously.', |
| fen: 'r1bqkbnr/ppp2ppp/2n5/3pp3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 4', |
| goal: 'Find the fork that wins material', |
| solution: ['e4d5'] |
| }); |
| |
| |
| trainingExercises.push({ |
| type: 'positional', |
| title: 'Pawn Structure', |
| description: 'Learn how to maintain strong pawn structures and avoid weaknesses.', |
| fen: 'rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2', |
| goal: 'Choose the move that maintains the best pawn structure', |
| solution: ['c2c4'] |
| }); |
| |
| |
| trainingExercises.push({ |
| type: 'endgame', |
| title: 'King and Pawn Endgame', |
| description: 'Practice basic king and pawn endgame techniques.', |
| fen: '8/8/8/4k3/4P3/8/4K3/8 w - - 0 1', |
| goal: 'Promote the pawn to a queen', |
| solution: ['e4e5', 'e5e6', 'e6e7', 'e7e8Q'] |
| }); |
| } |
| |
| |
| if (trainingExercises.length > 0) { |
| trainingSectionEl.classList.remove('hidden'); |
| currentTrainingIndex = 0; |
| startTrainingExercise(currentTrainingIndex); |
| } |
| } |
| |
| |
| function startTrainingExercise(index) { |
| if (index >= trainingExercises.length) { |
| trainingFocusEl.textContent = 'Training complete!'; |
| trainingTipsEl.textContent = 'You have completed all training exercises. Play another game to get more personalized training.'; |
| nextTrainingBtn.textContent = 'No more exercises'; |
| nextTrainingBtn.disabled = true; |
| return; |
| } |
| |
| const exercise = trainingExercises[index]; |
| |
| |
| game.load(exercise.fen); |
| board.position(exercise.fen); |
| |
| |
| trainingFocusEl.textContent = exercise.title; |
| trainingTipsEl.textContent = `${exercise.description}\n\nGoal: ${exercise.goal}`; |
| nextTrainingBtn.textContent = 'Skip Exercise'; |
| nextTrainingBtn.disabled = false; |
| |
| |
| highlightTrainingPosition(exercise); |
| |
| |
| |
| } |
| |
| |
| function highlightTrainingPosition(exercise) { |
| |
| const squares = boardEl.getElementsByTagName('square'); |
| for (let sq of squares) { |
| sq.classList.remove('training-highlight'); |
| } |
| |
| |
| if (exercise.type === 'tactics') { |
| |
| const fromSquare = exercise.solution[0].substring(0, 2); |
| const square = boardEl.querySelector(`[data-square="${fromSquare}"]`); |
| if (square) square.classList.add('training-highlight'); |
| } else if (exercise.type === 'positional') { |
| |
| const fenParts = exercise.fen.split(' '); |
| const rows = fenParts[0].split('/'); |
| for (let i = 0; i < rows.length; i++) { |
| let col = 0; |
| for (let j = 0; j < rows[i].length; j++) { |
| const c = rows[i][j]; |
| if (isNaN(c)) { |
| if (c === 'P' || c === 'p') { |
| const file = String.fromCharCode(97 + col); |
| const rank = 8 - i; |
| const square = boardEl.querySelector(`[data-square="${file + rank}"]`); |
| if (square) square.classList.add('training-highlight'); |
| } |
| col++; |
| } else { |
| col += parseInt(c); |
| } |
| } |
| } |
| } else if (exercise.type === 'endgame') { |
| |
| const fenParts = exercise.fen.split(' '); |
| const rows = fenParts[0].split('/'); |
| for (let i = 0; i < rows.length; i++) { |
| let col = 0; |
| for (let j = 0; j < rows[i].length; j++) { |
| const c = rows[i][j]; |
| if (isNaN(c)) { |
| if (c === 'K' || c === 'k' || c === 'P' || c === 'p') { |
| const file = String.fromCharCode(97 + col); |
| const rank = 8 - i; |
| const square = boardEl.querySelector(`[data-square="${file + rank}"]`); |
| if (square) square.classList.add('training-highlight'); |
| } |
| col++; |
| } else { |
| col += parseInt(c); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| newGameBtn.addEventListener('click', newGame); |
| |
| undoBtn.addEventListener('click', () => { |
| if (gameHistory.length > 0) { |
| game.undo(); |
| gameHistory.pop(); |
| lastMove = gameHistory.length > 0 ? gameHistory[gameHistory.length - 1] : null; |
| board.position(game.fen()); |
| updateMoveHistory(); |
| updateGameStatus(); |
| highlightLastMove(); |
| } |
| }); |
| |
| flipBoardBtn.addEventListener('click', () => { |
| playerColor = playerColor === 'white' ? 'black' : 'white'; |
| board.orientation(playerColor); |
| }); |
| |
| aiLevelEl.addEventListener('input', () => { |
| aiLevel = parseInt(aiLevelEl.value); |
| aiLevelValueEl.textContent = aiLevel; |
| |
| |
| const eloEstimate = 500 + (aiLevel * 100); |
| aiEloEl.textContent = `~${eloEstimate} ELO`; |
| |
| |
| stockfish.postMessage(`setoption name Skill Level value ${aiLevel}`); |
| }); |
| |
| nextTrainingBtn.addEventListener('click', () => { |
| currentTrainingIndex++; |
| startTrainingExercise(currentTrainingIndex); |
| }); |
| |
| closeTutorialBtn.addEventListener('click', () => { |
| tutorialModalEl.classList.add('hidden'); |
| }); |
| |
| startPlayingBtn.addEventListener('click', () => { |
| tutorialModalEl.classList.add('hidden'); |
| newGame(); |
| }); |
| |
| |
| initBoard(); |
| |
| |
| tutorialModalEl.classList.remove('hidden'); |
| }); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=olywwe/chesstes" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |