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>
22 KiB
Implementation Guide - HTML Chess Game
🎯 Purpose
This guide provides a step-by-step roadmap for implementing the HTML chess game. Follow this guide sequentially to build a robust, maintainable chess application.
📋 Prerequisites
Before starting implementation:
- Read HANDOFF_CHECKLIST.md
- Review API_REFERENCE.md
- Study architecture diagrams in diagrams/
- Understand chess rules from CHESS_RULES.md
- Set up development environment (see DEVELOPER_GUIDE.md)
🏗️ Implementation Phases
Phase 1: Project Setup and Core Architecture (Days 1-3)
1.1 Initialize Project Structure
# Create directory structure
mkdir -p chess-game/{css,js/{game,pieces,moves,ui,utils},assets/pieces,tests/{unit,integration}}
cd chess-game
1.2 Create Base HTML (index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chess Game</title>
<link rel="stylesheet" href="css/board.css">
<link rel="stylesheet" href="css/pieces.css">
<link rel="stylesheet" href="css/ui.css">
</head>
<body>
<div id="app">
<header>
<h1>Chess Game</h1>
<div id="game-status"></div>
</header>
<main>
<div id="chess-board"></div>
<aside id="game-info">
<div id="move-history"></div>
<div id="captured-pieces"></div>
<div id="game-controls"></div>
</aside>
</main>
</div>
<script type="module" src="js/main.js"></script>
</body>
</html>
Key Points:
- Use semantic HTML5 elements
- Include meta viewport for responsiveness
- Link CSS files in correct order
- Use type="module" for ES6 modules
1.3 Create Core Classes
Step 1: Board.js - Board state management
// js/game/Board.js
export class Board {
constructor() {
this.grid = this.initializeGrid();
this.setupInitialPosition();
}
initializeGrid() {
return Array(8).fill(null).map(() => Array(8).fill(null));
}
setupInitialPosition() {
// Initialize pieces in starting position
// See API_REFERENCE.md for detailed method signatures
}
getPiece(row, col) {
return this.grid[row][col];
}
setPiece(row, col, piece) {
this.grid[row][col] = piece;
}
movePiece(fromRow, fromCol, toRow, toCol) {
// Move piece and return captured piece if any
}
}
Step 2: Piece.js - Base piece class
// js/pieces/Piece.js
export class Piece {
constructor(color, position) {
this.color = color; // 'white' or 'black'
this.position = position; // {row, col}
this.hasMoved = false;
}
getValidMoves(board) {
// Override in subclasses
throw new Error('getValidMoves must be implemented');
}
isValidMove(board, toRow, toCol) {
const validMoves = this.getValidMoves(board);
return validMoves.some(move =>
move.row === toRow && move.col === toCol
);
}
}
Step 3: ChessGame.js - Game controller
// js/game/ChessGame.js
export class ChessGame {
constructor() {
this.board = new Board();
this.currentTurn = 'white';
this.gameState = 'active'; // 'active', 'check', 'checkmate', 'stalemate', 'draw'
this.moveHistory = [];
this.selectedSquare = null;
}
makeMove(fromRow, fromCol, toRow, toCol) {
// Validate and execute move
// Update game state
// Record in history
// Switch turns
}
}
Testing Phase 1:
// tests/unit/Board.test.js
describe('Board', () => {
it('should initialize 8x8 grid', () => {
const board = new Board();
expect(board.grid.length).toBe(8);
expect(board.grid[0].length).toBe(8);
});
it('should setup initial position correctly', () => {
const board = new Board();
// Verify piece positions
});
});
Phase 2: Piece Implementation (Days 4-7)
2.1 Implement Each Piece Type
Implementation Order (simplest to most complex):
- Rook (straight lines)
- Bishop (diagonals)
- Queen (rook + bishop)
- Knight (L-shapes)
- King (one square)
- Pawn (most complex with special moves)
Example: Rook.js
// js/pieces/Rook.js
import { Piece } from './Piece.js';
export class Rook extends Piece {
constructor(color, position) {
super(color, position);
this.type = 'rook';
}
getValidMoves(board) {
const moves = [];
const directions = [
[-1, 0], // up
[1, 0], // down
[0, -1], // left
[0, 1] // right
];
for (const [dRow, dCol] of directions) {
let currentRow = this.position.row + dRow;
let currentCol = this.position.col + dCol;
while (this.isInBounds(currentRow, currentCol)) {
const targetPiece = board.getPiece(currentRow, currentCol);
if (!targetPiece) {
// Empty square - can move here
moves.push({row: currentRow, col: currentCol});
} else {
// Piece in the way
if (targetPiece.color !== this.color) {
// Can capture opponent piece
moves.push({row: currentRow, col: currentCol});
}
break; // Can't move further in this direction
}
currentRow += dRow;
currentCol += dCol;
}
}
return moves;
}
isInBounds(row, col) {
return row >= 0 && row < 8 && col >= 0 && col < 8;
}
}
Critical Implementation Notes:
Pawn.js Special Cases:
getValidMoves(board) {
const moves = [];
const direction = this.color === 'white' ? -1 : 1;
const startRow = this.color === 'white' ? 6 : 1;
// Forward move
const oneForward = this.position.row + direction;
if (!board.getPiece(oneForward, this.position.col)) {
moves.push({row: oneForward, col: this.position.col});
// Two squares forward from starting position
if (this.position.row === startRow) {
const twoForward = this.position.row + (direction * 2);
if (!board.getPiece(twoForward, this.position.col)) {
moves.push({row: twoForward, col: this.position.col});
}
}
}
// Diagonal captures
const captureOffsets = [-1, 1];
for (const offset of captureOffsets) {
const captureCol = this.position.col + offset;
const targetPiece = board.getPiece(oneForward, captureCol);
if (targetPiece && targetPiece.color !== this.color) {
moves.push({row: oneForward, col: captureCol});
}
}
// En passant (handled in SpecialMoves.js)
return moves;
}
Testing Each Piece:
// tests/unit/pieces/Rook.test.js
describe('Rook', () => {
it('should move vertically and horizontally', () => {
const board = new Board();
const rook = new Rook('white', {row: 4, col: 4});
board.setPiece(4, 4, rook);
const moves = rook.getValidMoves(board);
// Should have 14 moves (7 vertical + 7 horizontal)
expect(moves.length).toBe(14);
});
it('should be blocked by pieces', () => {
// Test blocking scenarios
});
it('should capture opponent pieces', () => {
// Test capture scenarios
});
});
Phase 3: Move Validation and Special Moves (Days 8-12)
3.1 MoveValidator.js
Purpose: Validate moves, check for check/checkmate
// js/moves/MoveValidator.js
export class MoveValidator {
static isMoveLegal(board, piece, toRow, toCol, gameState) {
// 1. Check if move is in piece's valid moves
if (!piece.isValidMove(board, toRow, toCol)) {
return false;
}
// 2. Simulate move
const simulatedBoard = this.simulateMove(board, piece, toRow, toCol);
// 3. Check if own king is in check after move
if (this.isKingInCheck(simulatedBoard, piece.color)) {
return false;
}
return true;
}
static isKingInCheck(board, color) {
// Find king position
const kingPos = this.findKing(board, color);
// Check if any opponent piece can attack king
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
const piece = board.getPiece(row, col);
if (piece && piece.color !== color) {
const moves = piece.getValidMoves(board);
if (moves.some(m => m.row === kingPos.row && m.col === kingPos.col)) {
return true;
}
}
}
}
return false;
}
static isCheckmate(board, color) {
// King must be in check
if (!this.isKingInCheck(board, color)) {
return false;
}
// Check if any legal move exists
return !this.hasAnyLegalMove(board, color);
}
static isStalemate(board, color) {
// King must NOT be in check
if (this.isKingInCheck(board, color)) {
return false;
}
// But no legal moves available
return !this.hasAnyLegalMove(board, color);
}
}
3.2 SpecialMoves.js
Castling Implementation:
// js/moves/SpecialMoves.js
export class SpecialMoves {
static canCastle(board, king, rook) {
// 1. Neither piece has moved
if (king.hasMoved || rook.hasMoved) {
return false;
}
// 2. No pieces between king and rook
const [minCol, maxCol] = [
Math.min(king.position.col, rook.position.col),
Math.max(king.position.col, rook.position.col)
];
for (let col = minCol + 1; col < maxCol; col++) {
if (board.getPiece(king.position.row, col)) {
return false;
}
}
// 3. King not in check
if (MoveValidator.isKingInCheck(board, king.color)) {
return false;
}
// 4. King doesn't pass through check
const direction = rook.position.col > king.position.col ? 1 : -1;
for (let i = 1; i <= 2; i++) {
const col = king.position.col + (direction * i);
const simulatedBoard = this.simulateKingMove(board, king, col);
if (MoveValidator.isKingInCheck(simulatedBoard, king.color)) {
return false;
}
}
return true;
}
static executeCastle(board, king, rook) {
// Move king two squares
// Move rook to other side of king
// Mark both as moved
}
}
En Passant Implementation:
static canEnPassant(board, pawn, targetCol, gameState) {
// 1. Pawn must be on correct rank
const correctRank = pawn.color === 'white' ? 3 : 4;
if (pawn.position.row !== correctRank) {
return false;
}
// 2. Adjacent square has opponent pawn
const adjacentPawn = board.getPiece(pawn.position.row, targetCol);
if (!adjacentPawn || adjacentPawn.type !== 'pawn' || adjacentPawn.color === pawn.color) {
return false;
}
// 3. That pawn just moved two squares
const lastMove = gameState.moveHistory[gameState.moveHistory.length - 1];
if (!lastMove || lastMove.piece !== adjacentPawn) {
return false;
}
const moveDistance = Math.abs(lastMove.to.row - lastMove.from.row);
return moveDistance === 2;
}
Pawn Promotion:
static canPromote(pawn) {
const promotionRank = pawn.color === 'white' ? 0 : 7;
return pawn.position.row === promotionRank;
}
static promote(board, pawn, pieceType) {
// Replace pawn with chosen piece (queen, rook, bishop, knight)
const PieceClass = this.getPieceClass(pieceType);
const newPiece = new PieceClass(pawn.color, pawn.position);
board.setPiece(pawn.position.row, pawn.position.col, newPiece);
return newPiece;
}
Phase 4: UI Implementation (Days 13-17)
4.1 BoardRenderer.js
// js/ui/BoardRenderer.js
export class BoardRenderer {
constructor(boardElement) {
this.boardElement = boardElement;
this.selectedSquare = null;
this.highlightedMoves = [];
}
renderBoard(board, gameState) {
this.boardElement.innerHTML = '';
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
const square = this.createSquare(row, col);
const piece = board.getPiece(row, col);
if (piece) {
const pieceElement = this.createPieceElement(piece);
square.appendChild(pieceElement);
}
this.boardElement.appendChild(square);
}
}
}
createSquare(row, col) {
const square = document.createElement('div');
square.className = 'square';
square.classList.add((row + col) % 2 === 0 ? 'light' : 'dark');
square.dataset.row = row;
square.dataset.col = col;
return square;
}
createPieceElement(piece) {
const pieceEl = document.createElement('div');
pieceEl.className = `piece ${piece.color} ${piece.type}`;
pieceEl.draggable = true;
pieceEl.innerHTML = this.getPieceSymbol(piece);
return pieceEl;
}
highlightMoves(moves) {
this.clearHighlights();
moves.forEach(move => {
const square = this.getSquare(move.row, move.col);
square.classList.add('legal-move');
});
this.highlightedMoves = moves;
}
clearHighlights() {
this.highlightedMoves.forEach(move => {
const square = this.getSquare(move.row, move.col);
square.classList.remove('legal-move');
});
this.highlightedMoves = [];
}
}
4.2 DragDropHandler.js
// js/ui/DragDropHandler.js
export class DragDropHandler {
constructor(game, renderer) {
this.game = game;
this.renderer = renderer;
this.setupEventListeners();
}
setupEventListeners() {
const board = this.renderer.boardElement;
board.addEventListener('dragstart', (e) => this.onDragStart(e));
board.addEventListener('dragover', (e) => this.onDragOver(e));
board.addEventListener('drop', (e) => this.onDrop(e));
board.addEventListener('dragend', (e) => this.onDragEnd(e));
// Also support click-to-move
board.addEventListener('click', (e) => this.onClick(e));
}
onDragStart(e) {
if (!e.target.classList.contains('piece')) return;
const square = e.target.parentElement;
const row = parseInt(square.dataset.row);
const col = parseInt(square.dataset.col);
e.dataTransfer.setData('text/plain', JSON.stringify({row, col}));
e.dataTransfer.effectAllowed = 'move';
// Highlight legal moves
const piece = this.game.board.getPiece(row, col);
if (piece && piece.color === this.game.currentTurn) {
const legalMoves = this.game.getLegalMoves(piece);
this.renderer.highlightMoves(legalMoves);
}
}
onDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}
onDrop(e) {
e.preventDefault();
const from = JSON.parse(e.dataTransfer.getData('text/plain'));
const square = e.target.closest('.square');
if (!square) return;
const toRow = parseInt(square.dataset.row);
const toCol = parseInt(square.dataset.col);
this.game.makeMove(from.row, from.col, toRow, toCol);
}
onDragEnd(e) {
this.renderer.clearHighlights();
}
}
4.3 CSS Styling
board.css:
#chess-board {
display: grid;
grid-template-columns: repeat(8, 60px);
grid-template-rows: repeat(8, 60px);
gap: 0;
border: 2px solid #333;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.square {
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: background-color 0.2s;
}
.square.light {
background-color: #f0d9b5;
}
.square.dark {
background-color: #b58863;
}
.square.selected {
background-color: #9bc700;
}
.square.legal-move::after {
content: '';
width: 20px;
height: 20px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.2);
position: absolute;
}
.square.legal-move.has-piece::after {
width: 100%;
height: 100%;
border-radius: 0;
border: 3px solid rgba(255, 0, 0, 0.5);
}
Phase 5: Game State Management (Days 18-21)
5.1 GameState.js
// js/game/GameState.js
export class GameState {
constructor() {
this.moveHistory = [];
this.capturedPieces = {white: [], black: []};
this.currentMove = 0;
this.status = 'active';
}
recordMove(from, to, piece, captured, notation) {
this.moveHistory.push({
from,
to,
piece,
captured,
notation,
timestamp: Date.now()
});
this.currentMove++;
}
undo() {
if (this.currentMove === 0) return null;
this.currentMove--;
return this.moveHistory[this.currentMove];
}
redo() {
if (this.currentMove >= this.moveHistory.length) return null;
const move = this.moveHistory[this.currentMove];
this.currentMove++;
return move;
}
toFEN() {
// Export game state to FEN notation
// See CHESS_RULES.md for FEN format
}
fromFEN(fen) {
// Import game state from FEN notation
}
toPGN() {
// Export move history to PGN notation
}
}
5.2 Storage Integration
// js/utils/storage.js
export class GameStorage {
static save(gameState, board) {
const data = {
fen: gameState.toFEN(),
pgn: gameState.toPGN(),
timestamp: Date.now()
};
localStorage.setItem('chess-game-save', JSON.stringify(data));
}
static load() {
const saved = localStorage.getItem('chess-game-save');
return saved ? JSON.parse(saved) : null;
}
static clear() {
localStorage.removeItem('chess-game-save');
}
}
Phase 6: Testing and Polish (Days 22-25)
6.1 Unit Testing Checklist
- All piece movement tests
- Move validation tests
- Special move tests (castling, en passant, promotion)
- Check/checkmate detection tests
- Stalemate detection tests
- FEN/PGN notation tests
- Storage tests
6.2 Integration Testing
// tests/integration/gameplay.test.js
describe('Full Game Scenarios', () => {
it('should handle Scholar\'s Mate', () => {
const game = new ChessGame();
game.makeMove(6, 4, 4, 4); // e4
game.makeMove(1, 4, 3, 4); // e5
game.makeMove(7, 5, 4, 2); // Bc4
game.makeMove(1, 1, 2, 2); // Nc6
game.makeMove(7, 3, 3, 7); // Qh5
game.makeMove(1, 6, 2, 5); // Nf6
game.makeMove(3, 7, 1, 5); // Qxf7#
expect(game.gameState.status).toBe('checkmate');
expect(game.winner).toBe('white');
});
});
6.3 Performance Optimization
- Memoize legal move calculations
- Use efficient data structures
- Minimize DOM manipulations
- Lazy load piece images
- Debounce UI updates
6.4 Accessibility
- Add ARIA labels
- Keyboard navigation support
- Screen reader compatibility
- High contrast mode
- Focus indicators
🚨 Common Pitfalls and Solutions
Problem 1: Check Detection Recursion
Issue: Checking for check while validating moves causes infinite recursion
Solution:
// Separate validation from check detection
static getValidMoves(board, piece) {
// Get moves without check validation
}
static getLegalMoves(board, piece) {
// Filter valid moves by check constraint
return this.getValidMoves(board, piece)
.filter(move => !this.leavesKingInCheck(board, piece, move));
}
Problem 2: En Passant State
Issue: En passant opportunity expires after one turn
Solution: Track in game state:
class GameState {
constructor() {
this.enPassantTarget = null; // Reset after each move
}
}
Problem 3: Castling Through Check
Issue: Need to validate intermediate squares
Solution: Check each square king passes through:
for (let col = kingCol; col !== targetCol; col += direction) {
if (isSquareAttacked(board, kingRow, col, opponentColor)) {
return false;
}
}
Problem 4: Drag and Drop Touch Support
Issue: Drag and drop doesn't work on mobile
Solution: Add touch event handlers:
boardElement.addEventListener('touchstart', handleTouchStart);
boardElement.addEventListener('touchmove', handleTouchMove);
boardElement.addEventListener('touchend', handleTouchEnd);
✅ Implementation Checklist
Core Functionality
- Board initialization
- All piece types implemented
- Move validation working
- Check detection
- Checkmate detection
- Stalemate detection
- Castling
- En passant
- Pawn promotion
UI
- Board rendering
- Piece rendering
- Drag and drop
- Click to move
- Legal move highlighting
- Game status display
- Move history display
- Captured pieces display
Game Management
- New game
- Undo/redo
- Save/load
- Resign
- Offer/accept draw
Testing
- Unit tests (80%+ coverage)
- Integration tests
- Manual testing completed
- Browser compatibility tested
Polish
- Animations
- Sound effects (optional)
- Responsive design
- Accessibility features
- Error handling
📚 Next Steps
After completing implementation:
- Review DEVELOPER_GUIDE.md for deployment
- Conduct code review using provided checklist
- Perform user acceptance testing
- Deploy to production
Questions? Refer to API_REFERENCE.md for detailed method signatures and CHESS_RULES.md for chess logic clarification.