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();
907 | }
908 | const col_nums = [];
909 | for (let i = 1; i < 9; i++) {
910 | let letter;
911 | switch (i) {
912 | case 1:
913 | letter = "A";
914 | break;
915 | case 2:
916 | letter = "B";
917 | break;
918 | case 3:
919 | letter = "C";
920 | break;
921 | case 4:
922 | letter = "D";
923 | break;
924 | case 5:
925 | letter = "E";
926 | break;
927 | case 6:
928 | letter = "F";
929 | break;
930 | case 7:
931 | letter = "G";
932 | break;
933 | case 8:
934 | letter = "H";
935 | break;
936 | }
937 | col_nums.push();
938 | }
939 |
940 | const board = [];
941 | for (let i = 0; i < 8; i++) {
942 | const squareRows = [];
943 | for (let j = 0; j < 8; j++) {
944 | let square_corner = null;
945 | if (i == 0 && j == 0) {
946 | square_corner = " top_left_square ";
947 | } else if (i == 0 && j == 7) {
948 | square_corner = " top_right_square ";
949 | } else if (i == 7 && j == 0) {
950 | square_corner = " bottom_left_square ";
951 | } else if (i == 7 && j == 7) {
952 | square_corner = " bottom_right_square ";
953 | } else {
954 | square_corner = " ";
955 | }
956 |
957 | const copy_squares = this.state.squares.slice();
958 | let square_color = calc_squareColor(i, j, copy_squares);
959 | let square_cursor = "pointer";
960 | if (copy_squares[i * 8 + j].player != "w") square_cursor = "default";
961 | if (this.state.bot_running == 1 && !this.state.mated)
962 | square_cursor = "bot_running";
963 | if (this.state.mated) square_cursor = "default";
964 | if (this.state.history_num - 1 != this.state.turn_num)
965 | square_cursor = "not_allowed";
966 |
967 | squareRows.push(
968 | this.handleClick(i * 8 + j)}
975 | />
976 | );
977 | }
978 | board.push(