Restructured project from nested workspace pattern to flat single-repo layout. This eliminates redundant nesting and consolidates all project files under version control. ## Migration Summary **Before:** ``` alex/ (workspace, not versioned) ├── chess-game/ (git repo) │ ├── js/, css/, tests/ │ └── index.html └── docs/ (planning, not versioned) ``` **After:** ``` alex/ (git repo, everything versioned) ├── js/, css/, tests/ ├── index.html ├── docs/ (project documentation) ├── planning/ (historical planning docs) ├── .gitea/ (CI/CD) └── CLAUDE.md (configuration) ``` ## Changes Made ### Structure Consolidation - Moved all chess-game/ contents to root level - Removed redundant chess-game/ subdirectory - Flattened directory structure (eliminated one nesting level) ### Documentation Organization - Moved chess-game/docs/ → docs/ (project documentation) - Moved alex/docs/ → planning/ (historical planning documents) - Added CLAUDE.md (workspace configuration) - Added IMPLEMENTATION_PROMPT.md (original project prompt) ### Version Control Improvements - All project files now under version control - Planning documents preserved in planning/ folder - Merged .gitignore files (workspace + project) - Added .claude/ agent configurations ### File Updates - Updated .gitignore to include both workspace and project excludes - Moved README.md to root level - All import paths remain functional (relative paths unchanged) ## Benefits ✅ **Simpler Structure** - One level of nesting removed ✅ **Complete Versioning** - All documentation now in git ✅ **Standard Layout** - Matches open-source project conventions ✅ **Easier Navigation** - Direct access to all project files ✅ **CI/CD Compatible** - All workflows still functional ## Technical Validation - ✅ Node.js environment verified - ✅ Dependencies installed successfully - ✅ Dev server starts and responds - ✅ All core files present and accessible - ✅ Git repository functional ## Files Preserved **Implementation Files:** - js/ (3,517 lines of code) - css/ (4 stylesheets) - tests/ (87 test cases) - index.html - package.json **CI/CD Pipeline:** - .gitea/workflows/ci.yml - .gitea/workflows/release.yml **Documentation:** - docs/ (12+ documentation files) - planning/ (historical planning materials) - README.md **Configuration:** - jest.config.js, babel.config.cjs, playwright.config.js - .gitignore (merged) - CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
23 KiB
Performance Optimization Checklist - HTML Chess Game
Overview
This checklist provides a comprehensive guide for implementing performance optimizations throughout the development lifecycle. Use this as a companion to the Performance Budget document.
Pre-Implementation Optimizations
✅ Architecture Planning
-
Choose optimal data structures
- Use typed arrays for board representation (faster than objects)
- Implement bitboards for advanced features (optional)
- Plan for immutable game state (easier undo/redo)
- Design for minimal object creation in hot paths
-
Plan component interfaces
- Define clear boundaries between modules
- Minimize cross-module dependencies
- Design for lazy loading (AI module separate)
- Plan event system to reduce coupling
-
Optimize build pipeline
- Set up code splitting (core vs AI vs sounds)
- Configure tree shaking (ES6 modules)
- Enable minification and compression
- Set up source maps for debugging
✅ Development Environment
-
Performance tooling setup
- Install Chrome DevTools extensions
- Set up Lighthouse CI
- Configure bundle size analyzer
- Add performance test suite
- Set up memory profiling
-
Budgets configuration
- Add webpack-bundle-analyzer
- Configure size-limit package
- Set up performance budgets in webpack
- Create pre-commit hooks for budget checks
✅ Code Quality Standards
-
Establish coding patterns
- Use const/let (no var) for better optimization
- Prefer pure functions for testability
- Avoid premature abstraction
- Document performance-critical sections
- Use consistent naming conventions
-
Performance-aware coding
- Avoid nested loops where possible
- Use early returns to reduce nesting
- Cache expensive calculations
- Minimize DOM access in loops
- Prefer CSS classes over inline styles
During-Implementation Best Practices
⚡ JavaScript Performance
1. General Optimizations
- Variable usage
- Use local variables over object properties
- Cache array/string lengths in loops
- Minimize global variable access
- Use destructuring sparingly (creates overhead)
// ❌ Bad - Property access in loop
for (let i = 0; i < pieces.length; i++) {
if (pieces[i].color === this.currentColor) { ... }
}
// ✅ Good - Cached values
const pieceCount = pieces.length;
const currentColor = this.currentColor;
for (let i = 0; i < pieceCount; i++) {
if (pieces[i].color === currentColor) { ... }
}
- Function calls
- Minimize function call overhead in loops
- Inline trivial functions (< 3 lines)
- Use arrow functions for callbacks
- Avoid creating functions inside loops
// ❌ Bad - Function creation in loop
moves.forEach(function(move) {
validateMove(move);
});
// ✅ Good - Predefined function
moves.forEach(validateMove);
- Object operations
- Use Object.create(null) for dictionaries
- Prefer Map for key-value pairs with many operations
- Use WeakMap for memory-sensitive caches
- Avoid delete operator (use null instead)
// ❌ Bad - delete causes hidden class change
delete obj.property;
// ✅ Good - null maintains hidden class
obj.property = null;
2. Array Operations
- Efficient array methods
- Use for loops for performance-critical paths
- Prefer forEach/map/filter for readability (non-critical)
- Use Array.from() sparingly (creates new array)
- Preallocate arrays when size is known
// ❌ Bad - Dynamic growth
const moves = [];
for (let i = 0; i < 64; i++) {
moves.push(generateMove(i));
}
// ✅ Good - Preallocated
const moves = new Array(64);
for (let i = 0; i < 64; i++) {
moves[i] = generateMove(i);
}
- Array searching
- Use indexOf/includes for small arrays
- Use Set for large arrays (O(1) lookup)
- Break early from loops when found
- Consider binary search for sorted arrays
3. String Operations
- String concatenation
- Use template literals for readability
- Use array.join() for multiple concatenations
- Avoid + in loops
- Use String.prototype methods efficiently
// ❌ Bad - Multiple concatenations
let notation = '';
notation += piece;
notation += fromSquare;
notation += toSquare;
// ✅ Good - Template literal
const notation = `${piece}${fromSquare}${toSquare}`;
⚡ AI Performance
4. Search Algorithm Optimizations
- Alpha-Beta Pruning (CRITICAL - 10-100x improvement)
- Implement fail-soft alpha-beta
- Use proper window management
- Track pruning statistics
- Test with various positions
// ✅ Alpha-Beta Implementation
function alphaBeta(depth, alpha, beta, maximizing, gameState) {
if (depth === 0 || gameState.isTerminal()) {
return evaluate(gameState);
}
const moves = generateMoves(gameState);
if (maximizing) {
let maxEval = -Infinity;
for (const move of moves) {
const newState = makeMove(gameState, move);
const eval = alphaBeta(depth - 1, alpha, beta, false, newState);
maxEval = Math.max(maxEval, eval);
alpha = Math.max(alpha, eval);
if (beta <= alpha) break; // Beta cutoff
}
return maxEval;
} else {
// Similar for minimizing
}
}
- Move Ordering (HIGH - 2-3x improvement on top of alpha-beta)
- Evaluate captures first (MVV/LVA)
- Try check-giving moves early
- Use killer move heuristic
- Use hash move from transposition table
// ✅ Move Ordering
function orderMoves(moves, gameState) {
return moves.sort((a, b) => {
// 1. Hash move first
if (a === hashMove) return -1;
if (b === hashMove) return 1;
// 2. Captures (MVV/LVA - Most Valuable Victim, Least Valuable Attacker)
const aScore = getCaptureScore(a);
const bScore = getCaptureScore(b);
if (aScore !== bScore) return bScore - aScore;
// 3. Killer moves
if (isKillerMove(a)) return -1;
if (isKillerMove(b)) return 1;
// 4. History heuristic
return getHistoryScore(b) - getHistoryScore(a);
});
}
- Transposition Table (HIGH - 1.5-2x improvement)
- Use Zobrist hashing for positions
- Implement replacement strategy (depth-preferred)
- Store bounds (exact, lower, upper)
- Size table based on device memory
// ✅ Transposition Table
class TranspositionTable {
constructor(maxSize = 10_000_000) {
this.table = new Map();
this.maxSize = maxSize;
}
store(hash, depth, score, flag, bestMove) {
// Replacement strategy: prefer deeper searches
const existing = this.table.get(hash);
if (!existing || depth >= existing.depth) {
this.table.set(hash, { depth, score, flag, bestMove });
}
// Evict oldest entries if too large
if (this.table.size > this.maxSize) {
const firstKey = this.table.keys().next().value;
this.table.delete(firstKey);
}
}
probe(hash, depth, alpha, beta) {
const entry = this.table.get(hash);
if (!entry || entry.depth < depth) return null;
// Check if we can use this score
if (entry.flag === EXACT) return entry.score;
if (entry.flag === LOWER && entry.score >= beta) return entry.score;
if (entry.flag === UPPER && entry.score <= alpha) return entry.score;
return null;
}
}
- Iterative Deepening (MEDIUM - Better UX)
- Start at depth 1, increment each iteration
- Use previous iteration for move ordering
- Support time-based termination
- Return best move from completed depth
// ✅ Iterative Deepening
function iterativeDeepening(gameState, maxTime = 2000) {
const startTime = performance.now();
let bestMove = null;
let depth = 1;
while (performance.now() - startTime < maxTime) {
try {
const result = alphaBeta(depth, -Infinity, Infinity, true, gameState);
bestMove = result.move;
depth++;
} catch (e) {
if (e instanceof TimeoutError) break;
throw e;
}
}
return bestMove;
}
5. Evaluation Function Optimizations
- Incremental Updates (HIGH - 5x improvement)
- Track material score incrementally
- Update positional scores on move/unmove
- Only recalculate complex metrics when needed
- Cache king safety calculations
// ✅ Incremental Evaluation
class IncrementalEvaluator {
constructor() {
this.materialScore = 0;
this.positionalScore = 0;
}
makeMove(move) {
// Update material
if (move.captured) {
this.materialScore -= PIECE_VALUES[move.captured];
}
// Update positional (only changed squares)
this.positionalScore -= PIECE_SQUARE_TABLES[move.piece][move.from];
this.positionalScore += PIECE_SQUARE_TABLES[move.piece][move.to];
}
unmakeMove(move) {
// Reverse updates
if (move.captured) {
this.materialScore += PIECE_VALUES[move.captured];
}
this.positionalScore += PIECE_SQUARE_TABLES[move.piece][move.from];
this.positionalScore -= PIECE_SQUARE_TABLES[move.piece][move.to];
}
getScore() {
return this.materialScore + this.positionalScore;
}
}
- Piece-Square Tables (MEDIUM - 2x improvement)
- Precompute all positional values
- Use O(1) array lookups
- Separate tables for opening/middlegame/endgame
- Mirror tables for black pieces
// ✅ Piece-Square Tables
const PAWN_TABLE = [
[ 0, 0, 0, 0, 0, 0, 0, 0],
[ 50, 50, 50, 50, 50, 50, 50, 50],
[ 10, 10, 20, 30, 30, 20, 10, 10],
[ 5, 5, 10, 25, 25, 10, 5, 5],
[ 0, 0, 0, 20, 20, 0, 0, 0],
[ 5, -5,-10, 0, 0,-10, -5, 5],
[ 5, 10, 10,-20,-20, 10, 10, 5],
[ 0, 0, 0, 0, 0, 0, 0, 0]
];
function getPieceSquareScore(piece, square, color) {
const [rank, file] = squareToCoords(square);
const tableRank = color === 'white' ? rank : 7 - rank;
return PAWN_TABLE[tableRank][file];
}
6. Memory Optimizations
- Object Pooling (MEDIUM - 20-30% less GC)
- Pool move objects
- Pool position objects
- Reuse evaluation contexts
- Monitor pool size
// ✅ Object Pool
class ObjectPool {
constructor(factory, initialSize = 100) {
this.factory = factory;
this.pool = [];
for (let i = 0; i < initialSize; i++) {
this.pool.push(factory());
}
}
acquire() {
return this.pool.length > 0 ? this.pool.pop() : this.factory();
}
release(obj) {
obj.reset(); // Clear object state
this.pool.push(obj);
}
}
// Usage
const movePool = new ObjectPool(() => new Move(), 1000);
const move = movePool.acquire();
// ... use move ...
movePool.release(move);
- Garbage Collection Minimization
- Avoid creating objects in hot loops
- Reuse arrays instead of creating new ones
- Use primitive values where possible
- Clear references when done
⚡ DOM Performance
7. Rendering Optimizations
- Virtual DOM / Diffing (CRITICAL - 5-10x improvement)
- Track previous board state
- Only update changed squares
- Batch DOM updates
- Use DocumentFragment for batch inserts
// ✅ DOM Diffing
function updateBoard(oldState, newState) {
const changedSquares = [];
for (let i = 0; i < 64; i++) {
if (oldState[i] !== newState[i]) {
changedSquares.push(i);
}
}
// Only update changed squares
requestAnimationFrame(() => {
changedSquares.forEach(index => {
updateSquare(index, newState[index]);
});
});
}
- CSS Transform Animations (CRITICAL - GPU acceleration)
- Use transform instead of top/left
- Use translate3d for GPU acceleration
- Avoid animating layout properties
- Use will-change sparingly
/* ❌ Bad - Triggers layout */
.piece {
transition: top 0.3s, left 0.3s;
}
/* ✅ Good - GPU accelerated */
.piece {
transition: transform 0.3s;
will-change: transform;
}
// ✅ Transform Animation
function animatePiece(piece, fromSquare, toSquare) {
const fromPos = getSquarePosition(fromSquare);
const toPos = getSquarePosition(toSquare);
const deltaX = toPos.x - fromPos.x;
const deltaY = toPos.y - fromPos.y;
piece.style.transform = `translate3d(${deltaX}px, ${deltaY}px, 0)`;
}
- CSS Classes over Inline Styles (MEDIUM - 2x improvement)
- Define CSS classes for all states
- Use classList API for toggling
- Avoid style.property = value
- Batch class changes
// ❌ Bad - Inline styles
square.style.backgroundColor = '#f0d9b5';
square.style.boxShadow = '0 0 10px rgba(0,0,0,0.3)';
// ✅ Good - CSS classes
square.classList.add('highlighted');
- RequestAnimationFrame (MEDIUM - Smooth animations)
- Use RAF for all animations
- Batch reads and writes
- Avoid layout thrashing
- Cancel RAF on cleanup
// ✅ RequestAnimationFrame
function smoothUpdate() {
// Read phase (all DOM reads together)
const positions = pieces.map(p => p.getBoundingClientRect());
requestAnimationFrame(() => {
// Write phase (all DOM writes together)
positions.forEach((pos, i) => {
pieces[i].style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;
});
});
}
8. Event Handling
- Event Delegation (MEDIUM - Reduces listeners)
- Use single listener on board container
- Identify target square from event.target
- Avoid listeners on every square
- Clean up listeners on destroy
// ❌ Bad - 64 event listeners
squares.forEach(square => {
square.addEventListener('click', handleSquareClick);
});
// ✅ Good - 1 event listener
board.addEventListener('click', (event) => {
const square = event.target.closest('.square');
if (square) handleSquareClick(square);
});
- Debouncing and Throttling
- Debounce window resize handlers
- Throttle scroll handlers
- Use passive listeners for scroll/touch
- Remove listeners when not needed
// ✅ Passive Listeners
board.addEventListener('touchstart', handleTouch, { passive: true });
// ✅ Throttle
function throttle(func, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
func.apply(this, args);
}
};
}
window.addEventListener('resize', throttle(handleResize, 200));
⚡ Asset Optimization
9. Image Optimization
- SVG Sprites (MEDIUM - 50% size reduction)
- Combine all pieces into single SVG
- Use tags to reference pieces
- Optimize SVG with SVGO
- Inline critical SVG in HTML
<!-- ✅ SVG Sprite -->
<svg style="display: none;">
<symbol id="piece-king-white" viewBox="0 0 45 45">
<path d="..."/>
</symbol>
<!-- ... other pieces ... -->
</svg>
<!-- Usage -->
<svg class="piece"><use href="#piece-king-white"/></svg>
- Lazy Loading (MEDIUM - Faster initial load)
- Load sounds on first interaction
- Load AI module on game start
- Use loading="lazy" for images
- Prefetch critical assets
// ✅ Lazy Load AI
let aiModule = null;
async function loadAI() {
if (!aiModule) {
aiModule = await import('./ai-engine.js');
}
return aiModule;
}
10. Code Splitting
- Module Splitting (HIGH - 2x faster initial load)
- Separate core UI from AI
- Split by route (if multi-page)
- Use dynamic imports
- Analyze bundle with webpack-bundle-analyzer
// ✅ Code Splitting
// main.js - Core UI only (35KB)
import { ChessBoard } from './core/ChessBoard.js';
import { GameController } from './core/GameController.js';
// Lazy load AI when needed
async function startAIGame() {
const { AIPlayer } = await import('./ai/AIPlayer.js'); // +28KB
return new AIPlayer();
}
- Tree Shaking (MEDIUM - 20-30% reduction)
- Use ES6 modules (not CommonJS)
- Mark side-effect-free packages
- Avoid default exports for better shaking
- Import only what you need
// ❌ Bad - Imports everything
import _ from 'lodash';
// ✅ Good - Imports only needed functions
import { debounce, throttle } from 'lodash-es';
⚡ Web Workers (CRITICAL)
11. Background AI Computation
- Web Worker Setup
- Move AI calculation to worker
- Use structured clone for messages
- Handle worker errors gracefully
- Terminate worker when done
// ✅ Web Worker for AI
// main.js
const aiWorker = new Worker('ai-worker.js');
function calculateAIMove(gameState) {
return new Promise((resolve, reject) => {
aiWorker.onmessage = (e) => resolve(e.data.move);
aiWorker.onerror = reject;
aiWorker.postMessage({ type: 'calculate', gameState });
});
}
// ai-worker.js
self.onmessage = function(e) {
if (e.data.type === 'calculate') {
const move = calculateBestMove(e.data.gameState);
self.postMessage({ move });
}
};
- Message Optimization
- Minimize message size
- Use Transferable objects for large data
- Batch messages when possible
- Use SharedArrayBuffer for shared state (advanced)
Post-Implementation Optimization
🔍 Performance Testing
- Automated Performance Tests
- Add performance test suite
- Test AI calculation time
- Test rendering frame rate
- Test memory usage
- Test bundle size
// ✅ Performance Test Example
describe('Performance', () => {
it('should render in <16ms', () => {
const duration = measurePerformance(() => renderBoard());
expect(duration).toBeLessThan(16);
});
it('should calculate AI move in <1s', () => {
const duration = measurePerformance(() => ai.calculateMove(position));
expect(duration).toBeLessThan(1000);
});
});
-
Lighthouse Audits
- Run Lighthouse on every build
- Target score > 90
- Fix all critical issues
- Document score in README
-
Real Device Testing
- Test on 3+ different desktop browsers
- Test on 3+ different mobile devices
- Test on slow 3G connection
- Test with throttled CPU (6x slowdown)
🔍 Profiling
-
Chrome DevTools Profiling
- Record CPU profile during AI calculation
- Record performance timeline during gameplay
- Take heap snapshots to find leaks
- Analyze network waterfall
- Check for memory leaks with allocation timeline
-
Performance API
- Add performance.mark() for key operations
- Measure critical paths
- Log performance metrics
- Set up Real User Monitoring (future)
// ✅ Performance Measurement
performance.mark('ai-start');
const move = calculateBestMove(position);
performance.mark('ai-end');
performance.measure('ai-calculation', 'ai-start', 'ai-end');
const measures = performance.getEntriesByName('ai-calculation');
console.log(`AI calculation took ${measures[0].duration}ms`);
🔍 Optimization Opportunities
-
Bottleneck Analysis
- Identify slowest operations
- Profile with Chrome DevTools
- Measure before/after optimization
- Document improvements
-
Low-Hanging Fruit
- Cache expensive calculations
- Reduce unnecessary re-renders
- Minimize network requests
- Compress assets
- Enable gzip/brotli compression
🔍 Long-Term Monitoring
-
Regression Prevention
- Add performance budgets to CI
- Fail builds that exceed budgets
- Track performance over time
- Create performance dashboard
-
Continuous Improvement
- Review performance quarterly
- Update budgets as needed
- Adopt new browser features
- Monitor web performance best practices
Platform-Specific Optimizations
📱 Mobile Optimizations
-
Touch Interactions
- Use touch events (touchstart, touchmove, touchend)
- Add passive: true to touch listeners
- Implement touch-action CSS
- Provide larger touch targets (44x44px minimum)
-
Responsive Performance
- Use CSS media queries for layout
- Reduce AI depth on mobile
- Disable animations on low-end devices
- Use smaller transposition table
// ✅ Device-Specific Configuration
function getDeviceConfig() {
const cores = navigator.hardwareConcurrency || 2;
const memory = navigator.deviceMemory || 2;
if (cores >= 8 && memory >= 6) {
return { depth: 6, tableSize: 50_000_000, animations: true };
} else if (cores >= 4 && memory >= 2) {
return { depth: 5, tableSize: 10_000_000, animations: true };
} else {
return { depth: 4, tableSize: 3_000_000, animations: false };
}
}
🖥️ Desktop Optimizations
-
Keyboard Shortcuts
- Implement keyboard navigation
- Add undo/redo shortcuts (Ctrl+Z, Ctrl+Y)
- Support arrow keys for piece selection
- Add accessibility shortcuts
-
Advanced Features
- Enable deeper AI search
- Use larger transposition tables
- Add advanced animations
- Support multiple game modes
Final Performance Checklist
Before Release
-
Performance Metrics
- Lighthouse score > 90
- Bundle size < 150KB gzipped
- FCP < 500ms
- TTI < 1s
- 60fps rendering
- AI response < 1s (depth 5)
-
Cross-Browser Testing
- Chrome (latest 2 versions)
- Firefox (latest 2 versions)
- Safari (latest 2 versions)
- Edge (latest 2 versions)
-
Device Testing
- Desktop (1920x1080, 1366x768)
- Tablet (iPad, Android tablet)
- Mobile (iPhone, Android phone)
- Low-end device (throttled)
-
Network Testing
- 5G / Fast connection
- 4G / Regular connection
- 3G / Slow connection
- Offline mode (service worker)
Performance Optimization Priority
Critical (Do First)
- Alpha-Beta Pruning
- Web Workers
- DOM Diffing
- CSS Transforms
- Code Splitting
High (Do Second)
- Move Ordering
- Transposition Tables
- Bundle Optimization
- Mobile Optimization
Medium (Do Third)
- Object Pooling
- Iterative Deepening
- SVG Optimization
Resources
- Web.dev Performance
- Chrome DevTools Performance
- MDN Performance
- Chess Programming Wiki
- Webpack Bundle Analyzer
Remember: "Premature optimization is the root of all evil, but planning for performance is wisdom."
Document Version: 1.0.0 Last Updated: 2025-11-22 Owner: Performance Optimizer Agent