chess/docs/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

887 lines
22 KiB
Markdown

# 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](HANDOFF_CHECKLIST.md)
- [ ] Review [API_REFERENCE.md](API_REFERENCE.md)
- [ ] Study architecture diagrams in [diagrams/](diagrams/)
- [ ] Understand chess rules from [CHESS_RULES.md](CHESS_RULES.md)
- [ ] Set up development environment (see [DEVELOPER_GUIDE.md](DEVELOPER_GUIDE.md))
## 🏗️ Implementation Phases
### Phase 1: Project Setup and Core Architecture (Days 1-3)
#### 1.1 Initialize Project Structure
```bash
# 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)
```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
```javascript
// 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
```javascript
// 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
```javascript
// 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:**
```javascript
// 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**
```javascript
// 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:**
```javascript
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:**
```javascript
// 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
```javascript
// 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:**
```javascript
// 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:**
```javascript
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:**
```javascript
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
```javascript
// 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
```javascript
// 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:**
```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
```javascript
// 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
```javascript
// 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
```javascript
// 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:**
```javascript
// 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:
```javascript
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:
```javascript
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:
```javascript
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](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](API_REFERENCE.md) for detailed method signatures and [CHESS_RULES.md](CHESS_RULES.md) for chess logic clarification.