chess-tutor / index.html
AC-Angelo93's picture
undefined - Initial Deployment
b8e2629 verified
<!DOCTYPE html>
<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) !important;
}
.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>