jQuery Guide - RumenDamyanov/js-chess GitHub Wiki

jQuery Guide

Learn how to build chess applications using jQuery with the JS Chess project, leveraging the popular library for DOM manipulation and AJAX requests.

Overview

The jQuery implementation demonstrates how to use the classic JavaScript library to build interactive chess applications. It provides a balance between vanilla JavaScript and modern frameworks.

Project Structure

jquery/
├── index.html          # Main HTML page
├── css/
│   ├── style.css      # Component-specific styles
│   └── common.css     # Shared styles
├── js/
│   ├── app.js         # Main application logic
│   ├── chess.js       # Chess game logic
│   ├── board.js       # Board rendering with jQuery
│   └── utils.js       # Utility functions
└── assets/
    └── pieces/        # Chess piece images

Getting Started

1. Setup Development Environment

# Clone the repository
git clone --recursive https://github.com/RumenDamyanov/js-chess.git
cd js-chess

# Start the jQuery application
make run-jquery

# Or manually
cd jquery
python3 -m http.server 3001

2. Open in Browser

Visit http://localhost:3001 to see the jQuery chess application in action.

Core Implementation

HTML Structure

The main HTML file includes jQuery from CDN:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JS Chess - jQuery</title>
    <link rel="stylesheet" href="/shared/styles/common.css">
    <link rel="stylesheet" href="css/style.css">

    <!-- jQuery from CDN -->
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
    <header class="app-header">
        <nav class="app-nav">
            <div class="nav-brand">
                <h1>JS Chess</h1>
                <span class="framework-badge">jQuery</span>
            </div>
            <!-- Navigation links -->
        </nav>
    </header>

    <main class="container">
        <div class="game-container">
            <div class="game-board" id="chessBoard">
                <!-- Chess board squares generated by JavaScript -->
            </div>
            <div class="game-sidebar">
                <div class="game-controls">
                    <button id="newGameBtn" class="btn btn-primary">New Game</button>
                    <button id="undoBtn" class="btn btn-secondary">Undo Move</button>
                    <button id="hintBtn" class="btn btn-info">Get Hint</button>
                </div>
                <div class="game-status">
                    <div id="gameStatus">Ready to play</div>
                    <div id="currentPlayer">White to move</div>
                    <div id="capturedPieces"></div>
                </div>
                <div class="move-history">
                    <h3>Move History</h3>
                    <div id="moveList"></div>
                </div>
            </div>
        </div>
    </main>

    <script src="js/utils.js"></script>
    <script src="js/chess.js"></script>
    <script src="js/board.js"></script>
    <script src="js/app.js"></script>
</body>
</html>

Main Application Logic

The app.js file uses jQuery for DOM manipulation and event handling:

// app.js - jQuery-powered chess application
$(document).ready(function() {
    const chessApp = new ChessApp();
});

class ChessApp {
    constructor() {
        this.game = null;
        this.board = null;
        this.selectedSquare = null;
        this.validMoves = [];
        this.isPlayerTurn = true;
        this.moveHistory = [];

        this.init();
    }

    async init() {
        try {
            // Show loading state
            this.showLoading();

            // Initialize components
            this.board = new ChessBoard('#chessBoard');
            this.game = new ChessGame();

            // Setup event handlers
            this.setupEventHandlers();

            // Create initial game
            await this.newGame();

            // Hide loading state
            this.hideLoading();

            console.log('jQuery Chess application initialized');
        } catch (error) {
            console.error('Failed to initialize chess application:', error);
            this.showError('Failed to initialize game');
            this.hideLoading();
        }
    }

    setupEventHandlers() {
        // Button event handlers
        $('#newGameBtn').on('click', () => this.newGame());
        $('#undoBtn').on('click', () => this.undoMove());
        $('#hintBtn').on('click', () => this.getHint());

        // Board event handlers with jQuery delegation
        $('#chessBoard').on('click', '.square', (event) => {
            this.handleSquareClick($(event.currentTarget));
        });

        // Piece drag events
        $('#chessBoard').on('dragstart', '.piece', (event) => {
            this.handleDragStart($(event.currentTarget), event.originalEvent);
        });

        $('#chessBoard').on('dragover', '.square', (event) => {
            event.preventDefault();
        });

        $('#chessBoard').on('drop', '.square', (event) => {
            this.handleDrop($(event.currentTarget), event.originalEvent);
        });

        // Keyboard shortcuts
        $(document).on('keydown', (event) => this.handleKeydown(event));

        // Window resize handler
        $(window).on('resize', () => this.handleResize());

        // Settings modal handlers
        this.setupSettingsModal();
    }

    async newGame() {
        try {
            this.showLoading();

            // Reset application state
            this.selectedSquare = null;
            this.validMoves = [];
            this.isPlayerTurn = true;
            this.moveHistory = [];

            // Create new game via API
            const gameData = await this.game.createGame();

            // Render board
            this.board.render(gameData.board);

            // Update UI
            this.updateGameStatus('New game started');
            this.updateCurrentPlayer('white');
            this.updateMoveHistory();
            this.updateCapturedPieces([]);

            // Enable controls
            this.enableControls();

        } catch (error) {
            console.error('Failed to create new game:', error);
            this.showError('Failed to create new game');
        } finally {
            this.hideLoading();
        }
    }

    handleSquareClick($square) {
        if (!this.isPlayerTurn) return;

        const position = $square.data('position');

        // If clicking the same square, deselect
        if (this.selectedSquare === position) {
            this.clearSelection();
            return;
        }

        // If a square is selected and this is a valid move
        if (this.selectedSquare && this.validMoves.includes(position)) {
            this.makeMove(this.selectedSquare, position);
            return;
        }

        // If clicking on a piece, select it
        const $piece = $square.find('.piece');
        if ($piece.length && $piece.data('color') === 'white') {
            this.selectSquare(position);
        } else {
            this.clearSelection();
        }
    }

    selectSquare(position) {
        // Clear previous selection
        this.clearSelection();

        // Set new selection
        this.selectedSquare = position;

        // Highlight selected square
        $(`[data-position="${position}"]`).addClass('selected');

        // Get and highlight valid moves
        this.validMoves = this.game.getValidMoves(position);
        this.validMoves.forEach(move => {
            $(`[data-position="${move}"]`).addClass('valid-move');
        });

        // Add visual feedback
        this.showMoveCount(this.validMoves.length);
    }

    clearSelection() {
        // Remove all highlights
        $('.square').removeClass('selected valid-move last-move');

        // Clear state
        this.selectedSquare = null;
        this.validMoves = [];

        // Hide move count
        this.hideMoveCount();
    }

    async makeMove(from, to) {
        if (!this.isPlayerTurn) return;

        try {
            // Disable player input
            this.isPlayerTurn = false;
            this.disableControls();

            // Show move animation
            await this.board.animateMove(from, to);

            // Make move via API
            const result = await this.game.makeMove(from, to);

            if (result.success) {
                // Update board
                this.board.render(result.board);

                // Add to move history
                this.addMoveToHistory(result.move);

                // Clear selection
                this.clearSelection();

                // Update UI
                this.updateGameStatus(`Move: ${from}${to}`);

                // Check for game end
                if (result.gameOver) {
                    this.handleGameEnd(result);
                    return;
                }

                // Switch to AI turn
                this.updateCurrentPlayer('black');
                await this.makeAIMove();

            } else {
                this.showError(result.error || 'Invalid move');
                this.isPlayerTurn = true;
                this.enableControls();
            }

        } catch (error) {
            console.error('Failed to make move:', error);
            this.showError('Failed to make move');
            this.isPlayerTurn = true;
            this.enableControls();
        }
    }

    async makeAIMove() {
        try {
            // Show AI thinking state
            this.updateGameStatus('AI is thinking...');
            this.showSpinner('#gameStatus');

            // Add delay for better UX
            await this.delay(500);

            // Get AI move
            const aiResult = await this.game.getAIMove();

            if (aiResult.success) {
                // Animate AI move
                await this.board.animateMove(aiResult.move.from, aiResult.move.to);

                // Update board
                this.board.render(aiResult.board);

                // Add to move history
                this.addMoveToHistory(aiResult.move);

                // Update status
                this.updateGameStatus(`AI move: ${aiResult.move.from}${aiResult.move.to}`);

                // Check for game end
                if (aiResult.gameOver) {
                    this.handleGameEnd(aiResult);
                    return;
                }

                // Switch back to player
                this.updateCurrentPlayer('white');
                this.isPlayerTurn = true;
                this.enableControls();

            } else {
                this.showError('AI failed to make a move');
                this.isPlayerTurn = true;
                this.enableControls();
            }

        } catch (error) {
            console.error('AI move failed:', error);
            this.showError('AI move failed');
            this.isPlayerTurn = true;
            this.enableControls();
        } finally {
            this.hideSpinner('#gameStatus');
        }
    }

    handleGameEnd(result) {
        this.isPlayerTurn = false;
        this.disableControls();

        let message = '';
        let messageClass = 'game-end';

        if (result.checkmate) {
            const winner = result.winner === 'white' ? 'White' : 'Black';
            message = `Checkmate! ${winner} wins!`;
            messageClass += ' checkmate';
        } else if (result.stalemate) {
            message = 'Stalemate! Game is a draw.';
            messageClass += ' stalemate';
        } else if (result.draw) {
            message = 'Draw!';
            messageClass += ' draw';
        }

        this.updateGameStatus(message);
        $('#gameStatus').addClass(messageClass);

        // Show game end modal
        this.showGameEndModal(result);
    }

    addMoveToHistory(move) {
        this.moveHistory.push(move);
        this.updateMoveHistory();
    }

    updateMoveHistory() {
        const $moveList = $('#moveList');
        $moveList.empty();

        for (let i = 0; i < this.moveHistory.length; i += 2) {
            const moveNumber = Math.floor(i / 2) + 1;
            const whiteMove = this.moveHistory[i];
            const blackMove = this.moveHistory[i + 1];

            const $moveRow = $('<div class="move-row"></div>');
            $moveRow.append(`<span class="move-number">${moveNumber}.</span>`);
            $moveRow.append(`<span class="white-move">${whiteMove.san}</span>`);

            if (blackMove) {
                $moveRow.append(`<span class="black-move">${blackMove.san}</span>`);
            }

            $moveList.append($moveRow);
        }

        // Scroll to bottom
        $moveList.scrollTop($moveList[0].scrollHeight);
    }

    async getHint() {
        try {
            this.showLoading();

            const hint = await this.game.getHint();

            if (hint.success) {
                // Highlight suggested move
                this.clearSelection();
                $(`[data-position="${hint.from}"]`).addClass('hint-from');
                $(`[data-position="${hint.to}"]`).addClass('hint-to');

                this.showMessage(`Hint: Consider moving from ${hint.from} to ${hint.to}`, 'info');

                // Clear hint after 5 seconds
                setTimeout(() => {
                    $('.square').removeClass('hint-from hint-to');
                }, 5000);
            }

        } catch (error) {
            console.error('Failed to get hint:', error);
            this.showError('Failed to get hint');
        } finally {
            this.hideLoading();
        }
    }

    async undoMove() {
        try {
            this.showLoading();

            const result = await this.game.undoMove();

            if (result.success) {
                // Update board
                this.board.render(result.board);

                // Update move history
                this.moveHistory = this.moveHistory.slice(0, -2); // Remove last 2 moves
                this.updateMoveHistory();

                // Clear selection
                this.clearSelection();

                // Update status
                this.updateGameStatus('Move undone');
                this.updateCurrentPlayer('white');

                // Enable player turn
                this.isPlayerTurn = true;
                this.enableControls();
            }

        } catch (error) {
            console.error('Failed to undo move:', error);
            this.showError('Failed to undo move');
        } finally {
            this.hideLoading();
        }
    }

    // Drag and drop functionality
    handleDragStart($piece, event) {
        const $square = $piece.closest('.square');
        const position = $square.data('position');

        if (!this.isPlayerTurn || $piece.data('color') !== 'white') {
            event.preventDefault();
            return;
        }

        event.dataTransfer.setData('text/plain', position);
        event.dataTransfer.effectAllowed = 'move';

        // Select the piece
        this.selectSquare(position);
    }

    handleDrop($square, event) {
        event.preventDefault();

        const fromPosition = event.dataTransfer.getData('text/plain');
        const toPosition = $square.data('position');

        if (fromPosition && toPosition && this.validMoves.includes(toPosition)) {
            this.makeMove(fromPosition, toPosition);
        } else {
            this.clearSelection();
        }
    }

    // UI Helper Methods
    showLoading() {
        $('body').addClass('loading');
        $('.loading-overlay').show();
    }

    hideLoading() {
        $('body').removeClass('loading');
        $('.loading-overlay').hide();
    }

    showSpinner(selector) {
        $(selector).append('<i class="fas fa-spinner fa-spin loading-spinner"></i>');
    }

    hideSpinner(selector) {
        $(selector).find('.loading-spinner').remove();
    }

    enableControls() {
        $('.game-controls button').prop('disabled', false);
    }

    disableControls() {
        $('.game-controls button').prop('disabled', true);
    }

    updateGameStatus(message) {
        $('#gameStatus').removeClass('game-end checkmate stalemate draw').text(message);
    }

    updateCurrentPlayer(player) {
        const playerName = player === 'white' ? 'White' : 'Black';
        $('#currentPlayer').text(`${playerName} to move`);
    }

    updateCapturedPieces(captured) {
        const $captured = $('#capturedPieces');
        $captured.empty();

        if (captured.length > 0) {
            $captured.append('<h4>Captured:</h4>');
            captured.forEach(piece => {
                $captured.append(`<span class="captured-piece">${piece}</span>`);
            });
        }
    }

    showMoveCount(count) {
        $('#gameStatus').append(`<small> (${count} possible moves)</small>`);
    }

    hideMoveCount() {
        $('#gameStatus small').remove();
    }

    showError(message) {
        this.showMessage(message, 'error');
    }

    showMessage(message, type = 'info') {
        const $message = $(`
            <div class="message message-${type}">
                <span>${message}</span>
                <button class="close-btn">&times;</button>
            </div>
        `);

        $('body').append($message);

        // Auto-remove after 5 seconds
        setTimeout(() => $message.fadeOut(() => $message.remove()), 5000);

        // Manual close
        $message.find('.close-btn').on('click', () => {
            $message.fadeOut(() => $message.remove());
        });
    }

    showGameEndModal(result) {
        const $modal = $(`
            <div class="modal-overlay">
                <div class="modal">
                    <h2>Game Over</h2>
                    <p>${this.getGameEndMessage(result)}</p>
                    <div class="modal-actions">
                        <button class="btn btn-primary" id="newGameModalBtn">New Game</button>
                        <button class="btn btn-secondary" id="closeModalBtn">Close</button>
                    </div>
                </div>
            </div>
        `);

        $('body').append($modal);

        // Event handlers
        $modal.find('#newGameModalBtn').on('click', () => {
            $modal.remove();
            this.newGame();
        });

        $modal.find('#closeModalBtn').on('click', () => {
            $modal.remove();
        });

        // Close on overlay click
        $modal.on('click', (e) => {
            if (e.target === $modal[0]) {
                $modal.remove();
            }
        });
    }

    getGameEndMessage(result) {
        if (result.checkmate) {
            const winner = result.winner === 'white' ? 'White' : 'Black';
            return `${winner} wins by checkmate!`;
        } else if (result.stalemate) {
            return 'The game ends in stalemate.';
        } else if (result.draw) {
            return 'The game is a draw.';
        }
        return 'Game over.';
    }

    setupSettingsModal() {
        // Settings modal implementation
        $('#settingsBtn').on('click', () => {
            this.showSettingsModal();
        });
    }

    showSettingsModal() {
        // Settings modal with jQuery UI or custom implementation
        const $modal = $(`
            <div class="modal-overlay">
                <div class="modal settings-modal">
                    <h2>Settings</h2>
                    <form id="settingsForm">
                        <div class="form-group">
                            <label for="aiDifficulty">AI Difficulty:</label>
                            <select id="aiDifficulty" name="aiDifficulty">
                                <option value="easy">Easy</option>
                                <option value="medium">Medium</option>
                                <option value="hard">Hard</option>
                            </select>
                        </div>
                        <div class="form-group">
                            <label for="boardTheme">Board Theme:</label>
                            <select id="boardTheme" name="boardTheme">
                                <option value="classic">Classic</option>
                                <option value="modern">Modern</option>
                                <option value="wooden">Wooden</option>
                            </select>
                        </div>
                        <div class="form-group">
                            <label>
                                <input type="checkbox" id="showCoordinates" name="showCoordinates">
                                Show Coordinates
                            </label>
                        </div>
                        <div class="form-group">
                            <label>
                                <input type="checkbox" id="enableSounds" name="enableSounds">
                                Enable Sounds
                            </label>
                        </div>
                    </form>
                    <div class="modal-actions">
                        <button class="btn btn-primary" id="saveSettingsBtn">Save</button>
                        <button class="btn btn-secondary" id="cancelSettingsBtn">Cancel</button>
                    </div>
                </div>
            </div>
        `);

        $('body').append($modal);

        // Load current settings
        this.loadSettingsIntoForm($modal);

        // Event handlers
        $modal.find('#saveSettingsBtn').on('click', () => {
            this.saveSettingsFromForm($modal);
            $modal.remove();
        });

        $modal.find('#cancelSettingsBtn').on('click', () => {
            $modal.remove();
        });
    }

    handleKeydown(event) {
        switch (event.which) {
            case 78: // N key
                if (event.ctrlKey || event.metaKey) {
                    event.preventDefault();
                    this.newGame();
                }
                break;
            case 90: // Z key
                if (event.ctrlKey || event.metaKey) {
                    event.preventDefault();
                    this.undoMove();
                }
                break;
            case 72: // H key
                if (event.ctrlKey || event.metaKey) {
                    event.preventDefault();
                    this.getHint();
                }
                break;
            case 27: // Escape key
                this.clearSelection();
                break;
        }
    }

    handleResize() {
        // Responsive handling
        const boardSize = Math.min($(window).width() - 100, 600);
        $('#chessBoard').css({
            width: boardSize,
            height: boardSize
        });
    }

    delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

jQuery-Enhanced Chess Logic

The chess.js file uses jQuery's AJAX methods:

// chess.js - jQuery-enhanced chess game logic
class ChessGame {
    constructor() {
        this.gameId = null;
        this.baseURL = 'http://localhost:8080';
        this.gameState = null;
    }

    async createGame(options = {}) {
        try {
            const response = await $.ajax({
                url: `${this.baseURL}/api/games`,
                method: 'POST',
                contentType: 'application/json',
                data: JSON.stringify({
                    ai_enabled: true,
                    difficulty: options.difficulty || 'medium',
                    ...options
                }),
                timeout: 10000
            });

            this.gameId = response.id;
            this.gameState = response;

            return response;
        } catch (error) {
            console.error('Failed to create game:', error);
            throw new Error('Failed to create game: ' + (error.responseJSON?.message || error.message));
        }
    }

    async makeMove(from, to, promotion = null) {
        if (!this.gameId) {
            throw new Error('No active game');
        }

        try {
            const response = await $.ajax({
                url: `${this.baseURL}/api/games/${this.gameId}/moves`,
                method: 'POST',
                contentType: 'application/json',
                data: JSON.stringify({
                    from,
                    to,
                    promotion
                }),
                timeout: 10000
            });

            if (response.success) {
                this.gameState = response.gameState;
            }

            return response;
        } catch (error) {
            console.error('Failed to make move:', error);
            throw new Error('Failed to make move: ' + (error.responseJSON?.message || error.message));
        }
    }

    async getAIMove() {
        if (!this.gameId) {
            throw new Error('No active game');
        }

        try {
            const response = await $.ajax({
                url: `${this.baseURL}/api/games/${this.gameId}/ai-move`,
                method: 'POST',
                contentType: 'application/json',
                timeout: 30000 // AI moves can take longer
            });

            if (response.success) {
                this.gameState = response.gameState;
            }

            return response;
        } catch (error) {
            console.error('Failed to get AI move:', error);
            throw new Error('Failed to get AI move: ' + (error.responseJSON?.message || error.message));
        }
    }

    async getHint() {
        if (!this.gameId) {
            throw new Error('No active game');
        }

        try {
            const response = await $.ajax({
                url: `${this.baseURL}/api/games/${this.gameId}/hint`,
                method: 'GET',
                timeout: 10000
            });

            return response;
        } catch (error) {
            console.error('Failed to get hint:', error);
            throw new Error('Failed to get hint: ' + (error.responseJSON?.message || error.message));
        }
    }

    async undoMove() {
        if (!this.gameId) {
            throw new Error('No active game');
        }

        try {
            const response = await $.ajax({
                url: `${this.baseURL}/api/games/${this.gameId}/undo`,
                method: 'POST',
                contentType: 'application/json',
                timeout: 10000
            });

            if (response.success) {
                this.gameState = response.gameState;
            }

            return response;
        } catch (error) {
            console.error('Failed to undo move:', error);
            throw new Error('Failed to undo move: ' + (error.responseJSON?.message || error.message));
        }
    }

    getValidMoves(position) {
        // This should come from the API in a real implementation
        // For now, return a simple mock
        if (!this.gameState) return [];

        // Mock implementation
        return this.calculateValidMoves(position);
    }

    calculateValidMoves(position) {
        // Basic move calculation
        // In a real app, this would come from the backend
        const moves = [];

        // Add some basic logic here
        return moves;
    }
}

jQuery Board Rendering

The board.js file leverages jQuery for DOM manipulation:

// board.js - jQuery-enhanced chess board
class ChessBoard {
    constructor(selector) {
        this.$container = $(selector);
        this.squares = new Map();
        this.pieceSymbols = {
            'white': {
                'king': '♔', 'queen': '♕', 'rook': '♖',
                'bishop': '♗', 'knight': '♘', 'pawn': '♙'
            },
            'black': {
                'king': '♚', 'queen': '♛', 'rook': '♜',
                'bishop': '♝', 'knight': '♞', 'pawn': '♟'
            }
        };

        this.createBoard();
    }

    createBoard() {
        this.$container.empty().addClass('chess-board');

        for (let rank = 8; rank >= 1; rank--) {
            for (let file = 0; file < 8; file++) {
                const fileChar = String.fromCharCode(97 + file);
                const position = fileChar + rank;

                const $square = $('<div>')
                    .addClass('square')
                    .attr('data-position', position)
                    .addClass((rank + file) % 2 === 1 ? 'light' : 'dark');

                // Add coordinate labels
                if (file === 0) {
                    $square.append(`<span class="rank-label">${rank}</span>`);
                }

                if (rank === 1) {
                    $square.append(`<span class="file-label">${fileChar}</span>`);
                }

                this.$container.append($square);
                this.squares.set(position, $square);
            }
        }
    }

    render(boardData) {
        // Clear all pieces
        this.squares.forEach($square => {
            $square.find('.piece').remove();
        });

        if (!boardData) return;

        // Parse and render board data
        this.parseBoardData(boardData);
    }

    parseBoardData(boardData) {
        if (typeof boardData === 'string') {
            if (boardData.includes('/')) {
                this.renderFromFEN(boardData);
            } else {
                this.renderFromASCII(boardData);
            }
        } else if (Array.isArray(boardData)) {
            this.renderFromArray(boardData);
        }
    }

    renderFromFEN(fen) {
        const ranks = fen.split(' ')[0].split('/');

        for (let rankIndex = 0; rankIndex < 8; rankIndex++) {
            const rank = 8 - rankIndex;
            const rankData = ranks[rankIndex];
            let fileIndex = 0;

            for (const char of rankData) {
                if (char >= '1' && char <= '8') {
                    fileIndex += parseInt(char);
                } else {
                    const file = String.fromCharCode(97 + fileIndex);
                    const position = file + rank;
                    this.placePiece(position, char);
                    fileIndex++;
                }
            }
        }
    }

    placePiece(position, pieceCode) {
        const $square = this.squares.get(position);
        if (!$square) return;

        const isUpperCase = pieceCode === pieceCode.toUpperCase();
        const color = isUpperCase ? 'white' : 'black';
        const pieceType = this.getPieceType(pieceCode.toLowerCase());

        const $piece = $('<div>')
            .addClass('piece')
            .attr('draggable', true)
            .data({
                piece: pieceCode,
                color: color,
                type: pieceType
            })
            .text(this.pieceSymbols[color][pieceType]);

        $square.append($piece);
    }

    getPieceType(pieceCode) {
        const types = {
            'k': 'king', 'q': 'queen', 'r': 'rook',
            'b': 'bishop', 'n': 'knight', 'p': 'pawn'
        };
        return types[pieceCode] || 'pawn';
    }

    animateMove(from, to) {
        return new Promise((resolve) => {
            const $fromSquare = this.squares.get(from);
            const $toSquare = this.squares.get(to);

            if (!$fromSquare || !$toSquare) {
                resolve();
                return;
            }

            const $piece = $fromSquare.find('.piece');
            if (!$piece.length) {
                resolve();
                return;
            }

            // Get positions for animation
            const fromOffset = $fromSquare.offset();
            const toOffset = $toSquare.offset();

            // Create animated piece
            const $animPiece = $piece.clone()
                .addClass('animating')
                .css({
                    position: 'fixed',
                    left: fromOffset.left,
                    top: fromOffset.top,
                    zIndex: 1000,
                    pointerEvents: 'none'
                });

            $('body').append($animPiece);

            // Hide original piece
            $piece.hide();

            // Animate to destination
            $animPiece.animate({
                left: toOffset.left,
                top: toOffset.top
            }, 300, 'easeInOutQuad', () => {
                // Clean up animation
                $animPiece.remove();
                $piece.show();

                // Move piece to destination
                const $existingPiece = $toSquare.find('.piece');
                if ($existingPiece.length) {
                    $existingPiece.remove();
                }

                $toSquare.append($piece);
                resolve();
            });
        });
    }

    highlightSquares(positions, className) {
        positions.forEach(position => {
            const $square = this.squares.get(position);
            if ($square) {
                $square.addClass(className);
            }
        });
    }

    clearHighlights() {
        this.squares.forEach($square => {
            $square.removeClass('highlighted selected valid-move last-move hint-from hint-to');
        });
    }

    setTheme(theme) {
        this.$container.removeClass().addClass(`chess-board theme-${theme}`);
    }

    enableCoordinates() {
        this.$container.find('.rank-label, .file-label').show();
    }

    disableCoordinates() {
        this.$container.find('.rank-label, .file-label').hide();
    }
}

Enhanced Styling

jQuery-specific CSS enhancements:

/* style.css - jQuery specific styles */

/* Loading states */
.loading {
    cursor: wait;
}

.loading-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 9999;
}

.loading-spinner {
    color: var(--primary-color);
    margin-left: 0.5rem;
}

/* Enhanced game controls */
.game-controls {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.btn {
    padding: 0.75rem 1.5rem;
    border: none;
    border-radius: 6px;
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s ease;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
    transform: none !important;
}

.btn:hover:not(:disabled) {
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}

.btn-primary {
    background: linear-gradient(135deg, var(--primary-color), var(--primary-color-dark));
    color: white;
}

.btn-secondary {
    background: linear-gradient(135deg, var(--gray-500), var(--gray-600));
    color: white;
}

.btn-info {
    background: linear-gradient(135deg, #17a2b8, #138496);
    color: white;
}

/* Move history */
.move-history {
    background: var(--card-background);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    padding: 1rem;
    max-height: 200px;
    overflow-y: auto;
}

.move-history h3 {
    margin: 0 0 0.5rem 0;
    font-size: 1rem;
    color: var(--text-secondary);
}

#moveList {
    font-family: 'Courier New', monospace;
    font-size: 0.9rem;
}

.move-row {
    display: grid;
    grid-template-columns: 2rem 1fr 1fr;
    gap: 0.5rem;
    padding: 0.25rem 0;
    border-bottom: 1px solid var(--border-light);
}

.move-row:last-child {
    border-bottom: none;
}

.move-number {
    font-weight: bold;
    color: var(--text-secondary);
}

.white-move,
.black-move {
    padding: 0.2rem 0.4rem;
    border-radius: 3px;
    background: var(--gray-100);
}

.black-move {
    background: var(--gray-200);
}

/* Enhanced board styling */
.chess-board.theme-modern .square.light {
    background: linear-gradient(135deg, #f0f0f0, #e8e8e8);
}

.chess-board.theme-modern .square.dark {
    background: linear-gradient(135deg, #8b4513, #654321);
}

.chess-board.theme-wooden .square.light {
    background: linear-gradient(135deg, #deb887, #d2691e);
}

.chess-board.theme-wooden .square.dark {
    background: linear-gradient(135deg, #8b4513, #654321);
}

/* Drag and drop */
.piece[draggable="true"] {
    cursor: grab;
}

.piece[draggable="true"]:active {
    cursor: grabbing;
}

.square.drag-over {
    background-color: rgba(0, 255, 0, 0.3) !important;
}

/* Hint highlighting */
.square.hint-from {
    background-color: rgba(0, 123, 255, 0.4) !important;
    animation: pulse 1s infinite;
}

.square.hint-to {
    background-color: rgba(40, 167, 69, 0.4) !important;
    animation: pulse 1s infinite;
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.7; }
}

/* Messages */
.message {
    position: fixed;
    top: 20px;
    right: 20px;
    padding: 1rem 1.5rem;
    border-radius: 6px;
    color: white;
    z-index: 1000;
    display: flex;
    align-items: center;
    gap: 1rem;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    max-width: 400px;
}

.message-info {
    background: linear-gradient(135deg, #17a2b8, #138496);
}

.message-error {
    background: linear-gradient(135deg, #dc3545, #c82333);
}

.message-success {
    background: linear-gradient(135deg, #28a745, #218838);
}

.close-btn {
    background: none;
    border: none;
    color: white;
    font-size: 1.2rem;
    cursor: pointer;
    padding: 0;
    margin-left: auto;
}

/* Modal styling */
.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 10000;
}

.modal {
    background: var(--background-color);
    border-radius: 12px;
    padding: 2rem;
    max-width: 500px;
    width: 90%;
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}

.modal h2 {
    margin: 0 0 1rem 0;
    color: var(--text-primary);
}

.modal-actions {
    display: flex;
    gap: 1rem;
    justify-content: flex-end;
    margin-top: 2rem;
}

/* Settings form */
.form-group {
    margin-bottom: 1rem;
}

.form-group label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: 500;
    color: var(--text-primary);
}

.form-group input,
.form-group select {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    background: var(--input-background);
    color: var(--text-primary);
}

.form-group input[type="checkbox"] {
    width: auto;
    margin-right: 0.5rem;
}

/* Responsive design */
@media (max-width: 768px) {
    .game-container {
        flex-direction: column;
        align-items: center;
        gap: 1rem;
    }

    .game-sidebar {
        width: 100%;
        max-width: 320px;
    }

    .move-history {
        max-height: 150px;
    }

    .modal {
        margin: 1rem;
        padding: 1.5rem;
    }
}

jQuery UI Integration

For enhanced interactions, you can integrate jQuery UI:

<!-- Add jQuery UI -->
<link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/ui-lightness/jquery-ui.css">
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
// Add sortable move history
$('#moveList').sortable({
    disabled: true,
    cursor: 'pointer'
});

// Add draggable pieces with jQuery UI
$('.piece').draggable({
    revert: 'invalid',
    zIndex: 1000,
    helper: 'clone'
});

// Add droppable squares
$('.square').droppable({
    accept: '.piece',
    drop: function(event, ui) {
        const from = ui.draggable.closest('.square').data('position');
        const to = $(this).data('position');
        chessApp.makeMove(from, to);
    }
});

Testing

Test your jQuery implementation:

# Run jQuery-specific tests
make test-jquery

# Test checklist:
- jQuery library loads correctly
- AJAX requests work properly
- DOM manipulation functions correctly
- Event delegation works as expected
- Animations are smooth
- Responsive design adapts properly

Performance Considerations

  1. Use event delegation for dynamic content
  2. Cache jQuery selectors when used repeatedly
  3. Minimize DOM queries in loops
  4. Use CSS animations over jQuery animations when possible
  5. Debounce resize handlers

Browser Support

The jQuery implementation supports:

  • Chrome 30+
  • Firefox 25+
  • Safari 7+
  • IE 11+

Next Steps

  1. Review the API Integration Guide for backend communication
  2. Check the Chess Features Guide for advanced features
  3. See the UI/UX Guide for design enhancements
  4. Compare with other framework implementations

The jQuery implementation demonstrates how traditional JavaScript libraries can create rich, interactive chess applications with proven patterns and extensive browser support.

⚠️ **GitHub.com Fallback** ⚠️