|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>ChessMaster Pro</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script src="https://unpkg.com/feather-icons"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
|
|
<style> |
|
|
.chess-square { |
|
|
position: relative; |
|
|
width: 100%; |
|
|
padding-bottom: 100%; |
|
|
} |
|
|
.chess-piece { |
|
|
position: absolute; |
|
|
width: 80%; |
|
|
height: 80%; |
|
|
top: 10%; |
|
|
left: 10%; |
|
|
background-size: contain; |
|
|
background-repeat: no-repeat; |
|
|
background-position: center; |
|
|
cursor: pointer; |
|
|
transition: transform 0.2s ease; |
|
|
z-index: 10; |
|
|
} |
|
|
.chess-piece.dragging { |
|
|
z-index: 20; |
|
|
transform: scale(1.1); |
|
|
} |
|
|
.legal-move { |
|
|
position: absolute; |
|
|
width: 30%; |
|
|
height: 30%; |
|
|
border-radius: 50%; |
|
|
background-color: rgba(0, 255, 0, 0.3); |
|
|
top: 35%; |
|
|
left: 35%; |
|
|
z-index: 5; |
|
|
} |
|
|
.selected { |
|
|
background-color: rgba(255, 215, 0, 0.3) !important; |
|
|
} |
|
|
.check { |
|
|
background-color: rgba(255, 0, 0, 0.3) !important; |
|
|
} |
|
|
.captured-piece { |
|
|
width: 30px; |
|
|
height: 30px; |
|
|
background-size: contain; |
|
|
background-repeat: no-repeat; |
|
|
background-position: center; |
|
|
margin: 2px; |
|
|
} |
|
|
@media (max-width: 768px) { |
|
|
.captured-piece { |
|
|
width: 20px; |
|
|
height: 20px; |
|
|
} |
|
|
} |
|
|
.promotion-modal { |
|
|
display: none; |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
background-color: rgba(0, 0, 0, 0.7); |
|
|
z-index: 100; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
} |
|
|
.promotion-options { |
|
|
display: flex; |
|
|
background-color: #2d3748; |
|
|
padding: 20px; |
|
|
border-radius: 10px; |
|
|
} |
|
|
.promotion-piece { |
|
|
width: 60px; |
|
|
height: 60px; |
|
|
margin: 10px; |
|
|
background-size: contain; |
|
|
background-repeat: no-repeat; |
|
|
background-position: center; |
|
|
cursor: pointer; |
|
|
transition: transform 0.2s; |
|
|
} |
|
|
.promotion-piece:hover { |
|
|
transform: scale(1.1); |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-900 text-white min-h-screen flex flex-col"> |
|
|
<header class="bg-indigo-900 py-4 px-6 shadow-lg"> |
|
|
<div class="container mx-auto flex justify-between items-center"> |
|
|
<h1 class="text-2xl md:text-3xl font-bold flex items-center"> |
|
|
<i data-feather="shield" class="mr-2"></i> ChessMaster Pro |
|
|
</h1> |
|
|
<div class="flex space-x-4"> |
|
|
<button id="resetBtn" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded-lg flex items-center"> |
|
|
<i data-feather="refresh-cw" class="mr-2"></i> Reset |
|
|
</button> |
|
|
<button id="undoBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg flex items-center"> |
|
|
<i data-feather="corner-up-left" class="mr-2"></i> Undo |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
<main class="flex-grow container mx-auto px-4 py-8 flex flex-col md:flex-row items-center md:items-start justify-center gap-8"> |
|
|
|
|
|
<div class="w-full md:w-1/4 bg-gray-800 p-6 rounded-lg shadow-lg"> |
|
|
<div class="mb-6"> |
|
|
<h2 class="text-xl font-semibold mb-2">Game Status</h2> |
|
|
<div id="gameStatus" class="bg-gray-700 p-3 rounded">White's turn</div> |
|
|
</div> |
|
|
|
|
|
<div class="mb-6"> |
|
|
<h2 class="text-xl font-semibold mb-2">Captured Pieces</h2> |
|
|
<div class="bg-gray-700 p-3 rounded"> |
|
|
<h3 class="font-medium mb-1">White captured:</h3> |
|
|
<div id="whiteCaptured" class="flex flex-wrap"></div> |
|
|
|
|
|
<h3 class="font-medium mt-3 mb-1">Black captured:</h3> |
|
|
<div id="blackCaptured" class="flex flex-wrap"></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<h2 class="text-xl font-semibold mb-2">Move History</h2> |
|
|
<div id="moveHistory" class="bg-gray-700 p-3 rounded max-h-40 overflow-y-auto"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="w-full md:w-2/4 max-w-lg mx-auto"> |
|
|
<div class="bg-gray-800 p-4 rounded-lg shadow-lg"> |
|
|
<div id="chessBoard" class="grid grid-cols-8 aspect-square"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="w-full md:w-1/4 bg-gray-800 p-6 rounded-lg shadow-lg"> |
|
|
<div class="mb-6"> |
|
|
<h2 class="text-xl font-semibold mb-2">Current Player</h2> |
|
|
<div id="currentPlayer" class="bg-gray-700 p-3 rounded flex items-center"> |
|
|
<div class="w-6 h-6 bg-white rounded-full mr-2"></div> White |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mb-6"> |
|
|
<h2 class="text-xl font-semibold mb-2">Game Controls</h2> |
|
|
<div class="space-y-2"> |
|
|
<button id="flipBoardBtn" class="w-full bg-purple-600 hover:bg-purple-700 py-2 rounded flex items-center justify-center"> |
|
|
<i data-feather="rotate-cw" class="mr-2"></i> Flip Board |
|
|
</button> |
|
|
<button id="hintBtn" class="w-full bg-green-600 hover:bg-green-700 py-2 rounded flex items-center justify-center"> |
|
|
<i data-feather="help-circle" class="mr-2"></i> Show Hints |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<h2 class="text-xl font-semibold mb-2">Game Time</h2> |
|
|
<div class="bg-gray-700 p-3 rounded"> |
|
|
<div class="flex justify-between"> |
|
|
<span>White:</span> |
|
|
<span id="whiteTime">10:00</span> |
|
|
</div> |
|
|
<div class="flex justify-between mt-2"> |
|
|
<span>Black:</span> |
|
|
<span id="blackTime">10:00</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</main> |
|
|
|
|
|
|
|
|
<div id="promotionModal" class="promotion-modal"> |
|
|
<div class="promotion-options"> |
|
|
<div class="promotion-piece" data-piece="q"></div> |
|
|
<div class="promotion-piece" data-piece="r"></div> |
|
|
<div class="promotion-piece" data-piece="b"></div> |
|
|
<div class="promotion-piece" data-piece="n"></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<footer class="bg-indigo-900 py-4 text-center"> |
|
|
<p>ChessMaster Pro ♟️ - Play like a grandmaster!</p> |
|
|
</footer> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
feather.replace(); |
|
|
|
|
|
|
|
|
const chessBoard = document.getElementById('chessBoard'); |
|
|
const gameStatus = document.getElementById('gameStatus'); |
|
|
const currentPlayer = document.getElementById('currentPlayer'); |
|
|
const whiteCaptured = document.getElementById('whiteCaptured'); |
|
|
const blackCaptured = document.getElementById('blackCaptured'); |
|
|
const moveHistory = document.getElementById('moveHistory'); |
|
|
const resetBtn = document.getElementById('resetBtn'); |
|
|
const undoBtn = document.getElementById('undoBtn'); |
|
|
const flipBoardBtn = document.getElementById('flipBoardBtn'); |
|
|
const hintBtn = document.getElementById('hintBtn'); |
|
|
const promotionModal = document.getElementById('promotionModal'); |
|
|
|
|
|
let board = []; |
|
|
let selectedPiece = null; |
|
|
let legalMoves = []; |
|
|
let isWhiteTurn = true; |
|
|
let boardFlipped = false; |
|
|
let moveHistoryList = []; |
|
|
let capturedPieces = { white: [], black: [] }; |
|
|
|
|
|
|
|
|
function initializeBoard() { |
|
|
board = [ |
|
|
['br', 'bn', 'bb', 'bq', 'bk', 'bb', 'bn', 'br'], |
|
|
['bp', 'bp', 'bp', 'bp', 'bp', 'bp', 'bp', 'bp'], |
|
|
['', '', '', '', '', '', '', ''], |
|
|
['', '', '', '', '', '', '', ''], |
|
|
['', '', '', '', '', '', '', ''], |
|
|
['', '', '', '', '', '', '', ''], |
|
|
['wp', 'wp', 'wp', 'wp', 'wp', 'wp', 'wp', 'wp'], |
|
|
['wr', 'wn', 'wb', 'wq', 'wk', 'wb', 'wn', 'wr'] |
|
|
]; |
|
|
|
|
|
renderBoard(); |
|
|
updateGameStatus(); |
|
|
updateCapturedPieces(); |
|
|
moveHistory.innerHTML = ''; |
|
|
moveHistoryList = []; |
|
|
isWhiteTurn = true; |
|
|
updateCurrentPlayerDisplay(); |
|
|
} |
|
|
|
|
|
function getPieceImageUrl(piece) { |
|
|
const color = piece[0] === 'w' ? 'white' : 'black'; |
|
|
const pieceType = piece[1]; |
|
|
const pieceSymbols = { |
|
|
'p': '♟', 'r': '♜', 'n': '♞', 'b': '♝', 'q': '♛', 'k': '♚' |
|
|
}; |
|
|
|
|
|
|
|
|
const canvas = document.createElement('canvas'); |
|
|
canvas.width = 200; |
|
|
canvas.height = 200; |
|
|
const ctx = canvas.getContext('2d'); |
|
|
|
|
|
|
|
|
ctx.fillStyle = 'transparent'; |
|
|
ctx.fillRect(0, 0, 200, 200); |
|
|
|
|
|
|
|
|
ctx.font = '120px Arial, sans-serif'; |
|
|
ctx.fillStyle = color === 'white' ? '#ffffff' : '#000000'; |
|
|
ctx.textAlign = 'center'; |
|
|
ctx.textBaseline = 'middle'; |
|
|
ctx.fillText(pieceSymbols[pieceType], 100, 100); |
|
|
|
|
|
return canvas.toDataURL(); |
|
|
} |
|
|
|
|
|
|
|
|
function renderBoard() { |
|
|
chessBoard.innerHTML = ''; |
|
|
|
|
|
for (let row = 0; row < 8; row++) { |
|
|
for (let col = 0; col < 8; col++) { |
|
|
const displayRow = boardFlipped ? 7 - row : row; |
|
|
const displayCol = boardFlipped ? 7 - col : col; |
|
|
|
|
|
const square = document.createElement('div'); |
|
|
square.className = `chess-square ${(displayRow + displayCol) % 2 === 0 ? 'bg-amber-100' : 'bg-amber-800'}`; |
|
|
square.dataset.row = row; |
|
|
square.dataset.col = col; |
|
|
|
|
|
const piece = board[row][col]; |
|
|
if (piece) { |
|
|
const pieceElement = document.createElement('div'); |
|
|
pieceElement.className = `chess-piece ${piece[0] === 'w' ? 'white-piece' : 'black-piece'}`; |
|
|
pieceElement.style.backgroundImage = `url('${getPieceImageUrl(piece)})`; |
|
|
pieceElement.dataset.piece = piece; |
|
|
square.appendChild(pieceElement); |
|
|
} |
|
|
|
|
|
square.addEventListener('click', handleSquareClick); |
|
|
chessBoard.appendChild(square); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function handleSquareClick(event) { |
|
|
const square = event.target.closest('.chess-square'); |
|
|
if (!square) return; |
|
|
|
|
|
const row = parseInt(square.dataset.row); |
|
|
const col = parseInt(square.dataset.col); |
|
|
const piece = board[row][col]; |
|
|
|
|
|
|
|
|
if (selectedPiece) { |
|
|
|
|
|
const isLegalMove = legalMoves.some(move => |
|
|
move.toRow === row && move.toCol === col |
|
|
); |
|
|
|
|
|
if (isLegalMove) { |
|
|
movePiece(selectedPiece.row, selectedPiece.col, row, col); |
|
|
clearSelection(); |
|
|
} else if (piece && piece[0] === (isWhiteTurn ? 'w' : 'b')) { |
|
|
|
|
|
selectPiece(row, col); |
|
|
} else { |
|
|
|
|
|
clearSelection(); |
|
|
} |
|
|
} else if (piece && piece[0] === (isWhiteTurn ? 'w' : 'b')) { |
|
|
|
|
|
selectPiece(row, col); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function selectPiece(row, col) { |
|
|
clearSelection(); |
|
|
|
|
|
selectedPiece = { row, col, piece: board[row][col] }; |
|
|
highlightSquare(row, col, true); |
|
|
|
|
|
|
|
|
legalMoves = calculateLegalMoves(row, col); |
|
|
highlightLegalMoves(legalMoves); |
|
|
} |
|
|
|
|
|
|
|
|
function clearSelection() { |
|
|
if (selectedPiece) { |
|
|
highlightSquare(selectedPiece.row, selectedPiece.col, false); |
|
|
} |
|
|
|
|
|
selectedPiece = null; |
|
|
legalMoves = []; |
|
|
|
|
|
|
|
|
document.querySelectorAll('.legal-move').forEach(el => el.remove()); |
|
|
} |
|
|
|
|
|
|
|
|
function highlightSquare(row, col, isSelected) { |
|
|
const displayRow = boardFlipped ? 7 - row : row; |
|
|
const displayCol = boardFlipped ? 7 - col : col; |
|
|
const index = displayRow * 8 + displayCol; |
|
|
const square = chessBoard.children[index]; |
|
|
|
|
|
if (isSelected) { |
|
|
square.classList.add('selected'); |
|
|
} else { |
|
|
square.classList.remove('selected'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function highlightLegalMoves(moves) { |
|
|
moves.forEach(move => { |
|
|
const displayRow = boardFlipped ? 7 - move.toRow : move.toRow; |
|
|
const displayCol = boardFlipped ? 7 - move.toCol : move.toCol; |
|
|
const index = displayRow * 8 + displayCol; |
|
|
const square = chessBoard.children[index]; |
|
|
|
|
|
const indicator = document.createElement('div'); |
|
|
indicator.className = 'legal-move'; |
|
|
square.appendChild(indicator); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function calculateLegalMoves(row, col) { |
|
|
const piece = board[row][col]; |
|
|
const moves = []; |
|
|
|
|
|
|
|
|
if (piece[1] === 'p') { |
|
|
const direction = piece[0] === 'w' ? -1 : 1; |
|
|
|
|
|
|
|
|
if (isInBounds(row + direction, col) && !board[row + direction][col]) { |
|
|
moves.push({ toRow: row + direction, toCol: col }); |
|
|
|
|
|
|
|
|
if ((piece[0] === 'w' && row === 6) || (piece[0] === 'b' && row === 1)) { |
|
|
if (!board[row + 2 * direction][col]) { |
|
|
moves.push({ toRow: row + 2 * direction, toCol: col }); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (let offset of [-1, 1]) { |
|
|
if (isInBounds(row + direction, col + offset)) { |
|
|
const target = board[row + direction][col + offset]; |
|
|
if (target && target[0] !== piece[0]) { |
|
|
moves.push({ toRow: row + direction, toCol: col + offset }); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return moves; |
|
|
} |
|
|
|
|
|
|
|
|
function isInBounds(row, col) { |
|
|
return row >= 0 && row < 8 && col >= 0 && col < 8; |
|
|
} |
|
|
|
|
|
|
|
|
function movePiece(fromRow, fromCol, toRow, toCol) { |
|
|
const piece = board[fromRow][fromCol]; |
|
|
const targetPiece = board[toRow][toCol]; |
|
|
|
|
|
|
|
|
if (targetPiece) { |
|
|
capturedPieces[piece[0] === 'w' ? 'black' : 'white'].push(targetPiece); |
|
|
updateCapturedPieces(); |
|
|
} |
|
|
|
|
|
|
|
|
if (piece[1] === 'p' && (toRow === 0 || toRow === 7)) { |
|
|
showPromotionModal(fromRow, fromCol, toRow, toCol); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
board[toRow][toCol] = piece; |
|
|
board[fromRow][fromCol] = ''; |
|
|
|
|
|
|
|
|
const moveNotation = getMoveNotation(fromRow, fromCol, toRow, toCol, targetPiece); |
|
|
moveHistoryList.push(moveNotation); |
|
|
updateMoveHistory(); |
|
|
|
|
|
|
|
|
isWhiteTurn = !isWhiteTurn; |
|
|
updateCurrentPlayerDisplay(); |
|
|
|
|
|
renderBoard(); |
|
|
updateGameStatus(); |
|
|
} |
|
|
|
|
|
|
|
|
function getMoveNotation(fromRow, fromCol, toRow, toCol, capturedPiece) { |
|
|
const piece = board[fromRow][fromCol]; |
|
|
const pieceSymbol = piece[1] === 'p' ? '' : piece[1].toUpperCase(); |
|
|
const captureSymbol = capturedPiece ? 'x' : ''; |
|
|
const file = String.fromCharCode(97 + fromCol); |
|
|
const rank = 8 - fromRow; |
|
|
const toFile = String.fromCharCode(97 + toCol); |
|
|
const toRank = 8 - toRow; |
|
|
|
|
|
return `${isWhiteTurn ? 'White' : 'Black'}: ${pieceSymbol}${file}${rank}${captureSymbol}${toFile}${toRank}`; |
|
|
} |
|
|
|
|
|
|
|
|
function updateMoveHistory() { |
|
|
moveHistory.innerHTML = ''; |
|
|
moveHistoryList.forEach((move, index) => { |
|
|
const moveElement = document.createElement('div'); |
|
|
moveElement.textContent = `${index + 1}. ${move}`; |
|
|
moveHistory.appendChild(moveElement); |
|
|
}); |
|
|
|
|
|
|
|
|
moveHistory.scrollTop = moveHistory.scrollHeight; |
|
|
} |
|
|
|
|
|
|
|
|
function updateCapturedPieces() { |
|
|
whiteCaptured.innerHTML = ''; |
|
|
blackCaptured.innerHTML = ''; |
|
|
|
|
|
capturedPieces.white.forEach(piece => { |
|
|
const pieceElement = document.createElement('div'); |
|
|
pieceElement.className = 'captured-piece'; |
|
|
pieceElement.style.backgroundImage = `url('${getPieceImageUrl('w' + piece[1])})`; |
|
|
whiteCaptured.appendChild(pieceElement); |
|
|
}); |
|
|
|
|
|
capturedPieces.black.forEach(piece => { |
|
|
const pieceElement = document.createElement('div'); |
|
|
pieceElement.className = 'captured-piece'; |
|
|
pieceElement.style.backgroundImage = `url('${getPieceImageUrl('b' + piece[1])})`; |
|
|
blackCaptured.appendChild(pieceElement); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function updateGameStatus() { |
|
|
|
|
|
gameStatus.textContent = isWhiteTurn ? "White's turn" : "Black's turn"; |
|
|
gameStatus.className = 'bg-gray-700 p-3 rounded'; |
|
|
} |
|
|
|
|
|
|
|
|
function updateCurrentPlayerDisplay() { |
|
|
currentPlayer.innerHTML = isWhiteTurn ? |
|
|
'<div class="w-6 h-6 bg-white rounded-full mr-2"></div> White' : |
|
|
'<div class="w-6 h-6 bg-black border border-white rounded-full mr-2"></div> Black'; |
|
|
} |
|
|
|
|
|
|
|
|
function showPromotionModal(fromRow, fromCol, toRow, toCol) { |
|
|
promotionModal.style.display = 'flex'; |
|
|
|
|
|
|
|
|
const color = board[fromRow][fromCol][0]; |
|
|
const options = document.querySelectorAll('.promotion-piece'); |
|
|
|
|
|
options.forEach(option => { |
|
|
const pieceType = option.dataset.piece; |
|
|
option.style.backgroundImage = `url('${getPieceImageUrl(color + pieceType)})`; |
|
|
option.onclick = function() { |
|
|
|
|
|
board[toRow][toCol] = color + pieceType; |
|
|
board[fromRow][fromCol] = ''; |
|
|
|
|
|
|
|
|
const moveNotation = getMoveNotation(fromRow, fromCol, toRow, toCol, null) + '=' + pieceType.toUpperCase(); |
|
|
moveHistoryList.push(moveNotation); |
|
|
updateMoveHistory(); |
|
|
|
|
|
|
|
|
isWhiteTurn = !isWhiteTurn; |
|
|
updateCurrentPlayerDisplay(); |
|
|
|
|
|
renderBoard(); |
|
|
updateGameStatus(); |
|
|
|
|
|
|
|
|
promotionModal.style.display = 'none'; |
|
|
}; |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
resetBtn.addEventListener('click', initializeBoard); |
|
|
|
|
|
undoBtn.addEventListener('click', function() { |
|
|
if (moveHistoryList.length > 0) { |
|
|
|
|
|
alert("Undo functionality would be implemented here!"); |
|
|
} |
|
|
}); |
|
|
|
|
|
flipBoardBtn.addEventListener('click', function() { |
|
|
boardFlipped = !boardFlipped; |
|
|
renderBoard(); |
|
|
}); |
|
|
|
|
|
hintBtn.addEventListener('click', function() { |
|
|
if (selectedPiece) { |
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
for (let row = 0; row < 8; row++) { |
|
|
for (let col = 0; col < 8; col++) { |
|
|
const piece = board[row][col]; |
|
|
if (piece && piece[0] === (isWhiteTurn ? 'w' : 'b')) { |
|
|
const moves = calculateLegalMoves(row, col); |
|
|
if (moves.length > 0) { |
|
|
selectPiece(row, col); |
|
|
return; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
initializeBoard(); |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|