├── .eslintignore ├── src ├── Chessboard │ ├── export.js │ ├── __tests__ │ │ ├── Square.test.js │ │ ├── Notation.test.js │ │ ├── Piece.test.js │ │ └── helpers.test.js │ ├── PhantomPiece.js │ ├── ErrorBoundary.js │ ├── Row.js │ ├── CustomDragLayer.js │ ├── svg │ │ ├── whiteKing.js │ │ └── chesspieces │ │ │ └── standard.js │ ├── SparePieces.js │ ├── Notation.js │ ├── Square.js │ ├── helpers.js │ ├── Board.js │ ├── Piece.js │ └── index.js ├── img │ ├── elvis.png │ ├── lucena.png │ ├── sicilian.png │ ├── kingJames.png │ └── carlsenWorldChampionship2016.png ├── index.js ├── customRough.js ├── integrations │ ├── SpareOnDrop.js │ ├── AllowDrag.js │ ├── CustomizedBoard.js │ ├── RandomVsRandomGame.js │ ├── PrestoChango.js │ ├── PlayRandomMoveEngine.js │ ├── WithMoveValidation.js │ ├── UndoMove.js │ └── Stockfish.js ├── index.html └── Demo.js ├── .gitignore ├── commitlint.config.js ├── .prettierignore ├── .prettierrc ├── .travis.yml ├── .codecov.yml ├── .eslintrc.js ├── .babelrc ├── webpack.dev.js ├── webpack.common.js ├── jest.config.js ├── LICENSE ├── webpack.prod.js ├── chessboard.d.ts ├── CONTRIBUTING.md ├── .all-contributorsrc ├── CODE_OF_CONDUCT.md ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/* 2 | dist/* 3 | -------------------------------------------------------------------------------- /src/Chessboard/export.js: -------------------------------------------------------------------------------- 1 | export { default } from './index.js'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .nyc_output/ 3 | coverage/ 4 | dist/ 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /src/img/elvis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willb335/chessboardjsx/HEAD/src/img/elvis.png -------------------------------------------------------------------------------- /src/img/lucena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willb335/chessboardjsx/HEAD/src/img/lucena.png -------------------------------------------------------------------------------- /src/img/sicilian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willb335/chessboardjsx/HEAD/src/img/sicilian.png -------------------------------------------------------------------------------- /src/img/kingJames.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willb335/chessboardjsx/HEAD/src/img/kingJames.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | 3 | # editor configs 4 | .idea/ 5 | .vscode/ 6 | 7 | 8 | coverage/ 9 | dist/ 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "bracketSpacing": true, 4 | "jsxBracketSameLine": false, 5 | "semi": true, 6 | } 7 | -------------------------------------------------------------------------------- /src/img/carlsenWorldChampionship2016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willb335/chessboardjsx/HEAD/src/img/carlsenWorldChampionship2016.png -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Demo from './Demo'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | node_js: '8' 7 | branches: 8 | only: 9 | - master 10 | notifications: 11 | email: false 12 | script: 13 | - npm run validate 14 | before_script: 15 | - npm prune 16 | after_success: 17 | - npm run report-coverage 18 | - npm run semantic-release 19 | -------------------------------------------------------------------------------- /src/customRough.js: -------------------------------------------------------------------------------- 1 | import rough from 'roughjs'; 2 | 3 | export const roughSquare = ({ squareElement, squareWidth }) => { 4 | let rc = rough.svg(squareElement); 5 | const chessSquare = rc.rectangle(0, 0, squareWidth, squareWidth, { 6 | roughness: 0.8, 7 | fill: '#b58863', 8 | bowing: 2 9 | }); 10 | squareElement.appendChild(chessSquare); 11 | }; 12 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: "70...100" 9 | 10 | status: 11 | project: off 12 | patch: off 13 | changes: no 14 | 15 | parsers: 16 | gcov: 17 | branch_detection: 18 | conditional: yes 19 | loop: yes 20 | method: no 21 | macro: no 22 | 23 | comment: 24 | layout: "header, diff" 25 | behavior: default 26 | require_changes: no 27 | -------------------------------------------------------------------------------- /src/integrations/SpareOnDrop.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; // eslint-disable-line no-unused-vars 2 | import Chessboard from '../Chessboard'; 3 | 4 | function SpareOnDrop() { 5 | const onDrop = ({ sourceSquare, targetSquare, piece }) => { 6 | console.log('drop', piece, sourceSquare, targetSquare); 7 | }; 8 | 9 | return ( 10 |
11 | 12 |
13 | ); 14 | } 15 | 16 | export default SpareOnDrop; 17 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true, jest: true, es6: true }, 3 | globals: { module: false, document: false, require: false, __dirname: false }, 4 | extends: ['plugin:react/recommended', 'eslint:recommended'], 5 | parserOptions: { 6 | ecmaVersion: 2017, 7 | sourceType: 'module', // es6 import/export 8 | ecmaFeatures: { 9 | jsx: true 10 | } 11 | }, 12 | parser: 'babel-eslint', // class properties 13 | plugins: ['prettier', 'react'], 14 | rules: { 'no-console': 'off' } 15 | }; 16 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { "modules": false }], 4 | "@babel/preset-react" 5 | ], 6 | 7 | "plugins": [ 8 | "syntax-dynamic-import", 9 | "transform-class-properties", 10 | "@babel/plugin-proposal-object-rest-spread", 11 | "babel-plugin-lodash" 12 | ], 13 | "env": { 14 | "test": { 15 | "presets": [ 16 | ["@babel/preset-env", { "modules": "commonjs" }], 17 | "@babel/preset-react" 18 | ], 19 | "plugins": ["transform-es2015-modules-commonjs", "dynamic-import-node"] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Chessboard/__tests__/Square.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, cleanup } from 'react-testing-library'; 3 | import 'jest-dom/extend-expect'; 4 | 5 | import Chessboard from '../index'; 6 | 7 | afterEach(cleanup); 8 | 9 | test('checks if the square is in the DOM', () => { 10 | const { getByTestId } = render( 11 | 12 | ); 13 | 14 | const whiteSquare = getByTestId('white-square'); 15 | 16 | expect(whiteSquare).toHaveAttribute('style'); 17 | expect(whiteSquare).toBeInTheDOM(); 18 | }); 19 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | const merge = require('webpack-merge'); 5 | const common = require('./webpack.common.js'); 6 | module.exports = merge(common, { 7 | mode: 'development', 8 | devtool: 'inline-source-map', 9 | entry: { app: ['babel-polyfill', './src/index.js'] }, 10 | output: { 11 | path: path.resolve(__dirname, 'dist'), 12 | filename: '[name].js', 13 | library: 'chessboardjsx', 14 | libraryTarget: 'umd' 15 | }, 16 | devServer: { contentBase: path.resolve(__dirname, 'dist') }, 17 | plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })] 18 | }); 19 | -------------------------------------------------------------------------------- /src/Chessboard/PhantomPiece.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; // eslint-disable-line no-unused-vars 2 | import PropTypes from 'prop-types'; 3 | 4 | import { renderChessPiece } from './Piece'; 5 | 6 | PhantomPiece.propTypes = { 7 | width: PropTypes.number, 8 | phantomPieceValue: PropTypes.string, 9 | pieces: PropTypes.object, 10 | allowDrag: PropTypes.func 11 | }; 12 | 13 | function PhantomPiece({ width, pieces, phantomPieceValue, allowDrag }) { 14 | return renderChessPiece({ 15 | width, 16 | pieces, 17 | piece: phantomPieceValue, 18 | phantomPieceStyles: phantomPieceStyles(width), 19 | allowDrag 20 | }); 21 | } 22 | 23 | export default PhantomPiece; 24 | 25 | const phantomPieceStyles = width => ({ 26 | position: 'absolute', 27 | width: width / 8, 28 | height: width / 8, 29 | zIndex: 1 30 | }); 31 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 2 | 3 | module.exports = { 4 | resolve: { extensions: ['.js', '.jsx'] }, 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.js$/, 9 | exclude: /node_modules/, 10 | use: 'babel-loader' 11 | }, 12 | { 13 | test: /\.css$/, 14 | exclude: /\.module\.css$/, 15 | use: [{ loader: 'style-loader' }, { loader: 'css-loader' }] 16 | }, 17 | { 18 | test: /\.module\.css$/, 19 | use: [ 20 | { loader: 'style-loader' }, 21 | { loader: 'css-loader', options: { modules: true, camelCase: true } } 22 | ] 23 | }, 24 | 25 | { 26 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/, 27 | use: 'file-loader' 28 | } 29 | ] 30 | }, 31 | plugins: [new CleanWebpackPlugin(['dist'])] 32 | }; 33 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'chessboardjsx', 3 | testEnvironment: 'jsdom', 4 | collectCoverageFrom: [ 5 | '**/src/Chessboard/**/*.js', 6 | '!**/src/Chessboard/svg/**/*.js', 7 | '!**/src/Chessboard/ErrorBoundary.js', 8 | '!**/src/Chessboard/errorMessages.js' 9 | ], 10 | // coverageThreshold: { 11 | // global: { 12 | // branches: 20, 13 | // functions: 20, 14 | // lines: 20, 15 | // statements: 20 16 | // } 17 | // }, 18 | moduleFileExtensions: ['js', 'json'], 19 | 20 | moduleNameMapper: { 21 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 22 | '/test/file-mock.js', 23 | '\\.module\\.css$': 'identity-obj-proxy', 24 | '\\.css$': '/test/style-mock.js' 25 | }, 26 | transform: { '^.+\\.js$': 'babel-jest' }, 27 | testURL: 'http://localhost/' 28 | }; 29 | -------------------------------------------------------------------------------- /src/Chessboard/ErrorBoundary.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import error from './svg/whiteKing'; 5 | 6 | class ErrorBoundary extends Component { 7 | static propTypes = { children: PropTypes.object }; 8 | state = { hasError: false }; 9 | 10 | componentDidCatch(error) { 11 | this.setState({ hasError: true }); 12 | 13 | console.error(error.message); 14 | } 15 | 16 | render() { 17 | if (this.state.hasError) { 18 | return ( 19 |
20 |
{error.whiteKing}
21 |

Something went wrong

22 |
23 | ); 24 | } 25 | return this.props.children; 26 | } 27 | } 28 | 29 | export default ErrorBoundary; 30 | 31 | const container = { 32 | display: 'flex', 33 | justifyContent: 'center', 34 | alignItems: 'center', 35 | flexDirection: 'column' 36 | }; 37 | 38 | const whiteKingStyle = { 39 | width: 250, 40 | height: 250, 41 | transform: `rotate(90deg)` 42 | }; 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2018 William J. Bashelor 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const merge = require('webpack-merge'); 4 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 5 | const common = require('./webpack.common.js'); 6 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') 7 | // .BundleAnalyzerPlugin; 8 | const LodashModuleReplacementPlugin = require('lodash-webpack-plugin'); 9 | 10 | module.exports = merge(common, { 11 | mode: 'production', 12 | entry: { chessboard: './src/Chessboard/export.js' }, 13 | output: { 14 | path: path.resolve(__dirname, 'dist'), 15 | filename: '[name].min.js', 16 | library: 'chessboardjsx', 17 | libraryTarget: 'umd' 18 | }, 19 | devtool: 'source-map', 20 | plugins: [ 21 | new LodashModuleReplacementPlugin(), 22 | new webpack.DefinePlugin({ 23 | 'process.env.NODE_ENV': JSON.stringify('production') 24 | }), 25 | new UglifyJSPlugin({ 26 | sourceMap: true, 27 | uglifyOptions: { 28 | mangle: false, 29 | keep_classnames: true 30 | } 31 | }) 32 | // new BundleAnalyzerPlugin() 33 | ], 34 | externals: { react: 'react', 'react-dom': 'react-dom' } 35 | }); 36 | -------------------------------------------------------------------------------- /src/integrations/AllowDrag.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import Chessboard from '../Chessboard'; 5 | 6 | /* eslint react/display-name: 0 */ 7 | /* eslint react/prop-types: 0 */ 8 | 9 | class AllowDrag extends Component { 10 | static propTypes = { children: PropTypes.func }; 11 | 12 | allowDrag = ({ piece }) => { 13 | if (piece === 'wB' || piece === 'spare-wB') { 14 | return false; 15 | } 16 | return true; 17 | }; 18 | render() { 19 | return this.props.children({ allowDrag: this.allowDrag }); 20 | } 21 | } 22 | 23 | export default function AllowDragFeature() { 24 | return ( 25 |
26 | 27 | {({ allowDrag }) => ( 28 | (screenWidth < 500 ? 350 : 480)} 32 | position="start" 33 | boardStyle={{ 34 | borderRadius: '5px', 35 | boxShadow: `0 5px 15px rgba(0, 0, 0, 0.5)` 36 | }} 37 | dropOffBoard="trash" 38 | sparePieces={true} 39 | allowDrag={allowDrag} 40 | draggable={true} 41 | /> 42 | )} 43 | 44 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /src/integrations/CustomizedBoard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Chessboard from '../Chessboard'; 4 | import elvis from '../img/elvis.png'; 5 | import lebronJames from '../img/kingJames.png'; 6 | import { roughSquare } from '../customRough'; 7 | 8 | /* eslint react/display-name: 0 */ 9 | /* eslint react/prop-types: 0 */ 10 | export default function CustomizedBoard() { 11 | return ( 12 | (screenWidth < 500 ? 350 : 480)} 16 | roughSquare={roughSquare} 17 | position="start" 18 | boardStyle={{ 19 | borderRadius: '5px', 20 | boxShadow: `0 5px 15px rgba(0, 0, 0, 0.5)` 21 | }} 22 | dropOffBoard="trash" 23 | pieces={{ 24 | wK: ({ squareWidth, isDragging }) => ( 25 | {'elvis'} 33 | ), 34 | bK: ({ squareWidth, isDragging }) => ( 35 | {'lebron 43 | ) 44 | }} 45 | sparePieces={true} 46 | /> 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /chessboard.d.ts: -------------------------------------------------------------------------------- 1 | import { Component, CSSProperties } from 'react'; 2 | import { Square } from "chess.js" 3 | 4 | type Piece = 5 | 'wP' | 'wN' | 'wB' | 'wR' | 'wQ' | 'wK' | 6 | 'bP' | 'bN' | 'bB' | 'bR' | 'bQ' | 'bK' 7 | ; 8 | 9 | type Position = { 10 | [pos in Square]?: Piece 11 | } 12 | 13 | type CustomPieces = { 14 | [piece in Piece]?: (obj: {isDragging: boolean, squareWidth: number, droppedPiece: Piece, targetSquare: Square, sourceSquare: Square}) => JSX.Element 15 | } 16 | 17 | interface Props { 18 | allowDrag?: (obj: {piece: Piece, sourceSquare: Square}) => boolean, 19 | boardStyle?: CSSProperties, 20 | calcWidth?: (obj: {screenWidth: number, screenHeight: number}) => number, 21 | darkSquareStyle?: CSSProperties, 22 | draggable?: boolean, 23 | dropOffBoard?: 'snapback' | 'trash', 24 | dropSquareStyle?: CSSProperties, 25 | getPosition?: (currentPosition: Position) => void, 26 | id?: string | number, 27 | lightSquareStyle?: CSSProperties, 28 | onDragOverSquare?: (square: Square) => void, 29 | onDrop?: (obj: {sourceSquare: Square, targetSquare: Square, piece: Piece}) => void, 30 | onMouseOutSquare?: (square: Square) => void, 31 | onMouseOverSquare?: (square: Square) => void, 32 | onPieceClick?: (piece: Piece) => void, 33 | onSquareClick?: (square: Square) => void, 34 | onSquareRightClick?: (square: Square) => void, 35 | orientation?: 'white' | 'black', 36 | pieces?: CustomPieces, 37 | position?: string | Position, 38 | roughSquare?: (obj: {squareElement: SVGElement, squareWidth: number}) => void, 39 | showNotation?: boolean, 40 | sparePieces?: boolean, 41 | squareStyles?: {[square in Square]?: CSSProperties}, 42 | transitionDuration?: number, 43 | width?: number, 44 | undo?: boolean, 45 | } 46 | 47 | export default class Chessboard extends Component { 48 | } 49 | -------------------------------------------------------------------------------- /src/Chessboard/Row.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { COLUMNS } from './helpers'; 5 | 6 | class Row extends Component { 7 | static propTypes = { 8 | width: PropTypes.number, 9 | orientation: PropTypes.string, 10 | boardStyle: PropTypes.object, 11 | children: PropTypes.func, 12 | boardId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) 13 | }; 14 | 15 | render() { 16 | const { width, boardStyle, orientation, children, boardId } = this.props; 17 | let alpha = COLUMNS; 18 | let row = 8; 19 | let squareColor = 'white'; 20 | 21 | if (orientation === 'black') row = 1; 22 | 23 | return ( 24 |
28 | {[...Array(8)].map((_, r) => { 29 | row = orientation === 'black' ? row + 1 : row - 1; 30 | 31 | return ( 32 |
33 | {[...Array(8)].map((_, col) => { 34 | let square = 35 | orientation === 'black' 36 | ? alpha[7 - col] + (row - 1) 37 | : alpha[col] + (row + 1); 38 | 39 | if (col !== 0) { 40 | squareColor = squareColor === 'black' ? 'white' : 'black'; 41 | } 42 | return children({ square, squareColor, col, row, alpha }); 43 | })} 44 |
45 | ); 46 | })} 47 |
48 | ); 49 | } 50 | } 51 | 52 | export default Row; 53 | 54 | const boardStyles = width => ({ 55 | width, 56 | height: width, 57 | cursor: 'default' 58 | }); 59 | 60 | const rowStyles = width => ({ 61 | display: 'flex', 62 | flexWrap: 'nowrap', 63 | width 64 | }); 65 | -------------------------------------------------------------------------------- /src/Chessboard/__tests__/Notation.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-testing-library'; 3 | import 'jest-dom/extend-expect'; 4 | 5 | import Chessboard from '../index'; 6 | 7 | test('renders notation when notation prop is true', () => { 8 | const { getByTestId } = render(); 9 | 10 | const columnNotationA = getByTestId('column-b'); 11 | const columnNotationH = getByTestId('column-c'); 12 | const bottomLeftColumn = getByTestId('bottom-left-a'); 13 | const bottomLeftRow = getByTestId('bottom-left-1'); 14 | 15 | expect(columnNotationA).toBeVisible(); 16 | expect(columnNotationH).toBeVisible(); 17 | expect(bottomLeftColumn).toBeVisible(); 18 | expect(bottomLeftRow).toBeVisible(); 19 | }); 20 | 21 | test('renders notation when notation prop is omitted', () => { 22 | const { getByTestId } = render(); 23 | 24 | const columnNotationA = getByTestId('column-b'); 25 | const columnNotationH = getByTestId('column-c'); 26 | const bottomLeftColumn = getByTestId('bottom-left-a'); 27 | const bottomLeftRow = getByTestId('bottom-left-1'); 28 | 29 | expect(columnNotationA).toBeVisible(); 30 | expect(columnNotationH).toBeVisible(); 31 | expect(bottomLeftColumn).toBeVisible(); 32 | expect(bottomLeftRow).toBeVisible(); 33 | }); 34 | 35 | it('renders notations when orientation is black', () => { 36 | const { getByTestId } = render(); 37 | 38 | const columnNotationA = getByTestId('column-b'); 39 | const columnNotationH = getByTestId('column-c'); 40 | const bottomLeftColumn = getByTestId('bottom-left-h'); 41 | const bottomLeftRow = getByTestId('bottom-left-8'); 42 | 43 | expect(columnNotationA).toBeVisible(); 44 | expect(columnNotationH).toBeVisible(); 45 | expect(bottomLeftColumn).toBeVisible(); 46 | expect(bottomLeftRow).toBeVisible(); 47 | }); 48 | -------------------------------------------------------------------------------- /src/Chessboard/__tests__/Piece.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-testing-library'; 3 | import 'jest-dom/extend-expect'; 4 | 5 | import Chessboard from '../index'; 6 | 7 | test('renders pieces when position is "start"', () => { 8 | const { getByTestId } = render(); 9 | const whiteQueen = getByTestId('wQ-d1'); 10 | const blackKnight = getByTestId('bN-b8'); 11 | 12 | expect(whiteQueen).toBeInTheDOM(); 13 | expect(blackKnight).toBeInTheDOM(); 14 | }); 15 | 16 | test('renders no pieces when position is not provided', () => { 17 | const { queryByTestId } = render(); 18 | 19 | const whiteQueen = queryByTestId('wQ-d1'); 20 | const blackKnight = queryByTestId('bN-b8'); 21 | 22 | expect(whiteQueen).not.toBeInTheDOM(); 23 | expect(blackKnight).not.toBeInTheDOM(); 24 | }); 25 | 26 | test('renders a sparePiece sparePieces is true', () => { 27 | const { queryByTestId } = render( 28 | 29 | ); 30 | 31 | const whiteQueen = queryByTestId('spare-wQ'); 32 | const blackRook = queryByTestId('spare-bR'); 33 | const blackPawn = queryByTestId('spare-bP'); 34 | 35 | expect(whiteQueen).toBeInTheDOM(); 36 | expect(blackRook).toBeInTheDOM(); 37 | expect(blackPawn).toBeInTheDOM(); 38 | }); 39 | 40 | test('renders correct pieces when given a position object', () => { 41 | const { queryByTestId } = render( 42 | 49 | ); 50 | const whiteQueen = queryByTestId('wK-e4'); 51 | const blackRook = queryByTestId('bR-e1'); 52 | const blackKing = queryByTestId('bK-d6'); 53 | const blackBishop = queryByTestId('bB-f3'); 54 | 55 | expect(whiteQueen).toBeInTheDOM(); 56 | expect(blackRook).not.toBeInTheDOM(); 57 | expect(blackKing).toBeInTheDOM(); 58 | expect(blackBishop).not.toBeInTheDOM(); 59 | }); 60 | -------------------------------------------------------------------------------- /src/integrations/RandomVsRandomGame.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; // eslint-disable-line no-unused-vars 2 | import PropTypes from 'prop-types'; 3 | import Chess from 'chess.js'; 4 | 5 | import Chessboard from '../Chessboard'; 6 | 7 | class RandomVsRandom extends Component { 8 | static propTypes = { children: PropTypes.func }; 9 | 10 | state = { fen: 'start' }; 11 | 12 | componentDidMount() { 13 | this.game = new Chess(); 14 | setTimeout(() => this.makeRandomMove(), 1000); 15 | } 16 | 17 | componentWillUnmount() { 18 | window.clearTimeout(this.timer()); 19 | } 20 | 21 | timer = () => window.setTimeout(this.makeRandomMove, 1000); 22 | 23 | makeRandomMove = () => { 24 | let possibleMoves = this.game.moves(); 25 | 26 | // exit if the game is over 27 | if ( 28 | this.game.game_over() === true || 29 | this.game.in_draw() === true || 30 | possibleMoves.length === 0 31 | ) 32 | return; 33 | 34 | let randomIndex = Math.floor(Math.random() * possibleMoves.length); 35 | this.game.move(possibleMoves[randomIndex]); 36 | this.setState({ fen: this.game.fen() }); 37 | 38 | this.timer(); 39 | }; 40 | 41 | render() { 42 | const { fen } = this.state; 43 | return this.props.children({ position: fen }); 44 | } 45 | } 46 | 47 | /* eslint react/display-name: 0 */ 48 | /* eslint react/prop-types: 0 */ 49 | export default function RandomVsRandomGame() { 50 | return ( 51 |
52 | 53 | {({ position }) => ( 54 | (screenWidth < 500 ? 350 : 480)} 56 | id="random" 57 | position={position} 58 | transitionDuration={300} 59 | boardStyle={{ 60 | borderRadius: '5px', 61 | boxShadow: `0 5px 15px rgba(0, 0, 0, 0.5)` 62 | }} 63 | /> 64 | )} 65 | 66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /src/integrations/PrestoChango.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; // eslint-disable-line no-unused-vars 2 | import PropTypes from 'prop-types'; 3 | 4 | import Chessboard from '../Chessboard'; 5 | 6 | class PrestoChango extends Component { 7 | static propTypes = { children: PropTypes.func }; 8 | 9 | state = { fen: 'start' }; 10 | 11 | componentDidMount() { 12 | window.setTimeout(this.timer1); 13 | window.setTimeout(this.timer2); 14 | window.setTimeout(this.timer3); 15 | } 16 | 17 | componentWillUnmount() { 18 | window.clearTimeout(this.timer1); 19 | window.clearTimeout(this.timer2); 20 | window.clearTimeout(this.timer3); 21 | } 22 | 23 | timer1 = () => window.setTimeout(this.onTimer1, 3000); 24 | timer2 = () => window.setTimeout(this.onTimer2, 6000); 25 | timer3 = () => window.setTimeout(this.onTimer3, 9000); 26 | 27 | onTimer1 = () => { 28 | this.setState({ 29 | fen: 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 1 1' 30 | }); 31 | }; 32 | 33 | onTimer2 = () => { 34 | this.setState({ fen: '4K1k1/4P3/8/8/8/8/r7/1R6 w - - 0 1' }); 35 | }; 36 | 37 | onTimer3 = () => { 38 | this.setState({ fen: '4K1k1/4P3/8/8/8/8/r7/6R1 b - - 1 1' }); 39 | }; 40 | 41 | render() { 42 | const { fen } = this.state; 43 | return this.props.children({ position: fen }); 44 | } 45 | } 46 | 47 | /* eslint react/display-name: 0 */ 48 | /* eslint react/prop-types: 0 */ 49 | export default function PrestoChangoExample() { 50 | return ( 51 |
52 | 53 | {({ position }) => ( 54 | (screenWidth < 500 ? 350 : 480)} 56 | id="presto" 57 | position={position} 58 | transitionDuration={300} 59 | boardStyle={{ 60 | borderRadius: '5px', 61 | boxShadow: `0 5px 15px rgba(0, 0, 0, 0.5)` 62 | }} 63 | /> 64 | )} 65 | 66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /src/Chessboard/CustomDragLayer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { DragLayer } from 'react-dnd'; 4 | 5 | import { renderChessPiece } from './Piece'; 6 | 7 | class CustomDragLayer extends Component { 8 | static propTypes = { 9 | item: PropTypes.object, 10 | currentOffset: PropTypes.shape({ 11 | x: PropTypes.number.isRequired, 12 | y: PropTypes.number.isRequired 13 | }), 14 | isDragging: PropTypes.bool.isRequired, 15 | width: PropTypes.number, 16 | pieces: PropTypes.object, 17 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 18 | wasPieceTouched: PropTypes.bool, 19 | sourceSquare: PropTypes.string 20 | }; 21 | 22 | render() { 23 | const { 24 | isDragging, 25 | width, 26 | item, 27 | id, 28 | currentOffset, 29 | wasPieceTouched, 30 | pieces, 31 | sourceSquare 32 | } = this.props; 33 | 34 | return isDragging && item.board === id ? ( 35 |
36 |
37 | {renderChessPiece({ 38 | width, 39 | pieces, 40 | piece: item.piece, 41 | isDragging, 42 | customDragLayerStyles: { opacity: 1 }, 43 | sourceSquare 44 | })} 45 |
46 |
47 | ) : null; 48 | } 49 | } 50 | 51 | function collect(monitor) { 52 | return { 53 | item: monitor.getItem(), 54 | currentOffset: monitor.getSourceClientOffset(), 55 | isDragging: monitor.isDragging() 56 | }; 57 | } 58 | 59 | export default DragLayer(collect)(CustomDragLayer); 60 | 61 | const layerStyles = { 62 | position: 'fixed', 63 | pointerEvents: 'none', 64 | zIndex: 10, 65 | left: 0, 66 | top: 0 67 | }; 68 | 69 | const getItemStyle = (currentOffset, wasPieceTouched) => { 70 | if (!currentOffset) return { display: 'none' }; 71 | 72 | let { x, y } = currentOffset; 73 | const transform = wasPieceTouched 74 | ? `translate(${x}px, ${y + -25}px) scale(2)` 75 | : `translate(${x}px, ${y}px)`; 76 | 77 | return { transform }; 78 | }; 79 | -------------------------------------------------------------------------------- /src/Chessboard/svg/whiteKing.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default { 4 | whiteKing: ( 5 | 21 | 22 | 29 | 30 | 31 | ) 32 | }; 33 | -------------------------------------------------------------------------------- /src/Chessboard/SparePieces.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import Piece from './Piece'; 5 | import Chessboard from './index'; 6 | 7 | function SparePiecesTop() { 8 | return ; 9 | } 10 | 11 | function SparePiecesBottom() { 12 | return ; 13 | } 14 | 15 | class SparePieces extends Component { 16 | static propTypes = { top: PropTypes.bool }; 17 | 18 | static Top = SparePiecesTop; 19 | static Bottom = SparePiecesBottom; 20 | 21 | getOrientation = orientation => { 22 | const { top } = this.props; 23 | if (top) { 24 | return orientation === 'black' ? 'white' : 'black'; 25 | } 26 | return orientation === 'black' ? 'black' : 'white'; 27 | }; 28 | 29 | render() { 30 | return ( 31 | 32 | {context => { 33 | const spares = 34 | this.getOrientation(context.orientation) === 'black' 35 | ? ['bK', 'bQ', 'bR', 'bB', 'bN', 'bP'] 36 | : ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP']; 37 | 38 | return ( 39 |
40 | {spares.map(p => ( 41 |
42 | 61 |
62 | ))} 63 |
64 | ); 65 | }} 66 |
67 | ); 68 | } 69 | } 70 | 71 | export default SparePieces; 72 | 73 | const spareStyles = width => ({ 74 | display: 'flex', 75 | justifyContent: 'center', 76 | width 77 | }); 78 | -------------------------------------------------------------------------------- /src/Chessboard/__tests__/helpers.test.js: -------------------------------------------------------------------------------- 1 | import isEqual from 'lodash.isequal'; 2 | import diff from 'deep-diff'; 3 | 4 | import { fenToObj, validPositionObject, validFen } from '../helpers'; 5 | 6 | test('converts FEN string to position object', () => { 7 | const fen = '8/8/4k3/4P3/4K3/8/8/8 w - -'; 8 | expect(fenToObj(fen)).toEqual({ e4: 'wK', e6: 'bK', e5: 'wP' }); 9 | }); 10 | 11 | test('checks if is valid FEN', () => { 12 | const fen = '8/8/4k3/4P3/4K3/8/8/8 w - -'; 13 | const invalidFen = '8/8/7/4k3/4P3/4K3/8/8/8 w - -'; 14 | const invalidFen2 = '-5/8/4k3/4P3/4K3/8/8/8 w - -'; 15 | 16 | expect(validFen(fen)).toBe(true); 17 | expect(validFen(invalidFen)).toBe(false); 18 | expect(validFen(invalidFen2)).toBe(false); 19 | }); 20 | 21 | test('checks if is valid position object', () => { 22 | expect(validPositionObject({ e4: 'wK', e6: 'bK', e5: 'wP' })).toBe(true); 23 | expect(validPositionObject({ e4: 'wK', e6: 'bK', e5: 'wP', f9: 'wP' })).toBe( 24 | false 25 | ); 26 | expect(validPositionObject({ e4: 'wK', e6: 'bK', e5: 'wP', e1: 'bM' })).toBe( 27 | false 28 | ); 29 | }); 30 | 31 | test('check lodash isEqual', () => { 32 | const pos1 = { e4: 'wK', e6: 'bK', e5: 'wP' }; 33 | const pos2 = { e4: 'wK', e6: 'bK', e5: 'wP' }; 34 | const pos3 = { e4: 'wK', e6: 'bK', e5: 'wB' }; 35 | 36 | expect(isEqual(pos1, pos2)).toBe(true); 37 | expect(isEqual(pos1, pos3)).toBe(false); 38 | }); 39 | 40 | test('deep diff', () => { 41 | const constructPositionAttributes = (currentPosition, position) => { 42 | const difference = diff(currentPosition, position); 43 | const squaresAffected = difference.length; 44 | const sourceSquare = 45 | difference && difference[1] && difference && difference[1].kind === 'D' 46 | ? difference[1].path && difference[1].path[0] 47 | : difference[0].path && difference[0].path[0]; 48 | const targetSquare = 49 | difference && difference[1] && difference && difference[1].kind === 'D' 50 | ? difference[0] && difference[0].path[0] 51 | : difference[1] && difference[1].path[0]; 52 | const sourcePiece = 53 | difference && difference[1] && difference && difference[1].kind === 'D' 54 | ? difference[1] && difference[1].lhs 55 | : difference[1] && difference[1].rhs; 56 | return { sourceSquare, targetSquare, sourcePiece, squaresAffected }; 57 | }; 58 | 59 | const pos1 = { e4: 'wK', e6: 'bK', e5: 'wP' }; 60 | const pos2 = { e4: 'wK', e7: 'bK', e5: 'wP' }; 61 | 62 | expect(constructPositionAttributes(pos1, pos2).sourceSquare).toBe('e6'); 63 | expect(constructPositionAttributes(pos1, pos2).targetSquare).toBe('e7'); 64 | expect(constructPositionAttributes(pos1, pos2).sourcePiece).toBe('bK'); 65 | expect(constructPositionAttributes(pos1, pos2).squaresAffected).toBe(2); 66 | }); 67 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for being willing to contribute! 4 | 5 | **Working on your first Pull Request?** You can learn how from this _free_ 6 | series [How to Contribute to an Open Source Project on GitHub][egghead] 7 | 8 | ## Project setup 9 | 10 | 1. Fork and clone the repo 11 | 2. Create a branch for your PR 12 | 3. Run `npm run start` to start the development server 13 | 4. Use `./src/integrations/Demo.js` to test your changes 14 | 5. Follow the guidelines below for pushing changes 15 | 16 | > Tip: Keep your `master` branch pointing at the original repository and make 17 | > pull requests from branches on your fork. To do this, run: 18 | > 19 | > ``` 20 | > git remote add upstream https://github.com/willb335/chessboardjsx.git 21 | > git fetch upstream 22 | > git branch --set-upstream-to=upstream/master master 23 | > ``` 24 | > 25 | > This will add the original repository as a "remote" called "upstream," Then 26 | > fetch the git information from that remote, then set your local `master` 27 | > branch to use the upstream master branch whenever you run `git pull`. Then you 28 | > can make all of your pull request branches based on this `master` branch. 29 | > Whenever you want to update your version of `master`, do a regular `git pull`. 30 | 31 | ## Committing and Pushing changes 32 | 33 | Please make sure to run `npm run commit` before pushing changes. The project uses semantic versioning for documentation and the commit script walks you through the commit message based on the [Angular Git Commit Guidelines][angular] format. You can commit normally by bypassing the githooks using the --no-verify flag. 34 | 35 | ## Add yourself as a contributor 36 | 37 | This project follows the [all contributors][all-contributors] specification. To 38 | add yourself to the table of contributors on the `README.md`, please use the 39 | automated script as part of your PR: 40 | 41 | ```console 42 | npm run contributors:add 43 | ``` 44 | 45 | Follow the prompt and commit `.all-contributorsrc` and `README.md` in the PR. If 46 | you've already added yourself to the list and are making a new type of 47 | contribution, you can run it again and select the added contribution type. 48 | 49 | ## Help needed 50 | 51 | Please checkout the [the open issues][issues] 52 | 53 | Also, please watch the repo and respond to questions/bug reports/feature 54 | requests! Thanks! 55 | 56 | The information above is pulled from the popular [downshift][downshift] library. 57 | 58 | [egghead]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github 59 | [all-contributors]: https://github.com/kentcdodds/all-contributors 60 | [issues]: https://github.com/willb335/chessboardjsx/issues 61 | [downshift]: https://github.com/paypal/downshift 62 | [angular]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit 63 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "chessboardjsx", 3 | "projectOwner": "willb335", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": false, 11 | "contributors": [ 12 | { 13 | "login": "willb335", 14 | "name": "Will", 15 | "avatar_url": "https://avatars2.githubusercontent.com/u/10157307?v=4", 16 | "profile": "https://github.com/willb335", 17 | "contributions": [ 18 | "code", 19 | "doc", 20 | "example", 21 | "test" 22 | ] 23 | }, 24 | { 25 | "login": "a-bash", 26 | "name": "Andrew Bashelor", 27 | "avatar_url": "https://avatars3.githubusercontent.com/u/146082?v=4", 28 | "profile": "https://github.com/a-bash", 29 | "contributions": [ 30 | "doc" 31 | ] 32 | }, 33 | { 34 | "login": "yougotgotyo", 35 | "name": "yougotgotyo", 36 | "avatar_url": "https://avatars3.githubusercontent.com/u/25490975?v=4", 37 | "profile": "https://chadburg.com/", 38 | "contributions": [ 39 | "ideas" 40 | ] 41 | }, 42 | { 43 | "login": "csharptest", 44 | "name": "Roger Knapp", 45 | "avatar_url": "https://avatars0.githubusercontent.com/u/385366?v=4", 46 | "profile": "http://csharptest.net", 47 | "contributions": [ 48 | "ideas" 49 | ] 50 | }, 51 | { 52 | "login": "slig", 53 | "name": "Tiago Serafim", 54 | "avatar_url": "https://avatars1.githubusercontent.com/u/37779?v=4", 55 | "profile": "https://github.com/slig", 56 | "contributions": [ 57 | "code", 58 | "doc" 59 | ] 60 | }, 61 | { 62 | "login": "furrykef", 63 | "name": "Kef Schecter", 64 | "avatar_url": "https://avatars1.githubusercontent.com/u/536006?v=4", 65 | "profile": "http://www.furrykef.com/", 66 | "contributions": [ 67 | "bug" 68 | ] 69 | }, 70 | { 71 | "login": "nilsga", 72 | "name": "Nils-Helge Garli Hegvik", 73 | "avatar_url": "https://avatars0.githubusercontent.com/u/42919?v=4", 74 | "profile": "http://www.lånekalkulatoren.no", 75 | "contributions": [ 76 | "code" 77 | ] 78 | }, 79 | { 80 | "login": "levidurfee", 81 | "name": "Levi Durfee", 82 | "avatar_url": "https://avatars1.githubusercontent.com/u/10798199?v=4", 83 | "profile": "https://levi.lol/", 84 | "contributions": [ 85 | "code", 86 | "doc" 87 | ] 88 | }, 89 | { 90 | "login": "altruisticsoftware", 91 | "name": "Chris", 92 | "avatar_url": "https://avatars3.githubusercontent.com/u/12105346?v=4", 93 | "profile": "https://github.com/altruisticsoftware", 94 | "contributions": [ 95 | "code" 96 | ] 97 | }, 98 | { 99 | "login": "hwkerr", 100 | "name": "Harrison Kerr", 101 | "avatar_url": "https://avatars.githubusercontent.com/u/43761176?v=4", 102 | "profile": "https://hwkerr.github.io/", 103 | "contributions": [ 104 | "code" 105 | ] 106 | } 107 | ] 108 | } 109 | -------------------------------------------------------------------------------- /src/integrations/PlayRandomMoveEngine.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; // eslint-disable-line no-unused-vars 2 | import PropTypes from 'prop-types'; 3 | import Chess from 'chess.js'; 4 | 5 | import Chessboard from '../Chessboard'; 6 | 7 | class HumanVsRandom extends Component { 8 | static propTypes = { children: PropTypes.func }; 9 | 10 | state = { fen: 'start', squareStyles: {}, pieceSquare: '' }; 11 | 12 | componentDidMount() { 13 | this.game = new Chess(); 14 | } 15 | 16 | makeRandomMove = () => { 17 | let possibleMoves = this.game.moves(); 18 | 19 | // exit if the game is over 20 | if ( 21 | this.game.game_over() === true || 22 | this.game.in_draw() === true || 23 | possibleMoves.length === 0 24 | ) 25 | return; 26 | 27 | let randomIndex = Math.floor(Math.random() * possibleMoves.length); 28 | this.game.move(possibleMoves[randomIndex]); 29 | this.setState({ 30 | fen: this.game.fen(), 31 | squareStyles: { 32 | [this.game.history({ verbose: true })[this.game.history().length - 1] 33 | .to]: { 34 | backgroundColor: 'DarkTurquoise' 35 | } 36 | } 37 | }); 38 | }; 39 | 40 | onDrop = ({ sourceSquare, targetSquare }) => { 41 | // see if the move is legal 42 | var move = this.game.move({ 43 | from: sourceSquare, 44 | to: targetSquare, 45 | promotion: 'q' // always promote to a queen for example simplicity 46 | }); 47 | 48 | // illegal move 49 | if (move === null) return; 50 | 51 | this.setState({ fen: this.game.fen() }); 52 | 53 | window.setTimeout(this.makeRandomMove, 1000); 54 | }; 55 | 56 | onSquareClick = square => { 57 | this.setState({ 58 | squareStyles: { [square]: { backgroundColor: 'DarkTurquoise' } }, 59 | pieceSquare: square 60 | }); 61 | 62 | let move = this.game.move({ 63 | from: this.state.pieceSquare, 64 | to: square, 65 | promotion: 'q' // always promote to a queen for example simplicity 66 | }); 67 | 68 | // illegal move 69 | if (move === null) return; 70 | 71 | this.setState({ fen: this.game.fen() }); 72 | window.setTimeout(this.makeRandomMove, 1000); 73 | }; 74 | 75 | render() { 76 | const { fen, squareStyles } = this.state; 77 | return this.props.children({ 78 | position: fen, 79 | onDrop: this.onDrop, 80 | onSquareClick: this.onSquareClick, 81 | squareStyles 82 | }); 83 | } 84 | } 85 | 86 | export default function PlayRandomMoveEngine() { 87 | return ( 88 |
89 | 90 | {({ position, onDrop, onSquareClick, squareStyles }) => ( 91 | (screenWidth < 500 ? 350 : 480)} 93 | id="humanVsRandom" 94 | position={position} 95 | onDrop={onDrop} 96 | boardStyle={{ 97 | borderRadius: '5px', 98 | boxShadow: `0 5px 15px rgba(0, 0, 0, 0.5)` 99 | }} 100 | onSquareClick={onSquareClick} 101 | squareStyles={squareStyles} 102 | /> 103 | )} 104 | 105 |
106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at wbashelor@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chessboardjsx", 3 | "version": "2.4.7", 4 | "description": "Chessboard.jsx is a chessboard for React. Inspired by chessboard.js", 5 | "main": "dist/chessboard.min.js", 6 | "types": "chessboard.d.ts", 7 | "scripts": { 8 | "contributors:add": "all-contributors add", 9 | "contributors:generate": "all-contributors generate", 10 | "commit": "git-cz", 11 | "test": "jest --coverage", 12 | "build": "webpack --config webpack.prod.js", 13 | "lint": "eslint src", 14 | "validate": "npm-run-all --parallel test lint build", 15 | "travis-deploy-once": "travis-deploy-once", 16 | "semantic-release": "npx semantic-release", 17 | "commitmsg": "commitlint -e $GIT_PARAMS", 18 | "start": "webpack-dev-server --open --config webpack.dev.js", 19 | "report-coverage": "cat ./coverage/lcov.info | codecov" 20 | }, 21 | "keywords": [ 22 | "react", 23 | "chess", 24 | "chessboard" 25 | ], 26 | "files": [ 27 | "chessboard.d.ts", 28 | "dist" 29 | ], 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/willb335/chessboardjsx.git" 33 | }, 34 | "author": "William .J Bashelor ", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/willb335/chessboardjsx/issues" 38 | }, 39 | "homepage": "https://github.com/willb335/chessboardjsx#readme", 40 | "dependencies": { 41 | "deep-diff": "1.0.1", 42 | "lodash.isequal": "4.5.0", 43 | "react-dnd": "2.6.0", 44 | "react-dnd-html5-backend": "4.0.5", 45 | "react-dnd-multi-backend": "3.1.2" 46 | }, 47 | "devDependencies": { 48 | "@babel/core": "7.0.0-beta.51", 49 | "@babel/plugin-proposal-object-rest-spread": "7.0.0-beta.51", 50 | "@babel/preset-env": "7.0.0-beta.51", 51 | "@babel/preset-react": "7.0.0-beta.51", 52 | "@babel/register": "7.0.0-beta.51", 53 | "@commitlint/cli": "6.2.0", 54 | "@commitlint/config-conventional": "6.1.3", 55 | "@types/chess.js": "^0.10.0", 56 | "all-contributors-cli": "5.2.0", 57 | "babel-cli": "6.26.0", 58 | "babel-core": "7.0.0-bridge.0", 59 | "babel-eslint": "8.2.3", 60 | "babel-jest": "23.0.1", 61 | "babel-loader": "8.0.0-beta.3", 62 | "babel-plugin-dynamic-import-node": "1.2.0", 63 | "babel-plugin-lodash": "3.3.4", 64 | "babel-plugin-transform-class-properties": "6.24.1", 65 | "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", 66 | "babel-polyfill": "6.26.0", 67 | "chess.js": "0.10.2", 68 | "clean-webpack-plugin": "0.1.19", 69 | "codecov": "3.0.2", 70 | "commitizen": "2.10.1", 71 | "css-loader": "0.28.11", 72 | "cz-conventional-changelog": "2.1.0", 73 | "eslint": "4.19.1", 74 | "eslint-plugin-prettier": "^2.6.0", 75 | "eslint-plugin-react": "^7.8.2", 76 | "file-loader": "1.1.11", 77 | "ghooks": "2.0.4", 78 | "html-webpack-plugin": "3.2.0", 79 | "identity-obj-proxy": "3.0.0", 80 | "jest": "22.4.3", 81 | "jest-dom": "1.3.0", 82 | "kcd-scripts": "0.44.0", 83 | "lodash-webpack-plugin": "0.11.5", 84 | "mini-css-extract-plugin": "0.4.0", 85 | "npm-run-all": "4.1.3", 86 | "prettier": "1.12.1", 87 | "prop-types": "15.6.1", 88 | "react": "^16.4.0", 89 | "react-docgen": "2.20.1", 90 | "react-dom": "^16.4.0", 91 | "react-testing-library": "3.1.6", 92 | "roughjs": "2.2.3", 93 | "semantic-release": "^17.2.3", 94 | "style-loader": "0.21.0", 95 | "travis-deploy-once": "^5.0.0", 96 | "webpack": "4.12.0", 97 | "webpack-bundle-analyzer": "2.13.1", 98 | "webpack-cli": "3.0.3", 99 | "webpack-dev-server": "3.1.4", 100 | "webpack-merge": "4.1.2" 101 | }, 102 | "peerDependencies": { 103 | "react": ">= 16.3.2", 104 | "react-dom": ">= 16.3.2" 105 | }, 106 | "config": { 107 | "commitizen": { 108 | "path": "cz-conventional-changelog" 109 | }, 110 | "ghooks": { 111 | "pre-commit": "npm run validate", 112 | "commit-msg": "npm run commitmsg" 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Chessboard/Notation.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, Fragment } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const getRow = (orientation, row) => 5 | orientation === 'white' ? row + 1 : row - 1; 6 | 7 | const getColumn = (orientation, alpha, col) => 8 | orientation === 'black' ? alpha[7 - col] : alpha[col]; 9 | 10 | class Notation extends PureComponent { 11 | static propTypes = { 12 | row: PropTypes.number, 13 | col: PropTypes.number, 14 | alpha: PropTypes.array, 15 | orientation: PropTypes.string, 16 | width: PropTypes.number, 17 | lightSquareStyle: PropTypes.object, 18 | darkSquareStyle: PropTypes.object 19 | }; 20 | 21 | render() { 22 | const { 23 | row, 24 | col, 25 | orientation, 26 | lightSquareStyle, 27 | darkSquareStyle 28 | } = this.props; 29 | 30 | const whiteColor = lightSquareStyle.backgroundColor; 31 | const blackColor = darkSquareStyle.backgroundColor; 32 | 33 | const isRow = col === 0; 34 | const isColumn = 35 | (orientation === 'white' && row === 0) || 36 | (orientation === 'black' && row === 9); 37 | const isBottomLeftSquare = isRow && isColumn; 38 | 39 | if (isBottomLeftSquare) { 40 | return renderBottomLeft(this.props, { whiteColor }); 41 | } 42 | 43 | if (isColumn) { 44 | return renderLetters(this.props, { 45 | whiteColor, 46 | blackColor 47 | }); 48 | } 49 | 50 | if (isRow) { 51 | return renderNumbers(this.props, { 52 | whiteColor, 53 | blackColor, 54 | isRow, 55 | isBottomLeftSquare 56 | }); 57 | } 58 | 59 | return null; 60 | } 61 | } 62 | 63 | export default Notation; 64 | 65 | /* eslint react/prop-types: 0 */ 66 | function renderBottomLeft( 67 | { orientation, row, width, alpha, col }, 68 | { whiteColor } 69 | ) { 70 | return ( 71 | 72 |
80 | {getRow(orientation, row)} 81 |
82 |
90 | {getColumn(orientation, alpha, col)} 91 |
92 |
93 | ); 94 | } 95 | 96 | function renderLetters( 97 | { orientation, width, alpha, col }, 98 | { whiteColor, blackColor } 99 | ) { 100 | return ( 101 |
109 | {getColumn(orientation, alpha, col)} 110 |
111 | ); 112 | } 113 | 114 | function renderNumbers( 115 | { orientation, row, width }, 116 | { whiteColor, blackColor, isRow, isBottomLeftSquare } 117 | ) { 118 | return ( 119 |
134 | {getRow(orientation, row)} 135 |
136 | ); 137 | } 138 | 139 | const columnStyle = ({ col, width, blackColor, whiteColor }) => ({ 140 | fontSize: width / 48, 141 | color: col % 2 !== 0 ? blackColor : whiteColor 142 | }); 143 | 144 | const rowStyle = ({ 145 | row, 146 | width, 147 | blackColor, 148 | whiteColor, 149 | orientation, 150 | isBottomLeftSquare, 151 | isRow 152 | }) => { 153 | return { 154 | fontSize: width / 48, 155 | color: 156 | orientation === 'black' 157 | ? isRow && !isBottomLeftSquare && row % 2 === 0 158 | ? blackColor 159 | : whiteColor 160 | : isRow && !isBottomLeftSquare && row % 2 !== 0 161 | ? blackColor 162 | : whiteColor 163 | }; 164 | }; 165 | 166 | const alphaStyle = width => ({ 167 | alignSelf: 'flex-end', 168 | paddingLeft: width / 8 - width / 48 169 | }); 170 | 171 | const numericStyle = width => ({ 172 | alignSelf: 'flex-start', 173 | paddingRight: width / 8 - width / 48 174 | }); 175 | 176 | const notationStyle = { 177 | fontFamily: 'Helvetica Neue', 178 | zIndex: 3, 179 | position: 'absolute' 180 | }; 181 | -------------------------------------------------------------------------------- /src/Chessboard/Square.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { DropTarget } from 'react-dnd'; 4 | 5 | import { ItemTypes } from './helpers'; 6 | 7 | class Square extends Component { 8 | static propTypes = { 9 | connectDropTarget: PropTypes.func, 10 | width: PropTypes.number, 11 | squareColor: PropTypes.oneOf(['white', 'black']), 12 | children: PropTypes.oneOfType([PropTypes.array, PropTypes.node]), 13 | isOver: PropTypes.bool, 14 | square: PropTypes.string, 15 | setSquareCoordinates: PropTypes.func, 16 | lightSquareStyle: PropTypes.object, 17 | darkSquareStyle: PropTypes.object, 18 | roughSquare: PropTypes.func, 19 | onMouseOverSquare: PropTypes.func, 20 | onMouseOutSquare: PropTypes.func, 21 | dropSquareStyle: PropTypes.object, 22 | screenWidth: PropTypes.number, 23 | screenHeight: PropTypes.number, 24 | squareStyles: PropTypes.object, 25 | onDragOverSquare: PropTypes.func, 26 | onSquareClick: PropTypes.func, 27 | wasSquareClicked: PropTypes.func, 28 | onSquareRightClick: PropTypes.func 29 | }; 30 | 31 | componentDidMount() { 32 | const { square, setSquareCoordinates, width, roughSquare } = this.props; 33 | roughSquare({ squareElement: this.roughSquareSvg, squareWidth: width / 8 }); 34 | 35 | const { x, y } = this[square].getBoundingClientRect(); 36 | setSquareCoordinates(x, y, square); 37 | } 38 | 39 | componentDidUpdate(prevProps) { 40 | const { 41 | screenWidth, 42 | screenHeight, 43 | square, 44 | setSquareCoordinates 45 | } = this.props; 46 | 47 | const didScreenSizeChange = 48 | prevProps.screenWidth !== screenWidth || 49 | prevProps.screenHeight !== screenHeight; 50 | 51 | if (didScreenSizeChange) { 52 | const { x, y } = this[square].getBoundingClientRect(); 53 | setSquareCoordinates(x, y, square); 54 | } 55 | } 56 | 57 | onClick = () => { 58 | this.props.wasSquareClicked(true); 59 | this.props.onSquareClick(this.props.square); 60 | }; 61 | 62 | render() { 63 | const { 64 | connectDropTarget, 65 | width, 66 | squareColor, 67 | children, 68 | square, 69 | roughSquare, 70 | onMouseOverSquare, 71 | onMouseOutSquare, 72 | squareStyles, 73 | onDragOverSquare, 74 | onSquareRightClick 75 | } = this.props; 76 | 77 | return connectDropTarget( 78 |
(this[square] = ref)} 82 | style={defaultSquareStyle(this.props)} 83 | onMouseOver={() => onMouseOverSquare(square)} 84 | onMouseOut={() => onMouseOutSquare(square)} 85 | onDragEnter={() => onDragOverSquare(square)} 86 | onClick={() => this.onClick()} 87 | onContextMenu={e => { 88 | e.preventDefault(); 89 | onSquareRightClick(square); 90 | }} 91 | > 92 |
99 | {roughSquare.length ? ( 100 |
101 | {children} 102 | (this.roughSquareSvg = ref)} 109 | /> 110 |
111 | ) : ( 112 | children 113 | )} 114 |
115 |
116 | ); 117 | } 118 | } 119 | 120 | const squareTarget = { 121 | drop(props, monitor) { 122 | return { 123 | target: props.square, 124 | board: props.id, 125 | piece: monitor.getItem().piece, 126 | source: monitor.getItem().source 127 | }; 128 | } 129 | }; 130 | 131 | function collect(connect, monitor) { 132 | return { 133 | connectDropTarget: connect.dropTarget(), 134 | isOver: monitor.isOver() 135 | }; 136 | } 137 | 138 | export default DropTarget(ItemTypes.PIECE, squareTarget, collect)(Square); 139 | 140 | const defaultSquareStyle = props => { 141 | const { 142 | width, 143 | squareColor, 144 | isOver, 145 | darkSquareStyle, 146 | lightSquareStyle, 147 | dropSquareStyle 148 | } = props; 149 | 150 | return { 151 | ...{ 152 | ...size(width), 153 | ...center, 154 | ...(squareColor === 'black' ? darkSquareStyle : lightSquareStyle), 155 | ...(isOver && dropSquareStyle) 156 | } 157 | }; 158 | }; 159 | 160 | const center = { 161 | display: 'flex', 162 | justifyContent: 'center' 163 | }; 164 | 165 | const size = width => ({ 166 | width: width / 8, 167 | height: width / 8 168 | }); 169 | -------------------------------------------------------------------------------- /src/Chessboard/helpers.js: -------------------------------------------------------------------------------- 1 | import diff from 'deep-diff'; 2 | 3 | export const ItemTypes = { PIECE: 'piece' }; 4 | export const COLUMNS = 'abcdefgh'.split(''); 5 | 6 | export const constructPositionAttributes = (currentPosition, position) => { 7 | const difference = diff(currentPosition, position); 8 | const squaresAffected = difference.length; 9 | const sourceSquare = 10 | difference && difference[1] && difference && difference[1].kind === 'D' 11 | ? difference[1].path && difference[1].path[0] 12 | : difference[0].path && difference[0].path[0]; 13 | const targetSquare = 14 | difference && difference[1] && difference && difference[1].kind === 'D' 15 | ? difference[0] && difference[0].path[0] 16 | : difference[1] && difference[1].path[0]; 17 | const sourcePiece = 18 | difference && difference[1] && difference && difference[1].kind === 'D' 19 | ? difference[1] && difference[1].lhs 20 | : difference[1] && difference[1].rhs; 21 | return { sourceSquare, targetSquare, sourcePiece, squaresAffected }; 22 | }; 23 | 24 | function isString(s) { 25 | return typeof s === 'string'; 26 | } 27 | 28 | export function fenToObj(fen) { 29 | if (!validFen(fen)) return false; 30 | // cut off any move, castling, etc info from the end 31 | // we're only interested in position information 32 | fen = fen.replace(/ .+$/, ''); 33 | 34 | let rows = fen.split('/'); 35 | let position = {}; 36 | 37 | let currentRow = 8; 38 | for (let i = 0; i < 8; i++) { 39 | let row = rows[i].split(''); 40 | let colIdx = 0; 41 | 42 | // loop through each character in the FEN section 43 | for (let j = 0; j < row.length; j++) { 44 | // number / empty squares 45 | if (row[j].search(/[1-8]/) !== -1) { 46 | let numEmptySquares = parseInt(row[j], 10); 47 | colIdx = colIdx + numEmptySquares; 48 | } else { 49 | // piece 50 | let square = COLUMNS[colIdx] + currentRow; 51 | position[square] = fenToPieceCode(row[j]); 52 | colIdx = colIdx + 1; 53 | } 54 | } 55 | 56 | currentRow = currentRow - 1; 57 | } 58 | 59 | return position; 60 | } 61 | 62 | function expandFenEmptySquares(fen) { 63 | return fen 64 | .replace(/8/g, '11111111') 65 | .replace(/7/g, '1111111') 66 | .replace(/6/g, '111111') 67 | .replace(/5/g, '11111') 68 | .replace(/4/g, '1111') 69 | .replace(/3/g, '111') 70 | .replace(/2/g, '11'); 71 | } 72 | 73 | export function validFen(fen) { 74 | if (!isString(fen)) return false; 75 | 76 | // cut off any move, castling, etc info from the end 77 | // we're only interested in position information 78 | fen = fen.replace(/ .+$/, ''); 79 | 80 | // expand the empty square numbers to just 1s 81 | fen = expandFenEmptySquares(fen); 82 | 83 | // FEN should be 8 sections separated by slashes 84 | let chunks = fen.split('/'); 85 | if (chunks.length !== 8) return false; 86 | 87 | // check each section 88 | for (let i = 0; i < 8; i++) { 89 | if (chunks[i].length !== 8 || chunks[i].search(/[^kqrnbpKQRNBP1]/) !== -1) { 90 | return false; 91 | } 92 | } 93 | 94 | return true; 95 | } 96 | 97 | // convert FEN piece code to bP, wK, etc 98 | function fenToPieceCode(piece) { 99 | // black piece 100 | if (piece.toLowerCase() === piece) { 101 | return 'b' + piece.toUpperCase(); 102 | } 103 | 104 | // white piece 105 | return 'w' + piece.toUpperCase(); 106 | } 107 | 108 | function validSquare(square) { 109 | return isString(square) && square.search(/^[a-h][1-8]$/) !== -1; 110 | } 111 | 112 | function validPieceCode(code) { 113 | return isString(code) && code.search(/^[bw][KQRNBP]$/) !== -1; 114 | } 115 | 116 | export function validPositionObject(pos) { 117 | if (pos === null || typeof pos !== 'object') return false; 118 | 119 | for (let i in pos) { 120 | if (!pos.hasOwnProperty(i)) continue; 121 | 122 | if (!validSquare(i) || !validPieceCode(pos[i])) { 123 | return false; 124 | } 125 | } 126 | return true; 127 | } 128 | 129 | function squeezeFenEmptySquares(fen) { 130 | return fen 131 | .replace(/11111111/g, '8') 132 | .replace(/1111111/g, '7') 133 | .replace(/111111/g, '6') 134 | .replace(/11111/g, '5') 135 | .replace(/1111/g, '4') 136 | .replace(/111/g, '3') 137 | .replace(/11/g, '2'); 138 | } 139 | 140 | // convert bP, wK, etc code to FEN structure 141 | function pieceCodeToFen(piece) { 142 | let pieceCodeLetters = piece.split(''); 143 | 144 | // white piece 145 | if (pieceCodeLetters[0] === 'w') { 146 | return pieceCodeLetters[1].toUpperCase(); 147 | } 148 | 149 | // black piece 150 | return pieceCodeLetters[1].toLowerCase(); 151 | } 152 | 153 | // position object to FEN string 154 | // returns false if the obj is not a valid position object 155 | export function objToFen(obj) { 156 | if (!validPositionObject(obj)) return false; 157 | 158 | let fen = ''; 159 | 160 | let currentRow = 8; 161 | for (let i = 0; i < 8; i++) { 162 | for (let j = 0; j < 8; j++) { 163 | let square = COLUMNS[j] + currentRow; 164 | 165 | // piece exists 166 | if (obj.hasOwnProperty(square)) { 167 | fen = fen + pieceCodeToFen(obj[square]); 168 | } else { 169 | // empty space 170 | fen = fen + '1'; 171 | } 172 | } 173 | 174 | if (i !== 7) { 175 | fen = fen + '/'; 176 | } 177 | 178 | currentRow = currentRow - 1; 179 | } 180 | 181 | // squeeze the empty numbers together 182 | fen = squeezeFenEmptySquares(fen); 183 | 184 | return fen; 185 | } 186 | -------------------------------------------------------------------------------- /src/Chessboard/Board.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import Piece from './Piece'; 4 | import Square from './Square'; 5 | import Notation from './Notation'; 6 | import Chessboard from './index'; 7 | import PhantomPiece from './PhantomPiece'; 8 | import Row from './Row'; 9 | 10 | class Board extends Component { 11 | setSquareCoordinates = (x, y, square) => 12 | this.setState({ [square]: { x, y } }); 13 | 14 | getSquareCoordinates = (sourceSquare, targetSquare) => ({ 15 | sourceSquare: this.state[sourceSquare], 16 | targetSquare: this.state[targetSquare] 17 | }); 18 | 19 | showPhantom = ({ square, targetSquare, phantomPiece }) => { 20 | const isActivePiece = (square, targetSquare) => 21 | targetSquare && targetSquare === square; 22 | 23 | return ( 24 | phantomPiece && 25 | phantomPiece[targetSquare] && 26 | isActivePiece(square, targetSquare) 27 | ); 28 | }; 29 | 30 | hasPiece = (currentPosition, square) => 31 | currentPosition && 32 | Object.keys(currentPosition) && 33 | Object.keys(currentPosition).includes(square); 34 | 35 | render() { 36 | return ( 37 | 38 | {context => { 39 | return ( 40 | 46 | {({ square, squareColor, col, row, alpha }) => { 47 | return ( 48 | 69 | {this.hasPiece(context.currentPosition, square) ? ( 70 | 93 | ) : null} 94 | 95 | {this.showPhantom({ 96 | square, 97 | targetSquare: context.targetSquare, 98 | phantomPiece: context.phantomPiece 99 | }) && ( 100 | 109 | )} 110 | 111 | {context.showNotation && ( 112 | 121 | )} 122 | 123 | ); 124 | }} 125 | 126 | ); 127 | }} 128 | 129 | ); 130 | } 131 | } 132 | 133 | export default Board; 134 | -------------------------------------------------------------------------------- /src/integrations/WithMoveValidation.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; // eslint-disable-line no-unused-vars 2 | import PropTypes from 'prop-types'; 3 | import Chess from 'chess.js'; 4 | 5 | import Chessboard from '../Chessboard'; 6 | 7 | class HumanVsHuman extends Component { 8 | static propTypes = { children: PropTypes.func }; 9 | 10 | state = { 11 | fen: 'start', 12 | // square styles for active drop squares 13 | dropSquareStyle: {}, 14 | // custom square styles 15 | squareStyles: {}, 16 | // square with the currently clicked piece 17 | pieceSquare: '', 18 | // currently clicked square 19 | square: '', 20 | history: [] 21 | }; 22 | 23 | componentDidMount() { 24 | this.game = new Chess(); 25 | } 26 | 27 | // keep clicked square style and remove hint squares 28 | removeHighlightSquare = () => { 29 | this.setState(({ pieceSquare, history }) => ({ 30 | squareStyles: squareStyling({ pieceSquare, history }) 31 | })); 32 | }; 33 | 34 | // show possible moves 35 | highlightSquare = (sourceSquare, squaresToHighlight) => { 36 | const highlightStyles = [sourceSquare, ...squaresToHighlight].reduce( 37 | (a, c) => { 38 | return { 39 | ...a, 40 | ...{ 41 | [c]: { 42 | background: 43 | 'radial-gradient(circle, #fffc00 36%, transparent 40%)', 44 | borderRadius: '50%' 45 | } 46 | }, 47 | ...squareStyling({ 48 | history: this.state.history, 49 | pieceSquare: this.state.pieceSquare 50 | }) 51 | }; 52 | }, 53 | {} 54 | ); 55 | 56 | this.setState(({ squareStyles }) => ({ 57 | squareStyles: { ...squareStyles, ...highlightStyles } 58 | })); 59 | }; 60 | 61 | onDrop = ({ sourceSquare, targetSquare }) => { 62 | // see if the move is legal 63 | let move = this.game.move({ 64 | from: sourceSquare, 65 | to: targetSquare, 66 | promotion: 'q' // always promote to a queen for example simplicity 67 | }); 68 | 69 | // illegal move 70 | if (move === null) return; 71 | this.setState(({ history, pieceSquare }) => ({ 72 | fen: this.game.fen(), 73 | history: this.game.history({ verbose: true }), 74 | squareStyles: squareStyling({ pieceSquare, history }) 75 | })); 76 | }; 77 | 78 | onMouseOverSquare = square => { 79 | // get list of possible moves for this square 80 | let moves = this.game.moves({ 81 | square: square, 82 | verbose: true 83 | }); 84 | 85 | // exit if there are no moves available for this square 86 | if (moves.length === 0) return; 87 | 88 | let squaresToHighlight = []; 89 | for (var i = 0; i < moves.length; i++) { 90 | squaresToHighlight.push(moves[i].to); 91 | } 92 | 93 | this.highlightSquare(square, squaresToHighlight); 94 | }; 95 | 96 | onMouseOutSquare = square => this.removeHighlightSquare(square); 97 | 98 | // central squares get diff dropSquareStyles 99 | onDragOverSquare = square => { 100 | this.setState({ 101 | dropSquareStyle: 102 | square === 'e4' || square === 'd4' || square === 'e5' || square === 'd5' 103 | ? { backgroundColor: 'cornFlowerBlue' } 104 | : { boxShadow: 'inset 0 0 1px 4px rgb(255, 255, 0)' } 105 | }); 106 | }; 107 | 108 | onSquareClick = square => { 109 | this.setState(({ history }) => ({ 110 | squareStyles: squareStyling({ pieceSquare: square, history }), 111 | pieceSquare: square 112 | })); 113 | 114 | let move = this.game.move({ 115 | from: this.state.pieceSquare, 116 | to: square, 117 | promotion: 'q' // always promote to a queen for example simplicity 118 | }); 119 | 120 | // illegal move 121 | if (move === null) return; 122 | 123 | this.setState({ 124 | fen: this.game.fen(), 125 | history: this.game.history({ verbose: true }), 126 | pieceSquare: '' 127 | }); 128 | }; 129 | 130 | onSquareRightClick = square => 131 | this.setState({ 132 | squareStyles: { [square]: { backgroundColor: 'deepPink' } } 133 | }); 134 | 135 | render() { 136 | const { fen, dropSquareStyle, squareStyles } = this.state; 137 | 138 | return this.props.children({ 139 | squareStyles, 140 | position: fen, 141 | onMouseOverSquare: this.onMouseOverSquare, 142 | onMouseOutSquare: this.onMouseOutSquare, 143 | onDrop: this.onDrop, 144 | dropSquareStyle, 145 | onDragOverSquare: this.onDragOverSquare, 146 | onSquareClick: this.onSquareClick, 147 | onSquareRightClick: this.onSquareRightClick 148 | }); 149 | } 150 | } 151 | 152 | export default function WithMoveValidation() { 153 | return ( 154 |
155 | 156 | {({ 157 | position, 158 | onDrop, 159 | onMouseOverSquare, 160 | onMouseOutSquare, 161 | squareStyles, 162 | dropSquareStyle, 163 | onDragOverSquare, 164 | onSquareClick, 165 | onSquareRightClick 166 | }) => ( 167 | (screenWidth < 500 ? 350 : 480)} 170 | position={position} 171 | onDrop={onDrop} 172 | onMouseOverSquare={onMouseOverSquare} 173 | onMouseOutSquare={onMouseOutSquare} 174 | boardStyle={{ 175 | borderRadius: '5px', 176 | boxShadow: `0 5px 15px rgba(0, 0, 0, 0.5)` 177 | }} 178 | squareStyles={squareStyles} 179 | dropSquareStyle={dropSquareStyle} 180 | onDragOverSquare={onDragOverSquare} 181 | onSquareClick={onSquareClick} 182 | onSquareRightClick={onSquareRightClick} 183 | /> 184 | )} 185 | 186 |
187 | ); 188 | } 189 | 190 | const squareStyling = ({ pieceSquare, history }) => { 191 | const sourceSquare = history.length && history[history.length - 1].from; 192 | const targetSquare = history.length && history[history.length - 1].to; 193 | 194 | return { 195 | [pieceSquare]: { backgroundColor: 'rgba(255, 255, 0, 0.4)' }, 196 | ...(history.length && { 197 | [sourceSquare]: { 198 | backgroundColor: 'rgba(255, 255, 0, 0.4)' 199 | } 200 | }), 201 | ...(history.length && { 202 | [targetSquare]: { 203 | backgroundColor: 'rgba(255, 255, 0, 0.4)' 204 | } 205 | }) 206 | }; 207 | }; 208 | -------------------------------------------------------------------------------- /src/integrations/UndoMove.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Chess from 'chess.js'; 4 | 5 | import Chessboard from '../Chessboard'; 6 | 7 | class HumanVsHuman extends Component { 8 | static propTypes = { children: PropTypes.func }; 9 | 10 | state = { 11 | fen: 'start', 12 | // square styles for active drop square 13 | dropSquareStyle: {}, 14 | // custom square styles 15 | squareStyles: {}, 16 | // square with the currently clicked piece 17 | pieceSquare: '', 18 | // currently clicked square 19 | square: '', 20 | // array of past game moves 21 | history: [], 22 | // undo previous move 23 | undo: false 24 | }; 25 | 26 | componentDidMount() { 27 | this.game = new Chess(); 28 | } 29 | 30 | // keep clicked square style and remove hint squares 31 | removeHighlightSquare = () => { 32 | this.setState(({ pieceSquare, history }) => ({ 33 | squareStyles: squareStyling({ pieceSquare, history }) 34 | })); 35 | }; 36 | 37 | // show possible moves 38 | highlightSquare = (sourceSquare, squaresToHighlight) => { 39 | const highlightStyles = [sourceSquare, ...squaresToHighlight].reduce( 40 | (a, c) => { 41 | return { 42 | ...a, 43 | ...{ 44 | [c]: { 45 | background: 46 | 'radial-gradient(circle, #fffc00 36%, transparent 40%)', 47 | borderRadius: '50%' 48 | } 49 | }, 50 | ...squareStyling({ 51 | history: this.state.history, 52 | pieceSquare: this.state.pieceSquare 53 | }) 54 | }; 55 | }, 56 | {} 57 | ); 58 | 59 | this.setState(({ squareStyles }) => ({ 60 | squareStyles: { ...squareStyles, ...highlightStyles } 61 | })); 62 | }; 63 | 64 | onDrop = ({ sourceSquare, targetSquare, piece }) => { 65 | // see if the move is legal 66 | let move = this.game.move({ 67 | from: sourceSquare, 68 | to: targetSquare, 69 | promotion: 'q' // always promote to a queen for example simplicity 70 | }); 71 | 72 | // illegal move 73 | if (move === null) return; 74 | this.setState( 75 | ({ history, pieceSquare }) => { 76 | return { 77 | fen: this.game.fen(), 78 | history: this.game.history({ verbose: true }), 79 | squareStyles: squareStyling({ pieceSquare, history }) 80 | }; 81 | }, 82 | () => 83 | setTimeout(() => { 84 | const notKnights = piece !== 'wN' && piece !== 'bN'; 85 | this.setState(({ history, pieceSquare }) => { 86 | notKnights && this.game.undo(); 87 | 88 | return { 89 | fen: this.game.fen(), 90 | undo: notKnights ? true : false, 91 | history: this.game.history({ verbose: true }), 92 | squareStyles: squareStyling({ pieceSquare, history }) 93 | }; 94 | }); 95 | }, 1000) 96 | ); 97 | }; 98 | 99 | onMouseOverSquare = square => { 100 | // get list of possible moves for this square 101 | let moves = this.game.moves({ 102 | square: square, 103 | verbose: true 104 | }); 105 | 106 | // exit if there are no moves available for this square 107 | if (moves.length === 0) return; 108 | 109 | let squaresToHighlight = []; 110 | for (var i = 0; i < moves.length; i++) { 111 | squaresToHighlight.push(moves[i].to); 112 | } 113 | 114 | this.highlightSquare(square, squaresToHighlight); 115 | }; 116 | 117 | onMouseOutSquare = square => this.removeHighlightSquare(square); 118 | 119 | // central squares get diff dropSquareStyles 120 | onDragOverSquare = square => { 121 | this.setState({ 122 | dropSquareStyle: 123 | square === 'e4' || square === 'd4' || square === 'e5' || square === 'd5' 124 | ? { backgroundColor: 'cornFlowerBlue' } 125 | : { boxShadow: 'inset 0 0 1px 4px rgb(255, 255, 0)' } 126 | }); 127 | }; 128 | 129 | onSquareClick = square => { 130 | this.setState(({ history }) => ({ 131 | squareStyles: squareStyling({ pieceSquare: square, history }), 132 | pieceSquare: square 133 | })); 134 | 135 | let move = this.game.move({ 136 | from: this.state.pieceSquare, 137 | to: square, 138 | promotion: 'q' // always promote to a queen for example simplicity 139 | }); 140 | 141 | // illegal move 142 | if (move === null) return; 143 | 144 | this.setState({ 145 | fen: this.game.fen(), 146 | history: this.game.history({ verbose: true }), 147 | pieceSquare: '' 148 | }); 149 | }; 150 | 151 | onSquareRightClick = square => 152 | this.setState({ 153 | squareStyles: { [square]: { backgroundColor: 'deepPink' } } 154 | }); 155 | 156 | render() { 157 | const { fen, dropSquareStyle, squareStyles, undo } = this.state; 158 | return this.props.children({ 159 | squareStyles, 160 | position: fen, 161 | onMouseOverSquare: this.onMouseOverSquare, 162 | onMouseOutSquare: this.onMouseOutSquare, 163 | onDrop: this.onDrop, 164 | dropSquareStyle, 165 | onDragOverSquare: this.onDragOverSquare, 166 | onSquareClick: this.onSquareClick, 167 | onSquareRightClick: this.onSquareRightClick, 168 | undo 169 | }); 170 | } 171 | } 172 | 173 | export default function WithMoveValidation() { 174 | return ( 175 |
176 | 177 | {({ 178 | position, 179 | onDrop, 180 | onMouseOverSquare, 181 | onMouseOutSquare, 182 | squareStyles, 183 | dropSquareStyle, 184 | onDragOverSquare, 185 | onSquareClick, 186 | onSquareRightClick, 187 | undo 188 | }) => ( 189 | (screenWidth < 500 ? 350 : 480)} 192 | width={320} 193 | position={position} 194 | onDrop={onDrop} 195 | onMouseOverSquare={onMouseOverSquare} 196 | onMouseOutSquare={onMouseOutSquare} 197 | boardStyle={{ 198 | borderRadius: '5px', 199 | boxShadow: `0 5px 15px rgba(0, 0, 0, 0.5)` 200 | }} 201 | squareStyles={squareStyles} 202 | dropSquareStyle={dropSquareStyle} 203 | onDragOverSquare={onDragOverSquare} 204 | onSquareClick={onSquareClick} 205 | onSquareRightClick={onSquareRightClick} 206 | undo={undo} 207 | /> 208 | )} 209 | 210 |
211 | ); 212 | } 213 | 214 | const squareStyling = ({ pieceSquare, history }) => { 215 | const sourceSquare = history.length && history[history.length - 1].from; 216 | const targetSquare = history.length && history[history.length - 1].to; 217 | 218 | return { 219 | [pieceSquare]: { backgroundColor: 'rgba(255, 255, 0, 0.4)' }, 220 | ...(history.length && { 221 | [sourceSquare]: { 222 | backgroundColor: 'rgba(255, 255, 0, 0.4)' 223 | } 224 | }), 225 | ...(history.length && { 226 | [targetSquare]: { 227 | backgroundColor: 'rgba(255, 255, 0, 0.4)' 228 | } 229 | }) 230 | }; 231 | }; 232 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UNMAINTAINED 2 | 3 | 4 | 5 |
6 | 7 | lucena position 8 | Carlsen 2016 Championship 9 | sicilian defense 10 | 11 | **A chessboard for React inspired by [chessboard.js](https://github.com/oakmac/chessboardjs)** 12 | 13 | [![Build Status][build-badge]][build] 14 | [![Code Coverage][coverage-badge]][coverage] 15 | [![PRs Welcome][prs-badge]][prs] 16 | [![version][version-badge]][package] 17 | [![MIT License][license-badge]][license] 18 | [![Commitizen friendly][commitzen-badge]][commitzen] 19 | [![semantic-release][semantic-release-badge]][semantic-release] 20 | 21 |
22 | 23 | 24 | ## Installation 25 | 26 | This module is distributed via [npm][npm] which is bundled with [node][node] and 27 | should be installed as one of your project's `dependencies`: 28 | 29 | ``` 30 | npm install --save chessboardjsx 31 | ``` 32 | 33 | The package also depends on [React](https://reactjs.org/). Make sure you have that installed as well. 34 | 35 | ## Usage 36 | 37 | * [Props](https://www.chessboardjsx.com/props): get started with Chessboard.jsx 38 | * [With move validation](https://www.chessboardjsx.com/integrations/move-validation): how to integrate [chess.js](https://github.com/jhlywa/chess.js) 39 | * [Integrating with chess engines](https://www.chessboardjsx.com/integrations/stockfish): how to integrate with Stockfish, the ~~world's strongest~~ [world's second strongest](https://www.chess.com/news/view/google-s-alphazero-destroys-stockfish-in-100-game-match) chess engine 40 | * [Custom](https://www.chessboardjsx.com/custom): customize the board and pieces 41 | 42 | ## Contributing 43 | 44 | Please take a look at CONTRIBUTING.md to find out how to contribute. 45 | 46 | ## What is Chessboard.jsx? 47 | 48 | Chessboard.jsx is a React component with a flexible "just a board" API modeled from [chessboard.js](https://github.com/oakmac/chessboardjs). It's compatible with touch as well as standard HTML5 drag and drop. 49 | 50 | ## What can Chessboard.jsx **not** do? 51 | 52 | The scope of Chessboard.jsx is limited to "just a board." This is intentional and 53 | makes Chessboard.jsx flexible for handling a multitude of chess-related problems. 54 | 55 | Specifically, Chessboard.jsx does not understand anything about how the game of 56 | chess is played: how a knight moves, who's turn is it, is White in check?, etc. 57 | 58 | Fortunately, the powerful [chess.js](https://github.com/jhlywa/chess.js) library deals with exactly this sort of 59 | problem domain and plays nicely with Chessboard.jsx's flexible API. 60 | 61 | Here is a list of things that Chessboard.jsx is **not**: 62 | 63 | * A chess engine 64 | * A legal move validator 65 | * A PGN parser 66 | 67 | Chessboard.jsx is designed to work well with any of those software components, but the idea 68 | behind the library is that the logic that controls the board should be 69 | independent of those other domains. 70 | 71 | ## Contributors 72 | 73 | Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)): 74 | 75 | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! 76 | 77 | 78 | 79 | | [
Will](https://github.com/willb335)
[💻](https://github.com/willb335/chessboardjsx/commits?author=willb335 "Code") [📖](https://github.com/willb335/chessboardjsx/commits?author=willb335 "Documentation") [💡](#example-willb335 "Examples") [⚠️](https://github.com/willb335/chessboardjsx/commits?author=willb335 "Tests") | [
Andrew Bashelor](https://github.com/a-bash)
[📖](https://github.com/willb335/chessboardjsx/commits?author=a-bash "Documentation") | [
yougotgotyo](https://chadburg.com/)
[🤔](#ideas-yougotgotyo "Ideas, Planning, & Feedback") | [
Roger Knapp](http://csharptest.net)
[🤔](#ideas-csharptest "Ideas, Planning, & Feedback") | [
Tiago Serafim](https://github.com/slig)
[💻](https://github.com/willb335/chessboardjsx/commits?author=slig "Code") [📖](https://github.com/willb335/chessboardjsx/commits?author=slig "Documentation") | [
Kef Schecter](http://www.furrykef.com/)
[🐛](https://github.com/willb335/chessboardjsx/issues?q=author%3Afurrykef "Bug reports") | [
Nils-Helge Garli Hegvik](http://www.lånekalkulatoren.no)
[💻](https://github.com/willb335/chessboardjsx/commits?author=nilsga "Code") | 80 | | :---: | :---: | :---: | :---: | :---: | :---: | :---: | 81 | | [
Levi Durfee](https://levi.lol/)
[💻](https://github.com/willb335/chessboardjsx/commits?author=levidurfee "Code") [📖](https://github.com/willb335/chessboardjsx/commits?author=levidurfee "Documentation") | [
Chris](https://github.com/altruisticsoftware)
[💻](https://github.com/willb335/chessboardjsx/commits?author=altruisticsoftware "Code") | [
Harrison Kerr](https://hwkerr.github.io/)
[💻](https://github.com/willb335/chessboardjsx/commits?author=hwkerr "Code") | 82 | 83 | 84 | ## LICENSE 85 | 86 | MIT 87 | 88 | [npm]: https://www.npmjs.com/ 89 | [node]: https://nodejs.org 90 | [build-badge]: https://img.shields.io/travis/willb335/chessboardjsx.svg?style=flat-square 91 | [build]: https://travis-ci.org/willb335/chessboardjsx 92 | [coverage-badge]: https://img.shields.io/codecov/c/github/willb335/chessboardjsx.svg?style=flat-square 93 | [coverage]: https://codecov.io/github/willb335/chessboardjsx 94 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square 95 | [prs]: http://makeapullrequest.com 96 | [version-badge]: https://img.shields.io/npm/v/chessboardjsx.svg?style=flat-square 97 | [package]: https://www.npmjs.com/package/chessboardjsx 98 | [license-badge]: https://img.shields.io/npm/l/chessboardjsx.svg?style=flat-square 99 | [license]: https://github.com/willb335/chessboardjsx/blob/master/LICENSE 100 | [commitzen-badge]: https://img.shields.io/badge/commitizen-friendly-brightgreen.svg 101 | [commitzen]: http://commitizen.github.io/cz-cli/ 102 | [semantic-release-badge]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg 103 | [semantic-release]: https://github.com/semantic-release/semantic-release 104 | -------------------------------------------------------------------------------- /src/Demo.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import WithMoveValidation from './integrations/WithMoveValidation'; 4 | import PlayRandomMoveEngine from './integrations/PlayRandomMoveEngine'; 5 | import RandomVsRandomGame from './integrations/RandomVsRandomGame'; 6 | import CustomizedBoard from './integrations/CustomizedBoard'; 7 | import AllowDragFeature from './integrations/AllowDrag'; 8 | import PrestoChangoExample from './integrations/PrestoChango'; 9 | import UndoMove from './integrations/UndoMove'; 10 | import SpareOnDrop from './integrations/SpareOnDrop'; 11 | 12 | class Demo extends Component { 13 | state = { 14 | showCustomizedBoard: false, 15 | showWithMoveValidation: false, 16 | showRandomVsRandomGame: false, 17 | showPlayRandomMoveEngine: false, 18 | showAllowDragFeature: false, 19 | showPrestoChango: false, 20 | showUndoMove: false, 21 | showSpareOnDrop: false 22 | }; 23 | render() { 24 | return ( 25 |
26 |
27 | 44 | 64 | 81 | 98 | 115 | 135 | 152 | 169 |
170 |
171 | {this.state.showCustomizedBoard && } 172 | {this.state.showWithMoveValidation && } 173 | {this.state.showRandomVsRandomGame && } 174 | {this.state.showPlayRandomMoveEngine && } 175 | {this.state.showAllowDragFeature && } 176 | {this.state.showPrestoChango && } 177 | {this.state.showUndoMove && } 178 | {this.state.showSpareOnDrop && } 179 |
180 |
181 | ); 182 | } 183 | } 184 | 185 | export default Demo; 186 | 187 | const buttonStyle = { width: 200, height: 100, margin: 30, fontSize: 16 }; 188 | 189 | const buttonRow = { 190 | display: 'flex', 191 | justifyContent: 'space-around', 192 | alignItems: 'center', 193 | width: '100vw', 194 | flexWrap: 'wrap' 195 | }; 196 | 197 | const boardsContainer = { 198 | display: 'flex', 199 | justifyContent: 'space-around', 200 | alignItems: 'center', 201 | flexWrap: 'wrap', 202 | width: '100vw', 203 | marginTop: 30, 204 | marginBottom: 50 205 | }; 206 | -------------------------------------------------------------------------------- /src/integrations/Stockfish.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; // eslint-disable-line no-unused-vars 2 | import PropTypes from 'prop-types'; 3 | import Chess from 'chess.js'; 4 | 5 | const STOCKFISH = window.STOCKFISH; 6 | const game = new Chess(); 7 | 8 | class Engine extends Component { 9 | static propTypes = { children: PropTypes.func }; 10 | 11 | state = { fen: 'start' }; 12 | 13 | onDrop = (source, target) => { 14 | // see if the move is legal 15 | const move = game.move({ 16 | from: source, 17 | to: target, 18 | promotion: 'q' 19 | }); 20 | 21 | // illegal move 22 | if (move === null) return; 23 | 24 | return new Promise(resolve => { 25 | this.setState({ fen: game.fen() }); 26 | resolve(); 27 | }).then(() => this.engineGame().prepareMove()); 28 | }; 29 | 30 | engineGame = options => { 31 | options = options || {}; 32 | 33 | /// We can load Stockfish via Web Workers or via STOCKFISH() if loaded from a