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>
This commit is contained in:
Christoph Wagner
2025-11-23 10:05:26 +01:00
co-authored by Claude
parent 1fd28d10b4
commit 5ad0700b41
326 changed files with 107368 additions and 281 deletions
@@ -0,0 +1,274 @@
/**
* @file Board.js
* @description Represents the chess board and manages piece positions
* @author Implementation Team
*/
import { BOARD_SIZE, COLORS, INITIAL_POSITIONS } from '../utils/Constants.js';
import { isValidPosition, algebraicToPosition } from '../utils/Helpers.js';
import Pawn from './pieces/Pawn.js';
import Rook from './pieces/Rook.js';
import Knight from './pieces/Knight.js';
import Bishop from './pieces/Bishop.js';
import Queen from './pieces/Queen.js';
import King from './pieces/King.js';
/**
* @class Board
* @description Manages the 8x8 chess board and piece positions
*
* @example
* const board = new Board();
* board.reset(); // Setup initial position
* const piece = board.getPieceAt({row: 0, col: 0});
*/
class Board {
constructor() {
/**
* @property {Array<Array<Piece|null>>} _squares - 2D array representing the board
*/
this._squares = this._createEmptyBoard();
/**
* @property {Map<string, Piece>} _pieces - Map of all pieces (for quick lookup)
* Key format: "color-type-index" (e.g., "white-pawn-0")
*/
this._pieces = new Map();
}
/**
* Creates an empty 8x8 board
*
* @private
* @returns {Array<Array<null>>} Empty board array
*/
_createEmptyBoard() {
// TODO: Implement empty board creation
// Create 8x8 array filled with null
return [];
}
/**
* Gets the piece at a specific position
*
* @param {Object} position - Position {row, col}
* @returns {Piece|null} Piece at position or null if empty
* @throws {Error} If position is invalid
*
* @example
* const piece = board.getPieceAt({row: 0, col: 0});
*/
getPieceAt(position) {
// TODO: Implement get piece
// 1. Validate position
// 2. Return piece at position or null
return null;
}
/**
* Sets a piece at a specific position
*
* @param {Object} position - Position {row, col}
* @param {Piece|null} piece - Piece to place or null to clear
* @throws {Error} If position is invalid
*
* @example
* board.setPieceAt({row: 4, col: 4}, new Pawn('white', {row: 4, col: 4}));
*/
setPieceAt(position, piece) {
// TODO: Implement set piece
// 1. Validate position
// 2. Update _squares array
// 3. Update piece's position property
// 4. Update _pieces map if needed
}
/**
* Removes a piece from a specific position
*
* @param {Object} position - Position {row, col}
* @returns {Piece|null} Removed piece or null if position was empty
*
* @example
* const captured = board.removePieceAt({row: 4, col: 4});
*/
removePieceAt(position) {
// TODO: Implement remove piece
// 1. Get piece at position
// 2. Set position to null
// 3. Update _pieces map
// 4. Return removed piece
return null;
}
/**
* Moves a piece from one position to another
*
* @param {Object} from - Starting position {row, col}
* @param {Object} to - Target position {row, col}
* @returns {Piece|null} Captured piece or null
* @throws {Error} If no piece at 'from' position
*
* @example
* const captured = board.movePiece({row: 6, col: 4}, {row: 4, col: 4});
*/
movePiece(from, to) {
// TODO: Implement move piece
// 1. Get piece at 'from'
// 2. Throw error if no piece
// 3. Get piece at 'to' (captured piece)
// 4. Remove piece from 'from'
// 5. Place piece at 'to'
// 6. Update piece's position
// 7. Return captured piece
return null;
}
/**
* Gets all pieces on the board
*
* @returns {Array<Piece>} Array of all pieces
*
* @example
* const allPieces = board.getAllPieces();
* console.log(allPieces.length); // Should be 32 at game start
*/
getAllPieces() {
// TODO: Implement get all pieces
// Iterate through _squares and collect all non-null pieces
return [];
}
/**
* Gets all pieces of a specific color
*
* @param {string} color - Color to filter by ('white' or 'black')
* @returns {Array<Piece>} Array of pieces of the specified color
*
* @example
* const whitePieces = board.getPiecesByColor('white');
*/
getPiecesByColor(color) {
// TODO: Implement get pieces by color
// Filter all pieces by color
return [];
}
/**
* Finds the king of a specific color
*
* @param {string} color - Color of the king ('white' or 'black')
* @returns {Piece|null} King piece or null if not found
*
* @example
* const whiteKing = board.getKing('white');
*/
getKing(color) {
// TODO: Implement get king
// Find piece with type 'king' and matching color
return null;
}
/**
* Creates a deep copy of the board
*
* @returns {Board} Cloned board
*
* @example
* const boardCopy = board.clone();
*/
clone() {
// TODO: Implement clone
// 1. Create new Board instance
// 2. Deep copy all pieces
// 3. Maintain position references
return null;
}
/**
* Resets the board to initial chess position
*
* @example
* board.reset();
*/
reset() {
// TODO: Implement reset
// 1. Clear the board
// 2. Use INITIAL_POSITIONS to create pieces
// 3. Place each piece on the board
// Helper: Create piece based on type
const createPiece = (type, color, position) => {
// TODO: Implement piece factory
// Use switch/case or object map to create appropriate piece class
return null;
};
// TODO: Iterate through INITIAL_POSITIONS for both colors
// Create and place each piece
}
/**
* Clears the entire board
*
* @example
* board.clear();
*/
clear() {
// TODO: Implement clear
this._squares = this._createEmptyBoard();
this._pieces.clear();
}
/**
* Checks if a position is occupied
*
* @param {Object} position - Position {row, col}
* @returns {boolean} True if position has a piece
*
* @example
* if (board.isOccupied({row: 4, col: 4})) {
* console.log('Square is occupied');
* }
*/
isOccupied(position) {
// TODO: Implement is occupied
return false;
}
/**
* Checks if a position is occupied by an enemy piece
*
* @param {Object} position - Position {row, col}
* @param {string} playerColor - Color of the current player
* @returns {boolean} True if occupied by enemy piece
*
* @example
* if (board.isOccupiedByEnemy({row: 4, col: 4}, 'white')) {
* console.log('Can capture');
* }
*/
isOccupiedByEnemy(position, playerColor) {
// TODO: Implement is occupied by enemy
// 1. Check if occupied
// 2. Check if piece color is different from playerColor
return false;
}
/**
* Converts board to string representation (for debugging)
*
* @returns {string} String representation of the board
*
* @example
* console.log(board.toString());
*/
toString() {
// TODO: Implement toString
// Create ASCII representation of the board
// Use piece symbols or letters
return '';
}
}
export default Board;
@@ -0,0 +1,217 @@
/**
* @file Piece.js
* @description Base class for all chess pieces
* @author Implementation Team
*/
import { PIECE_TYPES } from '../utils/Constants.js';
/**
* @class Piece
* @description Abstract base class for chess pieces
* All specific piece classes (Pawn, Rook, etc.) inherit from this
*
* @example
* // Don't instantiate directly, use subclasses
* const pawn = new Pawn('white', {row: 6, col: 4});
*/
class Piece {
/**
* @param {string} color - Piece color ('white' or 'black')
* @param {Object} position - Initial position {row, col}
* @param {string} type - Piece type (from PIECE_TYPES)
*/
constructor(color, position, type) {
/**
* @property {string} color - Piece color
*/
this.color = color;
/**
* @property {Object} position - Current position {row, col}
*/
this.position = position;
/**
* @property {string} type - Piece type
*/
this.type = type;
/**
* @property {boolean} hasMoved - Whether piece has moved (for castling, pawn two-square)
*/
this.hasMoved = false;
/**
* @property {string} id - Unique identifier for the piece
*/
this.id = `${color}-${type}-${Date.now()}`;
}
/**
* Moves the piece to a new position
*
* @param {Object} newPosition - Target position {row, col}
*
* @example
* piece.move({row: 4, col: 4});
*/
move(newPosition) {
// TODO: Implement move
// 1. Update position
// 2. Set hasMoved to true
}
/**
* Gets all valid moves for this piece
* Must be implemented by subclasses
*
* @abstract
* @param {Board} board - Current board state
* @returns {Array<Object>} Array of valid positions {row, col}
*
* @example
* const validMoves = piece.getValidMoves(board);
*/
getValidMoves(board) {
// TODO: Override in subclasses
throw new Error('getValidMoves must be implemented by subclass');
}
/**
* Checks if this piece can move to a specific position
*
* @param {Object} position - Target position {row, col}
* @param {Board} board - Current board state
* @returns {boolean} True if move is valid
*
* @example
* if (piece.canMoveTo({row: 4, col: 4}, board)) {
* // Move is valid
* }
*/
canMoveTo(position, board) {
// TODO: Implement can move to
// Get valid moves and check if position is in the list
return false;
}
/**
* Creates a copy of this piece
*
* @returns {Piece} Cloned piece
*
* @example
* const pieceCopy = piece.clone();
*/
clone() {
// TODO: Implement clone
// Create new instance of same class with same properties
// Note: This is tricky because we need to know the actual subclass
return null;
}
/**
* Gets the piece's symbol for display
*
* @returns {string} Unicode symbol for the piece
*
* @example
* console.log(piece.getSymbol()); // → '♙'
*/
getSymbol() {
// TODO: Implement get symbol
// Use PIECE_SYMBOLS from Constants
return '';
}
/**
* Gets the piece's value for AI evaluation
*
* @returns {number} Piece value
*
* @example
* const value = piece.getValue(); // → 3 for knight
*/
getValue() {
// TODO: Implement get value
// Use PIECE_VALUES from Constants
return 0;
}
/**
* Checks if this piece is white
*
* @returns {boolean} True if piece is white
*/
get isWhite() {
// TODO: Implement is white
return false;
}
/**
* Checks if this piece is black
*
* @returns {boolean} True if piece is black
*/
get isBlack() {
// TODO: Implement is black
return false;
}
/**
* Gets a string representation of the piece
*
* @returns {string} String representation
*
* @example
* console.log(piece.toString()); // → "White Pawn at e2"
*/
toString() {
// TODO: Implement toString
// Format: "[Color] [Type] at [algebraic notation]"
return '';
}
/**
* Helper method to check if a position is occupied by an enemy
*
* @protected
* @param {Object} position - Position to check {row, col}
* @param {Board} board - Current board state
* @returns {boolean} True if position has enemy piece
*/
_isEnemyAt(position, board) {
// TODO: Implement enemy check
// Get piece at position and compare colors
return false;
}
/**
* Helper method to check if a position is occupied by friendly piece
*
* @protected
* @param {Object} position - Position to check {row, col}
* @param {Board} board - Current board state
* @returns {boolean} True if position has friendly piece
*/
_isFriendlyAt(position, board) {
// TODO: Implement friendly check
return false;
}
/**
* Helper method to check if a position is empty
*
* @protected
* @param {Object} position - Position to check {row, col}
* @param {Board} board - Current board state
* @returns {boolean} True if position is empty
*/
_isEmptyAt(position, board) {
// TODO: Implement empty check
return false;
}
}
export default Piece;
@@ -0,0 +1,186 @@
/**
* @file Constants.js
* @description Game-wide constants for the chess game
* @author Implementation Team
*/
/**
* Board dimensions
*/
export const BOARD_SIZE = 8;
/**
* Board boundaries
*/
export const BOARD_BOUNDS = Object.freeze({
MIN_ROW: 0,
MAX_ROW: 7,
MIN_COL: 0,
MAX_COL: 7
});
/**
* Player colors
*/
export const COLORS = Object.freeze({
WHITE: 'white',
BLACK: 'black'
});
/**
* Piece types
*/
export const PIECE_TYPES = Object.freeze({
PAWN: 'pawn',
ROOK: 'rook',
KNIGHT: 'knight',
BISHOP: 'bishop',
QUEEN: 'queen',
KING: 'king'
});
/**
* Game status
*/
export const GAME_STATUS = Object.freeze({
ACTIVE: 'active',
CHECK: 'check',
CHECKMATE: 'checkmate',
STALEMATE: 'stalemate',
DRAW: 'draw'
});
/**
* Unicode symbols for chess pieces
* Used for visual representation when not using images
*/
export const PIECE_SYMBOLS = Object.freeze({
'white-king': '♔',
'white-queen': '♕',
'white-rook': '♖',
'white-bishop': '♗',
'white-knight': '♘',
'white-pawn': '♙',
'black-king': '♚',
'black-queen': '♛',
'black-rook': '♜',
'black-bishop': '♝',
'black-knight': '♞',
'black-pawn': '♟'
});
/**
* Initial piece positions in algebraic notation
* Format: [piece_type, position]
*/
export const INITIAL_POSITIONS = Object.freeze({
white: [
// Pawns
['pawn', 'a2'], ['pawn', 'b2'], ['pawn', 'c2'], ['pawn', 'd2'],
['pawn', 'e2'], ['pawn', 'f2'], ['pawn', 'g2'], ['pawn', 'h2'],
// Pieces
['rook', 'a1'], ['knight', 'b1'], ['bishop', 'c1'], ['queen', 'd1'],
['king', 'e1'], ['bishop', 'f1'], ['knight', 'g1'], ['rook', 'h1']
],
black: [
// Pawns
['pawn', 'a7'], ['pawn', 'b7'], ['pawn', 'c7'], ['pawn', 'd7'],
['pawn', 'e7'], ['pawn', 'f7'], ['pawn', 'g7'], ['pawn', 'h7'],
// Pieces
['rook', 'a8'], ['knight', 'b8'], ['bishop', 'c8'], ['queen', 'd8'],
['king', 'e8'], ['bishop', 'f8'], ['knight', 'g8'], ['rook', 'h8']
]
});
/**
* Piece values for AI evaluation
*/
export const PIECE_VALUES = Object.freeze({
[PIECE_TYPES.PAWN]: 1,
[PIECE_TYPES.KNIGHT]: 3,
[PIECE_TYPES.BISHOP]: 3,
[PIECE_TYPES.ROOK]: 5,
[PIECE_TYPES.QUEEN]: 9,
[PIECE_TYPES.KING]: 1000 // Infinite value (game over if lost)
});
/**
* Event names for EventBus
*/
export const EVENTS = Object.freeze({
PIECE_SELECTED: 'piece:selected',
PIECE_DESELECTED: 'piece:deselected',
PIECE_MOVED: 'piece:moved',
PIECE_CAPTURED: 'piece:captured',
PIECE_PROMOTED: 'piece:promoted',
GAME_CHECK: 'game:check',
GAME_CHECKMATE: 'game:checkmate',
GAME_STALEMATE: 'game:stalemate',
GAME_DRAW: 'game:draw',
GAME_OVER: 'game:over',
TURN_CHANGED: 'turn:changed',
MOVE_INVALID: 'move:invalid'
});
/**
* CSS class names
*/
export const CSS_CLASSES = Object.freeze({
SQUARE_LIGHT: 'square--light',
SQUARE_DARK: 'square--dark',
SQUARE_SELECTED: 'square--selected',
SQUARE_VALID_MOVE: 'square--valid-move',
SQUARE_CHECK: 'square--check',
SQUARE_LAST_MOVE: 'square--last-move',
PIECE_DRAGGING: 'piece--dragging'
});
/**
* AI difficulty levels
*/
export const AI_LEVELS = Object.freeze({
EASY: { depth: 1, name: 'Easy' },
MEDIUM: { depth: 2, name: 'Medium' },
HARD: { depth: 3, name: 'Hard' },
EXPERT: { depth: 4, name: 'Expert' }
});
/**
* Direction vectors for piece movement
* Used by sliding pieces (rook, bishop, queen)
*/
export const DIRECTIONS = Object.freeze({
ORTHOGONAL: [
{ row: -1, col: 0 }, // Up
{ row: 1, col: 0 }, // Down
{ row: 0, col: -1 }, // Left
{ row: 0, col: 1 } // Right
],
DIAGONAL: [
{ row: -1, col: -1 }, // Up-left
{ row: -1, col: 1 }, // Up-right
{ row: 1, col: -1 }, // Down-left
{ row: 1, col: 1 } // Down-right
],
KNIGHT: [
{ row: -2, col: -1 }, { row: -2, col: 1 },
{ row: -1, col: -2 }, { row: -1, col: 2 },
{ row: 1, col: -2 }, { row: 1, col: 2 },
{ row: 2, col: -1 }, { row: 2, col: 1 }
]
});
/**
* Files (columns) mapping
*/
export const FILES = Object.freeze({
a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h: 7
});
/**
* Ranks (rows) mapping
* Note: rank 1 is row 7 in our array (bottom of board)
*/
export const RANKS = Object.freeze({
1: 7, 2: 6, 3: 5, 4: 4, 5: 3, 6: 2, 7: 1, 8: 0
});
@@ -0,0 +1,139 @@
/**
* @file EventBus.js
* @description Simple event bus for component communication
* @author Implementation Team
*/
/**
* @class EventBus
* @description Facilitates publish-subscribe pattern for loose coupling between components
*
* @example
* const eventBus = new EventBus();
* eventBus.subscribe('piece:moved', (data) => console.log(data));
* eventBus.publish('piece:moved', {from: 'e2', to: 'e4'});
*/
class EventBus {
constructor() {
/**
* @property {Map} _subscribers - Map of event names to arrays of callbacks
*/
this._subscribers = new Map();
}
/**
* Subscribe to an event
*
* @param {string} event - Event name
* @param {Function} callback - Function to call when event is published
* @returns {Function} Unsubscribe function
*
* @example
* const unsubscribe = eventBus.subscribe('game:check', handleCheck);
* // Later: unsubscribe()
*/
subscribe(event, callback) {
// TODO: Implement subscription
// 1. Check if event exists in _subscribers
// 2. If not, create new array for this event
// 3. Add callback to array
// 4. Return unsubscribe function that removes this callback
// Return unsubscribe function
return () => {
// TODO: Implement unsubscribe logic
};
}
/**
* Publish an event with data
*
* @param {string} event - Event name
* @param {*} data - Data to pass to subscribers
*
* @example
* eventBus.publish('piece:moved', {
* piece: pawn,
* from: {row: 6, col: 4},
* to: {row: 4, col: 4}
* });
*/
publish(event, data) {
// TODO: Implement publishing
// 1. Get all subscribers for this event
// 2. Call each callback with the data
// 3. Handle any errors in callbacks (try-catch)
}
/**
* Unsubscribe a specific callback from an event
*
* @param {string} event - Event name
* @param {Function} callback - Callback to remove
*
* @example
* eventBus.unsubscribe('game:check', handleCheck);
*/
unsubscribe(event, callback) {
// TODO: Implement unsubscribe
// 1. Get subscribers for event
// 2. Find and remove the callback
}
/**
* Remove all subscribers for an event
*
* @param {string} event - Event name
*
* @example
* eventBus.clear('game:over');
*/
clear(event) {
// TODO: Implement clear
// Remove all subscribers for the event
}
/**
* Remove all subscribers for all events
*
* @example
* eventBus.clearAll();
*/
clearAll() {
// TODO: Implement clear all
this._subscribers.clear();
}
/**
* Get count of subscribers for an event
*
* @param {string} event - Event name
* @returns {number} Number of subscribers
*
* @example
* const count = eventBus.getSubscriberCount('piece:moved');
*/
getSubscriberCount(event) {
// TODO: Implement subscriber count
return 0; // Replace with actual logic
}
/**
* Check if an event has any subscribers
*
* @param {string} event - Event name
* @returns {boolean} True if event has subscribers
*
* @example
* if (eventBus.hasSubscribers('game:over')) {
* // Do something
* }
*/
hasSubscribers(event) {
// TODO: Implement has subscribers check
return false; // Replace with actual logic
}
}
// Export singleton instance
export default new EventBus();
@@ -0,0 +1,225 @@
/**
* @file Helpers.js
* @description Utility functions for the chess game
* @author Implementation Team
*/
import { BOARD_BOUNDS, FILES, RANKS } from './Constants.js';
/**
* Checks if a position is within the board boundaries
*
* @param {number} row - Row index (0-7)
* @param {number} col - Column index (0-7)
* @returns {boolean} True if position is valid
*
* @example
* isValidPosition(3, 4) // → true
* isValidPosition(8, 0) // → false
*/
export function isValidPosition(row, col) {
// TODO: Implement validation
// Check if row and col are within BOARD_BOUNDS
return false; // Replace with actual logic
}
/**
* Checks if an object is a valid position object
*
* @param {Object} position - Position object to validate
* @returns {boolean} True if position object is valid
*
* @example
* isValidPositionObject({row: 3, col: 4}) // → true
* isValidPositionObject({x: 3, y: 4}) // → false
*/
export function isValidPositionObject(position) {
// TODO: Implement validation
// Check if position has row and col properties
// Check if both are valid
return false; // Replace with actual logic
}
/**
* Compares two positions for equality
*
* @param {Object} pos1 - First position {row, col}
* @param {Object} pos2 - Second position {row, col}
* @returns {boolean} True if positions are equal
*
* @example
* positionsEqual({row: 3, col: 4}, {row: 3, col: 4}) // → true
*/
export function positionsEqual(pos1, pos2) {
// TODO: Implement comparison
return false; // Replace with actual logic
}
/**
* Converts algebraic notation to position coordinates
*
* @param {string} algebraic - Algebraic notation (e.g., 'e4')
* @returns {Object|null} Position {row, col} or null if invalid
*
* @example
* algebraicToPosition('e4') // → {row: 4, col: 4}
* algebraicToPosition('a1') // → {row: 7, col: 0}
*/
export function algebraicToPosition(algebraic) {
// TODO: Implement conversion
// Parse file (letter) and rank (number)
// Use FILES and RANKS mappings
// Validate input
return null; // Replace with actual logic
}
/**
* Converts position coordinates to algebraic notation
*
* @param {Object} position - Position {row, col}
* @returns {string|null} Algebraic notation or null if invalid
*
* @example
* positionToAlgebraic({row: 4, col: 4}) // → 'e4'
* positionToAlgebraic({row: 7, col: 0}) // → 'a1'
*/
export function positionToAlgebraic(position) {
// TODO: Implement conversion
// Convert col to file letter (a-h)
// Convert row to rank number (1-8)
return null; // Replace with actual logic
}
/**
* Creates a deep clone of an object
*
* @param {*} obj - Object to clone
* @returns {*} Deep copy of the object
*
* @example
* const copy = deepClone({a: {b: 1}});
*/
export function deepClone(obj) {
// TODO: Implement deep cloning
// Handle arrays, objects, and primitives
// Consider using JSON.parse(JSON.stringify()) or custom logic
return null; // Replace with actual logic
}
/**
* Calculates the distance between two positions
*
* @param {Object} pos1 - First position {row, col}
* @param {Object} pos2 - Second position {row, col}
* @returns {Object} Distance {rows: number, cols: number}
*
* @example
* getDistance({row: 0, col: 0}, {row: 3, col: 4})
* // → {rows: 3, cols: 4}
*/
export function getDistance(pos1, pos2) {
// TODO: Implement distance calculation
return { rows: 0, cols: 0 }; // Replace with actual logic
}
/**
* Gets all positions in a line between two positions
* Does not include the start and end positions
*
* @param {Object} from - Starting position {row, col}
* @param {Object} to - Ending position {row, col}
* @returns {Array<Object>} Array of positions between from and to
*
* @example
* getPositionsBetween({row: 0, col: 0}, {row: 3, col: 0})
* // → [{row: 1, col: 0}, {row: 2, col: 0}]
*/
export function getPositionsBetween(from, to) {
// TODO: Implement path calculation
// Only works for straight lines (orthogonal or diagonal)
// Returns empty array if not a straight line
return []; // Replace with actual logic
}
/**
* Checks if a path between two positions is clear (no pieces blocking)
*
* @param {Object} from - Starting position {row, col}
* @param {Object} to - Ending position {row, col}
* @param {Board} board - Board instance
* @returns {boolean} True if path is clear
*
* @example
* isPathClear({row: 0, col: 0}, {row: 3, col: 0}, board)
*/
export function isPathClear(from, to, board) {
// TODO: Implement path checking
// Get positions between from and to
// Check if any position has a piece
return false; // Replace with actual logic
}
/**
* Gets the opposite color
*
* @param {string} color - Current color ('white' or 'black')
* @returns {string} Opposite color
*
* @example
* getOppositeColor('white') // → 'black'
*/
export function getOppositeColor(color) {
// TODO: Implement color toggle
return null; // Replace with actual logic
}
/**
* Formats a move for display
*
* @param {Object} move - Move object {piece, from, to, captured}
* @returns {string} Formatted move string
*
* @example
* formatMove({piece: pawn, from: {row: 6, col: 4}, to: {row: 4, col: 4}})
* // → "e2-e4"
*/
export function formatMove(move) {
// TODO: Implement move formatting
// Use algebraic notation
// Include piece type, capture notation, etc.
return ''; // Replace with actual logic
}
/**
* Generates a unique ID
*
* @returns {string} Unique identifier
*
* @example
* const id = generateId(); // → "1234567890-abcdef"
*/
export function generateId() {
// TODO: Implement ID generation
// Use timestamp + random string
return ''; // Replace with actual logic
}
/**
* Debounces a function call
*
* @param {Function} func - Function to debounce
* @param {number} wait - Wait time in milliseconds
* @returns {Function} Debounced function
*
* @example
* const debouncedSave = debounce(saveGame, 500);
*/
export function debounce(func, wait) {
// TODO: Implement debounce
let timeout;
return function executedFunction(...args) {
// Clear existing timeout
// Set new timeout
// Execute function after wait time
};
}