├── .github └── workflows │ └── production.yml ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.css ├── App.js ├── Field.js ├── PersistState.js ├── RightColumn.js ├── const │ └── board.js ├── img │ ├── bishop_black.png │ ├── bishop_white.png │ ├── game_over.jpg │ ├── king_black.png │ ├── king_white.png │ ├── knight_black.png │ ├── knight_white.png │ ├── pawn_black.png │ ├── pawn_white.png │ ├── queen_black.png │ ├── queen_white.png │ ├── rook_black.png │ └── rook_white.png ├── index.css └── index.js └── yarn.lock /.github/workflows/production.yml: -------------------------------------------------------------------------------- 1 | name: Production Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [12.x] 14 | 15 | steps: 16 | - uses: actions/checkout@v1 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - name: Yarn Install 22 | run: | 23 | yarn install 24 | - name: Production Build 25 | env: 26 | REACT_APP_JS_CHESS_API: ${{ secrets.REACT_APP_JS_CHESS_API }} 27 | REACT_APP_ANALYTICS_CODE: ${{ secrets.REACT_APP_ANALYTICS_CODE }} 28 | run: | 29 | yarn build 30 | - name: Deploy to S3 31 | uses: jakejarvis/s3-sync-action@master 32 | with: 33 | args: --acl public-read --delete 34 | env: 35 | AWS_S3_BUCKET: ${{ secrets.AWS_PRODUCTION_BUCKET_NAME }} 36 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 37 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 38 | AWS_REGION: ${{ secrets.AWS_REGION }} 39 | SOURCE_DIR: "build" 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | .idea 4 | .env 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Josef Jadrny 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | This repository is a single-page React app example for [js-chess-engine](https://github.com/josefjadrny/js-chess-engine) backend. 3 | 4 | [Live DEMO](http://chess.josefjadrny.info) 5 | 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chess-js-app", 3 | "description": "Single-page React app example for js-chess engine backend", 4 | "version": "1.1.6", 5 | "author": "bc.josefjadrny@gmail.com", 6 | "license": "MIT", 7 | "dependencies": { 8 | "local-storage": "^2.0.0", 9 | "react": "^16.13.1", 10 | "react-dom": "^16.13.1", 11 | "react-ga": "^2.7.0", 12 | "react-scripts": "3.4.1" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "eject": "react-scripts eject" 18 | }, 19 | "eslintConfig": { 20 | "extends": "react-app" 21 | }, 22 | "browserslist": { 23 | "production": [ 24 | ">1%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | Chess 16 | 17 | 18 | 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Chess", 3 | "name": "Example chess application for js-chess-engine.", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .js_chess { 2 | display: flex; 3 | flex-direction: row; 4 | width: 100%; 5 | } 6 | @media all and (max-width: 959px) { 7 | .js_chess { 8 | flex-direction: column; 9 | } 10 | } 11 | .column { 12 | display: flex; 13 | padding: 10px; 14 | } 15 | 16 | .column_left { 17 | flex: 2; 18 | } 19 | .column_right { 20 | flex: 1; 21 | } 22 | .menu { 23 | margin: 0 auto; 24 | width: 90%; 25 | } 26 | .board { 27 | display: -ms-grid; 28 | -ms-grid-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; 29 | -ms-grid-rows: auto; 30 | display: grid; 31 | grid-template-columns: repeat(8, 1fr); 32 | grid-template-rows: auto; 33 | border: 10px solid #999999; 34 | width: 90%; 35 | margin: 0 auto; 36 | max-width: 700px; 37 | } 38 | ::-webkit-scrollbar { 39 | width: 12px; 40 | } 41 | 42 | ::-webkit-scrollbar-track { 43 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 44 | border-radius: 10px; 45 | } 46 | 47 | ::-webkit-scrollbar-thumb { 48 | border-radius: 10px; 49 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5); 50 | } 51 | @media all and (max-height: 768px) { 52 | .board { 53 | max-width: 520px; 54 | } 55 | } 56 | .board.finished { 57 | opacity: .25; 58 | pointer-events: none; 59 | } 60 | .board .field { 61 | display: block; 62 | padding-top: 100%; 63 | background-size: 100% 100%; 64 | background-repeat:no-repeat; 65 | } 66 | .board .field:hover { 67 | -webkit-box-shadow:inset 0px 0px 0px 3px #3366CC; 68 | -moz-box-shadow:inset 0px 0px 0px 3px #3366CC; 69 | box-shadow:inset 0px 0px 0px 3px #3366CC; 70 | } 71 | .board .lastMove { 72 | -webkit-box-shadow:inset 0px 0px 0px 2px #FF0033; 73 | -moz-box-shadow:inset 0px 0px 0px 2px #FF0033; 74 | box-shadow:inset 0px 0px 0px 2px #FF0033; 75 | } 76 | .board .moveFrom { 77 | -webkit-box-shadow:inset 0px 0px 0px 3px #3366CC; 78 | -moz-box-shadow:inset 0px 0px 0px 3px #3366CC; 79 | box-shadow:inset 0px 0px 0px 3px #3366CC; 80 | } 81 | .board .moveTo { 82 | -webkit-box-shadow:inset 0px 0px 0px 2px #33CC33; 83 | -moz-box-shadow:inset 0px 0px 0px 2px #33CC33; 84 | box-shadow:inset 0px 0px 0px 2px #33CC33; 85 | } 86 | .board .pieceP { 87 | background-image: url(./img/pawn_white.png); 88 | } 89 | .board .pieceK { 90 | background-image: url(./img/king_white.png); 91 | } 92 | .board .pieceQ { 93 | background-image: url(./img/queen_white.png); 94 | } 95 | .board .pieceR { 96 | background-image: url(./img/rook_white.png); 97 | } 98 | .board .pieceB { 99 | background-image: url(./img/bishop_white.png); 100 | } 101 | .board .pieceN { 102 | background-image: url(./img/knight_white.png); 103 | } 104 | .board .piecep { 105 | background-image: url(./img/pawn_black.png); 106 | } 107 | .board .piecek { 108 | background-image: url(./img/king_black.png); 109 | } 110 | .board .pieceq { 111 | background-image: url(./img/queen_black.png); 112 | } 113 | .board .piecer { 114 | background-image: url(./img/rook_black.png); 115 | } 116 | .board .pieceb { 117 | background-image: url(./img/bishop_black.png); 118 | } 119 | .board .piecen { 120 | background-image: url(./img/knight_black.png); 121 | } 122 | .overlay { 123 | background-image: url(./img/game_over.jpg); 124 | background-size: 40% 20%; 125 | background-repeat:no-repeat; 126 | background-position: center; 127 | width: 100%; 128 | height: 100%; 129 | } 130 | 131 | body { 132 | padding: 0; 133 | margin: 0; 134 | background-color: #212121; 135 | font-size: 20px; 136 | color: #cccccc; 137 | } 138 | a:visited{ 139 | color: #4CAF50;; 140 | } 141 | a:link{ 142 | color: #4CAF50;; 143 | } 144 | 145 | body .field { 146 | background-color: #cccccc; 147 | } 148 | body .field:nth-of-type(-2n+8), body .field:nth-of-type(8) ~ *:nth-of-type(-2n+15), body .field:nth-of-type(16) ~ *:nth-of-type(-2n+24), body .field:nth-of-type(24) ~ *:nth-of-type(-2n+31), body .field:nth-of-type(32) ~ *:nth-of-type(-2n+40), body .field:nth-of-type(40) ~ *:nth-of-type(-2n+47), body .field:nth-of-type(48) ~ *:nth-of-type(-2n+56), body .field:nth-of-type(56) ~ *:nth-of-type(-2n+63) { 149 | background-color: #666666; 150 | } 151 | button { 152 | background-color: #4CAF50; 153 | border: none; 154 | color: white; 155 | padding: 15px 32px; 156 | text-align: center; 157 | text-decoration: none; 158 | display: inline-block; 159 | font-size: 20px; 160 | width: 100%; 161 | border-radius: 10px; 162 | } 163 | button:hover { 164 | background-color: #006400; 165 | } 166 | button:disabled { 167 | opacity: .5; 168 | pointer-events: none; 169 | } 170 | .menu #history { 171 | background-color: #f0f7fb; 172 | line-height: 18px; 173 | overflow: hidden; 174 | margin: 10px 0px 10px 0px; 175 | text-align: center; 176 | padding: 5px; 177 | color: #666666; 178 | border-radius: 10px; 179 | overflow-y: scroll; 180 | height: 105px; 181 | font-size: 18px; 182 | } 183 | .menu #copyright { 184 | margin-top: 50px; 185 | text-align: center; 186 | } 187 | .menu #confirmation { 188 | margin-top: 35px; 189 | } 190 | .menu #level { 191 | margin-top: 35px; 192 | } 193 | /* Customize the label (the level_wrapper) */ 194 | .level_wrapper { 195 | display: block; 196 | position: relative; 197 | padding-left: 35px; 198 | margin-bottom: 15px; 199 | cursor: pointer; 200 | -webkit-user-select: none; 201 | -moz-user-select: none; 202 | -ms-user-select: none; 203 | user-select: none; 204 | } 205 | 206 | /* Hide the browser's default radio button */ 207 | .level_wrapper input { 208 | position: absolute; 209 | opacity: 0; 210 | cursor: pointer; 211 | height: 0; 212 | width: 0; 213 | } 214 | 215 | /* Create a custom radio button */ 216 | .checkmark { 217 | position: absolute; 218 | top: 0; 219 | left: 0; 220 | height: 25px; 221 | width: 25px; 222 | background-color: #eee; 223 | border-radius: 50%; 224 | } 225 | 226 | /* On mouse-over, add a grey background color */ 227 | .level_wrapper:hover input ~ .checkmark { 228 | background-color: #ccc; 229 | } 230 | 231 | /* When the radio button is checked, add a blue background */ 232 | .level_wrapper input:checked ~ .checkmark { 233 | background-color: #4CAF50; 234 | } 235 | 236 | /* Create the indicator (the dot/circle - hidden when not checked) */ 237 | .checkmark:after { 238 | content: ""; 239 | position: absolute; 240 | display: none; 241 | } 242 | 243 | /* Show the indicator (dot/circle) when checked */ 244 | .level_wrapper input:checked ~ .checkmark:after { 245 | display: block; 246 | } 247 | 248 | /* Style the indicator (dot/circle) */ 249 | .level_wrapper .checkmark:after { 250 | top: 9px; 251 | left: 9px; 252 | width: 8px; 253 | height: 8px; 254 | border-radius: 50%; 255 | background: white; 256 | } 257 | /* The switch - the box around the slider */ 258 | .switch { 259 | position: relative; 260 | display: inline-block; 261 | width: 60px; 262 | height: 30px; 263 | } 264 | 265 | /* Hide default HTML checkbox */ 266 | .switch input { 267 | opacity: 0; 268 | width: 0; 269 | height: 0; 270 | } 271 | 272 | /* The slider */ 273 | .slider { 274 | position: absolute; 275 | cursor: pointer; 276 | top: 0; 277 | left: 0; 278 | right: 0; 279 | bottom: 0; 280 | background-color: #ccc; 281 | -webkit-transition: .4s; 282 | transition: .4s; 283 | } 284 | 285 | .slider:before { 286 | position: absolute; 287 | content: ""; 288 | height: 22px; 289 | width: 22px; 290 | left: 4px; 291 | bottom: 4px; 292 | background-color: white; 293 | -webkit-transition: .4s; 294 | transition: .4s; 295 | } 296 | 297 | input:checked + .slider { 298 | background-color: #4CAF50; 299 | } 300 | 301 | input:focus + .slider { 302 | box-shadow: 0 0 1px #4CAF50; 303 | } 304 | 305 | input:checked + .slider:before { 306 | -webkit-transform: translateX(26px); 307 | -ms-transform: translateX(26px); 308 | transform: translateX(26px); 309 | } 310 | 311 | /* Rounded sliders */ 312 | .slider.round { 313 | border-radius: 34px; 314 | } 315 | 316 | .slider.round:before { 317 | border-radius: 50%; 318 | } 319 | .loading { 320 | cursor: wait; 321 | } 322 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import Field from './Field'; 3 | import PersistState from './PersistState'; 4 | import RightColumn from './RightColumn.js' 5 | import {NEW_GAME_BOARD_CONFIG, ROWS, COLUMNS, COLORS, SETTINGS, PERSIST_STATE_NAMESPACE, MOVE_SOUND} from './const/board' 6 | import ReactGA from 'react-ga'; 7 | import { get } from 'local-storage' 8 | import './App.css' 9 | const API_URIS = { 10 | MOVES: 'moves', 11 | STATUS: 'status', 12 | MOVE: 'move', 13 | AI_MOVE: 'aimove' 14 | } 15 | const moveSound = new Audio(`data:audio/wav;base64,${MOVE_SOUND}`) 16 | 17 | ReactGA.initialize(process.env.REACT_APP_ANALYTICS_CODE) 18 | ReactGA.pageview(window.location.pathname + window.location.search) 19 | 20 | function App() { 21 | const savedSettings = get(`${PERSIST_STATE_NAMESPACE}_settings`) 22 | const savedChess = get(`${PERSIST_STATE_NAMESPACE}_chess`) 23 | const [chess, setChess] = useState( 24 | savedChess && typeof savedChess === 'object' ? Object.assign({}, NEW_GAME_BOARD_CONFIG, savedChess) : { ...NEW_GAME_BOARD_CONFIG } 25 | ) 26 | const [settings, setSettings] = useState( 27 | savedSettings && typeof savedSettings === 'object' ? Object.assign({}, SETTINGS, savedSettings) : { ...SETTINGS } 28 | ) 29 | const [loading, setLoading] = useState(false) 30 | const board = getBoard() 31 | 32 | useEffect(() => { 33 | getMoves() 34 | // eslint-disable-next-line react-hooks/exhaustive-deps 35 | }, []) 36 | 37 | useEffect(() => { 38 | if (chess.turn === COLORS.BLACK && !chess.isFinished) { 39 | aiMove() 40 | } 41 | // eslint-disable-next-line react-hooks/exhaustive-deps 42 | }, [chess.turn]) 43 | 44 | return ( 45 |
46 |
47 |
48 |
49 | {board} 50 |
51 |
52 |
53 |
54 |
55 | handleNewGameClick() } 59 | onComputerLevelClick={handleChangeComputerLevelClick} 60 | onConfirmationToggleClick={() => handleChangeConfirmationToggleClick() } 61 | onSoundToggleClick={() => handleChangeSoundToggleClick() } 62 | onConfirmationClick={() => handleChangeConfirmationClick() } 63 | /> 64 |
65 | 69 |
70 |
71 | ) 72 | 73 | function getBoard() { 74 | const fields = [] 75 | Object.assign([], ROWS).reverse().map(row => { 76 | return COLUMNS.map(column => { 77 | const location = `${column}${row}` 78 | return fields.push( handleFieldClick(location) } 82 | chess={chess} 83 | settings={settings} 84 | />) 85 | }) 86 | }) 87 | return fields 88 | } 89 | 90 | async function handleFieldClick(field) { 91 | if (chess.move.from && chess.moves[chess.move.from].includes(field)) { 92 | chess.move.to = field 93 | await setChess({...chess}) 94 | if (settings.confirmation) { 95 | 96 | } else { 97 | return performMove(chess.move.from, chess.move.to) 98 | } 99 | } else if (chess.moves[field]) { 100 | setChess(Object.assign({}, chess, { move:{ from: field }})) 101 | } else { 102 | setChess(Object.assign({}, chess, { move:{ from: null }})) 103 | } 104 | } 105 | 106 | async function performMove (from, to) { 107 | chess.history.push({ from, to }) 108 | chess.move.from = from 109 | chess.move.to = to 110 | setChess(Object.assign({}, chess, { move: {} }, await sendRequest(`${API_URIS.MOVE}?from=${from}&to=${to}`) )) 111 | if (settings.sound) { 112 | moveSound.play() 113 | } 114 | } 115 | 116 | async function aiMove() { 117 | const aiMove = await sendRequest(`${API_URIS.AI_MOVE}?level=${settings.computerLevel}`) 118 | const from = Object.keys(aiMove)[0] 119 | const to = Object.values(aiMove)[0] 120 | return await performMove(from, to) 121 | } 122 | 123 | async function getMoves () { 124 | const moves = await sendRequest(API_URIS.MOVES) 125 | setChess(Object.assign({}, chess, { moves })) 126 | } 127 | 128 | async function handleNewGameClick() { 129 | await setChess(Object.assign(chess, {pieces: {}}, NEW_GAME_BOARD_CONFIG)) 130 | await getMoves() 131 | } 132 | 133 | async function sendRequest(url) { 134 | await setLoading(true) 135 | try { 136 | const res = await fetch( 137 | `${process.env.REACT_APP_JS_CHESS_API}${url}`, 138 | { method: 'POST', body: JSON.stringify(chess), headers: { 'Content-Type': 'application/json' }} 139 | ) 140 | if (res.status !== 200) { 141 | throw new Error(`Server returns ${res.status}`) 142 | } 143 | await setLoading(false) 144 | return res.json().then(res => {return res}) 145 | } catch (error) { 146 | await setLoading(false) 147 | chess.history.push({ from: 'Error', to: error.message }) 148 | return {} 149 | } 150 | } 151 | 152 | async function handleChangeComputerLevelClick(level) { 153 | await setSettings(Object.assign({}, settings, {computerLevel: level})) 154 | } 155 | 156 | async function handleChangeConfirmationToggleClick() { 157 | await setSettings(Object.assign({}, settings,{confirmation: settings.confirmation ? false : true})) 158 | } 159 | 160 | async function handleChangeSoundToggleClick() { 161 | await setSettings(Object.assign({}, settings,{sound: settings.sound ? false : true})) 162 | } 163 | 164 | async function handleChangeConfirmationClick() { 165 | return performMove(chess.move.from, chess.move.to) 166 | } 167 | } 168 | 169 | export default App 170 | -------------------------------------------------------------------------------- /src/Field.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Field(props) { 4 | const { 5 | onClick, 6 | location, 7 | chess, 8 | settings 9 | } = props 10 | 11 | let piece = chess.pieces[location] || '' 12 | if (settings.confirmation) { 13 | if (chess.move.from && chess.move.to) { 14 | if(location === chess.move.from) { 15 | piece = '' 16 | } 17 | if(location === chess.move.to) { 18 | piece = chess.pieces[chess.move.from] 19 | } 20 | } 21 | } 22 | 23 | const historyClass = chess.history.length && 24 | (chess.history[chess.history.length - 1].from === location || 25 | chess.history[chess.history.length - 1].to === location) ? 'lastMove' : '' 26 | const moveFromClass = chess.move.from === location ? 'moveFrom' : '' 27 | const moveToClass = chess.move.from && chess.moves && chess.moves[chess.move.from] && chess.moves[chess.move.from].includes(location) ? 'moveTo' : '' 28 | 29 | return ( 30 |
31 |
32 | ) 33 | } 34 | 35 | export default Field 36 | -------------------------------------------------------------------------------- /src/PersistState.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { set } from 'local-storage' 3 | import { PERSIST_STATE_NAMESPACE } from './const/board' 4 | 5 | function PersistState(props) { 6 | const { 7 | settings, 8 | chess, 9 | } = props 10 | 11 | useEffect(() => { 12 | set(`${PERSIST_STATE_NAMESPACE}_settings`, settings) 13 | }, [settings]); 14 | 15 | useEffect(() => { 16 | set(`${PERSIST_STATE_NAMESPACE}_chess`, chess) 17 | // eslint-disable-next-line react-hooks/exhaustive-deps 18 | }, [chess && chess.turn]); 19 | 20 | return ( 21 |
22 |
23 | ) 24 | } 25 | 26 | export default PersistState 27 | -------------------------------------------------------------------------------- /src/RightColumn.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {COMPUTER_LEVELS} from './const/board' 3 | 4 | function RightColumn(props) { 5 | const { 6 | onNewGameClick, 7 | onComputerLevelClick, 8 | onConfirmationToggleClick, 9 | onSoundToggleClick, 10 | onConfirmationClick, 11 | chess, 12 | settings, 13 | loading, 14 | } = props 15 | 16 | return ( 17 |
18 |
19 | 20 |
21 |
22 | HISTORY
23 | {chess.history.map(record => { 24 | return ` ${record.from}-${record.to} ` 25 | })} 26 |
27 |
28 |

Computer level

29 | {Object.keys(COMPUTER_LEVELS).map(level => { 30 | return 40 | })} 41 |
42 |
43 |

Move confirmation

44 | 52 | {settings.confirmation ? 53 |

: 54 | '' 55 | } 56 | 57 |
58 |
59 |

Sounds

60 | 68 |
69 | 75 |
76 | ) 77 | } 78 | 79 | export default RightColumn 80 | -------------------------------------------------------------------------------- /src/const/board.js: -------------------------------------------------------------------------------- 1 | export const COLUMNS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] 2 | export const ROWS = ['1', '2', '3', '4', '5', '6', '7', '8'] 3 | export const COLORS = { 4 | BLACK: 'black', 5 | WHITE: 'white' 6 | } 7 | export const COMPUTER_LEVELS = { 8 | "Monkey": 0, 9 | "Beginner": 1, 10 | "Intermediate": 2, 11 | "Advanced": 3, 12 | } 13 | export const SETTINGS = { 14 | computerLevel: COMPUTER_LEVELS.Intermediate, 15 | confirmation: false, 16 | sound: true, 17 | } 18 | export const PERSIST_STATE_NAMESPACE = 'js_chess_app' 19 | export const NEW_GAME_BOARD_CONFIG = { 20 | turn: COLORS.WHITE, 21 | pieces: { 22 | E1: 'K', 23 | D1: 'Q', 24 | A1: 'R', 25 | H1: 'R', 26 | C1: 'B', 27 | F1: 'B', 28 | B1: 'N', 29 | G1: 'N', 30 | A2: 'P', 31 | B2: 'P', 32 | C2: 'P', 33 | D2: 'P', 34 | E2: 'P', 35 | F2: 'P', 36 | G2: 'P', 37 | H2: 'P', 38 | E8: 'k', 39 | D8: 'q', 40 | A8: 'r', 41 | H8: 'r', 42 | C8: 'b', 43 | F8: 'b', 44 | B8: 'n', 45 | G8: 'n', 46 | A7: 'p', 47 | B7: 'p', 48 | C7: 'p', 49 | D7: 'p', 50 | E7: 'p', 51 | F7: 'p', 52 | G7: 'p', 53 | H7: 'p', 54 | }, 55 | moves: {}, 56 | move: {}, 57 | history: [], 58 | isFinished: false, 59 | checkMate: false, 60 | castling: { 61 | whiteShort: true, 62 | blackShort: true, 63 | whiteLong: true, 64 | blackLong: true, 65 | }, 66 | fullMove: 1, 67 | halfMove: 0, 68 | } 69 | export const MOVE_SOUND = 'UklGRnwFAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YYgEAAANAG4A5AAmAO4ANAHfAM0AsACXAOMAQwEDAVUAzwC8AP0A0gClAPwA/QBxAFsAXADEAC4BtQDrAC4BNgEbAhUEfQjzEMQcQSzAOrpEOEeNPXYdmerdxeG0uqearpSzbLRfvMjRZPUpJrNTF3Izbw9dsDnNFxjy3doAzTrQU9IW12HTpdhu30brKPVm/+QMWBjJHh0bZBQADoYGRvy+9ATvROs76Lvgitcg08DV49+u8xwLoyD+Lm0xaC1VI30ZsRQeDcj/oO7B2A7IM776vv3Kp+BL9d4LNBoPIzAmNy61Mjc2RzawMgQnrxiQCDf37Omb4ararNZW1zPbu+Ii7Mr4AgcpFt0j0i0mMEUqNB2oDHH9Tu2X4rnYHtKizCvM09K23p7wWQVRGDIlEyiHJd0dYRAoB74At/q294/10/E38BLvCfCm9C79MAX0C2QPmRCKD8kObwxuCNYD3v1g+LryKuzF5+jlFObN6ATwA/et/i4FmAi0DE0PZBEPEi4SUg9tCpIELv+b+db08/EW8avxSvKz8lTzh/Rs9uT4Ovud/ab/igDPARICXgJCAocBYQG6AL//Sf+//2cBhQNoBpwJkAw3DtEO9Q2HDL4J/QR7/3v6UPUg8A3tcOr+6T/rHu7A8mv5pv8uBpwKlgzTDaINBAzMCXAGhAKo/9v8qvo8+f74K/mr+f/5Kvr/+c75GPk3+Lr3i/cL+OX4P/p5+139wv/9AUUEOQbRB3YIFQjxBtYFiQR7A2ICAAH+/s78PPp49wH10fIP8eTvOu9d77TwVfLQ9A33evnt+3D+rgE4BHoFbAYUBhwGPgXCBFQD3wAj/1z9DPyc+yX8Bv5D/xYA8f+n/nr9E/zt+gj6w/nf+fP5hfo6+7/86v5wAbEDqAWMBuYGPQczB8IGIgYFBSQDbQFPAKP/gP6f/Y79Of38/Qr/RAAoAUECAgPqAgcD+QJ+A/wCigL7AToB8QC7AFsBRwLlAiQD9QLRApsCOQLlAfgAGwD7/iP+xv2n/ev9y/6S/vr9Q/1w/Hf8p/xQ/Mr8Pf3q/T3/IAFAAzYEGQTyA9oDbwO0AqoBrQBD/6f9evy3++L6AftO+9j79/t9+7/63/m8+LL3HPfu9uX2U/fC9xz5LPsg/cH+BwAxAf0BGgI7An8CRAKxAqMCGQJaAY8Ae/9T/nX9SPxC+5f6JPrp+av5afm7+RT6WPoS+7z7nPyI/aL+yf+zAJEBgwJVA9YD+wOTA1sDqQJoAqQB+QCFAOH/Fv/3/Rb9avzs+/L7Gvxf/J/8jPx8/Jz8C/1F/eb9zf51/2kAcgEZAmMCowJyAhkCygEsAVUAj//i/mP+x/1u/XH9wf00/oD+Pf7G/c/97f0L/lT+nP70/uH+nf65/h3/uv/k/7X/Zf8F/1P++P2k/TH9wvxL/Mf7Yftb+4n77vsy/D78rPxD/Xf90/1l/qP+GP9m//L/sgAoAUUBZgG2AZIB0QE3AqUC1wLFAtcCmwL5Ad0B1AGAAd4BDwI3Am0CnAKsAmkC4AFaAYsAFQBi/2j/IP+8/kxJU1RUAAAASU5GT0lOQU0YAAAAY2hlc3NfbW92ZV9vbl9hbGFiYXN0ZXIASUFSVAYAAABtaDJvAABJQ1JEBgAAADIwMTYAAElHTlIMAAAAU291bmQgQ2xpcAAAaWQzIGwAAABJRDMEAEAAAABhAAAADAEgBQsuT0NfVENPTgAAAAsAAABTb3VuZCBDbGlwVElUMgAAABgAAABjaGVzc19tb3ZlX29uX2FsYWJhc3RlclREUkMAAAAFAAAAMjAxNlRQRTEAAAAFAAAAbWgybwA=' 70 | -------------------------------------------------------------------------------- /src/img/bishop_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/bishop_black.png -------------------------------------------------------------------------------- /src/img/bishop_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/bishop_white.png -------------------------------------------------------------------------------- /src/img/game_over.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/game_over.jpg -------------------------------------------------------------------------------- /src/img/king_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/king_black.png -------------------------------------------------------------------------------- /src/img/king_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/king_white.png -------------------------------------------------------------------------------- /src/img/knight_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/knight_black.png -------------------------------------------------------------------------------- /src/img/knight_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/knight_white.png -------------------------------------------------------------------------------- /src/img/pawn_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/pawn_black.png -------------------------------------------------------------------------------- /src/img/pawn_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/pawn_white.png -------------------------------------------------------------------------------- /src/img/queen_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/queen_black.png -------------------------------------------------------------------------------- /src/img/queen_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/queen_white.png -------------------------------------------------------------------------------- /src/img/rook_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/rook_black.png -------------------------------------------------------------------------------- /src/img/rook_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefjadrny/js-chess-engine-app/cf2de0da1e07022b281bab7b055cb3dd054e8094/src/img/rook_white.png -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('app') 11 | ); 12 | --------------------------------------------------------------------------------