UNPKG

chessground12

Version:
204 lines (190 loc) 8.04 kB
import type { HeadlessState } from './state'; import { setCheck, setSelected } from './board'; import type { DrawShape, DrawBrushes } from './draw'; import { setPredropDests, readPockets } from './pocket'; import { Color, Dests, DropDests, Elements, FEN, Geometry, Key, MoveMetadata, Notation, Piece, PocketRoles, Role, SetPremoveMetadata, Variant, dimensions, } from './types'; export interface Config { fen?: FEN; // chess position in Forsyth notation orientation?: Color; // board orientation. white | black turnColor?: Color; // turn to play. white | black check?: Color | boolean; // true for current color, false to unset lastMove?: Key[]; // squares part of the last move ["c3", "c4"] selected?: Key; // square currently selected "a1" coordinates?: boolean; // include coords attributes 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) addDimensionsCssVars?: boolean; // add --cg-width and --cg-height CSS vars containing the board's dimensions to the document root 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 highlight?: { lastMove?: boolean; // add last-move class to squares check?: boolean; // add check class to squares }; animation?: { enabled?: boolean; duration?: number; }; movable?: { free?: boolean; // all moves are valid - board editor color?: Color | 'both'; // color that can move. white | black | both | undefined dests?: Dests; // valid moves. {"a2" ["a3" "a4"] "b1" ["a3" "c3"]} showDests?: boolean; // whether to add the move-dest class on squares events?: { after?: (orig: Key, dest: Key, metadata: MoveMetadata) => void; // called after the move has been played afterNewPiece?: (role: Role, key: Key, metadata: 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?: Key[]; // premove destinations for the current selection events?: { set?: (orig: Key, dest: Key, metadata?: 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 showDropDests?: boolean; dropDests?: Key[]; current?: { // See corresponding type in state.ts for more comments role: Role; key: Key; }; events?: { set?: (role: Role, key: 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: Key, dest: Key, capturedPiece?: Piece) => void; dropNewPiece?: (piece: Piece, key: Key) => void; select?: (key: Key) => void; // called when a square is selected insert?: (elements: Elements) => void; // when the board DOM has been (re)inserted pocketSelect: (piece: Piece) => void; }; dropmode?: { active?: boolean; piece?: Piece; showDropDests?: boolean; // whether to add the move-dest class on squares for drops dropDests?: DropDests; // see corresponding state.ts type for comments }; 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; pieces?: { baseUrl?: string; }; onChange?: (shapes: DrawShape[]) => void; // called after drawable shapes change }; geometry?: Geometry; // dim3x4 | dim5x5 | dim7x7 | dim8x8 | dim9x9 | dim10x8 | dim9x10 | dim10x10 variant?: Variant; chess960?: boolean; notation?: Notation; pocketRoles?: PocketRoles; // what pieces have slots in the pocket for each color } 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.dropmode?.dropDests) state.dropmode.dropDests = undefined; if (config.drawable?.autoShapes) state.drawable.autoShapes = []; deepMerge(state, config); if (config.geometry) state.dimensions = dimensions[config.geometry]; // if a fen was provided, replace the pieces if (config.fen) { const pieces = new Map(); // prevent calling cancel() if piece drag is already started from pocket! const draggedPiece = state.pieces.get('a0'); if (draggedPiece !== undefined) pieces.set('a0', draggedPiece); state.pieces = pieces; state.drawable.shapes = []; if (state.pocketRoles) { state.pockets = readPockets(config.fen, state.pocketRoles); } } // 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); setPredropDests(state); // TODO: integrate pocket with the "selected" infrastructure and move this in setSelected() applyAnimation(state, config); if (!state.movable.rookCastle && state.movable.dests) { const rank = state.movable.color === 'white' ? '1' : '8', kingStartPos = ('e' + rank) as Key, dests = state.movable.dests.get(kingStartPos), king = state.pieces.get(kingStartPos); if (!dests || !king || king.role !== 'k-piece') return; state.movable.dests.set( kingStartPos, dests.filter( d => !(d === 'a' + rank && dests.includes(('c' + rank) as Key)) && !(d === 'h' + rank && dests.includes(('g' + rank) as Key)), ), ); } } // eslint-disable-next-line function deepMerge(base: any, extend: any): void { for (const key in extend) { if (isObject(base[key]) && isObject(extend[key])) deepMerge(base[key], extend[key]); else base[key] = extend[key]; } } function isObject(o: unknown): boolean { return typeof o === 'object'; }