Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Adaptive Chess - Learn With Me</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --primary: #2c3e50; | |
| --secondary: #3498db; | |
| --accent: #e74c3c; | |
| --light: #ecf0f1; | |
| --dark: #1a252f; | |
| --light-square: #f0d9b5; | |
| --dark-square: #b58863; | |
| --highlight: rgba(255, 255, 0, 0.4); | |
| --possible-move: rgba(0, 0, 0, 0.2); | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, var(--dark) 0%, var(--primary) 100%); | |
| min-height: 100vh; | |
| color: var(--light); | |
| overflow-x: hidden; | |
| } | |
| header { | |
| text-align: center; | |
| padding: 1.5rem; | |
| background: rgba(0, 0, 0, 0.3); | |
| backdrop-filter: blur(10px); | |
| } | |
| header h1 { | |
| font-size: clamp(1.5rem, 4vw, 2.5rem); | |
| margin-bottom: 0.5rem; | |
| background: linear-gradient(90deg, var(--secondary), var(--accent)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| } | |
| .built-with { | |
| font-size: 0.85rem; | |
| color: var(--light); | |
| opacity: 0.8; | |
| } | |
| .built-with a { | |
| color: var(--secondary); | |
| text-decoration: none; | |
| transition: color 0.3s; | |
| } | |
| .built-with a:hover { | |
| color: var(--accent); | |
| } | |
| .container { | |
| display: flex; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| align-items: flex-start; | |
| gap: 2rem; | |
| padding: 2rem; | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| } | |
| .game-section { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| .board-container { | |
| position: relative; | |
| padding: 10px; | |
| background: linear-gradient(145deg, #34495e, #2c3e50); | |
| border-radius: 12px; | |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); | |
| } | |
| .board { | |
| display: grid; | |
| grid-template-columns: repeat(8, 1fr); | |
| width: clamp(280px, 80vw, 560px); | |
| aspect-ratio: 1; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| } | |
| .square { | |
| aspect-ratio: 1; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| font-size: clamp(2rem, 8vw, 4rem); | |
| cursor: pointer; | |
| position: relative; | |
| transition: transform 0.1s; | |
| user-select: none; | |
| } | |
| .square.light { | |
| background: var(--light-square); | |
| } | |
| .square.dark { | |
| background: var(--dark-square); | |
| } | |
| .square.selected { | |
| background: var(--highlight) ; | |
| } | |
| .square.possible-move::after { | |
| content: ''; | |
| position: absolute; | |
| width: 30%; | |
| height: 30%; | |
| background: var(--possible-move); | |
| border-radius: 50%; | |
| } | |
| .square.possible-capture::after { | |
| content: ''; | |
| position: absolute; | |
| width: 90%; | |
| height: 90%; | |
| border: 4px solid var(--possible-move); | |
| border-radius: 50%; | |
| background: transparent; | |
| } | |
| .square.last-move { | |
| background: rgba(155, 199, 0, 0.4) ; | |
| } | |
| .square.check { | |
| background: radial-gradient(circle, var(--accent) 0%, transparent 70%) ; | |
| } | |
| .piece { | |
| text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); | |
| transition: transform 0.15s; | |
| } | |
| .square:hover .piece { | |
| transform: scale(1.1); | |
| } | |
| .info-panel { | |
| background: rgba(0, 0, 0, 0.4); | |
| backdrop-filter: blur(10px); | |
| border-radius: 12px; | |
| padding: 1.5rem; | |
| width: clamp(280px, 80vw, 350px); | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .stat-card { | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 8px; | |
| padding: 1rem; | |
| } | |
| .stat-card h3 { | |
| font-size: 0.85rem; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| color: var(--secondary); | |
| margin-bottom: 0.5rem; | |
| } | |
| .stat-value { | |
| font-size: 1.8rem; | |
| font-weight: bold; | |
| } | |
| .level-bar { | |
| width: 100%; | |
| height: 10px; | |
| background: rgba(255, 255, 255, 0.2); | |
| border-radius: 5px; | |
| overflow: hidden; | |
| margin-top: 0.5rem; | |
| } | |
| .level-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, var(--secondary), var(--accent)); | |
| transition: width 0.5s ease; | |
| border-radius: 5px; | |
| } | |
| .status { | |
| text-align: center; | |
| padding: 0.75rem; | |
| border-radius: 8px; | |
| font-weight: bold; | |
| background: rgba(255, 255, 255, 0.1); | |
| } | |
| .status.your-turn { | |
| background: rgba(46, 204, 113, 0.3); | |
| color: #2ecc71; | |
| } | |
| .status.ai-turn { | |
| background: rgba(231, 76, 60, 0.3); | |
| color: #e74c3c; | |
| } | |
| .status.game-over { | |
| background: rgba(155, 89, 182, 0.3); | |
| color: #9b59b6; | |
| } | |
| .buttons { | |
| display: flex; | |
| gap: 0.5rem; | |
| flex-wrap: wrap; | |
| } | |
| button { | |
| flex: 1; | |
| min-width: 100px; | |
| padding: 0.75rem 1rem; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 0.9rem; | |
| font-weight: bold; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, var(--secondary), #2980b9); | |
| color: white; | |
| } | |
| .btn-secondary { | |
| background: rgba(255, 255, 255, 0.2); | |
| color: white; | |
| } | |
| button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3); | |
| } | |
| .move-history { | |
| max-height: 200px; | |
| overflow-y: auto; | |
| background: rgba(0, 0, 0, 0.2); | |
| border-radius: 8px; | |
| padding: 0.75rem; | |
| } | |
| .move-history::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .move-history::-webkit-scrollbar-track { | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 3px; | |
| } | |
| .move-history::-webkit-scrollbar-thumb { | |
| background: var(--secondary); | |
| border-radius: 3px; | |
| } | |
| .move-row { | |
| display: flex; | |
| padding: 0.25rem 0; | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.9rem; | |
| } | |
| .move-number { | |
| width: 30px; | |
| color: var(--secondary); | |
| } | |
| .move-white, | |
| .move-black { | |
| flex: 1; | |
| } | |
| .thinking { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| justify-content: center; | |
| } | |
| .thinking-dots { | |
| display: flex; | |
| gap: 4px; | |
| } | |
| .thinking-dots span { | |
| width: 8px; | |
| height: 8px; | |
| background: var(--secondary); | |
| border-radius: 50%; | |
| animation: bounce 1.4s infinite ease-in-out both; | |
| } | |
| .thinking-dots span:nth-child(1) { | |
| animation-delay: -0.32s; | |
| } | |
| .thinking-dots span:nth-child(2) { | |
| animation-delay: -0.16s; | |
| } | |
| @keyframes bounce { | |
| 0%, | |
| 80%, | |
| 100% { | |
| transform: scale(0); | |
| } | |
| 40% { | |
| transform: scale(1); | |
| } | |
| } | |
| .promotion-modal { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.8); | |
| display: none; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 1000; | |
| } | |
| .promotion-modal.active { | |
| display: flex; | |
| } | |
| .promotion-options { | |
| display: flex; | |
| gap: 1rem; | |
| background: var(--primary); | |
| padding: 1.5rem; | |
| border-radius: 12px; | |
| } | |
| .promotion-piece { | |
| font-size: 3rem; | |
| padding: 0.5rem 1rem; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| } | |
| .promotion-piece:hover { | |
| background: var(--secondary); | |
| transform: scale(1.1); | |
| } | |
| .performance-graph { | |
| height: 80px; | |
| display: flex; | |
| align-items: flex-end; | |
| gap: 2px; | |
| padding: 0.5rem; | |
| background: rgba(0, 0, 0, 0.2); | |
| border-radius: 8px; | |
| } | |
| .graph-bar { | |
| flex: 1; | |
| background: var(--secondary); | |
| border-radius: 2px 2px 0 0; | |
| transition: height 0.3s; | |
| min-height: 4px; | |
| } | |
| .graph-bar.win { | |
| background: #2ecc71; | |
| } | |
| .graph-bar.loss { | |
| background: #e74c3c; | |
| } | |
| .graph-bar.draw { | |
| background: #f39c12; | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 1rem; | |
| gap: 1rem; | |
| } | |
| .info-panel { | |
| width: 100%; | |
| } | |
| header { | |
| padding: 1rem; | |
| } | |
| } | |
| .captured-pieces { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 2px; | |
| min-height: 30px; | |
| font-size: 1.2rem; | |
| } | |
| .advantage { | |
| font-size: 0.9rem; | |
| color: var(--secondary); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>♔ Adaptive Chess</h1> | |
| <p class="built-with">Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" | |
| target="_blank">anycoder</a></p> | |
| </header> | |
| <div class="container"> | |
| <div class="game-section"> | |
| <div class="captured-pieces" id="captured-black"></div> | |
| <div class="board-container"> | |
| <div class="board" id="board"></div> | |
| </div> | |
| <div class="captured-pieces" id="captured-white"></div> | |
| </div> | |
| <div class="info-panel"> | |
| <div id="status" class="status your-turn">Your Turn (White)</div> | |
| <div class="stat-card"> | |
| <h3>AI Skill Level</h3> | |
| <div class="stat-value" id="skill-level">1</div> | |
| <div class="level-bar"> | |
| <div class="level-fill" id="level-fill" style="width: 5%"></div> | |
| </div> | |
| <small style="opacity: 0.7">Adapts to your play style</small> | |
| </div> | |
| <div class="stat-card"> | |
| <h3>Your Performance</h3> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;"> | |
| <span>Wins: <strong id="wins">0</strong></span> | |
| <span>Draws: <strong id="draws">0</strong></span> | |
| <span>Losses: <strong id="losses">0</strong></span> | |
| </div> | |
| <div class="performance-graph" id="performance-graph"></div> | |
| </div> | |
| <div class="stat-card"> | |
| <h3>Move History</h3> | |
| <div class="move-history" id="move-history"></div> | |
| </div> | |
| <div class="buttons"> | |
| <button class="btn-primary" onclick="newGame()">New Game</button> | |
| <button class="btn-secondary" onclick="undoMove()">Undo</button> | |
| </div> | |
| <div class="buttons"> | |
| <button class="btn-secondary" onclick="flipBoard()">Flip Board</button> | |
| <button class="btn-secondary" onclick="resetProgress()">Reset AI</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="promotion-modal" id="promotion-modal"> | |
| <div class="promotion-options" id="promotion-options"></div> | |
| </div> | |
| <script> | |
| // Chess piece unicode symbols | |
| const PIECES = { | |
| 'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙', | |
| 'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟' | |
| }; | |
| // Game state | |
| let board = []; | |
| let selectedSquare = null; | |
| let possibleMoves = []; | |
| let currentTurn = 'white'; | |
| let moveHistory = []; | |
| let gameHistory = []; | |
| let castlingRights = { K: true, Q: true, k: true, q: true }; | |
| let enPassantSquare = null; | |
| let halfMoveClock = 0; | |
| let fullMoveNumber = 1; | |
| let isFlipped = false; | |
| let lastMove = null; | |
| let capturedPieces = { white: [], black: [] }; | |
| // AI and adaptive learning state | |
| let skillLevel = 1; | |
| let maxSkillLevel = 20; | |
| let playerStats = { wins: 0, draws: 0, losses: 0 }; | |
| let gameResults = []; | |
| let playerMoveQuality = []; | |
| let isAIThinking = false; | |
| // Piece values for evaluation | |
| const PIECE_VALUES = { | |
| 'p': 100, 'n': 320, 'b': 330, 'r': 500, 'q': 900, 'k': 20000, | |
| 'P': 100, 'N': 320, 'B': 330, 'R': 500, 'Q': 900, 'K': 20000 | |
| }; | |
| // Position tables for piece-square evaluation | |
| const PAWN_TABLE = [ | |
| 0, 0, 0, 0, 0, 0, 0, 0, | |
| 50, 50, 50, 50, 50, 50, 50, 50, | |
| 10, 10, 20, 30, 30, 20, 10, 10, | |
| 5, 5, 10, 25, 25, 10, 5, 5, | |
| 0, 0, 0, 20, 20, 0, 0, 0, | |
| 5, -5,-10, 0, 0,-10, -5, 5, | |
| 5, 10, 10,-20,-20, 10, 10, 5, | |
| 0, 0, 0, 0, 0, 0, 0, 0 | |
| ]; | |
| const KNIGHT_TABLE = [ | |
| -50,-40,-30,-30,-30,-30,-40,-50, | |
| -40,-20, 0, 0, 0, 0,-20,-40, | |
| -30, 0, 10, 15, 15, 10, 0,-30, | |
| -30, 5, 15, 20, 20, 15, 5,-30, | |
| -30, 0, 15, 20, 20, 15, 0,-30, | |
| -30, 5, 10, 15, 15, 10, 5,-30, | |
| -40,-20, 0, 5, 5, 0,-20,-40, | |
| -50,-40,-30,-30,-30,-30,-40,-50 | |
| ]; | |
| const BISHOP_TABLE = [ | |
| -20,-10,-10,-10,-10,-10,-10,-20, | |
| -10, 0, 0, 0, 0, 0, 0,-10, | |
| -10, 0, 5, 10, 10, 5, 0,-10, | |
| -10, 5, 5, 10, 10, 5, 5,-10, | |
| -10, 0, 10, 10, 10, 10, 0,-10, | |
| -10, 10, 10, 10, 10, 10, 10,-10, | |
| -10, 5, 0, 0, 0, 0, 5,-10, | |
| -20,-10,-10,-10,-10,-10,-10,-20 | |
| ]; | |
| const ROOK_TABLE = [ | |
| 0, 0, 0, 0, 0, 0, 0, 0, | |
| 5, 10, 10, 10, 10, 10, 10, 5, | |
| -5, 0, 0, 0, 0, 0, 0, -5, | |
| -5, 0, 0, 0, 0, 0, 0, -5, | |
| -5, 0, 0, 0, 0, 0, 0, -5, | |
| -5, 0, 0, 0, 0, 0, 0, -5, | |
| -5, 0, 0, 0, 0, 0, 0, -5, | |
| 0, 0, 0, 5, 5, 0, 0, 0 | |
| ]; | |
| const QUEEN_TABLE = [ | |
| -20,-10,-10, -5, -5,-10,-10,-20, | |
| -10, 0, 0, 0, 0, 0, 0,-10, | |
| -10, 0, 5, 5, 5, 5, 0,-10, | |
| -5, 0, 5, 5, 5, 5, 0, -5, | |
| 0, 0, 5, 5, 5, 5, 0, -5, | |
| -10, 5, 5, 5, 5, 5, 0,-10, | |
| -10, 0, 5, 0, 0, 0, 0,-10, | |
| -20,-10,-10, -5, -5,-10,-10,-20 | |
| ]; | |
| const KING_TABLE = [ | |
| -30,-40,-40,-50,-50,-40,-40,-30, | |
| -30,-40,-40,-50,-50,-40,-40,-30, | |
| -30,-40,-40,-50,-50,-40,-40,-30, | |
| -30,-40,-40,-50,-50,-40,-40,-30, | |
| -20,-30,-30,-40,-40,-30,-30,-20, | |
| -10,-20,-20,-20,-20,-20,-20,-10, | |
| 20, 20, 0, 0, 0, 0, 20, 20, | |
| 20, 30, 10, 0, 0, 10, 30, 20 | |
| ]; | |
| const KING_ENDGAME_TABLE = [ | |
| -50,-40,-30,-20,-20,-30,-40,-50, | |
| -30,-20,-10, 0, 0,-10,-20,-30, | |
| -30,-10, 20, 30, 30, 20,-10,-30, | |
| -30,-10, 30, 40, 40, 30,-10,-30, | |
| -30,-10, 30, 40, 40, 30,-10,-30, | |
| -30,-10, 20, 30, 30, 20,-10,-30, | |
| -30,-30, 0, 0, 0, 0,-30,-30, | |
| -50,-30,-30,-30,-30,-30,-30,-50 | |
| ]; | |
| // Initialize the game | |
| function initGame() { | |
| loadProgress(); | |
| setupBoard(); | |
| renderBoard(); | |
| updateUI(); | |
| } | |
| function setupBoard() { | |
| board = [ | |
| ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'], | |
| ['p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'], | |
| [null, null, null, null, null, null, null, null], | |
| [null, null, null, null, null, null, null, null], | |
| [null, null, null, null, null, null, null, null], | |
| [null, null, null, null, null, null, null, null], | |
| ['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'], | |
| ['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R'] | |
| ]; | |
| currentTurn = 'white'; | |
| selectedSquare = null; | |
| possibleMoves = []; | |
| moveHistory = []; | |
| gameHistory = []; | |
| castlingRights = { K: true, Q: true, k: true, q: true }; | |
| enPassantSquare = null; | |
| halfMoveClock = 0; | |
| fullMoveNumber = 1; | |
| lastMove = null; | |
| capturedPieces = { white: [], black: [] }; | |
| playerMoveQuality = []; | |
| } | |
| function renderBoard() { | |
| const boardEl = document.getElementById('board'); | |
| boardEl.innerHTML = ''; | |
| for (let row = 0; row < 8; row++) { | |
| for (let col = 0; col < 8; col++) { | |
| const displayRow = isFlipped ? 7 - row : row; | |
| const displayCol = isFlipped ? 7 - col : col; | |
| const square = document.createElement('div'); | |
| const isLight = (displayRow + displayCol) % 2 === 0; | |
| square.className = `square ${isLight ? 'light' : 'dark'}`; | |
| square.dataset.row = displayRow; | |
| square.dataset.col = displayCol; | |
| // Highlight last move | |
| if (lastMove) { | |
| if ((displayRow === lastMove.fromRow && displayCol === lastMove.fromCol) || | |
| (displayRow === lastMove.toRow && displayCol === lastMove.toCol)) { | |
| square.classList.add('last-move'); | |
| } | |
| } | |
| // Highlight selected square | |
| if (selectedSquare && selectedSquare.row === displayRow && selectedSquare.col === displayCol) { | |
| square.classList.add('selected'); | |
| } | |
| // Show possible moves | |
| const isPossibleMove = possibleMoves.some(m => m.toRow === displayRow && m.toCol === displayCol); | |
| if (isPossibleMove) { | |
| const targetPiece = board[displayRow][displayCol]; | |
| if (targetPiece) { | |
| square.classList.add('possible-capture'); | |
| } else { | |
| square.classList.add('possible-move'); | |
| } | |
| } | |
| // Check highlight | |
| const piece = board[displayRow][displayCol]; | |
| if (piece && piece.toLowerCase() === 'k') { | |
| const pieceColor = piece === piece.toUpperCase() ? 'white' : 'black'; | |
| if (isInCheck(pieceColor)) { | |
| square.classList.add('check'); | |
| } | |
| } | |
| // Add piece | |
| if (piece) { | |
| const pieceEl = document.createElement('span'); | |
| pieceEl.className = 'piece'; | |
| pieceEl.textContent = PIECES[piece]; | |
| square.appendChild(pieceEl); | |
| } | |
| square.addEventListener('click', () => handleSquareClick(displayRow, displayCol)); | |
| boardEl.appendChild(square); | |
| } | |
| } | |
| updateCapturedPieces(); | |
| } | |
| function updateCapturedPieces() { | |
| const whiteEl = document.getElementById('captured-white'); | |
| const blackEl = document.getElementById('captured-black'); | |
| whiteEl.innerHTML = capturedPieces.white.map(p => PIECES[p]).join(' '); | |
| blackEl.innerHTML = capturedPieces.black.map(p => PIECES[p]).join(' '); | |
| // Calculate material advantage | |
| const whiteValue = capturedPieces.black.reduce((sum, p) => sum + PIECE_VALUES[p], 0); | |
| const blackValue = capturedPieces.white.reduce((sum, p) => sum + PIECE_VALUES[p], 0); | |
| const diff = whiteValue - blackValue; | |
| if (diff > 0) { | |
| whiteEl.innerHTML += ` <span class="advantage">+${Math.floor(diff/100)}</span>`; | |
| } else if (diff < 0) { | |
| blackEl.innerHTML += ` <span class="advantage">+${Math.floor(-diff/100)}</span>`; | |
| } | |
| } | |
| function handleSquareClick(row, col) { | |
| if (isAIThinking) return; | |
| if (currentTurn !== 'white') return; | |
| const piece = board[row][col]; | |
| if (selectedSquare) { | |
| // Check if clicking on a possible move | |
| const move = possibleMoves.find(m => m.toRow === row && m.toCol === col); | |
| if (move) { | |
| makeMove(move); | |
| return; | |
| } | |
| } | |
| // Select a new piece | |
| if (piece && isWhitePiece(piece)) { | |
| selectedSquare = { row, col }; | |
| possibleMoves = getLegalMoves(row, col); | |
| renderBoard(); | |
| } else { | |
| selectedSquare = null; | |
| possibleMoves = []; | |
| renderBoard(); | |
| } | |
| } | |
| function isWhitePiece(piece) { | |
| return piece && piece === piece.toUpperCase(); | |
| } | |
| function isBlackPiece(piece) { | |
| return piece && piece === piece.toLowerCase(); | |
| } | |
| function getPieceColor(piece) { | |
| if (!piece) return null; | |
| return piece === piece.toUpperCase() ? 'white' : 'black'; | |
| } | |
| function getLegalMoves(row, col) { | |
| const piece = board[row][col]; | |
| if (!piece) return []; | |
| const moves = getPseudoLegalMoves(row, col); | |
| const color = getPieceColor(piece); | |
| // Filter out moves that leave king in check | |
| return moves.filter(move => { | |
| const testBoard = JSON.parse(JSON.stringify(board)); | |
| testBoard[move.toRow][move.toCol] = testBoard[move.fromRow][move.fromCol]; | |
| testBoard[move.fromRow][move.fromCol] = null; | |
| // Handle en passant capture | |
| if (move.enPassant) { | |
| testBoard[move.fromRow][move.toCol] = null; | |
| } | |
| // Handle castling - move rook | |
| if (move.castling) { | |
| if (move.toCol === 6) { // Kingside | |
| testBoard[move.toRow][5] = testBoard[move.toRow][7]; | |
| testBoard[move.toRow][7] = null; | |
| } else { // Queenside | |
| testBoard[move.toRow][3] = testBoard[move.toRow][0]; | |
| testBoard[move.toRow][0] = null; | |
| } | |
| } | |
| return !isInCheckWithBoard(color, testBoard); | |
| }); | |
| } | |
| function getPseudoLegalMoves(row, col) { | |
| const piece = board[row][col]; | |
| if (!piece) return []; | |
| const pieceType = piece.toLowerCase(); | |
| const color = getPieceColor(piece); | |
| const moves = []; | |
| switch (pieceType) { | |
| case 'p': | |
| moves.push(...getPawnMoves(row, col, color)); | |
| break; | |
| case 'n': | |
| moves.push(...getKnightMoves(row, col, color)); | |
| break; | |
| case 'b': | |
| moves.push(...getBishopMoves(row, col, color)); | |
| break; | |
| case 'r': | |
| moves.push(...getRookMoves(row, col, color)); | |
| break; | |
| case 'q': | |
| moves.push(...getQueenMoves(row, col, color)); | |
| break; | |
| case 'k': | |
| moves.push(...getKingMoves(row, col, color)); | |
| break; | |
| } | |
| return moves; | |
| } | |
| function getPawnMoves(row, col, color) { | |
| const moves = []; | |
| const direction = color === 'white' ? -1 : 1; | |
| const startRow = color === 'white' ? 6 : 1; | |
| const promotionRow = color === 'white' ? 0 : 7; | |
| // Forward move | |
| if (isValidSquare(row + direction, col) && !board[row + direction][col]) { | |
| if (row + direction === promotionRow) { | |
| moves.push({ fromRow: row, fromCol: col, toRow: row + direction, toCol: col, promotion: true }); | |
| } else { | |
| moves.push({ fromRow: row, fromCol: col, toRow: row + direction, toCol: col }); | |
| } | |
| // Double move from start | |
| if (row === startRow && !board[row + 2 * direction][col]) { | |
| moves.push({ fromRow: row, fromCol: col, toRow: row + 2 * direction, toCol: col, doublePawn: true }); | |
| } | |
| } | |
| // Captures | |
| for (const dc of [-1, 1]) { | |
| const newCol = col + dc; | |
| if (isValidSquare(row + direction, newCol)) { | |
| const target = board[row + direction][newCol]; | |
| if (target && getPieceColor(target) !== color) { | |
| if (row + direction === promotionRow) { | |
| moves.push({ fromRow: row, fromCol: col, toRow: row + direction, toCol: newCol, promotion: true }); | |
| } else { | |
| moves.push({ fromRow: row, fromCol: col, toRow: row + direction, toCol: newCol }); | |
| } | |
| } | |
| // En passant | |
| if (enPassantSquare && enPassantSquare.row === row + direction && enPassantSquare.col === newCol) { | |
| moves.push({ fromRow: row, fromCol: col, toRow: row + direction, toCol: newCol, enPassant: true }); | |
| } | |
| } | |
| } | |
| return moves; | |
| } | |
| function getKnightMoves(row, col, color) { | |
| const moves = []; | |
| const offsets = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]]; | |
| for (const [dr, dc] of offsets) { | |
| const newRow = row + dr; | |
| const newCol = col + dc; | |
| if (isValidSquare(newRow, newCol)) { | |
| const target = board[newRow][newCol]; | |
| if (!target || getPieceColor(target) !== color) { | |
| moves.push({ fromRow: row, fromCol: col, toRow: newRow, toCol: newCol }); | |
| } | |
| } | |
| } | |
| return moves; | |
| } | |
| function getSlidingMoves(row, col, color, directions) { | |
| const moves = []; | |
| for (const [dr, dc] of directions) { | |
| let newRow = row + dr; | |
| let newCol = col + dc; | |
| while (isValidSquare(newRow, newCol)) { | |
| const target = board[newRow][newCol]; | |
| if (!target) { | |
| moves.push({ fromRow: row, fromCol: col, toRow: newRow, toCol: newCol }); | |
| } else { | |
| if (getPieceColor(target) !== color) { | |
| moves.push({ fromRow: row, fromCol: col, toRow: newRow, toCol: newCol }); | |
| } | |
| break; | |
| } | |
| newRow += dr; | |
| newCol += dc; | |
| } | |
| } | |
| return moves; | |
| } | |
| function getBishopMoves(row, col, color) { | |
| return getSlidingMoves(row, col, color, [[-1, -1], [-1, 1], [1, -1], [1, 1]]); | |
| } | |
| function getRookMoves(row, col, color) { | |
| return getSlidingMoves(row, col, color, [[-1, 0], [1, 0], [0, -1], [0, 1]]); | |
| } | |
| function getQueenMoves(row, col, color) { | |
| return [...getBishopMoves(row, col, color), ...getRookMoves(row, col, color)]; | |
| } | |
| function getKingMoves(row, col, color) { | |
| const moves = []; | |
| const offsets = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]; | |
| for (const [dr, dc] of offsets) { | |
| const newRow = row + dr; | |
| const newCol = col + dc; | |
| if (isValidSquare(newRow, newCol)) { | |
| const target = board[newRow][newCol]; | |
| if (!target || getPieceColor(target) !== color) { | |
| moves.push({ fromRow: row, fromCol: col, toRow: newRow, toCol: newCol }); | |
| } | |
| } | |
| } | |
| // Castling | |
| if (!isInCheck(color)) { | |
| const kingRow = color === 'white' ? 7 : 0; | |
| if (row === kingRow && col === 4) { | |
| // Kingside | |
| const kingSideRight = color === 'white' ? castlingRights.K : castlingRights.k; | |
| if (kingSideRight && !board[kingRow][5] && !board[kingRow][6]) { | |
| if (!isSquareAttacked(kingRow, 5, color) && !isSquareAttacked(kingRow, 6, color)) { | |
| moves.push({ fromRow: row, fromCol: col, toRow: kingRow, toCol: 6, castling: true }); | |
| } | |
| } | |
| // Queenside | |
| const queenSideRight = color === 'white' ? castlingRights.Q : castlingRights.q; | |
| if (queenSideRight && !board[kingRow][1] && !board[kingRow][2] && !board[kingRow][3]) { | |
| if (!isSquareAttacked(kingRow, 2, color) && !isSquareAttacked(kingRow, 3, color)) { | |
| moves.push({ fromRow: row, fromCol: col, toRow: kingRow, toCol: 2, castling: true }); | |
| } | |
| } | |
| } | |
| } | |
| return moves; | |
| } | |
| function isValidSquare(row, col) { | |
| return row >= 0 && row < 8 && col >= 0 && col < 8; | |
| } | |
| function isInCheck(color) { | |
| return isInCheckWithBoard(color, board); | |
| } | |
| function isInCheckWithBoard(color, testBoard) { | |
| // Find king | |
| let kingRow, kingCol; | |
| for (let r = 0; r < 8; r++) { | |
| for (let c = 0; c < 8; c++) { | |
| const piece = testBoard[r][c]; | |
| if (piece && piece.toLowerCase() === 'k' && getPieceColor(piece) === color) { | |
| kingRow = r; | |
| kingCol = c; | |
| break; | |
| } | |
| } | |
| } | |
| return isSquareAttackedWithBoard(kingRow, kingCol, color, testBoard); | |
| } | |
| function isSquareAttacked(row, col, defendingColor) { | |
| return isSquareAttackedWithBoard(row, col, defendingColor, board); | |
| } | |
| function isSquareAttackedWithBoard(row, col, defendingColor, testBoard) { |