chess/planning/IMPLEMENTATION_GUIDE.md
Christoph Wagner 5ad0700b41 refactor: Consolidate repository structure - flatten from workspace pattern
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>
2025-11-23 10:05:26 +01:00

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:

🏗️ 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):

  1. Rook (straight lines)
  2. Bishop (diagonals)
  3. Queen (rook + bishop)
  4. Knight (L-shapes)
  5. King (one square)
  6. 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:

  1. Review DEVELOPER_GUIDE.md for deployment
  2. Conduct code review using provided checklist
  3. Perform user acceptance testing
  4. Deploy to production

Questions? Refer to API_REFERENCE.md for detailed method signatures and CHESS_RULES.md for chess logic clarification.