├── .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 |
70 |
71 | This web site is using an open-source
72 | js-chess-engine.
73 |
74 |
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 |
--------------------------------------------------------------------------------