UNPKG

cm-chessboard-position-editor

Version:
220 lines (209 loc) 9.31 kB
/** * Author and copyright: Stefan Haack (https://shaack.com) * Repository: https://github.com/shaack/cm-chessboard-postion-editor * License: MIT, see file 'LICENSE' */ import {Extension} from "cm-chessboard/src/model/Extension.js" import { PROMOTION_DIALOG_RESULT_TYPE, PromotionDialog } from "cm-chessboard/src/extensions/promotion-dialog/PromotionDialog.js" import {COLOR, INPUT_EVENT_TYPE} from "cm-chessboard/src/Chessboard.js" import {MOVE_CANCELED_REASON} from "cm-chessboard/src/view/VisualMoveInput.js" import {MARKER_TYPE} from "cm-chessboard/src/extensions/markers/Markers.js" import {SelectPieceDialog} from "./extensions/SelectPieceDialog.js" export const POSITION_CHANGE_TYPE = { move: "move", capture: "capture", castling: "castling", promotion: "promotion", createPiece: "createPiece", removePiece: "removePiece" } export class PositionEditor extends Extension { /** @constructor */ constructor(chessboard, props = {}) { super(chessboard) if (!chessboard.getExtension(PromotionDialog)) { chessboard.addExtension(PromotionDialog) } if (!chessboard.getExtension(SelectPieceDialog)) { chessboard.addExtension(SelectPieceDialog) } if (props.onPositionChanged) { // deprecated 2023-09-15 console.warn("onPositionChanged is deprecated, use onPositionChange") props.onPositionChange = props.onPositionChanged } this.props = { autoSpecialMoves: true, // castling, en passant, promotion onPositionChange: undefined, // callback after each position change markers: { addPiece: {...MARKER_TYPE.frame} } } Object.assign(this.props, props) this.clickListener = this.onSquareClick.bind(this) this.state = { selectPieceDialogShown: false, promotionDialogShown: false } this.chessboard.enableMoveInput((event) => { switch (event.type) { case INPUT_EVENT_TYPE.moveInputStarted: return true case INPUT_EVENT_TYPE.moveInputCanceled: return this.onMoveInputCanceled(event) case INPUT_EVENT_TYPE.validateMoveInput: return this.onValidateMoveInput(event) case INPUT_EVENT_TYPE.moveInputFinished: return this.onMoveInputFinished(event) } }) this.chessboard.context.addEventListener("click", this.clickListener) } onMoveInputCanceled(event) { // remove piece if it was moved out of the board if (event.reason === MOVE_CANCELED_REASON.movedOutOfBoard) { this.chessboard.setPiece(event.squareFrom, null) if (this.props.onPositionChange) { this.props.onPositionChange({ position: this.chessboard.getPosition(), type: POSITION_CHANGE_TYPE.removePiece }) } } } onValidateMoveInput(event) { if (this.chessboard.getPiece(event.squareTo)) { this.captured = this.chessboard.getPiece(event.squareTo) } else { this.captured = null } return true } onMoveInputFinished(event) { let castling, enPassant, promotion if (event.squareTo && this.props.autoSpecialMoves) { castling = this.handleCastling(event) if (!castling) { enPassant = this.handleEnPassant(event) if (!enPassant) { promotion = this.handlePromotion(event) } } } if (this.props.onPositionChange) { let type = POSITION_CHANGE_TYPE.move if (castling) { type = POSITION_CHANGE_TYPE.castling } else if (enPassant || this.captured) { type = POSITION_CHANGE_TYPE.capture } if (!promotion && event.legalMove) { this.props.onPositionChange({position: this.chessboard.getPosition(), type: type}) } } } handlePromotion(event) { const piece = this.chessboard.getPiece(event.squareTo) const color = piece[0] const type = piece[1] if (type === "p" && (color === COLOR.white && event.squareTo[1] === "8" || color === COLOR.black && event.squareTo[1] === "1")) { this.state.promotionDialogShown = true this.chessboard.showPromotionDialog(event.squareTo, color, (result) => { if (result.type === PROMOTION_DIALOG_RESULT_TYPE.pieceSelected) { this.chessboard.setPiece(event.squareFrom, null, false) this.chessboard.setPiece(result.square, result.piece, true) if (this.props.onPositionChange) { this.props.onPositionChange({ position: this.chessboard.getPosition(), type: POSITION_CHANGE_TYPE.promotion }) } } else { this.chessboard.movePiece(event.squareTo, event.squareFrom, true) } setTimeout(() => { this.state.promotionDialogShown = false }, 500) }) return true } return false } handleCastling(event) { const piece = this.chessboard.getPiece(event.squareTo) const color = piece[0] const type = piece[1] if (type === "k") { if (event.squareFrom[1] === "1" && event.squareTo[1] === "1" && color === COLOR.white) { if (event.squareFrom[0] === "e" && event.squareTo[0] === "g" && this.chessboard.getPiece("h1") === "wr") { this.chessboard.movePiece("h1", "f1", true) return true } else if (event.squareFrom[0] === "e" && event.squareTo[0] === "c" && this.chessboard.getPiece("a1") === "wr") { this.chessboard.movePiece("a1", "d1", true) return true } } else if (event.squareFrom[1] === "8" && event.squareTo[1] === "8" && color === COLOR.black) { if (event.squareFrom[0] === "e" && event.squareTo[0] === "g" && this.chessboard.getPiece("h8") === "br") { this.chessboard.movePiece("h8", "f8", true) return true } else if (event.squareFrom[0] === "e" && event.squareTo[0] === "c" && this.chessboard.getPiece("a8") === "br") { this.chessboard.movePiece("a8", "d8", true) return true } } } return false } handleEnPassant(event) { const piece = this.chessboard.getPiece(event.squareTo) const color = piece[0] const type = piece[1] if (type === "p") { const rankFrom = parseInt(event.squareFrom[1], 10) const rankTo = parseInt(event.squareTo[1], 10) const fileFrom = COLOR.black.charCodeAt(0) - event.squareFrom[0].charCodeAt(0) + 8 const fileTo = COLOR.black.charCodeAt(0) - event.squareTo[0].charCodeAt(0) + 8 if (color === COLOR.white && rankFrom === 5 && rankTo === 6 && Math.abs(fileFrom - fileTo) === 1 && !this.captured && this.chessboard.getPiece(event.squareTo[0] + event.squareFrom[1]) === "bp") { this.chessboard.setPiece(event.squareTo[0] + event.squareFrom[1], null, true) return true } if (color === COLOR.black && rankFrom === 4 && rankTo === 3 && Math.abs(fileFrom - fileTo) === 1 && !this.captured && this.chessboard.getPiece(event.squareTo[0] + event.squareFrom[1]) === "wp") { this.chessboard.setPiece(event.squareTo[0] + event.squareFrom[1], null, true) return true } } return false } onSquareClick(event) { if (this.state.promotionDialogShown) { return } const square = event.target.getAttribute("data-square") if (square && !this.chessboard.getPiece(square)) { if (!this.state.selectPieceDialogShown) { this.chessboard.showSelectPieceDialog(square, (result) => { if (result) { this.chessboard.setPiece(square, result.piece, true) if (this.props.onPositionChange) { this.props.onPositionChange({ position: this.chessboard.getPosition(), type: POSITION_CHANGE_TYPE.createPiece }) } } setTimeout(() => { this.state.selectPieceDialogShown = false this.chessboard.removeMarkers(this.props.markers.addPiece, square) }) }) this.chessboard.addMarker(this.props.markers.addPiece, square) } this.state.selectPieceDialogShown = true } } }