UNPKG

ngx-chess-board

Version:
649 lines (578 loc) 21.5 kB
import { EventEmitter } from '@angular/core'; import { HistoryMove } from '../history-move-provider/history-move'; import { ColorInput, PieceTypeInput } from '../utils/inputs/piece-type-input'; import { AbstractEngineFacade } from './abstract-engine-facade'; import { BoardLoader } from './board-state-provider/board-loader/board-loader'; import { BoardState } from './board-state-provider/board-state/board-state'; import { BoardStateProvider } from './board-state-provider/board-state/board-state-provider'; import { MoveStateProvider } from './board-state-provider/board-state/move-state-provider'; import { ClickUtils } from './click/click-utils'; import { Arrow } from './drawing-tools/shapes/arrow'; import { Circle } from './drawing-tools/shapes/circle'; import { DrawPoint } from './drawing-tools/draw-point'; import { DrawProvider } from './drawing-tools/draw-provider'; import { Board } from '../models/board'; import { Color } from '../models/pieces/color'; import { King } from '../models/pieces/king'; import { Pawn } from '../models/pieces/pawn'; import { Piece } from '../models/pieces/piece'; import { Point } from '../models/pieces/point'; import { DefaultPgnProcessor } from './pgn/default-pgn-processor'; import { AvailableMoveDecorator } from './piece-decorator/available-move-decorator'; import { PiecePromotionResolver } from '../piece-promotion/piece-promotion-resolver'; import { MoveUtils } from '../utils/move-utils'; import { MoveChange } from './outputs/move-change/move-change'; import { PieceFactory } from './utils/piece-factory'; export class EngineFacade extends AbstractEngineFacade { _selected = false; drawPoint: DrawPoint; drawProvider: DrawProvider; boardStateProvider: BoardStateProvider; moveStateProvider: MoveStateProvider; moveChange: EventEmitter<MoveChange>; private historyMoveCandidate: HistoryMove; constructor( board: Board, moveChange: EventEmitter<MoveChange> ) { super(board); this.moveChange = moveChange; this.boardLoader = new BoardLoader(this); this.boardLoader.addPieces(); this.boardStateProvider = new BoardStateProvider(); } public reset(): void { this.boardStateProvider.clear(); this.moveHistoryProvider.clear(); this.boardLoader.addPieces(); this.board.reset(); this.coords.reset(); this.drawProvider.clear(); this.pgnProcessor.reset(); } public undo(): void { if (!this.boardStateProvider.isEmpty()) { const lastBoard = this.boardStateProvider.pop().board; if (this.board.reverted) { lastBoard.reverse(); } this.board = lastBoard; this.board.possibleCaptures = []; this.board.possibleMoves = []; this.board.activePiece = null; this.moveHistoryProvider.pop(); this.board.calculateFEN(); this.pgnProcessor.removeLast(); } } saveMoveClone() { const clone = this.board.clone(); if (this.board.reverted) { clone.reverse(); } this.moveStateProvider.addMove(new BoardState(clone)); } public move(coords: string) { if (coords) { const sourceIndexes = MoveUtils.translateCoordsToIndex( coords.substring(0, 2), this.board.reverted ); const destIndexes = MoveUtils.translateCoordsToIndex( coords.substring(2, 4), this.board.reverted ); const srcPiece = this.board.getPieceByPoint( sourceIndexes.yAxis, sourceIndexes.xAxis ); if (srcPiece) { if ( (this.board.currentWhitePlayer && srcPiece.color === Color.BLACK) || (!this.board.currentWhitePlayer && srcPiece.color === Color.WHITE) ) { return; } this.prepareActivePiece(srcPiece, srcPiece.point); if ( this.board.isPointInPossibleMoves( new Point(destIndexes.yAxis, destIndexes.xAxis) ) || this.board.isPointInPossibleCaptures( new Point(destIndexes.yAxis, destIndexes.xAxis) ) ) { this.saveClone(); this.movePiece( srcPiece, new Point(destIndexes.yAxis, destIndexes.xAxis), coords.length === 5 ? +coords.substring(4, 5) : 0 ); this.board.lastMoveSrc = new Point( sourceIndexes.yAxis, sourceIndexes.xAxis ); this.board.lastMoveDest = new Point( destIndexes.yAxis, destIndexes.xAxis ); this.disableSelection(); } else { this.disableSelection(); } } } } prepareActivePiece(pieceClicked: Piece, pointClicked: Point) { this.board.activePiece = pieceClicked; this._selected = true; this.board.possibleCaptures = new AvailableMoveDecorator( pieceClicked, pointClicked, this.board.currentWhitePlayer ? Color.WHITE : Color.BLACK, this.board ).getPossibleCaptures(); this.board.possibleMoves = new AvailableMoveDecorator( pieceClicked, pointClicked, this.board.currentWhitePlayer ? Color.WHITE : Color.BLACK, this.board ).getPossibleMoves(); } onPieceClicked(pieceClicked, pointClicked) { if ( (this.board.currentWhitePlayer && pieceClicked.color === Color.BLACK) || (!this.board.currentWhitePlayer && pieceClicked.color === Color.WHITE) ) { return; } this.prepareActivePiece(pieceClicked, pointClicked); } public handleClickEvent(pointClicked: Point, isMouseDown: boolean) { let moving = false; if ((( this.board.isPointInPossibleMoves(pointClicked) || this.board.isPointInPossibleCaptures(pointClicked) ) || this.freeMode) && pointClicked.isInRange()) { this.saveClone(); this.board.lastMoveSrc = new Point( this.board.activePiece.point.row, this.board.activePiece.point.col ); this.board.lastMoveDest = pointClicked.clone(); this.movePiece(this.board.activePiece, pointClicked); if (!this.board.activePiece.point.isEqual(this.board.lastMoveSrc)) { moving = true; } } if (isMouseDown || moving) { this.disableSelection(); } this.disableSelection(); const pieceClicked = this.board.getPieceByPoint( pointClicked.row, pointClicked.col ); if (pieceClicked && !moving) { this.onFreeMode(pieceClicked); this.onPieceClicked(pieceClicked, pointClicked); } } onMouseDown( event: MouseEvent, pointClicked: Point, left?: number, top?: number ) { this.moveDone = false; if (event.button !== 0) { this.drawPoint = ClickUtils.getDrawingPoint( this.heightAndWidth, this.colorStrategy, event.x, event.y, event.ctrlKey, event.altKey, event.shiftKey, left, top ); return; } this.drawProvider.clear(); if ( this.board.activePiece && pointClicked.isEqual(this.board.activePiece.point) ) { this.disabling = true; return; } const pieceClicked = this.board.getPieceByPoint( pointClicked.row, pointClicked.col ); if (this.freeMode) { if (pieceClicked) { if (event.ctrlKey) { this.board.pieces = this.board.pieces.filter(e => e !== pieceClicked); return; } this.board.currentWhitePlayer = (pieceClicked.color === Color.WHITE); } } if (this.isPieceDisabled(pieceClicked)) { return; } if (this._selected) { this.handleClickEvent(pointClicked, true); } else { if (pieceClicked) { this.onFreeMode(pieceClicked); this.onPieceClicked(pieceClicked, pointClicked); } } } onMouseUp( event: MouseEvent, pointClicked: Point, left: number, top: number ) { this.moveDone = false; if (event.button !== 0 && !this.drawDisabled) { this.addDrawPoint( event.x, event.y, event.ctrlKey, event.altKey, event.shiftKey, left, top ); return; } this.drawProvider.clear(); if (this.dragDisabled) { return; } if ( this.board.activePiece && pointClicked.isEqual(this.board.activePiece.point) && this.disabling ) { this.disableSelection(); this.disabling = false; return; } const pieceClicked = this.board.getPieceByPoint( pointClicked.row, pointClicked.col ); if (this.isPieceDisabled(pieceClicked)) { return; } if (this._selected) { this.handleClickEvent(pointClicked, false); // this.possibleMoves = activePiece.getPossibleMoves(); } } saveClone() { const clone = this.board.clone(); if (this.board.reverted) { clone.reverse(); } this.boardStateProvider.addMove(new BoardState(clone)); } movePiece(toMovePiece: Piece, newPoint: Point, promotionIndex?: number) { const destPiece = this.board.pieces.find( (piece) => piece.point.col === newPoint.col && piece.point.row === newPoint.row ); this.pgnProcessor.process( this.board, toMovePiece, newPoint, destPiece ); if (destPiece && toMovePiece.color !== destPiece.color) { this.board.pieces = this.board.pieces.filter( (piece) => piece !== destPiece ); } else { if (destPiece && toMovePiece.color === destPiece.color) { return; } } this.historyMoveCandidate = new HistoryMove( MoveUtils.format(toMovePiece.point, newPoint, this.board.reverted), toMovePiece.constant.name, toMovePiece.color === Color.WHITE ? 'white' : 'black', !!destPiece ); this.moveHistoryProvider.addMove(this.historyMoveCandidate); if (toMovePiece instanceof King) { const squaresMoved = Math.abs(newPoint.col - toMovePiece.point.col); if (squaresMoved > 1) { if (newPoint.col < 3) { const leftRook = this.board.getPieceByField( toMovePiece.point.row, 0 ); if (!this.freeMode) { leftRook.point.col = this.board.reverted ? 2 : 3; } } else { const rightRook = this.board.getPieceByField( toMovePiece.point.row, 7 ); if (!this.freeMode) { rightRook.point.col = this.board.reverted ? 4 : 5; } } } } if (toMovePiece instanceof Pawn) { this.board.checkIfPawnTakesEnPassant(newPoint); this.board.checkIfPawnEnpassanted(toMovePiece, newPoint); } else { this.board.enPassantPoint = null; this.board.enPassantPiece = null; } toMovePiece.point = newPoint; this.increaseFullMoveCount(); this.board.currentWhitePlayer = !this.board.currentWhitePlayer; if (!this.checkForPawnPromote(toMovePiece, promotionIndex)) { this.afterMoveActions(); } } checkForPawnPromote(toPromotePiece: Piece, promotionIndex?: number) { if (!(toPromotePiece instanceof Pawn)) { return; } if (toPromotePiece.point.row === 0 || toPromotePiece.point.row === 7) { this.board.pieces = this.board.pieces.filter( (piece) => piece !== toPromotePiece ); // When we make move manually, we pass promotion index already, so we don't need // to acquire it from promote dialog if (!promotionIndex) { this.openPromoteDialog(toPromotePiece); } else { PiecePromotionResolver.resolvePromotionChoice( this.board, toPromotePiece, promotionIndex ); this.afterMoveActions(promotionIndex); } return true; } } afterMoveActions(promotionIndex?: number) { this.checkIfPawnFirstMove(this.board.activePiece); this.checkIfRookMoved(this.board.activePiece); this.checkIfKingMoved(this.board.activePiece); this.board.blackKingChecked = this.board.isKingInCheck( Color.BLACK, this.board.pieces ); this.board.whiteKingChecked = this.board.isKingInCheck( Color.WHITE, this.board.pieces ); const check = this.board.blackKingChecked || this.board.whiteKingChecked; const checkmate = this.checkForPossibleMoves(Color.BLACK) || this.checkForPossibleMoves(Color.WHITE); const stalemate = this.checkForPat(Color.BLACK) || this.checkForPat(Color.WHITE); this.historyMoveCandidate.setGameStates(check, stalemate, checkmate); this.pgnProcessor.processChecks(checkmate, check, stalemate); this.pgnProcessor.addPromotionChoice(promotionIndex); this.disabling = false; this.board.calculateFEN(); const lastMove = this.moveHistoryProvider.getLastMove(); if (lastMove && promotionIndex) { lastMove.move += promotionIndex; } this.moveChange.emit({ ...lastMove, check, checkmate, stalemate, fen: this.board.fen, pgn: { pgn: this.pgnProcessor.getPGN() }, freeMode: this.freeMode }); this.moveDone = true; } checkForPat(color: Color) { if (color === Color.WHITE && !this.board.whiteKingChecked) { return this.checkForPossibleMoves(color); } else { if (color === Color.BLACK && !this.board.blackKingChecked) { return this.checkForPossibleMoves(color); } } } openPromoteDialog(piece: Piece) { if (piece.color === this.board.activePiece.color) { this.modal.open((index) => { PiecePromotionResolver.resolvePromotionChoice( this.board, piece, index ); this.afterMoveActions(index); }); } } checkForPossibleMoves(color: Color): boolean { return !this.board.pieces .filter((piece) => piece.color === color) .some( (piece) => piece .getPossibleMoves() .some( (move) => !MoveUtils.willMoveCauseCheck( color, piece.point.row, piece.point.col, move.row, move.col, this.board ) ) || piece .getPossibleCaptures() .some( (capture) => !MoveUtils.willMoveCauseCheck( color, piece.point.row, piece.point.col, capture.row, capture.col, this.board ) ) ); } disableSelection() { this._selected = false; this.board.possibleCaptures = []; this.board.activePiece = null; this.board.possibleMoves = []; } /** * Processes logic to allow freeMode based logic processing */ onFreeMode(pieceClicked) { if ( !this.freeMode || pieceClicked === undefined || pieceClicked === null ) { return; } // sets player as white in-case white pieces are selected, and vice-versa when black is selected this.board.currentWhitePlayer = pieceClicked.color === Color.WHITE; } isPieceDisabled(pieceClicked: Piece) { if (pieceClicked && pieceClicked.point) { const foundCapture = this.board.possibleCaptures.find( (capture) => capture.col === pieceClicked.point.col && capture.row === pieceClicked.point.row ); if (foundCapture) { return false; } } return ( pieceClicked && ((this.lightDisabled && pieceClicked.color === Color.WHITE) || (this.darkDisabled && pieceClicked.color === Color.BLACK)) ); } addDrawPoint( x: number, y: number, crtl: boolean, alt: boolean, shift: boolean, left: number, top: number ) { const upPoint = ClickUtils.getDrawingPoint( this.heightAndWidth, this.colorStrategy, x, y, crtl, alt, shift, left, top ); if (this.drawPoint.isEqual(upPoint)) { const circle = new Circle(); circle.drawPoint = upPoint; if (!this.drawProvider.containsCircle(circle)) { this.drawProvider.addCircle(circle); } else { this.drawProvider.reomveCircle(circle); } } else { const arrow = new Arrow(); arrow.start = this.drawPoint; arrow.end = upPoint; if (!this.drawProvider.containsArrow(arrow)) { this.drawProvider.addArrow(arrow); } else { this.drawProvider.removeArrow(arrow); } } } increaseFullMoveCount() { if (!this.board.currentWhitePlayer) { ++this.board.fullMoveCount; } } addPiece( pieceTypeInput: PieceTypeInput, colorInput: ColorInput, coords: string ) { if (this.freeMode && coords && pieceTypeInput > 0 && colorInput > 0) { let indexes = MoveUtils.translateCoordsToIndex( coords, this.board.reverted ); let existing = this.board.getPieceByPoint( indexes.yAxis, indexes.xAxis ); if (existing) { this.board.pieces = this.board.pieces.filter(e => e !== existing); } let createdPiece = PieceFactory.create( indexes, pieceTypeInput, colorInput, this.board ); this.saveClone(); this.board.pieces.push(createdPiece); this.afterMoveActions(); } } }