diff --git a/index.html b/index.html index d646a5d..dfcb2f2 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,7 @@ Chessical + diff --git a/scripts/board.js b/scripts/board.js index 27f07aa..ede3354 100644 --- a/scripts/board.js +++ b/scripts/board.js @@ -13,6 +13,8 @@ let whiteKingsideRookMoved = false; let whiteQueensideRookMoved = false; let blackKingsideRookMoved = false; let blackQueensideRookMoved = false; +let selectedPiece = null; +let selectedSquare = null; function initBoard() { const initialBoard = [ @@ -64,6 +66,8 @@ function initBoard() { document.querySelectorAll('.piece').forEach(piece => { piece.addEventListener('dragstart', handleDragStart); }); + + initializeTouchAndClickEvents(); } function handleDragStart(event) { @@ -160,6 +164,7 @@ function handleDrop(event) { } targetSquare.appendChild(draggedPiece); + handlePawnPromotion(targetSquare, draggedPiece); const currentPosition = getCurrentPosition(); @@ -174,6 +179,16 @@ function handleDrop(event) { updateTurn(); updateEvaluationBar(); + + // Add checkmate detection here + const pieceColor = draggedPiece.getAttribute('data-piece').split('_')[0]; + const opponentIsWhite = pieceColor === 'black'; + + if (isCheckmate(opponentIsWhite)) { + setTimeout(() => { + showGameEndModal(`${pieceColor.charAt(0).toUpperCase() + pieceColor.slice(1)} wins by checkmate!`); + }, 100); // Small delay to ensure the UI updates first + } } draggedPiece.classList.remove('dragging'); @@ -560,3 +575,168 @@ function doesMoveResolveCheck(piece, sourceIndex, targetIndex) { // Initialize the board with pieces and listeners initBoard(); + +// Add this function after handleDrop +function handlePawnPromotion(square, piece) { + const row = Math.floor(parseInt(square.getAttribute('data-index')) / 8); + const pieceColor = piece.getAttribute('data-piece').split('_')[0]; + + // Check if pawn reached the opposite end + if (piece.getAttribute('data-piece').includes('pawn') && + ((pieceColor === 'white' && row === 0) || (pieceColor === 'black' && row === 7))) { + + // Create and show promotion modal + const modal = document.createElement('div'); + modal.className = 'promotion-modal'; + + // Add promotion piece options + const pieces = ['queen', 'rook', 'bishop', 'knight']; + pieces.forEach(pieceType => { + const option = document.createElement('img'); + option.src = `${piecesFolder}${pieceColor}_${pieceType}.png`; + option.className = 'promotion-option'; + option.addEventListener('click', () => { + // Update the piece + piece.src = option.src; + piece.setAttribute('data-piece', `${pieceColor}_${pieceType}`); + modal.remove(); + updateBoardState(); + updateEvaluationBar(); + }); + modal.appendChild(option); + }); + + document.body.appendChild(modal); + } +} + +// Add these functions after your existing isKingInCheck function +function isCheckmate(isWhiteKing) { + // First verify the king is in check + const kingPos = findKingPosition(isWhiteKing); + if (!kingPos || !isKingInCheck(kingPos.row, kingPos.col, isWhiteKing)) { + return false; + } + + // Generate all possible moves + const allMoves = generateAllPossibleMoves(isWhiteKing); + + // Try each move to see if it gets out of check + for (const move of allMoves) { + const sourceSquare = document.querySelector(`[data-index="${move.from}"]`); + const piece = sourceSquare.querySelector('.piece'); + if (doesMoveResolveCheck(piece, move.from, move.to)) { + return false; + } + } + + return true; +} + +function showGameEndModal(message) { + const modal = document.createElement('div'); + modal.className = 'promotion-modal'; // Reuse the promotion modal styling + + const messageElement = document.createElement('p'); + messageElement.textContent = message; + messageElement.style.textAlign = 'center'; + messageElement.style.marginBottom = '10px'; + + const newGameButton = document.createElement('button'); + newGameButton.textContent = 'New Game'; + newGameButton.style.padding = '5px 10px'; + newGameButton.onclick = () => { + location.reload(); + }; + + modal.appendChild(messageElement); + modal.appendChild(newGameButton); + document.body.appendChild(modal); +} + +function initializeTouchAndClickEvents() { + // Add click handlers to all squares + document.querySelectorAll('.square').forEach(square => { + square.addEventListener('click', handleSquareClick); + }); +} + +function handleSquareClick(event) { + const square = event.currentTarget; + const piece = square.querySelector('.piece'); + + // If no piece is selected + if (!selectedPiece) { + if (piece) { + // Select the piece + selectedPiece = piece; + selectedSquare = square; + square.classList.add('selected-square'); + } + return; + } + + // If a piece is already selected + if (selectedPiece) { + const sourceIndex = parseInt(selectedSquare.getAttribute('data-index')); + const targetIndex = parseInt(square.getAttribute('data-index')); + + // Clear previous selection + selectedSquare.classList.remove('selected-square'); + + // If clicking the same square, deselect the piece + if (square === selectedSquare) { + selectedPiece = null; + selectedSquare = null; + return; + } + + // Try to make the move + if (isValidMove(selectedPiece, sourceIndex, targetIndex)) { + const targetPiece = square.querySelector('.piece'); + + // Handle capture + if (targetPiece) { + const capturedPieceColor = targetPiece.getAttribute('data-piece').split('_')[0]; + if (capturedPieceColor === selectedPiece.getAttribute('data-piece').split('_')[0]) { + selectedPiece = null; + selectedSquare = null; + return; + } + targetPiece.remove(); + + if (capturedPieceColor === 'white') { + capturedWhitePieces.push(targetPiece); + } else { + capturedBlackPieces.push(targetPiece); + } + + displayCapturedPieces(); + } + + // Move the piece + square.appendChild(selectedPiece); + handlePawnPromotion(square, selectedPiece); + + const currentPosition = getCurrentPosition(); + positionHistory.push(currentPosition); + + updateTurn(); + updateEvaluationBar(); + + // Check for checkmate + const pieceColor = selectedPiece.getAttribute('data-piece').split('_')[0]; + const opponentIsWhite = pieceColor === 'black'; + + if (isCheckmate(opponentIsWhite)) { + setTimeout(() => { + showGameEndModal(`${pieceColor.charAt(0).toUpperCase() + pieceColor.slice(1)} wins by checkmate!`); + }, 100); + } + } + + // Reset selection + selectedPiece = null; + selectedSquare = null; + } +} diff --git a/styles.css b/styles.css index ca7a111..3ecc8cc 100644 --- a/styles.css +++ b/styles.css @@ -115,4 +115,92 @@ h1{ background: rgba(255, 255, 255, 0.8); padding: 2px 4px; border-radius: 3px; +} + +.promotion-modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: white; + padding: 20px; + border: 2px solid #333; + border-radius: 8px; + display: flex; + gap: 10px; + z-index: 1000; + box-shadow: 0 0 10px rgba(0,0,0,0.5); +} + +.promotion-option { + width: 60px; + height: 60px; + cursor: pointer; + transition: transform 0.2s; +} + +.promotion-option:hover { + transform: scale(1.1); +} + +/* Add responsive design */ +@media (max-width: 768px) { + #board { + width: 100vw; + height: 100vw; + max-width: 640px; + max-height: 640px; + } + + .square { + width: 12.5vw; + height: 12.5vw; + max-width: 80px; + max-height: 80px; + } + + .piece { + width: 100%; + height: 100%; + } + + #evaluation-bar { + right: 10px; + height: 300px; + } + + .captured-pieces { + padding: 2px; + } + + .captured-pieces .piece { + width: 30px; + height: 30px; + } +} + +/* Add touch support */ +@media (hover: none) { + .square:hover { + background-color: inherit; + } + + .square.touch-selected { + background-color: #c8e6c9; + } +} + +.selected-square { + background-color: rgba(255, 255, 0, 0.4) !important; +} + +/* Improve touch targets */ +@media (hover: none) { + .square:hover { + background-color: inherit; + } + + .piece { + -webkit-tap-highlight-color: transparent; + } } \ No newline at end of file