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>
887 lines
22 KiB
Markdown
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.
|