UNPKG

chessground

Version:
166 lines (154 loc) 7.26 kB
import { HeadlessState } from './state.js'; import { setCheck, setSelected } from './board.js'; import { read as fenRead } from './fen.js'; import { DrawShape, DrawBrushes } from './draw.js'; import * as cg from './types.js'; export interface Config { fen?: cg.FEN; // chess position in Forsyth notation orientation?: cg.Color; // board orientation. white | black turnColor?: cg.Color; // turn to play. white | black check?: cg.Color | boolean; // true for current color, false to unset lastMove?: cg.Key[]; // squares part of the last move ["c3", "c4"] selected?: cg.Key; // square currently selected "a1" coordinates?: boolean; // include coords attributes coordinatesOnSquares?: boolean; // include coords attributes on every square autoCastle?: boolean; // immediately complete the castle by moving the rook after king move viewOnly?: boolean; // don't bind events: the user will never be able to move pieces around disableContextMenu?: boolean; // because who needs a context menu on a chessboard addPieceZIndex?: boolean; // adds z-index values to pieces (for 3D) addDimensionsCssVarsTo?: HTMLElement; // add ---cg-width and ---cg-height CSS vars containing the board's dimensions to this element blockTouchScroll?: boolean; // block scrolling via touch dragging on the board, e.g. for coordinate training // pieceKey: boolean; // add a data-key attribute to piece elements trustAllEvents?: boolean; // disable checking for human only input (e.isTrusted) highlight?: { lastMove?: boolean; // add last-move class to squares check?: boolean; // add check class to squares custom?: cg.SquareClasses; // add custom classes to custom squares }; animation?: { enabled?: boolean; duration?: number; }; movable?: { free?: boolean; // all moves are valid - board editor color?: cg.Color | 'both'; // color that can move. white | black | both | undefined dests?: cg.Dests; // valid moves. {"a2" ["a3" "a4"] "b1" ["a3" "c3"]} showDests?: boolean; // whether to add the move-dest class on squares events?: { after?: (orig: cg.Key, dest: cg.Key, metadata: cg.MoveMetadata) => void; // called after the move has been played afterNewPiece?: (role: cg.Role, key: cg.Key, metadata: cg.MoveMetadata) => void; // called after a new piece is dropped on the board }; rookCastle?: boolean; // castle by moving the king to the rook }; premovable?: { enabled?: boolean; // allow premoves for color that can not move showDests?: boolean; // whether to add the premove-dest class on squares castle?: boolean; // whether to allow king castle premoves dests?: cg.Key[]; // premove destinations for the current selection customDests?: cg.Dests; // use custom valid premoves. {"a2" ["a3" "a4"] "b1" ["a3" "c3"]} events?: { set?: (orig: cg.Key, dest: cg.Key, metadata?: cg.SetPremoveMetadata) => void; // called after the premove has been set unset?: () => void; // called after the premove has been unset }; }; predroppable?: { enabled?: boolean; // allow predrops for color that can not move events?: { set?: (role: cg.Role, key: cg.Key) => void; // called after the predrop has been set unset?: () => void; // called after the predrop has been unset }; }; draggable?: { enabled?: boolean; // allow moves & premoves to use drag'n drop distance?: number; // minimum distance to initiate a drag; in pixels autoDistance?: boolean; // lets chessground set distance to zero when user drags pieces showGhost?: boolean; // show ghost of piece being dragged deleteOnDropOff?: boolean; // delete a piece when it is dropped off the board }; selectable?: { // disable to enforce dragging over click-click move enabled?: boolean; }; events?: { change?: () => void; // called after the situation changes on the board // called after a piece has been moved. // capturedPiece is undefined or like {color: 'white'; 'role': 'queen'} move?: (orig: cg.Key, dest: cg.Key, capturedPiece?: cg.Piece) => void; dropNewPiece?: (piece: cg.Piece, key: cg.Key) => void; select?: (key: cg.Key) => void; // called when a square is selected insert?: (elements: cg.Elements) => void; // when the board DOM has been (re)inserted }; drawable?: { enabled?: boolean; // can draw visible?: boolean; // can view defaultSnapToValidMove?: boolean; // false to keep the drawing if a movable piece is clicked. // Clicking an empty square or immovable piece will clear the drawing regardless. eraseOnClick?: boolean; shapes?: DrawShape[]; autoShapes?: DrawShape[]; brushes?: DrawBrushes; onChange?: (shapes: DrawShape[]) => void; // called after drawable shapes change }; } export function applyAnimation(state: HeadlessState, config: Config): void { if (config.animation) { deepMerge(state.animation, config.animation); // no need for such short animations if ((state.animation.duration || 0) < 70) state.animation.enabled = false; } } export function configure(state: HeadlessState, config: Config): void { // don't merge destinations and autoShapes. Just override. if (config.movable?.dests) state.movable.dests = undefined; if (config.drawable?.autoShapes) state.drawable.autoShapes = []; deepMerge(state, config); // if a fen was provided, replace the pieces if (config.fen) { state.pieces = fenRead(config.fen); state.drawable.shapes = config.drawable?.shapes || []; } // apply config values that could be undefined yet meaningful if ('check' in config) setCheck(state, config.check || false); if ('lastMove' in config && !config.lastMove) state.lastMove = undefined; // in case of ZH drop last move, there's a single square. // if the previous last move had two squares, // the merge algorithm will incorrectly keep the second square. else if (config.lastMove) state.lastMove = config.lastMove; // fix move/premove dests if (state.selected) setSelected(state, state.selected); applyAnimation(state, config); if (!state.movable.rookCastle && state.movable.dests) { const rank = state.movable.color === 'white' ? '1' : '8', kingStartPos = ('e' + rank) as cg.Key, dests = state.movable.dests.get(kingStartPos), king = state.pieces.get(kingStartPos); if (!dests || !king || king.role !== 'king') return; state.movable.dests.set( kingStartPos, dests.filter( d => !(d === 'a' + rank && dests.includes(('c' + rank) as cg.Key)) && !(d === 'h' + rank && dests.includes(('g' + rank) as cg.Key)), ), ); } } function deepMerge(base: any, extend: any): void { for (const key in extend) { if (Object.prototype.hasOwnProperty.call(extend, key)) { if ( Object.prototype.hasOwnProperty.call(base, key) && isPlainObject(base[key]) && isPlainObject(extend[key]) ) deepMerge(base[key], extend[key]); else base[key] = extend[key]; } } } function isPlainObject(o: unknown): boolean { if (typeof o !== 'object' || o === null) return false; const proto = Object.getPrototypeOf(o); return proto === Object.prototype || proto === null; }