├── LICENSE ├── README.md ├── css ├── how_to_play.css └── style.css ├── how_to_play.html ├── images ├── black_bishop.png ├── black_king.png ├── black_knight.png ├── black_pawn.png ├── black_queen.png ├── black_rook.png ├── favicon.png ├── react.png ├── white_bishop.png ├── white_king.png ├── white_knight.png ├── white_pawn.png ├── white_queen.png └── white_rook.png ├── index.html ├── js └── chess.js ├── preview.png └── sfx ├── Black_Defeat.mp3 ├── Capture.mp3 ├── Check.mp3 ├── Check_Flash.mp3 ├── Move.mp3 ├── Stalemate.mp3 └── White_Defeat.mp3 /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Arpan Sahoo 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 | # ReactJS Chess 2 | 3 | This is a web chess game coded in React. Feel free to contribute as this is an open source project! 4 | 5 | ## How to Play 6 | 7 | Click the piece you want to move, and then click the desired destination. Users play against a simple chess engine. 8 | 9 | ## Features 10 | 11 | You play as the white player, and black is controlled by a simple chess AI (implemented using the minimax algorithm). Most of the rules of chess, from castling to not being able to put yourself in check, are fully implemented. Draws and time limits are not enabled. I also implemented time travel, so that users can view previous moves. 12 | 13 | ## Demo 14 | 15 | You can play here: [ReactJS Chess](https://arpansahoo.github.io/ReactJS-Chess/). 16 | 17 | ![](/preview.png) 18 | 19 | ## Goals for Next Release 20 | 21 | v2.0 will have the following: (1) improved chess engine and (2) option to battle friends via the Internet. 22 | 23 | ## Acknowledgements 24 | 25 | UI inspired by [codethejason/checkers](https://github.com/codethejason/checkers). 26 | -------------------------------------------------------------------------------- /css/how_to_play.css: -------------------------------------------------------------------------------- 1 | body, 2 | html, 3 | footer { 4 | background-color: #ebedef; 5 | color: #242424; 6 | font-family: "Raleway", sans-serif; 7 | overflow-x: hidden; 8 | } 9 | 10 | .wrapper { 11 | display: block; 12 | margin-right: auto; 13 | margin-left: auto; 14 | max-width: 90%; 15 | padding-right: 30px; 16 | padding-top: 5px; 17 | padding-bottom: 80px; 18 | } 19 | 20 | .paragraph_wrapper { 21 | padding-bottom: 1px; 22 | } 23 | 24 | .header { 25 | color: #00a494; 26 | font-size: 280%; 27 | line-height: 1.2; 28 | font-weight: normal; 29 | } 30 | 31 | .paragraph { 32 | font-size: 160%; 33 | line-height: 1.3; 34 | font-weight: 300; 35 | } 36 | 37 | .italic { 38 | font-style: italic; 39 | } 40 | 41 | a:link { 42 | color: #00a494; 43 | } 44 | a:visited { 45 | color: #00a494; 46 | } 47 | a:hover { 48 | color: #242424; 49 | } 50 | a:active { 51 | color: #242424; 52 | } 53 | 54 | .footer { 55 | position: fixed; 56 | width: 100%; 57 | background: #242424; 58 | color: white; 59 | text-align: center; 60 | font-size: 1.3em; 61 | bottom: 0; 62 | left: 0; 63 | } 64 | 65 | .footer_img { 66 | height: 20px; 67 | width: auto; 68 | } 69 | 70 | @media only screen and (max-width: 600px) { 71 | .wrapper { 72 | padding-left: 4%; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | background-color: #242424; 4 | color: white; 5 | font-family: "Raleway", sans-serif; 6 | font-weight: lighter; 7 | overflow-x: hidden; 8 | } 9 | 10 | @-webkit-keyframes bounceInDown { 11 | from, 12 | 60%, 13 | 75%, 14 | 90%, 15 | to { 16 | -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 17 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 18 | } 19 | 20 | 0% { 21 | opacity: 0; 22 | -webkit-transform: translate3d(0, -3000px, 0); 23 | transform: translate3d(0, -3000px, 0); 24 | } 25 | 26 | 60% { 27 | opacity: 1; 28 | -webkit-transform: translate3d(0, 25px, 0); 29 | transform: translate3d(0, 25px, 0); 30 | } 31 | 32 | 75% { 33 | -webkit-transform: translate3d(0, -10px, 0); 34 | transform: translate3d(0, -10px, 0); 35 | } 36 | 37 | 90% { 38 | -webkit-transform: translate3d(0, 5px, 0); 39 | transform: translate3d(0, 5px, 0); 40 | } 41 | 42 | to { 43 | -webkit-transform: translate3d(0, 0, 0); 44 | transform: translate3d(0, 0, 0); 45 | } 46 | } 47 | 48 | @keyframes bounceInDown { 49 | from, 50 | 60%, 51 | 75%, 52 | 90%, 53 | to { 54 | -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 55 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 56 | } 57 | 58 | 0% { 59 | opacity: 0; 60 | -webkit-transform: translate3d(0, -3000px, 0); 61 | transform: translate3d(0, -3000px, 0); 62 | } 63 | 64 | 60% { 65 | opacity: 1; 66 | -webkit-transform: translate3d(0, 25px, 0); 67 | transform: translate3d(0, 25px, 0); 68 | } 69 | 70 | 75% { 71 | -webkit-transform: translate3d(0, -10px, 0); 72 | transform: translate3d(0, -10px, 0); 73 | } 74 | 75 | 90% { 76 | -webkit-transform: translate3d(0, 5px, 0); 77 | transform: translate3d(0, 5px, 0); 78 | } 79 | 80 | to { 81 | -webkit-transform: translate3d(0, 0, 0); 82 | transform: translate3d(0, 0, 0); 83 | } 84 | } 85 | 86 | .bounceInDown { 87 | -webkit-animation: bounceInDown 1.3s; 88 | animation: bounceInDown 1.3s; 89 | } 90 | 91 | /* unvisited link */ 92 | a:link { 93 | color: #242424; 94 | } 95 | 96 | /* visited link */ 97 | a:visited { 98 | color: #242424; 99 | } 100 | 101 | /* mouse over link */ 102 | a:hover { 103 | color: #00a25f; 104 | } 105 | 106 | /* selected link */ 107 | a:active { 108 | color: #00a25f; 109 | } 110 | 111 | .right_screen { 112 | position: relative; 113 | display: inline-block; 114 | height: 100%; 115 | width: 50%; 116 | } 117 | 118 | .left_screen { 119 | position: relative; 120 | float: left; 121 | height: 100%; 122 | width: 50%; 123 | overflow: auto; 124 | } 125 | 126 | .header_font { 127 | font-size: 2.5em; 128 | line-height: 1; 129 | } 130 | 131 | .header_2_font { 132 | font-size: 2em; 133 | line-height: 1; 134 | } 135 | 136 | .medium_font { 137 | font-size: 1.2em; 138 | line-height: 1; 139 | } 140 | 141 | .small_font { 142 | font-size: 1.2em; 143 | line-height: 1; 144 | } 145 | 146 | .wrapper { 147 | margin-left: 5%; 148 | width: 90%; 149 | height: auto; 150 | display: flex; 151 | padding-bottom: 0px; 152 | padding-top: 0px; 153 | } 154 | 155 | .player_box { 156 | display: inline-block; 157 | outline: none !important; 158 | width: 45%; 159 | background-color: #deeaee; 160 | padding: 0 15px 5px; 161 | border-top-right-radius: 0px; 162 | border-top-left-radius: 3px; 163 | color: #242424; 164 | text-align: center; 165 | } 166 | 167 | .black_player_color { 168 | background-color: #242424; 169 | color: white; 170 | border-top-right-radius: 3px; 171 | border-top-left-radius: 0px; 172 | } 173 | 174 | .highlight_box { 175 | width: 45%; 176 | background-color: #00ba6d; 177 | padding: 0 20px 5px; 178 | padding-right: 50px; 179 | padding-bottom: 8px; 180 | color: white; 181 | text-align: center; 182 | margin-bottom: 20px; 183 | } 184 | 185 | .side_box { 186 | width: 65%; 187 | box-sizing: border-box; 188 | background-color: #77e1d7; 189 | color: #242424; 190 | border-radius: 3px; 191 | box-shadow: 1px 1px 3px #232621; 192 | position: relative; 193 | top: calc(50% - 41vmin); 194 | left: 13%; 195 | margin: 0 0 30 0; 196 | padding-bottom: 1px; 197 | } 198 | 199 | .transparent { 200 | background-color: transparent; 201 | } 202 | 203 | .content { 204 | padding-left: 20px; 205 | padding-bottom: 20px; 206 | padding-top: 5px; 207 | margin-right: 10px; 208 | } 209 | 210 | .title { 211 | padding-bottom: 0px; 212 | } 213 | 214 | .collected { 215 | outline: none !important; 216 | border: none; 217 | border-width: 0px; 218 | background-color: transparent; 219 | height: 22px; 220 | width: 22px; 221 | background-position: center; 222 | 223 | background-size: 65%; 224 | background-repeat: no-repeat; 225 | background-position: center; 226 | display: inline-block; 227 | } 228 | 229 | .mate_wrapper { 230 | text-align: center; 231 | } 232 | 233 | .button_wrapper { 234 | width: 100%; 235 | height: auto; 236 | display: flex; 237 | align-items: center; 238 | justify-content: center; 239 | padding-bottom: 0px; 240 | padding-top: 0px; 241 | } 242 | 243 | .reset_button { 244 | position: relative; 245 | text-align: center; 246 | width: 35%; 247 | cursor: pointer; 248 | outline: none; 249 | color: white; 250 | background-color: #00ba6d; 251 | border: none; 252 | border-radius: 5px; 253 | font-size: 0.8em; 254 | transition: background-color 0.5s; 255 | margin-bottom: 0px; 256 | margin-top: 5px; 257 | margin-right: 10px; 258 | font-family: "Raleway", sans-serif; 259 | font-weight: 300; 260 | } 261 | 262 | .button_font { 263 | font-size: 1.2em; 264 | line-height: 1; 265 | } 266 | 267 | .history { 268 | width: 10%; 269 | margin-right: 10px; 270 | } 271 | 272 | .reset_button:hover { 273 | background-color: #00a25f; 274 | } 275 | 276 | .reset_button:active { 277 | transform: translateY(1px); 278 | } 279 | 280 | .table { 281 | position: absolute; 282 | top: calc(50% - 42vmin); 283 | left: calc(50% - 46vmin); 284 | width: 80vmin; 285 | height: 80vmin; 286 | box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.2), 0 0px 15px 0 rgba(0, 0, 0, 0.19); 287 | } 288 | 289 | .square { 290 | outline: none !important; 291 | border: none; 292 | border-width: 0px; 293 | float: left; 294 | height: 10vmin; 295 | width: 10vmin; 296 | transition: background-color 0.7s; 297 | -webkit-transition: background-color 0.7s; 298 | } 299 | 300 | .pointer { 301 | cursor: pointer; 302 | } 303 | 304 | .not_allowed { 305 | cursor: not-allowed; 306 | } 307 | 308 | .default { 309 | cursor: default; 310 | } 311 | 312 | .bot_running { 313 | cursor: progress; 314 | } 315 | 316 | .label { 317 | outline: none !important; 318 | background-color: transparent; 319 | color: #deeaee; 320 | border: none; 321 | font-size: 120%; 322 | height: 10vmin; 323 | width: 10vmin; 324 | } 325 | 326 | .row_label { 327 | outline: none !important; 328 | position: relative; 329 | top: calc(50% - 42vmin); 330 | left: calc(50% - 55vmin); 331 | width: 10vmin; 332 | height: 80vmin; 333 | } 334 | 335 | .col_label { 336 | outline: none !important; 337 | position: relative; 338 | top: calc(50% - 43vmin); 339 | left: calc(50% - 46vmin); 340 | width: 80vmin; 341 | height: 10vmin; 342 | } 343 | 344 | .piece { 345 | height: 60%; 346 | width: auto; 347 | } 348 | 349 | .white_square { 350 | background-color: #deeaee; 351 | } 352 | .black_square { 353 | background-color: #034f84; 354 | } 355 | 356 | .top_left_square { 357 | border-radius: 8px 0px 0px 0px; 358 | } 359 | 360 | .top_right_square { 361 | border-radius: 0px 8px 0px 0px; 362 | } 363 | 364 | .bottom_left_square { 365 | border-radius: 0px 0px 0px 8px; 366 | } 367 | 368 | .bottom_right_square { 369 | border-radius: 0px 0px 8px 0px; 370 | } 371 | 372 | .selected_white_square { 373 | background-color: #00e686; 374 | } 375 | 376 | .selected_black_square { 377 | background-color: #00c070; 378 | } 379 | 380 | .highlighted_white_square { 381 | background-color: #94fffe; 382 | } 383 | 384 | .highlighted_black_square { 385 | background-color: #77e1d7; 386 | } 387 | 388 | .in_check_square_white { 389 | animation: flash-white 0.5s; 390 | background-color: #deeaee; 391 | } 392 | 393 | .in_check_square_black { 394 | animation: flash-black 0.5s; 395 | background-color: #034f84; 396 | } 397 | 398 | .checked_square { 399 | background-color: #ff4233; 400 | } 401 | 402 | @-webkit-keyframes flash-white { 403 | from, 404 | 50%, 405 | to { 406 | background-color: #ff4233; 407 | } 408 | 409 | 25%, 410 | 75% { 411 | background-color: #deeaee; 412 | } 413 | } 414 | 415 | @-webkit-keyframes flash-black { 416 | from, 417 | 50%, 418 | to { 419 | background-color: #ff4233; 420 | } 421 | 422 | 25%, 423 | 75% { 424 | background-color: #034f84; 425 | } 426 | } 427 | 428 | .stale_square { 429 | background-color: #ffac2c; 430 | } 431 | 432 | @media screen and (max-width: 1100px) { 433 | .right_screen { 434 | width: 100%; 435 | height: 100vmin; 436 | margin-left: 5px; 437 | } 438 | 439 | .left_screen { 440 | width: 100% !important; 441 | height: auto; 442 | } 443 | 444 | .row_label { 445 | top: calc(50% - 42vmin); 446 | left: calc(50% - 52vmin); 447 | } 448 | 449 | .col_label { 450 | top: calc(50% - 42vmin); 451 | left: calc(50% - 42vmin); 452 | } 453 | 454 | .table { 455 | top: calc(50% - 42vmin); 456 | left: calc(50% - 42vmin); 457 | } 458 | 459 | .side_box { 460 | width: 60%; 461 | top: calc(50% - 41vmin); 462 | left: 17%; 463 | margin: 40 0 20 0; 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /how_to_play.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | How to Play ReactJS Chess 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 19 | 20 |
21 |
22 |

How to Use ReactJS Chess

23 |

Hello, challenger! Welcome to this majestic realm. You are the white player, 24 | and your opponent is the black player (a rather unsociable bot). To play, click on the piece you want to move, 25 | and then click on the desired destination.

26 |

ReactJS Chess makes chess easy for novice players; when you select a piece, the game 27 | highlights valid destinations. You can also view previous moves by clicking on the arrow buttons, and you 28 | can even reset the game. But keep in mind that only a weakling would retreat!

29 |
30 |
31 |

Rules of Chess

32 |

Don't be a barbarian—learn the rules of chess! Visit 33 | this link 34 | to do just that. ReactJS Chess implements all of these rules, except for draws (other than stalemate), 35 | resigning, and time limits.

36 |
37 |

Hope you enjoy playing!

38 |
39 | 40 | 41 | 42 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /images/black_bishop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/black_bishop.png -------------------------------------------------------------------------------- /images/black_king.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/black_king.png -------------------------------------------------------------------------------- /images/black_knight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/black_knight.png -------------------------------------------------------------------------------- /images/black_pawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/black_pawn.png -------------------------------------------------------------------------------- /images/black_queen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/black_queen.png -------------------------------------------------------------------------------- /images/black_rook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/black_rook.png -------------------------------------------------------------------------------- /images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/favicon.png -------------------------------------------------------------------------------- /images/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/react.png -------------------------------------------------------------------------------- /images/white_bishop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/white_bishop.png -------------------------------------------------------------------------------- /images/white_king.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/white_king.png -------------------------------------------------------------------------------- /images/white_knight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/white_knight.png -------------------------------------------------------------------------------- /images/white_pawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/white_pawn.png -------------------------------------------------------------------------------- /images/white_queen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/white_queen.png -------------------------------------------------------------------------------- /images/white_rook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpansahoo/ReactJS-Chess/bd29ab18994b6d906ba513a1b67558536ef57785/images/white_rook.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ReactJS Chess 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 |
15 | 20 | 25 | 26 | 30 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /js/chess.js: -------------------------------------------------------------------------------- 1 | // return a square with the chess piece 2 | function Square(props) { 3 | if (props.value != null) { 4 | return ( 5 | 11 | ); 12 | } else { 13 | return ( 14 | 20 | ); 21 | } 22 | } 23 | 24 | class Board extends React.Component { 25 | // initialize the board 26 | constructor() { 27 | super(); 28 | this.state = { 29 | squares: initializeBoard(), 30 | source: -1, 31 | turn: "w", 32 | true_turn: "w", 33 | turn_num: 0, 34 | first_pos: null, 35 | second_pos: null, 36 | repetition: 0, 37 | white_king_has_moved: 0, 38 | black_king_has_moved: 0, 39 | left_black_rook_has_moved: 0, 40 | right_black_rook_has_moved: 0, 41 | left_white_rook_has_moved: 0, 42 | right_white_rook_has_moved: 0, 43 | passant_pos: 65, 44 | bot_running: 0, 45 | pieces_collected_by_white: [], 46 | pieces_collected_by_black: [], 47 | history: [initializeBoard()], 48 | history_num: 1, 49 | history_h1: [null], 50 | history_h2: [null], 51 | history_h3: [null], 52 | history_h4: [null], 53 | history_white_collection: [null], 54 | history_black_collection: [null], 55 | mated: false, 56 | move_made: false, 57 | capture_made: false, 58 | check_flash: false, 59 | viewing_history: false, 60 | just_clicked: false, 61 | }; 62 | } 63 | 64 | // reset the board 65 | reset() { 66 | if ( 67 | this.state.history_num - 1 == this.state.turn_num && 68 | this.state.turn == "b" && 69 | !this.state.mated 70 | ) 71 | return "cannot reset"; 72 | this.setState({ 73 | squares: initializeBoard(), 74 | source: -1, 75 | turn: "w", 76 | true_turn: "w", 77 | turn_num: 0, 78 | first_pos: null, 79 | second_pos: null, 80 | repetition: 0, 81 | white_king_has_moved: 0, 82 | black_king_has_moved: 0, 83 | left_black_rook_has_moved: 0, 84 | right_black_rook_has_moved: 0, 85 | left_white_rook_has_moved: 0, 86 | right_white_rook_has_moved: 0, 87 | passant_pos: 65, 88 | bot_running: 0, 89 | pieces_collected_by_white: [], 90 | pieces_collected_by_black: [], 91 | history: [initializeBoard()], 92 | history_num: 1, 93 | history_h1: [0], 94 | history_h2: [0], 95 | history_h3: [null], 96 | history_h4: [null], 97 | history_white_collection: [null], 98 | history_black_collection: [null], 99 | mated: false, 100 | move_made: false, 101 | capture_made: false, 102 | check_flash: false, 103 | viewing_history: false, 104 | just_clicked: false, 105 | }); 106 | } 107 | 108 | // full function for executing a move 109 | execute_move(player, squares, start, end) { 110 | let copy_squares = squares.slice(); 111 | 112 | // clear highlights 113 | copy_squares = clear_highlight(copy_squares).slice(); 114 | if (player == "w") { 115 | copy_squares = clear_possible_highlight(copy_squares).slice(); 116 | for (let j = 0; j < 64; j++) { 117 | // user has heeded warning 118 | if (copy_squares[j].ascii == "k") { 119 | copy_squares[j].in_check = 0; 120 | break; 121 | } 122 | } 123 | } 124 | 125 | // note if king or rook has moved (castling not allowed if these have moved) 126 | if (copy_squares[start].ascii == (player == "w" ? "k" : "K")) { 127 | if (player == "w") { 128 | this.setState({ 129 | white_king_has_moved: 1, 130 | }); 131 | } else { 132 | this.setState({ 133 | black_king_has_moved: 1, 134 | }); 135 | } 136 | } 137 | if (copy_squares[start].ascii == (player == "w" ? "r" : "R")) { 138 | if (start == (player == "w" ? 56 : 0)) { 139 | if (player == "w") { 140 | this.setState({ 141 | left_white_rook_has_moved: 1, 142 | }); 143 | } else { 144 | this.setState({ 145 | left_black_rook_has_moved: 1, 146 | }); 147 | } 148 | } else if (start == (player == "w" ? 63 : 7)) { 149 | if (player == "w") { 150 | this.setState({ 151 | right_white_rook_has_moved: 1, 152 | }); 153 | } else { 154 | this.setState({ 155 | right_black_rook_has_moved: 1, 156 | }); 157 | } 158 | } 159 | } 160 | 161 | // add captured pieces to collection 162 | const collection = 163 | player == "w" 164 | ? this.state.pieces_collected_by_white.slice() 165 | : this.state.pieces_collected_by_black.slice(); 166 | if (copy_squares[end].ascii != null) { 167 | collection.push(); 168 | this.setState({ 169 | capture_made: true, 170 | }); 171 | } 172 | if (copy_squares[start].ascii == (player == "w" ? "p" : "P")) { 173 | if (end - start == (player == "w" ? -9 : 7)) { 174 | // black going down to the left OR white going up to the left 175 | if (start - 1 == this.state.passant_pos) 176 | collection.push(); 177 | } else if (end - start == (player == "w" ? -7 : 9)) { 178 | // black going down to the right OR white going up to the right 179 | if (start + 1 == this.state.passant_pos) 180 | collection.push(); 181 | } 182 | } 183 | 184 | // make the move 185 | copy_squares = this.make_move(copy_squares, start, end).slice(); 186 | 187 | // en passant helper 188 | var passant_true = 189 | player == "w" 190 | ? copy_squares[end].ascii == "p" && 191 | start >= 48 && 192 | start <= 55 && 193 | end - start == -16 194 | : copy_squares[end].ascii == "P" && 195 | start >= 8 && 196 | start <= 15 && 197 | end - start == 16; 198 | let passant = passant_true ? end : 65; 199 | 200 | // highlight mate 201 | if (player == "w") { 202 | copy_squares = highlight_mate( 203 | "b", 204 | copy_squares, 205 | this.checkmate("b", copy_squares), 206 | this.stalemate("b", copy_squares) 207 | ).slice(); 208 | } else { 209 | copy_squares = highlight_mate( 210 | "w", 211 | copy_squares, 212 | this.checkmate("w", copy_squares), 213 | this.stalemate("w", copy_squares) 214 | ).slice(); 215 | } 216 | 217 | // adding state to history array 218 | const copy_history = this.state.history.slice(); 219 | const copy_history_h1 = this.state.history_h1.slice(); 220 | const copy_history_h2 = this.state.history_h2.slice(); 221 | const copy_history_h3 = this.state.history_h3.slice(); 222 | const copy_history_h4 = this.state.history_h4.slice(); 223 | const copy_white_collection = this.state.history_white_collection.slice(); 224 | const copy_black_collection = this.state.history_black_collection.slice(); 225 | copy_history.push(copy_squares); 226 | copy_history_h1.push(start); 227 | copy_history_h2.push(end); 228 | copy_white_collection.push( 229 | player == "w" ? collection : this.state.pieces_collected_by_white 230 | ); 231 | copy_black_collection.push( 232 | player == "b" ? collection : this.state.pieces_collected_by_black 233 | ); 234 | 235 | var isKing = 236 | copy_squares[end].ascii == "k" || copy_squares[end].ascii == "K"; 237 | if (isKing && Math.abs(end - start) == 2) { 238 | if (end == (copy_squares[end].ascii == "k" ? 62 : 6)) { 239 | copy_history_h3.push(end - 1); 240 | copy_history_h4.push(end + 1); 241 | } else if (end == (copy_squares[end].ascii == "k" ? 58 : 2)) { 242 | copy_history_h3.push(end + 1); 243 | copy_history_h4.push(end - 2); 244 | } 245 | } else { 246 | copy_history_h3.push(null); 247 | copy_history_h4.push(null); 248 | } 249 | 250 | let check_mated = 251 | this.checkmate("w", copy_squares) || this.checkmate("b", copy_squares); 252 | let stale_mated = 253 | (this.stalemate("w", copy_squares) && player == "b") || 254 | (this.stalemate("b", copy_squares) && player == "w"); 255 | 256 | this.setState({ 257 | passant_pos: passant, 258 | history: copy_history, 259 | history_num: this.state.history_num + 1, 260 | history_h1: copy_history_h1, 261 | history_h2: copy_history_h2, 262 | history_h3: copy_history_h3, 263 | history_h4: copy_history_h4, 264 | history_white_collection: copy_white_collection, 265 | history_black_collection: copy_black_collection, 266 | squares: copy_squares, 267 | source: -1, 268 | turn_num: this.state.turn_num + 1, 269 | mated: check_mated || stale_mated ? true : false, 270 | turn: player == "b" ? "w" : "b", 271 | true_turn: player == "b" ? "w" : "b", 272 | bot_running: player == "b" ? 0 : 1, 273 | move_made: true, 274 | }); 275 | 276 | // set state 277 | if (player == "b") { 278 | this.setState({ 279 | first_pos: start, 280 | second_pos: end, 281 | pieces_collected_by_black: collection, 282 | }); 283 | } else { 284 | this.setState({ 285 | pieces_collected_by_white: collection, 286 | }); 287 | } 288 | } 289 | 290 | // make a move 291 | make_move(squares, start, end, passant_pos) { 292 | const copy_squares = squares.slice(); 293 | // castling 294 | var isKing = 295 | copy_squares[start].ascii == "k" || copy_squares[start].ascii == "K"; 296 | if (isKing && Math.abs(end - start) == 2) { 297 | if (end == (copy_squares[start].ascii == "k" ? 62 : 6)) { 298 | copy_squares[end - 1] = copy_squares[end + 1]; 299 | copy_squares[end - 1].highlight = 1; 300 | copy_squares[end + 1] = new filler_piece(null); 301 | copy_squares[end + 1].highlight = 1; 302 | } else if (end == (copy_squares[start].ascii == "k" ? 58 : 2)) { 303 | copy_squares[end + 1] = copy_squares[end - 2]; 304 | copy_squares[end + 1].highlight = 1; 305 | copy_squares[end - 2] = new filler_piece(null); 306 | copy_squares[end - 2].highlight = 1; 307 | } 308 | } 309 | 310 | // en passant 311 | var passant = passant_pos == null ? this.state.passant_pos : passant_pos; 312 | if (copy_squares[start].ascii.toLowerCase() == "p") { 313 | if (end - start == -7 || end - start == 9) { 314 | // white going up to the right 315 | if (start + 1 == passant) 316 | copy_squares[start + 1] = new filler_piece(null); 317 | } else if (end - start == -9 || end - start == 7) { 318 | // white going up to the left 319 | if (start - 1 == passant) 320 | copy_squares[start - 1] = new filler_piece(null); 321 | } 322 | } 323 | 324 | // make the move 325 | copy_squares[end] = copy_squares[start]; 326 | copy_squares[end].highlight = 1; 327 | copy_squares[start] = new filler_piece(null); 328 | copy_squares[start].highlight = 1; 329 | 330 | // pawn promotion 331 | if (copy_squares[end].ascii == "p" && end >= 0 && end <= 7) { 332 | copy_squares[end] = new Queen("w"); 333 | copy_squares[end].highlight = 1; 334 | } 335 | if (copy_squares[end].ascii == "P" && end >= 56 && end <= 63) { 336 | copy_squares[end] = new Queen("b"); 337 | copy_squares[end].highlight = 1; 338 | } 339 | 340 | return copy_squares; 341 | } 342 | 343 | // returns true if castling is allowed 344 | castling_allowed(start, end, squares) { 345 | const copy_squares = squares.slice(); 346 | var player = copy_squares[start].player; 347 | var delta_pos = end - start; 348 | if (start != (player == "w" ? 60 : 4)) return false; 349 | if ( 350 | (delta_pos == 2 351 | ? copy_squares[end + 1].ascii 352 | : copy_squares[end - 2].ascii) != (player == "w" ? "r" : "R") 353 | ) 354 | return false; 355 | if ( 356 | (player == "w" 357 | ? this.state.white_king_has_moved 358 | : this.state.black_king_has_moved) != 0 359 | ) 360 | return false; 361 | if (player == "w") { 362 | if ( 363 | (delta_pos == 2 364 | ? this.state.right_white_rook_has_moved 365 | : this.state.left_white_rook_has_moved) != 0 366 | ) 367 | return false; 368 | } else if (player == "b") { 369 | if ( 370 | (delta_pos == 2 371 | ? this.state.right_black_rook_has_moved 372 | : this.state.left_black_rook_has_moved) != 0 373 | ) 374 | return false; 375 | } 376 | 377 | return true; 378 | } 379 | // returns true if a piece is trying to skip over another piece 380 | blockers_exist(start, end, squares) { 381 | var start_row = 8 - Math.floor(start / 8); 382 | var start_col = (start % 8) + 1; 383 | var end_row = 8 - Math.floor(end / 8); 384 | var end_col = (end % 8) + 1; 385 | let row_diff = end_row - start_row; 386 | let col_diff = end_col - start_col; 387 | let row_ctr = 0; 388 | let col_ctr = 0; 389 | const copy_squares = squares.slice(); 390 | 391 | // return true if the piece in question is skipping over a piece 392 | while (col_ctr != col_diff || row_ctr != row_diff) { 393 | let position = 394 | 64 - start_row * 8 + -8 * row_ctr + (start_col - 1 + col_ctr); 395 | if ( 396 | copy_squares[position].ascii != null && 397 | copy_squares[position] != copy_squares[start] 398 | ) 399 | return true; 400 | if (col_ctr != col_diff) { 401 | if (col_diff > 0) { 402 | ++col_ctr; 403 | } else { 404 | --col_ctr; 405 | } 406 | } 407 | if (row_ctr != row_diff) { 408 | if (row_diff > 0) { 409 | ++row_ctr; 410 | } else { 411 | --row_ctr; 412 | } 413 | } 414 | } 415 | return false; 416 | } 417 | // return true if pawn is not breaking any of its rules 418 | good_pawn(start, end, squares, passant_pos) { 419 | var passant = passant_pos == null ? this.state.passant_pos : passant_pos; 420 | var start_row = 8 - Math.floor(start / 8); 421 | var start_col = (start % 8) + 1; 422 | var end_row = 8 - Math.floor(end / 8); 423 | var end_col = (end % 8) + 1; 424 | var row_diff = end_row - start_row; 425 | var col_diff = end_col - start_col; 426 | const copy_squares = squares.slice(); 427 | 428 | // only allow 2 space move if the pawn is in the start position 429 | if (row_diff == 2 || row_diff == -2) { 430 | if (copy_squares[start].player == "w" && (start < 48 || start > 55)) 431 | return false; 432 | if (copy_squares[start].player == "b" && (start < 8 || start > 15)) 433 | return false; 434 | } 435 | // cannot move up/down if there is a piece 436 | if (copy_squares[end].ascii != null) { 437 | if (col_diff == 0) return false; 438 | } 439 | // cannot move diagonally if there is no piece to capture UNLESS it's en passant 440 | if (row_diff == 1 && col_diff == 1) { 441 | // white going up and right 442 | if (copy_squares[end].ascii == null) { 443 | if (copy_squares[start + 1].ascii != "P" || passant != start + 1) 444 | return false; 445 | } 446 | } else if (row_diff == 1 && col_diff == -1) { 447 | // white going up and left 448 | if (copy_squares[end].ascii == null) { 449 | if (copy_squares[start - 1].ascii != "P" || passant != start - 1) 450 | return false; 451 | } 452 | } else if (row_diff == -1 && col_diff == 1) { 453 | // black going down and right 454 | if (copy_squares[end].ascii == null) { 455 | if (copy_squares[start + 1].ascii != "p" || passant != start + 1) 456 | return false; 457 | } 458 | } else if (row_diff == -1 && col_diff == -1) { 459 | // black going down and left 460 | if (copy_squares[end].ascii == null) { 461 | if (copy_squares[start - 1].ascii != "p" || passant != start - 1) 462 | return false; 463 | } 464 | } 465 | 466 | return true; 467 | } 468 | // return true if move from start to end is illegal 469 | invalid_move(start, end, squares, passant_pos) { 470 | const copy_squares = squares.slice(); 471 | // if the piece is a bishop, queen, rook, or pawn, 472 | // it cannot skip over pieces 473 | var bqrpk = 474 | copy_squares[start].ascii.toLowerCase() == "r" || 475 | copy_squares[start].ascii.toLowerCase() == "q" || 476 | copy_squares[start].ascii.toLowerCase() == "b" || 477 | copy_squares[start].ascii.toLowerCase() == "p" || 478 | copy_squares[start].ascii.toLowerCase() == "k"; 479 | let invalid = 480 | bqrpk == true && this.blockers_exist(start, end, copy_squares) == true; 481 | if (invalid) return invalid; 482 | // checking for certain rules regarding the pawn 483 | var pawn = copy_squares[start].ascii.toLowerCase() == "p"; 484 | invalid = 485 | pawn == true && 486 | this.good_pawn(start, end, copy_squares, passant_pos) == false; 487 | if (invalid) return invalid; 488 | // checking for if castling is allowed 489 | var king = copy_squares[start].ascii.toLowerCase() == "k"; 490 | if (king && Math.abs(end - start) == 2) 491 | invalid = this.castling_allowed(start, end, copy_squares) == false; 492 | 493 | return invalid; 494 | } 495 | // returns true if there are any possible moves 496 | can_move_there(start, end, squares, passant_pos) { 497 | const copy_squares = squares.slice(); 498 | if (start == end) 499 | // cannot move to the position you're already sitting in 500 | return false; 501 | 502 | // player cannot capture her own piece 503 | // and piece must be able to physically move from start to end 504 | var player = copy_squares[start].player; 505 | if ( 506 | player == copy_squares[end].player || 507 | copy_squares[start].can_move(start, end) == false 508 | ) 509 | return false; 510 | // player cannot make an invalid move 511 | if (this.invalid_move(start, end, copy_squares, passant_pos) == true) 512 | return false; 513 | 514 | // cannot castle if in check 515 | var cant_castle = 516 | copy_squares[start].ascii == (player == "w" ? "k" : "K") && 517 | Math.abs(end - start) == 2 && 518 | this.in_check(player, copy_squares); 519 | if (cant_castle) return false; 520 | 521 | // king cannot castle through check 522 | if ( 523 | copy_squares[start].ascii == (player == "w" ? "k" : "K") && 524 | Math.abs(end - start) == 2 525 | ) { 526 | var delta_pos = end - start; 527 | const test_squares = squares.slice(); 528 | test_squares[start + (delta_pos == 2 ? 1 : -1)] = test_squares[start]; 529 | test_squares[start] = new filler_piece(null); 530 | if (this.in_check(player, test_squares)) return false; 531 | } 532 | 533 | // player cannot put or keep herself in check 534 | const check_squares = squares.slice(); 535 | check_squares[end] = check_squares[start]; 536 | check_squares[start] = new filler_piece(null); 537 | if (check_squares[end].ascii == "p" && end >= 0 && end <= 7) { 538 | check_squares[end] = new Queen("w"); 539 | } else if (check_squares[end].ascii == "P" && end >= 56 && end <= 63) { 540 | check_squares[end] = new Queen("b"); 541 | } 542 | if (this.in_check(player, check_squares) == true) return false; 543 | 544 | return true; 545 | } 546 | 547 | // returns true if player is in check 548 | in_check(player, squares) { 549 | let king = player == "w" ? "k" : "K"; 550 | let position_of_king = null; 551 | const copy_squares = squares.slice(); 552 | for (let i = 0; i < 64; i++) { 553 | if (copy_squares[i].ascii == king) { 554 | position_of_king = i; 555 | break; 556 | } 557 | } 558 | 559 | // traverse through the board and determine 560 | // any of the opponent's pieces can legally take the player's king 561 | for (let i = 0; i < 64; i++) { 562 | if (copy_squares[i].player != player) { 563 | if ( 564 | copy_squares[i].can_move(i, position_of_king) == true && 565 | this.invalid_move(i, position_of_king, copy_squares) == false 566 | ) 567 | return true; 568 | } 569 | } 570 | return false; 571 | } 572 | // return true if player is in stalemate 573 | stalemate(player, squares) { 574 | if (this.in_check(player, squares)) return false; 575 | 576 | // if there is even only 1 way to move her piece, 577 | // the player is not in stalemate 578 | for (let i = 0; i < 64; i++) { 579 | if (squares[i].player == player) { 580 | for (let j = 0; j < 64; j++) { 581 | if (this.can_move_there(i, j, squares)) return false; 582 | } 583 | } 584 | } 585 | return true; 586 | } 587 | // return true if player is in checkmate 588 | checkmate(player, squares) { 589 | if (!this.in_check(player, squares)) return false; 590 | // if there is even only 1 way to move her piece, 591 | // the player is not in checkmate 592 | for (let i = 0; i < 64; i++) { 593 | if (squares[i].player == player) { 594 | for (let j = 0; j < 64; j++) { 595 | if (this.can_move_there(i, j, squares)) return false; 596 | } 597 | } 598 | } 599 | return true; 600 | } 601 | 602 | // helper function for minimax: calculate black's status using piece values 603 | evaluate_black(squares) { 604 | let total_eval = 0; 605 | for (let i = 0; i < 64; i++) total_eval += get_piece_value(squares[i], i); 606 | return total_eval; 607 | } 608 | // helper function for execute_bot: minimax algorithm for chess bot 609 | minimax( 610 | depth, 611 | is_black_player, 612 | alpha, 613 | beta, 614 | squares, 615 | RA_of_starts, 616 | RA_of_ends, 617 | passant_pos 618 | ) { 619 | const copy_squares = squares.slice(); 620 | if (depth == 0) { 621 | return this.evaluate_black(copy_squares); 622 | } 623 | 624 | let best_value = is_black_player ? -9999 : 9999; 625 | // iterate through the possible start positions 626 | for (let i = 0; i < 64; i++) { 627 | let start = RA_of_starts[i]; 628 | let isPlayerPiece = 629 | copy_squares[start].ascii != null && 630 | copy_squares[start].player == (is_black_player ? "b" : "w"); 631 | 632 | // start should be the position of a piece owned by the player 633 | if (isPlayerPiece) { 634 | /* iterate through the possible end positions for each possible start position 635 | * and use recursion to see what the value of each possible move will be a few moves 636 | * down the road. if the move being analyzed is black's turn, the value will maximize 637 | * best_value; but if the move being analyzed is white's turn, the value will minimize 638 | * best_value 639 | */ 640 | for (let j = 0; j < 64; j++) { 641 | let end = RA_of_ends[j]; 642 | if ( 643 | this.can_move_there(start, end, copy_squares, passant_pos) == true 644 | ) { 645 | const test_squares = squares.slice() 646 | // make the move on test board 647 | const test_squares_2 = this.make_move( 648 | test_squares, 649 | start, 650 | end, 651 | passant_pos 652 | ).slice() 653 | // en passant helper 654 | var passant = 65; 655 | if ( 656 | test_squares[end].ascii == (is_black_player ? "P" : "p") && 657 | start >= (is_black_player ? 8 : 48) && 658 | start <= (is_black_player ? 15 : 55) && 659 | end - start == (is_black_player ? 16 : -16) 660 | ) { 661 | passant = end; 662 | } 663 | 664 | // black player maximizes value, white player minimizes value 665 | let value = this.minimax( 666 | depth - 1, 667 | !is_black_player, 668 | alpha, 669 | beta, 670 | test_squares_2, 671 | RA_of_starts, 672 | RA_of_ends, 673 | passant 674 | ); 675 | if (is_black_player) { 676 | if (value > best_value) best_value = value; 677 | alpha = Math.max(alpha, best_value); //alpha-beta pruning 678 | if (best_value >= beta) return best_value; 679 | } else { 680 | if (value < best_value) best_value = value; 681 | beta = Math.min(beta, best_value); //alpha-beta pruning 682 | if (best_value <= alpha) return best_value; 683 | } 684 | } 685 | } 686 | } 687 | } 688 | 689 | return best_value; 690 | } 691 | // Chess bot for black player 692 | execute_bot(depth, passed_in_squares) { 693 | if (this.state.mated) return "bot cannot run"; 694 | const copy_squares = passed_in_squares.slice(); 695 | let rand_start = 100; 696 | let rand_end = 100; 697 | let RA_of_starts = []; 698 | let RA_of_ends = []; 699 | for (let i = 0; i < 64; i++) { 700 | RA_of_starts.push(i); 701 | RA_of_ends.push(i); 702 | } 703 | RA_of_starts = shuffle(RA_of_starts); 704 | RA_of_ends = shuffle(RA_of_ends); 705 | 706 | // create array of possible moves 707 | let moves = []; 708 | for (let i = 0; i < 64; i++) { 709 | let start = RA_of_starts[i]; 710 | let isBlackPiece = 711 | copy_squares[start].ascii != null && copy_squares[start].player == "b"; 712 | if (isBlackPiece) { 713 | for (let j = 0; j < 64; j++) { 714 | let end = RA_of_ends[j]; 715 | if (this.can_move_there(start, end, copy_squares) == true) { 716 | moves.push(start); 717 | moves.push(end); 718 | } 719 | } 720 | } 721 | } 722 | 723 | let best_value = -9999; 724 | /* iterate through the possible movements and choose the movement from start to end that results in the best 725 | * position for black in terms of value calculated by evaluate_black; minimax algo lets bot look ahead a few 726 | * moves and thereby pick the move that results in the best value in the long run 727 | */ 728 | for (let i = 0; i < moves.length; i += 2) { 729 | let start = moves[i]; 730 | let end = moves[i + 1]; 731 | // 3-fold repetiton by bot NOT ALLOWED if there are other move options 732 | if ( 733 | moves.length > 2 && 734 | this.state.repetition >= 2 && 735 | start == this.state.second_pos && 736 | end == this.state.first_pos 737 | ) { 738 | this.setState({ 739 | repetition: 0, 740 | }); 741 | } else { 742 | const test_squares = passed_in_squares.slice(); 743 | // make the move 744 | const test_squares_2 = this.make_move(test_squares, start, end).slice(); 745 | // en passant helper 746 | var passant_pos = 65; 747 | if ( 748 | test_squares[start].ascii == "P" && 749 | start >= 8 && 750 | start <= 15 && 751 | end - start == 16 752 | ) 753 | passant_pos = end; 754 | 755 | // board evaluation using mini_max algorithm by looking at future turns 756 | let board_eval = this.minimax( 757 | depth - 1, 758 | false, 759 | -1000, 760 | 1000, 761 | test_squares_2, 762 | RA_of_starts, 763 | RA_of_ends, 764 | passant_pos 765 | ); 766 | if (board_eval >= best_value) { 767 | best_value = board_eval; 768 | rand_start = start; 769 | rand_end = end; 770 | } 771 | } 772 | } 773 | 774 | if (rand_end != 100) { 775 | // rand_end == 100 indicates that black is in checkmate/stalemate 776 | // increment this.state.repetition if black keeps moving a piece back and forth consecutively 777 | if ( 778 | rand_start == this.state.second_pos && 779 | rand_end == this.state.first_pos 780 | ) { 781 | let reps = this.state.repetition + 1; 782 | this.setState({ 783 | repetition: reps, 784 | }); 785 | } else { 786 | this.setState({ 787 | repetition: 0, 788 | }); 789 | } 790 | 791 | this.execute_move("b", copy_squares, rand_start, rand_end); 792 | } 793 | } 794 | 795 | // handle user action of clicking square on board 796 | handleClick(i) { 797 | let copy_squares = this.state.squares.slice(); 798 | 799 | if (this.state.history_num - 1 != this.state.turn_num) { 800 | return "currently viewing history"; 801 | } 802 | 803 | if (this.state.mated) return "game-over"; 804 | 805 | // first click 806 | if (this.state.source == -1 && this.state.bot_running == 0) { 807 | // no source has been selected yet 808 | // can only pick a piece that is your own 809 | if (copy_squares[i].player != this.state.turn) return -1; 810 | 811 | //can only pick a piece that is not a blank square 812 | if (copy_squares[i].player != null) { 813 | this.setState({ 814 | check_flash: false, 815 | just_clicked: false, 816 | move_made: false, 817 | capture_made: false, 818 | viewing_history: false, 819 | }); 820 | 821 | copy_squares = clear_check_highlight(copy_squares, "w").slice(); 822 | copy_squares[i].highlight = 1; // highlight selected piece 823 | 824 | // highlight legal moves 825 | for (let j = 0; j < 64; j++) { 826 | if (this.can_move_there(i, j, copy_squares)) 827 | copy_squares[j].possible = 1; 828 | } 829 | 830 | this.setState({ 831 | source: i, // set the source to the first click 832 | squares: copy_squares, 833 | }); 834 | } 835 | } 836 | 837 | // second click (to move piece from the source to destination) 838 | if (this.state.source > -1) { 839 | var cannibalism = copy_squares[i].player == this.state.turn; 840 | /* if user is trying to select one of her other pieces, 841 | * change highlight to the new selection, but do not move any pieces 842 | */ 843 | if (cannibalism == true && this.state.source != i) { 844 | copy_squares[i].highlight = 1; 845 | copy_squares[this.state.source].highlight = 0; 846 | copy_squares = clear_possible_highlight(copy_squares).slice(); 847 | for (let j = 0; j < 64; j++) { 848 | if (this.can_move_there(i, j, copy_squares)) 849 | copy_squares[j].possible = 1; 850 | } 851 | this.setState({ 852 | source: i, // set source to the new clicks 853 | squares: copy_squares, 854 | }); 855 | } else { 856 | // user is trying to move her piece to empty space or to capture opponent's piece 857 | if (!this.can_move_there(this.state.source, i, copy_squares)) { 858 | // un-highlight selection if invalid move was attempted 859 | copy_squares[this.state.source].highlight = 0; 860 | copy_squares = clear_possible_highlight(copy_squares).slice(); 861 | // if user is in check, highlight king in red if user tries a move that doesn't get her 862 | // out of check 863 | if ( 864 | i != this.state.source && 865 | this.in_check("w", copy_squares) == true 866 | ) { 867 | for (let j = 0; j < 64; j++) { 868 | if (copy_squares[j].ascii == "k") { 869 | copy_squares[j].in_check = 1; 870 | break; 871 | } 872 | } 873 | this.setState({ 874 | check_flash: true, 875 | }); 876 | } 877 | this.setState({ 878 | source: -1, 879 | squares: copy_squares, 880 | }); 881 | return "invalid move"; 882 | } 883 | 884 | this.execute_move("w", copy_squares, this.state.source, i); 885 | 886 | setTimeout(() => { 887 | this.setState({ 888 | move_made: false, 889 | capture_made: false, 890 | }); 891 | }, 200); 892 | 893 | // chess bot for black player 894 | let search_depth = 3; 895 | setTimeout(() => { 896 | this.execute_bot(search_depth, this.state.squares); 897 | }, 700); 898 | } 899 | } 900 | } 901 | 902 | // Render the page 903 | render() { 904 | const row_nums = []; 905 | for (let i = 8; i > 0; i--) { 906 | row_nums.push(