UNPKG

@noe_rls/cm-chessboard

Version:

A JavaScript chessboard which is lightweight, ES6 module based, responsive, SVG rendered and without dependencies.

277 lines (251 loc) 10.4 kB
/** * Author and copyright: Stefan Haack (https://shaack.com) * Repository: https://github.com/shaack/cm-chessboard * License: MIT, see file 'LICENSE' */ import {SQUARE_COORDINATES, ChessboardView} from "./ChessboardView.js" import {ChessboardState} from "./ChessboardState.js" export const COLOR = { white: "w", black: "b" } export const INPUT_EVENT_TYPE = { moveStart: "moveStart", moveDone: "moveDone", moveCanceled: "moveCanceled" } export const SQUARE_SELECT_TYPE = { primary: "primary", secondary: "secondary" } export const BORDER_TYPE = { none: "none", // no border thin: "thin", // thin border frame: "frame" // wide border with coordinates in it } export const MARKER_TYPE = { frame: {class: "marker-frame", slice: "markerFrame"}, square: {class: "marker-square", slice: "markerSquare"}, dot: {class: "marker-dot", slice: "markerDot"}, circle: {class: "marker-circle", slice: "markerCircle"} } export const PIECE = { wp: "wp", wb: "wb", wn: "wn", wr: "wr", wq: "wq", wk: "wk", bp: "bp", bb: "bb", bn: "bn", br: "br", bq: "bq", bk: "bk", } export const FEN_START_POSITION = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" export const FEN_EMPTY_POSITION = "8/8/8/8/8/8/8/8" export class Chessboard { constructor(element, props = {}) { // TODO rename element to context if (!element) { throw new Error("container element is " + element) } this.element = element let defaultProps = { position: "empty", // set as fen, "start" or "empty" orientation: COLOR.white, // white on bottom style: { cssClass: "default", showCoordinates: true, // show ranks and files borderType: BORDER_TYPE.none, // thin: thin border, frame: wide border with coordinates in it, none: no border aspectRatio: 1, // height/width. Set to `undefined`, if you want to define it only in the css. moveFromMarker: MARKER_TYPE.frame, // the marker used to mark the start square moveToMarker: MARKER_TYPE.frame, // the marker used to mark the square where the figure is moving to moveMarker: MARKER_TYPE.frame, // deprecated => moveFromMarker // TODO remove in future hoverMarker: MARKER_TYPE.frame // deprecated => moveToMarker // TODO remove in future }, responsive: true, // resizes the board based on element size animationDuration: 300, // pieces animation duration in milliseconds sprite: { url: "./assets/images/chessboard-sprite.svg", // pieces and markers are stored as svg sprite size: 40, // the sprite size, defaults to 40x40px cache: true // cache the sprite inline, in the HTML } } this.props = {} Object.assign(this.props, defaultProps) Object.assign(this.props, props) this.props.sprite = defaultProps.sprite this.props.style = defaultProps.style if (props.sprite) { Object.assign(this.props.sprite, props.sprite) } if (props.style) { Object.assign(this.props.style, props.style) } if(this.props.style.moveMarker !== MARKER_TYPE.frame) { // TODO remove in future console.warn("this.props.style.moveMarker is deprecated, use this.props.style.moveFromMarker") this.props.style.moveFromMarker = this.props.style.moveMarker } if(this.props.style.hoverMarker !== MARKER_TYPE.frame) { // TODO remove in future console.warn("this.props.style.hoverMarker is deprecated, use this.props.style.moveToMarker") this.props.style.moveToMarker = this.props.style.hoverMarker } if (this.props.style.aspectRatio) { this.element.style.height = (this.element.offsetWidth * this.props.style.aspectRatio) + "px" } this.state = new ChessboardState() this.state.orientation = this.props.orientation this.view = new ChessboardView(this, (view) => { if (this.props.position === "start") { this.state.setPosition(FEN_START_POSITION) } else if (this.props.position === "empty" || this.props.position === undefined) { this.state.setPosition(FEN_EMPTY_POSITION) } else { this.state.setPosition(this.props.position) } view.redraw() }) } // API // setPiece(square, piece) { this.state.setPiece(this.state.squareToIndex(square), piece) this.view.drawPieces(this.state.squares) } getPiece(square) { return this.state.squares[this.state.squareToIndex(square)] } movePiece(squareFrom, squareTo, animated = true) { return new Promise((resolve, reject) => { const prevSquares = this.state.squares.slice(0) // clone const pieceFrom = this.getPiece(squareFrom) if(!pieceFrom) { reject("no piece on square " + squareFrom) } else { this.state.squares[this.state.squareToIndex(squareFrom)] = null this.state.squares[this.state.squareToIndex(squareTo)] = pieceFrom if (animated) { this.view.animatePieces(prevSquares, this.state.squares, () => { resolve() }) } else { this.view.drawPieces(this.state.squares) resolve() } } }) } setPosition(fen, animated = true) { return new Promise((resolve) => { if (fen === "start") { fen = FEN_START_POSITION } else if (fen === "empty") { fen = FEN_EMPTY_POSITION } const currentFen = this.state.getPosition() const fenParts = fen.split(" ") const fenNormalized = fenParts[0] if (fenNormalized !== currentFen) { const prevSquares = this.state.squares.slice(0) // clone this.state.setPosition(fen) if (animated) { this.view.animatePieces(prevSquares, this.state.squares.slice(0), () => { resolve() }) } else { this.view.drawPieces(this.state.squares) resolve() } } else { resolve() } }) } getPosition() { return this.state.getPosition() } addMarker(square, type) { if (!type) { console.error("Error addMarker(), type is " + type) } this.state.addMarker(this.state.squareToIndex(square), type) this.view.drawMarkers() } getMarkers(square = undefined, type = undefined) { const markersFound = [] this.state.markers.forEach((marker) => { const markerSquare = SQUARE_COORDINATES[marker.index] if (!square && (!type || type === marker.type) || !type && square === markerSquare || type === marker.type && square === markerSquare) { markersFound.push({square: SQUARE_COORDINATES[marker.index], type: marker.type}) } }) return markersFound } removeMarkers(square = undefined, type = undefined) { const index = square ? this.state.squareToIndex(square) : undefined this.state.removeMarkers(index, type) this.view.drawMarkers() } setOrientation(color) { this.state.orientation = color return this.view.redraw() } getOrientation() { return this.state.orientation } destroy() { this.view.destroy() this.view = undefined this.state = undefined if (this.squareSelectListener) { this.element.removeEventListener("contextmenu", this.squareSelectListener) this.element.removeEventListener("mouseup", this.squareSelectListener) this.element.removeEventListener("touchend", this.squareSelectListener) } } enableMoveInput(eventHandler, color = undefined) { this.view.enableMoveInput(eventHandler, color) } disableMoveInput() { this.view.disableMoveInput() } // TODO remove deprecated function // noinspection JSUnusedGlobalSymbols enableContextInput(eventHandler) { console.warn("enableContextInput() is deprecated, use enableSquareSelect()") this.enableSquareSelect(function (event) { if (event.type === SQUARE_SELECT_TYPE.secondary) { eventHandler(event) } }) } // TODO remove deprecated function // noinspection JSUnusedGlobalSymbols disableContextInput() { this.disableSquareSelect() } enableSquareSelect(eventHandler) { if (this.squareSelectListener) { console.warn("squareSelectListener already existing") return } this.squareSelectListener = function (e) { const index = e.target.getAttribute("data-index") if (e.type === "contextmenu") { // disable context menu e.preventDefault() return } eventHandler({ chessboard: this, type: e.button === 2 ? SQUARE_SELECT_TYPE.secondary : SQUARE_SELECT_TYPE.primary, square: SQUARE_COORDINATES[index] }) } this.element.addEventListener("contextmenu", this.squareSelectListener) this.element.addEventListener("mouseup", this.squareSelectListener) this.element.addEventListener("touchend", this.squareSelectListener) this.state.squareSelectEnabled = true this.view.setCursor() } disableSquareSelect() { this.element.removeEventListener("contextmenu", this.squareSelectListener) this.element.removeEventListener("mouseup", this.squareSelectListener) this.element.removeEventListener("touchend", this.squareSelectListener) this.squareSelectListener = undefined this.state.squareSelectEnabled = false this.view.setCursor() } }