Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Interactive Chess Game</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"> | |
| <style> | |
| .chess-board { | |
| width: 100%; | |
| max-width: 500px; | |
| aspect-ratio: 1/1; | |
| } | |
| .square { | |
| position: relative; | |
| width: 12.5%; | |
| height: 12.5%; | |
| float: left; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| } | |
| .square:hover { | |
| opacity: 0.8; | |
| transform: scale(1.05); | |
| } | |
| .light { | |
| background-color: #f0d9b5; | |
| background-image: linear-gradient(145deg, #f0d9b5 0%, #e6c99a 100%); | |
| } | |
| .dark { | |
| background-color: #b58863; | |
| background-image: linear-gradient(145deg, #b58863 0%, #9c6e4a 100%); | |
| } | |
| .selected { | |
| background-color: rgba(0, 255, 0, 0.5) ; | |
| } | |
| .possible-move { | |
| position: absolute; | |
| width: 30%; | |
| height: 30%; | |
| border-radius: 50%; | |
| background-color: rgba(0, 0, 0, 0.3); | |
| } | |
| .piece { | |
| font-size: 2.5rem; | |
| width: 100%; | |
| height: 100%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| cursor: grab; | |
| user-select: none; | |
| } | |
| .piece:active { | |
| cursor: grabbing; | |
| } | |
| .white { | |
| color: #f8f8f8; | |
| text-shadow: 1px 1px 2px rgba(0,0,0,0.5); | |
| filter: drop-shadow(1px 1px 1px rgba(0,0,0,0.3)); | |
| } | |
| .black { | |
| color: #3a3a3a; | |
| text-shadow: 1px 1px 2px rgba(255,255,255,0.5); | |
| filter: drop-shadow(1px 1px 1px rgba(0,0,0,0.3)); | |
| } | |
| @media (max-width: 640px) { | |
| .piece { | |
| font-size: 1.8rem; | |
| } | |
| } | |
| </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">Interactive Chess Tutor</h1> | |
| <p class="text-lg text-gray-600">Learn chess tactics while you play</p> | |
| </header> | |
| <div class="flex flex-col lg:flex-row gap-8 items-center lg:items-start justify-center"> | |
| <!-- Chess Board --> | |
| <div class="bg-amber-900 p-6 rounded-lg shadow-xl border-4 border-amber-700"> | |
| <div class="flex flex-col"> | |
| <div class="flex justify-end pr-2 mb-1"> | |
| <div class="w-full max-w-[500px] flex justify-between px-[4.5%]"> | |
| <span class="text-gray-700 font-medium w-[12.5%] text-center">a</span> | |
| <span class="text-gray-700 font-medium w-[12.5%] text-center">b</span> | |
| <span class="text-gray-700 font-medium w-[12.5%] text-center">c</span> | |
| <span class="text-gray-700 font-medium w-[12.5%] text-center">d</span> | |
| <span class="text-gray-700 font-medium w-[12.5%] text-center">e</span> | |
| <span class="text-gray-700 font-medium w-[12.5%] text-center">f</span> | |
| <span class="text-gray-700 font-medium w-[12.5%] text-center">g</span> | |
| <span class="text-gray-700 font-medium w-[12.5%] text-center">h</span> | |
| </div> | |
| </div> | |
| <div class="flex"> | |
| <div class="flex flex-col justify-between pr-2"> | |
| <span class="text-gray-700 font-medium h-[12.5%] flex items-center justify-center">8</span> | |
| <span class="text-gray-700 font-medium h-[12.5%] flex items-center justify-center">7</span> | |
| <span class="text-gray-700 font-medium h-[12.5%] flex items-center justify-center">6</span> | |
| <span class="text-gray-700 font-medium h-[12.5%] flex items-center justify-center">5</span> | |
| <span class="text-gray-700 font-medium h-[12.5%] flex items-center justify-center">4</span> | |
| <span class="text-gray-700 font-medium h-[12.5%] flex items-center justify-center">3</span> | |
| <span class="text-gray-700 font-medium h-[12.5%] flex items-center justify-center">2</span> | |
| <span class="text-gray-700 font-medium h-[12.5%] flex items-center justify-center">1</span> | |
| </div> | |
| <div id="chess-board" class="chess-board mx-auto border-4 border-amber-800"></div> | |
| </div> | |
| </div> | |
| <div class="mt-4 flex justify-between items-center"> | |
| <button id="undo-btn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"> | |
| <i class="fas fa-undo mr-2"></i>Undo | |
| </button> | |
| <div id="turn-indicator" class="text-lg font-semibold"> | |
| White's turn | |
| </div> | |
| <button id="new-game-btn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded"> | |
| <i class="fas fa-plus mr-2"></i>New Game | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Game Info Panel --> | |
| <div class="bg-white p-6 rounded-lg shadow-xl w-full max-w-md"> | |
| <div class="mb-6"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-4">Move Suggestions</h2> | |
| <div id="suggestions" class="bg-gray-50 p-4 rounded-lg min-h-32"> | |
| <p class="text-gray-600">Make your first move to get suggestions</p> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-4">Game Statistics</h2> | |
| <div class="grid grid-cols-2 gap-4"> | |
| <div class="bg-blue-50 p-3 rounded-lg"> | |
| <p class="text-sm text-blue-600">White Pieces</p> | |
| <p id="white-captures" class="text-xl font-bold">0 captured</p> | |
| </div> | |
| <div class="bg-gray-800 p-3 rounded-lg"> | |
| <p class="text-sm text-gray-300">Black Pieces</p> | |
| <p id="black-captures" class="text-xl font-bold text-white">0 captured</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-4">Chess Tactics</h2> | |
| <div id="tactics-info" class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="mb-4"> | |
| <h3 class="font-semibold text-lg text-gray-700">Fork</h3> | |
| <p class="text-gray-600 text-sm">A single piece attacks two or more opponent pieces simultaneously.</p> | |
| </div> | |
| <div class="mb-4"> | |
| <h3 class="font-semibold text-lg text-gray-700">Pin</h3> | |
| <p class="text-gray-600 text-sm">A piece is restricted from moving because doing so would expose a more valuable piece to capture.</p> | |
| </div> | |
| <div> | |
| <h3 class="font-semibold text-lg text-gray-700">Skewer</h3> | |
| <p class="text-gray-600 text-sm">Similar to a pin, but the more valuable piece is in front of the less valuable one.</p> | |
| </div> | |
| </div> | |
| <button id="next-tactic-btn" class="mt-4 w-full bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded"> | |
| <i class="fas fa-random mr-2"></i>Show Another Tactic | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Move History --> | |
| <div class="mt-8 bg-white p-6 rounded-lg shadow-xl max-w-4xl mx-auto"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-4">Move History</h2> | |
| <div id="move-history" class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-2"> | |
| <!-- Moves will be added here --> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Chess game state | |
| const gameState = { | |
| board: [], | |
| selectedSquare: null, | |
| possibleMoves: [], | |
| turn: 'white', | |
| moveHistory: [], | |
| capturedPieces: { white: [], black: [] }, | |
| currentTacticIndex: 0 | |
| }; | |
| // Chess pieces Unicode characters | |
| const pieces = { | |
| king: { white: '♔', black: '♚' }, | |
| queen: { white: '♕', black: '♛' }, | |
| rook: { white: '♖', black: '♜' }, | |
| bishop: { white: '♗', black: '♝' }, | |
| knight: { white: '♘', black: '♞' }, | |
| pawn: { white: '♙', black: '♟' } | |
| }; | |
| // Chess tactics information | |
| const tactics = [ | |
| { | |
| name: "Fork", | |
| description: "A single piece attacks two or more opponent pieces simultaneously.", | |
| example: "A knight attacking both the queen and rook at the same time." | |
| }, | |
| { | |
| name: "Pin", | |
| description: "A piece is restricted from moving because doing so would expose a more valuable piece to capture.", | |
| example: "A bishop pinning a knight to the king." | |
| }, | |
| { | |
| name: "Skewer", | |
| description: "Similar to a pin, but the more valuable piece is in front of the less valuable one.", | |
| example: "A rook skewering the queen with the king behind it." | |
| }, | |
| { | |
| name: "Discovered Attack", | |
| description: "Moving a piece reveals an attack by another piece.", | |
| example: "Moving a pawn to reveal a bishop's attack on the queen." | |
| }, | |
| { | |
| name: "Zwischenzug", | |
| description: "An 'in-between move' that interrupts the expected sequence of play.", | |
| example: "Instead of recapturing immediately, making a threatening move first." | |
| }, | |
| { | |
| name: "En Passant", | |
| description: "A special pawn capture that can occur immediately after a pawn moves two squares.", | |
| example: "Capturing a pawn that just moved two squares as if it had moved only one." | |
| }, | |
| { | |
| name: "Castling", | |
| description: "A special move involving the king and a rook for defensive purposes.", | |
| example: "Moving the king two squares toward a rook, then the rook to the other side." | |
| } | |
| ]; | |
| // Initialize the board | |
| function initBoard() { | |
| const boardElement = document.getElementById('chess-board'); | |
| boardElement.innerHTML = ''; | |
| // Create initial board setup | |
| gameState.board = [ | |
| ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'], | |
| ['p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'], | |
| ['', '', '', '', '', '', '', ''], | |
| ['', '', '', '', '', '', '', ''], | |
| ['', '', '', '', '', '', '', ''], | |
| ['', '', '', '', '', '', '', ''], | |
| ['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'], | |
| ['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R'] | |
| ]; | |
| // Create board squares | |
| for (let row = 0; row < 8; row++) { | |
| for (let col = 0; col < 8; col++) { | |
| const square = document.createElement('div'); | |
| square.className = `square ${(row + col) % 2 === 0 ? 'light' : 'dark'}`; | |
| square.dataset.row = row; | |
| square.dataset.col = col; | |
| const piece = gameState.board[row][col]; | |
| if (piece) { | |
| const pieceElement = document.createElement('div'); | |
| pieceElement.className = `piece ${piece === piece.toLowerCase() ? 'black' : 'white'}`; | |
| // Set the appropriate Unicode character | |
| const pieceType = piece.toLowerCase(); | |
| const color = piece === piece.toLowerCase() ? 'black' : 'white'; | |
| if (pieceType === 'k') pieceElement.textContent = pieces.king[color]; | |
| else if (pieceType === 'q') pieceElement.textContent = pieces.queen[color]; | |
| else if (pieceType === 'r') pieceElement.textContent = pieces.rook[color]; | |
| else if (pieceType === 'b') pieceElement.textContent = pieces.bishop[color]; | |
| else if (pieceType === 'n') pieceElement.textContent = pieces.knight[color]; | |
| else if (pieceType === 'p') pieceElement.textContent = pieces.pawn[color]; | |
| square.appendChild(pieceElement); | |
| } | |
| square.addEventListener('click', () => handleSquareClick(row, col)); | |
| boardElement.appendChild(square); | |
| } | |
| } | |
| // Reset game state | |
| gameState.selectedSquare = null; | |
| gameState.possibleMoves = []; | |
| gameState.turn = 'white'; | |
| gameState.moveHistory = []; | |
| gameState.capturedPieces = { white: [], black: [] }; | |
| // Update UI | |
| updateTurnIndicator(); | |
| updateCapturedPieces(); | |
| clearMoveHistory(); | |
| updateSuggestions(); | |
| } | |
| // Handle square click | |
| function handleSquareClick(row, col) { | |
| const piece = gameState.board[row][col]; | |
| // If no square is selected and the clicked square has a piece of the current turn's color | |
| if (!gameState.selectedSquare && piece && | |
| ((gameState.turn === 'white' && piece === piece.toUpperCase()) || | |
| (gameState.turn === 'black' && piece === piece.toLowerCase()))) { | |
| // Select the square | |
| gameState.selectedSquare = { row, col }; | |
| highlightSquare(row, col, true); | |
| // Calculate possible moves | |
| gameState.possibleMoves = calculatePossibleMoves(row, col); | |
| highlightPossibleMoves(); | |
| return; | |
| } | |
| // If a square is already selected | |
| if (gameState.selectedSquare) { | |
| const { row: selectedRow, col: selectedCol } = gameState.selectedSquare; | |
| // Check if the clicked square is a possible move | |
| const isPossibleMove = gameState.possibleMoves.some(move => | |
| move.row === row && move.col === col | |
| ); | |
| if (isPossibleMove) { | |
| // Move the piece | |
| movePiece(selectedRow, selectedCol, row, col); | |
| } | |
| // Deselect the square | |
| highlightSquare(selectedRow, selectedCol, false); | |
| clearPossibleMoves(); | |
| gameState.selectedSquare = null; | |
| gameState.possibleMoves = []; | |
| } | |
| } | |
| // Calculate possible moves for a piece | |
| function calculatePossibleMoves(row, col) { | |
| const piece = gameState.board[row][col].toLowerCase(); | |
| const color = gameState.board[row][col] === gameState.board[row][col].toLowerCase() ? 'black' : 'white'; | |
| const moves = []; | |
| // Pawn moves | |
| if (piece === 'p') { | |
| const direction = color === 'white' ? -1 : 1; | |
| const startRow = color === 'white' ? 6 : 1; | |
| // Forward move | |
| if (row + direction >= 0 && row + direction < 8 && | |
| gameState.board[row + direction][col] === '') { | |
| moves.push({ row: row + direction, col }); | |
| // Double move from starting position | |
| if (row === startRow && gameState.board[row + 2 * direction][col] === '') { | |
| moves.push({ row: row + 2 * direction, col }); | |
| } | |
| } | |
| // Captures | |
| for (const captureCol of [col - 1, col + 1]) { | |
| if (captureCol >= 0 && captureCol < 8 && row + direction >= 0 && row + direction < 8) { | |
| const targetPiece = gameState.board[row + direction][captureCol]; | |
| if (targetPiece !== '' && | |
| ((color === 'white' && targetPiece === targetPiece.toLowerCase()) || | |
| (color === 'black' && targetPiece === targetPiece.toUpperCase()))) { | |
| moves.push({ row: row + direction, col: captureCol }); | |
| } | |
| } | |
| } | |
| } | |
| // Rook moves (and queen's rook-like moves) | |
| if (piece === 'r' || piece === 'q') { | |
| // Horizontal and vertical moves | |
| const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]; | |
| for (const [dr, dc] of directions) { | |
| for (let i = 1; i < 8; i++) { | |
| const newRow = row + i * dr; | |
| const newCol = col + i * dc; | |
| if (newRow < 0 || newRow >= 8 || newCol < 0 || newCol >= 8) break; | |
| const targetPiece = gameState.board[newRow][newCol]; | |
| if (targetPiece === '') { | |
| moves.push({ row: newRow, col: newCol }); | |
| } else { | |
| if ((color === 'white' && targetPiece === targetPiece.toLowerCase()) || | |
| (color === 'black' && targetPiece === targetPiece.toUpperCase())) { | |
| moves.push({ row: newRow, col: newCol }); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // Bishop moves (and queen's bishop-like moves) | |
| if (piece === 'b' || piece === 'q') { | |
| // Diagonal moves | |
| const directions = [[1, 1], [1, -1], [-1, 1], [-1, -1]]; | |
| for (const [dr, dc] of directions) { | |
| for (let i = 1; i < 8; i++) { | |
| const newRow = row + i * dr; | |
| const newCol = col + i * dc; | |
| if (newRow < 0 || newRow >= 8 || newCol < 0 || newCol >= 8) break; | |
| const targetPiece = gameState.board[newRow][newCol]; | |
| if (targetPiece === '') { | |
| moves.push({ row: newRow, col: newCol }); | |
| } else { | |
| if ((color === 'white' && targetPiece === targetPiece.toLowerCase()) || | |
| (color === 'black' && targetPiece === targetPiece.toUpperCase())) { | |
| moves.push({ row: newRow, col: newCol }); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // Knight moves | |
| if (piece === 'n') { | |
| const knightMoves = [ | |
| [2, 1], [2, -1], [-2, 1], [-2, -1], | |
| [1, 2], [1, -2], [-1, 2], [-1, -2] | |
| ]; | |
| for (const [dr, dc] of knightMoves) { | |
| const newRow = row + dr; | |
| const newCol = col + dc; | |
| if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 8) { | |
| const targetPiece = gameState.board[newRow][newCol]; | |
| if (targetPiece === '' || | |
| ((color === 'white' && targetPiece === targetPiece.toLowerCase()) || | |
| (color === 'black' && targetPiece === targetPiece.toUpperCase()))) { | |
| moves.push({ row: newRow, col: newCol }); | |
| } | |
| } | |
| } | |
| } | |
| // King moves | |
| if (piece === 'k') { | |
| for (let dr = -1; dr <= 1; dr++) { | |
| for (let dc = -1; dc <= 1; dc++) { | |
| if (dr === 0 && dc === 0) continue; | |
| const newRow = row + dr; | |
| const newCol = col + dc; | |
| if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 8) { | |
| const targetPiece = gameState.board[newRow][newCol]; | |
| if (targetPiece === '' || | |
| ((color === 'white' && targetPiece === targetPiece.toLowerCase()) || | |
| (color === 'black' && targetPiece === targetPiece.toUpperCase()))) { | |
| moves.push({ row: newRow, col: newCol }); | |
| } | |
| } | |
| } | |
| } | |
| // Castling (simplified - doesn't check for checks or moved pieces) | |
| if (row === (color === 'white' ? 7 : 0)) { | |
| // Kingside | |
| if (gameState.board[row][5] === '' && gameState.board[row][6] === '' && | |
| gameState.board[row][7].toLowerCase() === 'r') { | |
| moves.push({ row, col: 6, isCastle: true, rookCol: 7, newRookCol: 5 }); | |
| } | |
| // Queenside | |
| if (gameState.board[row][3] === '' && gameState.board[row][2] === '' && | |
| gameState.board[row][1] === '' && gameState.board[row][0].toLowerCase() === 'r') { | |
| moves.push({ row, col: 2, isCastle: true, rookCol: 0, newRookCol: 3 }); | |
| } | |
| } | |
| } | |
| return moves; | |
| } | |
| // Move a piece | |
| function movePiece(fromRow, fromCol, toRow, toCol) { | |
| const piece = gameState.board[fromRow][fromCol]; | |
| const targetPiece = gameState.board[toRow][toCol]; | |
| // Record the move | |
| const moveNotation = getMoveNotation(fromRow, fromCol, toRow, toCol); | |
| gameState.moveHistory.push({ | |
| turn: gameState.turn, | |
| from: { row: fromRow, col: fromCol }, | |
| to: { row: toRow, col: toCol }, | |
| piece, | |
| captured: targetPiece, | |
| notation: moveNotation | |
| }); | |
| // Handle capture | |
| if (targetPiece !== '') { | |
| const capturedColor = targetPiece === targetPiece.toLowerCase() ? 'black' : 'white'; | |
| gameState.capturedPieces[capturedColor].push(targetPiece); | |
| updateCapturedPieces(); | |
| } | |
| // Handle castling | |
| const move = gameState.possibleMoves.find(m => m.row === toRow && m.col === toCol); | |
| if (move && move.isCastle) { | |
| // Move the rook | |
| gameState.board[toRow][move.newRookCol] = gameState.board[toRow][move.rookCol]; | |
| gameState.board[toRow][move.rookCol] = ''; | |
| // Update the board display for the rook | |
| updateSquare(toRow, move.rookCol); | |
| updateSquare(toRow, move.newRookCol); | |
| } | |
| // Move the piece | |
| gameState.board[toRow][toCol] = piece; | |
| gameState.board[fromRow][fromCol] = ''; | |
| // Update the board display | |
| updateSquare(fromRow, fromCol); | |
| updateSquare(toRow, toCol); | |
| // Switch turns | |
| gameState.turn = gameState.turn === 'white' ? 'black' : 'white'; | |
| updateTurnIndicator(); | |
| // Update move history | |
| updateMoveHistory(); | |
| // Update suggestions | |
| updateSuggestions(); | |
| } | |
| // Update a square on the board | |
| function updateSquare(row, col) { | |
| const boardElement = document.getElementById('chess-board'); | |
| const squareIndex = row * 8 + col; | |
| const square = boardElement.children[squareIndex]; | |
| // Clear the square | |
| square.innerHTML = ''; | |
| // Set the appropriate class for the square | |
| square.className = `square ${(row + col) % 2 === 0 ? 'light' : 'dark'}`; | |
| // Add the piece if there is one | |
| const piece = gameState.board[row][col]; | |
| if (piece) { | |
| const pieceElement = document.createElement('div'); | |
| pieceElement.className = `piece ${piece === piece.toLowerCase() ? 'black' : 'white'}`; | |
| // Set the appropriate Unicode character | |
| const pieceType = piece.toLowerCase(); | |
| const color = piece === piece.toLowerCase() ? 'black' : 'white'; | |
| if (pieceType === 'k') pieceElement.textContent = pieces.king[color]; | |
| else if (pieceType === 'q') pieceElement.textContent = pieces.queen[color]; | |
| else if (pieceType === 'r') pieceElement.textContent = pieces.rook[color]; | |
| else if (pieceType === 'b') pieceElement.textContent = pieces.bishop[color]; | |
| else if (pieceType === 'n') pieceElement.textContent = pieces.knight[color]; | |
| else if (pieceType === 'p') pieceElement.textContent = pieces.pawn[color]; | |
| square.appendChild(pieceElement); | |
| } | |
| // Reattach the click handler | |
| square.addEventListener('click', () => handleSquareClick(row, col)); | |
| } | |
| // Highlight a square | |
| function highlightSquare(row, col, highlight) { | |
| const boardElement = document.getElementById('chess-board'); | |
| const squareIndex = row * 8 + col; | |
| const square = boardElement.children[squareIndex]; | |
| if (highlight) { | |
| square.classList.add('selected'); | |
| } else { | |
| square.classList.remove('selected'); | |
| } | |
| } | |
| // Highlight possible moves | |
| function highlightPossibleMoves() { | |
| clearPossibleMoves(); | |
| for (const move of gameState.possibleMoves) { | |
| const boardElement = document.getElementById('chess-board'); | |
| const squareIndex = move.row * 8 + move.col; | |
| const square = boardElement.children[squareIndex]; | |
| const moveMarker = document.createElement('div'); | |
| moveMarker.className = 'possible-move'; | |
| square.appendChild(moveMarker); | |
| } | |
| } | |
| // Clear possible move highlights | |
| function clearPossibleMoves() { | |
| const boardElement = document.getElementById('chess-board'); | |
| const moveMarkers = boardElement.querySelectorAll('.possible-move'); | |
| for (const marker of moveMarkers) { | |
| marker.remove(); | |
| } | |
| } | |
| // Update turn indicator | |
| function updateTurnIndicator() { | |
| const turnIndicator = document.getElementById('turn-indicator'); | |
| turnIndicator.textContent = `${gameState.turn.charAt(0).toUpperCase() + gameState.turn.slice(1)}'s turn`; | |
| turnIndicator.className = `text-lg font-semibold ${gameState.turn === 'white' ? 'text-gray-800' : 'text-gray-800'}`; | |
| } | |
| // Update captured pieces display | |
| function updateCapturedPieces() { | |
| const whiteCaptures = document.getElementById('white-captures'); | |
| const blackCaptures = document.getElementById('black-captures'); | |
| whiteCaptures.textContent = `${gameState.capturedPieces.black.length} captured`; | |
| blackCaptures.textContent = `${gameState.capturedPieces.white.length} captured`; | |
| } | |
| // Update move history | |
| function updateMoveHistory() { | |
| const moveHistoryElement = document.getElementById('move-history'); | |
| // Clear existing moves | |
| moveHistoryElement.innerHTML = ''; | |
| // Add moves in pairs (white and black) | |
| for (let i = 0; i < gameState.moveHistory.length; i += 2) { | |
| const whiteMove = gameState.moveHistory[i]; | |
| const blackMove = gameState.moveHistory[i + 1]; | |
| const movePair = document.createElement('div'); | |
| movePair.className = 'bg-gray-100 p-2 rounded flex items-center justify-between'; | |
| const moveNumber = document.createElement('span'); | |
| moveNumber.className = 'text-gray-500 text-sm'; | |
| moveNumber.textContent = `${Math.floor(i / 2) + 1}.`; | |
| const whiteMoveSpan = document.createElement('span'); | |
| whiteMoveSpan.className = 'font-medium'; | |
| whiteMoveSpan.textContent = whiteMove.notation; | |
| movePair.appendChild(moveNumber); | |
| movePair.appendChild(whiteMoveSpan); | |
| if (blackMove) { | |
| const blackMoveSpan = document.createElement('span'); | |
| blackMoveSpan.className = 'font-medium'; | |
| blackMoveSpan.textContent = blackMove.notation; | |
| movePair.appendChild(blackMoveSpan); | |
| } | |
| moveHistoryElement.appendChild(movePair); | |
| } | |
| } | |
| // Clear move history | |
| function clearMoveHistory() { | |
| const moveHistoryElement = document.getElementById('move-history'); | |
| moveHistoryElement.innerHTML = ''; | |
| } | |
| // Get algebraic notation for a move | |
| function getMoveNotation(fromRow, fromCol, toRow, toCol) { | |
| const piece = gameState.board[fromRow][fromCol].toLowerCase(); | |
| const file = String.fromCharCode(97 + fromCol); | |
| const rank = 8 - fromRow; | |
| const capture = gameState.board[toRow][toCol] !== '' ? 'x' : ''; | |
| const destFile = String.fromCharCode(97 + toCol); | |
| const destRank = 8 - toRow; | |
| // For pawns | |
| if (piece === 'p') { | |
| if (capture) { | |
| return `${file}${capture}${destFile}${destRank}`; | |
| } else { | |
| return `${destFile}${destRank}`; | |
| } | |
| } | |
| // For other pieces | |
| const pieceSymbol = piece === 'n' ? 'N' : | |
| piece === 'b' ? 'B' : | |
| piece === 'r' ? 'R' : | |
| piece === 'q' ? 'Q' : | |
| piece === 'k' ? 'K' : ''; | |
| return `${pieceSymbol}${capture}${destFile}${destRank}`; | |
| } | |
| // Update move suggestions | |
| function updateSuggestions() { | |
| const suggestionsElement = document.getElementById('suggestions'); | |
| if (gameState.moveHistory.length === 0) { | |
| suggestionsElement.innerHTML = ` | |
| <p class="text-gray-600">Suggested first moves:</p> | |
| <ul class="mt-2 ml-4 list-disc"> | |
| <li>e4 (King's Pawn)</li> | |
| <li>d4 (Queen's Pawn)</li> | |
| <li>Nf3 (King's Knight)</li> | |
| </ul> | |
| `; | |
| return; | |
| } | |
| // Simple suggestion logic (in a real app, this would use a chess engine) | |
| const lastMove = gameState.moveHistory[gameState.moveHistory.length - 1]; | |
| const color = gameState.turn; | |
| // Find all pieces of the current color | |
| const pieces = []; | |
| for (let row = 0; row < 8; row++) { | |
| for (let col = 0; col < 8; col++) { | |
| const piece = gameState.board[row][col]; | |
| if (piece && | |
| ((color === 'white' && piece === piece.toUpperCase()) || | |
| (color === 'black' && piece === piece.toLowerCase()))) { | |
| pieces.push({ row, col, piece }); | |
| } | |
| } | |
| } | |
| // Find possible moves for all pieces | |
| const allMoves = []; | |
| for (const piece of pieces) { | |
| const moves = calculatePossibleMoves(piece.row, piece.col); | |
| for (const move of moves) { | |
| allMoves.push({ | |
| from: { row: piece.row, col: piece.col }, | |
| to: { row: move.row, col: move.col }, | |
| piece: piece.piece | |
| }); | |
| } | |
| } | |
| // Filter for captures | |
| const captures = allMoves.filter(move => | |
| gameState.board[move.to.row][move.to.col] !== '' | |
| ); | |
| // Filter for checks (simplified) | |
| const checks = allMoves.filter(move => { | |
| // Simulate the move | |
| const originalPiece = gameState.board[move.to.row][move.to.col]; | |
| gameState.board[move.to.row][move.to.col] = move.piece; | |
| gameState.board[move.from.row][move.from.col] = ''; | |
| // Check if the opponent's king is under attack | |
| let kingFound = false; | |
| for (let row = 0; row < 8; row++) { | |
| for (let col = 0; col < 8; col++) { | |
| const piece = gameState.board[row][col]; | |
| if (piece.toLowerCase() === 'k' && | |
| ((color === 'white' && piece === piece.toLowerCase()) || | |
| (color === 'black' && piece === piece.toUpperCase()))) { | |
| kingFound = true; | |
| break; | |
| } | |
| } | |
| } | |
| // Undo the move | |
| gameState.board[move.from.row][move.from.col] = move.piece; | |
| gameState.board[move.to.row][move.to.col] = originalPiece; | |
| return kingFound; | |
| }); | |
| // Generate suggestions | |
| let suggestionsHTML = ''; | |
| if (checks.length > 0) { | |
| suggestionsHTML += `<p class="text-green-600 font-medium">Check opportunities:</p>`; | |
| suggestionsHTML += `<ul class="mt-1 ml-4 list-disc">`; | |
| for (let i = 0; i < Math.min(2, checks.length); i++) { | |
| const move = checks[i]; | |
| const notation = getMoveNotation(move.from.row, move.from.col, move.to.row, move.to.col); | |
| suggestionsHTML += `<li>${notation}</li>`; | |
| } | |
| suggestionsHTML += `</ul>`; | |
| } | |
| if (captures.length > 0) { | |
| suggestionsHTML += `<p class="mt-2 text-blue-600 font-medium">Capture opportunities:</p>`; | |
| suggestionsHTML += `<ul class="mt-1 ml-4 list-disc">`; | |
| for (let i = 0; i < Math.min(3, captures.length); i++) { | |
| const move = captures[i]; | |
| const notation = getMoveNotation(move.from.row, move.from.col, move.to.row, move.to.col); | |
| suggestionsHTML += `<li>${notation}</li>`; | |
| } | |
| suggestionsHTML += `</ul>`; | |
| } | |
| if (suggestionsHTML === '') { | |
| // Suggest developing pieces | |
| const developingMoves = allMoves.filter(move => { | |
| const piece = move.piece.toLowerCase(); | |
| return (piece === 'n' || piece === 'b') && | |
| gameState.board[move.to.row][move.to.col] === ''; | |
| }); | |
| if (developingMoves.length > 0) { | |
| suggestionsHTML += `<p class="text-purple-600 font-medium">Develop your pieces:</p>`; | |
| suggestionsHTML += `<ul class="mt-1 ml-4 list-disc">`; | |
| for (let i = 0; i < Math.min(3, developingMoves.length); i++) { | |
| const move = developingMoves[i]; | |
| const notation = getMoveNotation(move.from.row, move.from.col, move.to.row, move.to.col); | |
| suggestionsHTML += `<li>${notation}</li>`; | |
| } | |
| suggestionsHTML += `</ul>`; | |
| } else { | |
| suggestionsHTML = `<p class="text-gray-600">No obvious suggestions. Consider improving your position.</p>`; | |
| } | |
| } | |
| suggestionsElement.innerHTML = suggestionsHTML; | |
| } | |
| // Show next tactic | |
| function showNextTactic() { | |
| gameState.currentTacticIndex = (gameState.currentTacticIndex + 1) % tactics.length; | |
| const tactic = tactics[gameState.currentTacticIndex]; | |
| const tacticsInfo = document.getElementById('tactics-info'); | |
| tacticsInfo.innerHTML = ` | |
| <div class="mb-4"> | |
| <h3 class="font-semibold text-lg text-gray-700">${tactic.name}</h3> | |
| <p class="text-gray-600 text-sm">${tactic.description}</p> | |
| <p class="text-gray-500 text-xs mt-1 italic">Example: ${tactic.example}</p> | |
| </div> | |
| `; | |
| } | |
| // Undo last move | |
| function undoLastMove() { | |
| if (gameState.moveHistory.length === 0) return; | |
| const lastMove = gameState.moveHistory.pop(); | |
| // Move the piece back | |
| gameState.board[lastMove.from.row][lastMove.from.col] = lastMove.piece; | |
| gameState.board[lastMove.to.row][lastMove.to.col] = lastMove.captured || ''; | |
| // Handle castling | |
| if (lastMove.notation.includes('O-O')) { | |
| const rookCol = lastMove.to.col > lastMove.from.col ? 5 : 3; | |
| const originalRookCol = lastMove.to.col > lastMove.from.col ? 7 : 0; | |
| gameState.board[lastMove.from.row][originalRookCol] = | |
| lastMove.piece === 'K' ? 'R' : 'r'; | |
| gameState.board[lastMove.from.row][rookCol] = ''; | |
| updateSquare(lastMove.from.row, rookCol); | |
| updateSquare(lastMove.from.row, originalRookCol); | |
| } | |
| // Update captured pieces if needed | |
| if (lastMove.captured) { | |
| const capturedColor = lastMove.captured === lastMove.captured.toLowerCase() ? 'black' : 'white'; | |
| gameState.capturedPieces[capturedColor].pop(); | |
| updateCapturedPieces(); | |
| } | |
| // Update the board display | |
| updateSquare(lastMove.from.row, lastMove.from.col); | |
| updateSquare(lastMove.to.row, lastMove.to.col); | |
| // Switch turns back | |
| gameState.turn = lastMove.turn; | |
| updateTurnIndicator(); | |
| // Update move history | |
| updateMoveHistory(); | |
| // Update suggestions | |
| updateSuggestions(); | |
| } | |
| // Event listeners | |
| document.getElementById('new-game-btn').addEventListener('click', initBoard); | |
| document.getElementById('next-tactic-btn').addEventListener('click', showNextTactic); | |
| document.getElementById('undo-btn').addEventListener('click', undoLastMove); | |
| // Initialize the board | |
| initBoard(); | |
| showNextTactic(); | |
| }); | |
| </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=AC-Angelo93/chess-tutor" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |