jQuery Guide - RumenDamyanov/js-chess GitHub Wiki
Learn how to build chess applications using jQuery with the JS Chess project, leveraging the popular library for DOM manipulation and AJAX requests.
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.
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
# 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
Visit http://localhost:3001
to see the jQuery chess application in action.
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>
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">×</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));
}
}
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;
}
}
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();
}
}
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;
}
}
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);
}
});
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
- Use event delegation for dynamic content
- Cache jQuery selectors when used repeatedly
- Minimize DOM queries in loops
- Use CSS animations over jQuery animations when possible
- Debounce resize handlers
The jQuery implementation supports:
- Chrome 30+
- Firefox 25+
- Safari 7+
- IE 11+
- Review the API Integration Guide for backend communication
- Check the Chess Features Guide for advanced features
- See the UI/UX Guide for design enhancements
- 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.