import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { ChessPieceComponent } from '../chess-piece/chess-piece.component'; interface BoardSquare { coordinate: string; isLight: boolean; pieceCode: string | null; } type BoardTheme = 'arabian' | 'classic'; @Component({ selector: 'app-chess-board', standalone: true, imports: [ChessPieceComponent], templateUrl: './chess-board.component.html', styleUrl: './chess-board.component.css' }) export class ChessBoardComponent implements OnChanges { @Input({ required: true }) fen = ''; @Input() selectedSquare: string | null = null; @Input() highlightedSquares: string[] = []; @Input() boardTheme: BoardTheme = 'arabian'; @Output() squareSelected = new EventEmitter(); squares: BoardSquare[] = []; private highlightedSquareSet = new Set(); private draggingFromSquare: string | null = null; private dragOverSquare: string | null = null; private suppressNextClick = false; ngOnChanges(changes: SimpleChanges): void { if (changes['fen']) { this.squares = this.buildSquares(this.fen); } if (changes['highlightedSquares']) { this.highlightedSquareSet = new Set(this.highlightedSquares); } } trackByCoordinate(_index: number, square: BoardSquare): string { return square.coordinate; } onSquareClick(square: BoardSquare): void { if (this.suppressNextClick) { this.suppressNextClick = false; return; } this.squareSelected.emit(square.coordinate); } onPieceDragStart(event: DragEvent, square: BoardSquare): void { if (!square.pieceCode) { event.preventDefault(); return; } this.draggingFromSquare = square.coordinate; if (event.dataTransfer) { event.dataTransfer.setData('text/plain', square.coordinate); event.dataTransfer.effectAllowed = 'move'; } this.squareSelected.emit(square.coordinate); } onSquareDragOver(event: DragEvent, square: BoardSquare): void { if (!this.draggingFromSquare) { return; } event.preventDefault(); if (event.dataTransfer) { event.dataTransfer.dropEffect = 'move'; } this.dragOverSquare = square.coordinate === this.draggingFromSquare ? null : square.coordinate; } onSquareDrop(event: DragEvent, square: BoardSquare): void { event.preventDefault(); if (!this.draggingFromSquare) { return; } const fromSquare = this.draggingFromSquare; this.clearDragState(); if (fromSquare === square.coordinate) { return; } this.suppressNextClick = true; this.squareSelected.emit(square.coordinate); } onSquareDragEnd(): void { this.clearDragState(); } isSelected(square: BoardSquare): boolean { return this.selectedSquare === square.coordinate; } isHighlighted(square: BoardSquare): boolean { return this.highlightedSquareSet.has(square.coordinate); } isDraggingSource(square: BoardSquare): boolean { return this.draggingFromSquare === square.coordinate; } isDragOver(square: BoardSquare): boolean { return this.dragOverSquare === square.coordinate; } private buildSquares(fen: string): BoardSquare[] { const placement = fen.split(' ')[0] ?? ''; const rows = placement.split('/'); if (rows.length !== 8) { return []; } const squares: BoardSquare[] = []; rows.forEach((row, rowIndex) => { let column = 0; for (const char of row) { if (char >= '1' && char <= '8') { const emptyCount = Number(char); for (let step = 0; step < emptyCount; step += 1) { squares.push(this.createSquare(rowIndex, column, null)); column += 1; } } else { squares.push(this.createSquare(rowIndex, column, char)); column += 1; } } }); return squares; } private createSquare(rowIndex: number, column: number, pieceCode: string | null): BoardSquare { const file = String.fromCharCode(97 + column); const rank = String(8 - rowIndex); return { coordinate: `${file}${rank}`, isLight: (rowIndex + column) % 2 === 0, pieceCode }; } private clearDragState(): void { this.draggingFromSquare = null; this.dragOverSquare = null; } }