1use std::cmp::PartialEq;
2use std::collections::HashMap;
3use serde::Serialize;
4use crate::Color::{BLACK, WHITE};
5use crate::Move::{CAPTURE, REGULAR};
6use crate::Status::{BLACK_TO_MOVE, WHITE_TO_MOVE};
7use std::io::*;
8
9const STARTING_FEN: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
10
11#[derive(PartialEq, Debug)]
12pub enum Status {
13 WHITE_TO_MOVE,
14 BLACK_TO_MOVE,
15 DRAW,
16 WHITE_HAS_CHECKMATE,
17 BLACK_HAS_CHECKMATE,
18}
19
20pub type ChessBoard = Vec<Vec<char>>;
21
22#[derive(Debug)]
23pub struct Board {
24 pub board: ChessBoard,
25}
26
27impl Board {
28 pub fn create() -> Board {
29 Board {
30 board: convert_fen_to_vector(STARTING_FEN),
31 }
32 }
33
34 pub fn create_from_fen(FEN: &str) -> Board {
35 Board {
36 board: convert_fen_to_vector(FEN),
37 }
38 }
39
40 pub fn get(&self, rank: usize, file: usize) -> char {
41 self.board[rank][file]
42 }
43
44 pub fn pushRow(&mut self, row: Vec<char>) {
45 self.board.push(row);
46 }
47
48 pub fn clone(&self) -> Self {
49 Board {
51 board: self.board.clone(), }
53 }
54
55 pub fn make_move(board: &Board, start: Position, end: &Position, piece: char) -> Board {
56 let mut new_board = board.clone();
57 new_board.board[start.rank][start.file] = '-';
58 new_board.board[end.rank][end.file] = piece;
59 new_board
60 }
61 pub fn print(&self) {
172 for row in &self.board {
173 println!("{:?}", row);
174 }
175 }
176
177 }
209
210#[derive(Serialize)]
211#[derive(Debug)]
212#[derive(Eq, Hash, PartialEq)]
213pub struct Position {
214 rank: usize,
215 file: usize
216}
217
218impl Position {
219 pub fn create(rank: usize, file: usize) -> Position {
220 Position {
221 rank,
222 file
223 }
224 }
225}
226
227#[derive(PartialEq, Debug)]
228pub enum Color {
229 WHITE,
230 BLACK
231}
232
233pub enum Move {
234 REGULAR,
235 CAPTURE,
236 CASTLE
237}
238pub struct Game {
239 board: Board,
240 status: Status,
241 current_move: Move,
242 white_castle_short: bool,
243 white_castle_long: bool,
244 black_castle_short: bool,
245 black_castle_long: bool,
246 en_passant_possible: bool,
247}
248
249impl Game {
250 pub fn new() -> Game {
251 Game {
252 status: WHITE_TO_MOVE,
253 current_move: REGULAR,
254 white_castle_short: true,
255 white_castle_long: true,
256 black_castle_short: true,
257 black_castle_long: true,
258 en_passant_possible: false,
259 board: Board::create(),
260 }
261 }
262}
263
264pub fn convert_fen_to_vector(fen: &str) -> ChessBoard {
265 let mut board: ChessBoard = Vec::new();
266 let position = fen.split(" ").collect::<Vec<&str>>()[0];
267
268 for rank in position.split("/").collect::<Vec<&str>>() {
269 let mut row_vector = Vec::new();
270
271 for square in rank.chars() {
272 match square.to_digit(10) {
273 Some(num) => {
274 let num = num as i32;
275
276 for _i in 0..num {
277 row_vector.push('-');
278 }
279 }
280
281 None => row_vector.push(square)
282 }
283 }
284
285 board.push(row_vector);
286 }
287
288 board
289}
290
291fn get_piece_from_position(board: &Board, piece_pos: &Position) -> char {
292 board.get(piece_pos.rank, piece_pos.file)
293}
294
295fn get_castling_position(game: &Game) -> Vec<Position> {
296 let mut positons = Vec::new();
297
298 if game.status == WHITE_TO_MOVE {
299 if game.white_castle_short {
300 if game.board.get(7, 5) == '-' && game.board.get(7, 6) == '-' {
301 positons.push(Position {
302 rank: 7,
303 file: 6
304 })
305 }
306 }
307
308 if game.white_castle_short {
309 if game.board.get(7, 1) == '-' && game.board.get(7, 2) == '-' && game.board.get(7, 3) == '-' {
310 positons.push(Position {
311 rank: 7,
312 file: 2
313 })
314 }
315 }
316 }
317
318 else {
319 if game.black_castle_short {
320 if game.board.get(0, 5) == '-' && game.board.get(0, 6) == '-' {
321 positons.push(Position {
322 rank: 0,
323 file: 6
324 })
325 }
326 }
327
328 if game.black_castle_long {
329 if game.board.get(0, 1) == '-' && game.board.get(0, 2) == '-' && game.board.get(0, 3) == '-' {
330 positons.push(Position {
331 rank: 0,
332 file: 2
333 })
334 }
335 }
336 }
337
338 positons
339}
340
341fn has_enemy_piece(board: &Board, pos: &Position, current_piece: char) -> bool {
342 let piece = get_piece_from_position(&board, &pos);
343
344 if current_piece.is_uppercase() && piece.is_lowercase() {
345 return true;
346 }
347
348 if current_piece.is_lowercase() && piece.is_uppercase() {
349 return true;
350 }
351
352 false
353}
354
355fn get_pawn_capture_pos(board: &Board, pawn_pos: &Position, pawn: char) -> Vec<Position> {
356 let mut target: Vec<Position> = Vec::new();
357 let mut target_rank = 0;
358
359 if pawn.is_lowercase() {
360 target_rank = pawn_pos.rank + 1;
361 }
362
363 else {
364 target_rank = pawn_pos.rank - 1;
365 }
366
367 if (pawn_pos.file > 0) {
368 let square2 = pawn_pos.file - 1;
369 let target2 = Position::create(target_rank, square2);
370 let has_enemy2 = has_enemy_piece(&board, &target2, pawn);
371
372 if pawn_pos.file == 7 && has_enemy2 {
373 target.push(Position::create(target_rank, square2));
374 }
375
376 else if has_enemy2 {
377 target.push(Position::create(target_rank, square2))
378 }
379 }
380
381 if (pawn_pos.file < 7) {
382 let square1 = pawn_pos.file + 1;
383 let target1 = Position::create(target_rank, square1);
384 let has_enemy1 = has_enemy_piece(&board, &target1, pawn);
385
386 if pawn_pos.file == 0 && has_enemy1 {
387 target.push(Position::create(target_rank, square1));
388 }
389
390 else if has_enemy1 {
391 target.push(Position::create(target_rank, square1));
392 }
393 }
394
395 target
396}
397
398fn get_horizontal_moves(board: &Board, piece_pos: &Position) -> Vec<Position> {
399 let mut positions: Vec<Position> = Vec::new();
400 let rank = piece_pos.rank;
401 let mut file = piece_pos.file;
402
403 loop {
404 if file == 7 {
405 if rank != piece_pos.rank || file != piece_pos.file {
406 positions.push(Position::create(rank, 7));
407 }
408
409 file = piece_pos.file;
410
411 break;
412 }
413
414 if rank == piece_pos.rank && file == piece_pos.file {
415 file += 1;
416 continue;
417 }
418
419 if board.get(rank, file) != '-' {
420 if is_enemy(board, piece_pos, &Position::create(rank, file)) {
421 positions.push(Position::create(rank, file));
422 }
423
424 file = piece_pos.file;
425
426 break;
427 }
428
429 positions.push(Position::create(rank, file));
430
431 file += 1;
432 }
433
434 loop {
435 if file == 0 {
436 if rank != piece_pos.rank || file != piece_pos.file {
437 positions.push(Position::create(rank, 0));
438 }
439
440 file = piece_pos.file;
441
442 break;
443 }
444
445 if rank == piece_pos.rank && file == piece_pos.file {
446 file -= 1;
447 continue;
448 }
449
450 if board.get(rank, file) != '-' {
451 if is_enemy(board, piece_pos, &Position::create(rank, file)) {
452 positions.push(Position::create(rank, file));
453 }
454
455 file = piece_pos.file;
456
457 break;
458 }
459
460 positions.push(Position::create(rank, file));
461
462 file -= 1;
463 }
464
465 positions
466}
467
468fn is_enemy(board: &Board, current_pos: &Position, target_pos: &Position) -> bool {
469 let current_piece = board.get(current_pos.rank, current_pos.file);
470
471 if current_piece.is_lowercase() {
472 if board.get(target_pos.rank, target_pos.file).is_uppercase() {
473 return true;
474 }
475 }
476
477 else {
478 if board.get(target_pos.rank, target_pos.file).is_lowercase() {
479 return true;
480 }
481 }
482
483 false
484}
485
486fn get_vertical_moves(board: &Board, piece_pos: &Position) -> Vec<Position> {
487 let mut positions: Vec<Position> = Vec::new();
488 let mut rank = piece_pos.rank;
489 let file = piece_pos.file;
490
491 loop {
492 if rank == 7 {
493 if file != piece_pos.file || rank != piece_pos.rank {
494 positions.push(Position::create(7, file));
495 }
496
497 rank = piece_pos.rank;
498
499 break;
500 }
501
502 if rank == piece_pos.rank && file == piece_pos.file {
503 rank += 1;
504 continue;
505 }
506
507 if board.get(rank, file) != '-' {
508 if is_enemy(board, piece_pos, &Position::create(rank, file)) {
509 positions.push(Position::create(rank, file));
510 }
511
512 rank = piece_pos.rank;
513
514 break;
515 }
516
517 positions.push(Position::create(rank, file));
518
519 rank += 1;
520 }
521
522 loop {
523 if rank == 0 {
524 if file != piece_pos.file || rank != piece_pos.rank {
525 positions.push(Position::create(rank, file));
526 }
527
528 rank = piece_pos.rank;
529
530 break;
531 }
532
533 if rank == piece_pos.rank && file == piece_pos.file {
534 rank -= 1;
535 continue;
536 }
537
538 if board.get(rank, file) != '-' {
539 if is_enemy(board, piece_pos, &Position::create(rank, file)) {
540 positions.push(Position::create(rank, file));
541 }
542
543 rank = piece_pos.rank;
544
545 break;
546 }
547
548 positions.push(Position::create(rank, file));
549
550 rank -= 1;
551 }
552
553 positions
554}
555
556fn get_diagonal_moves(board: &Board, piece_pos: &Position) -> Vec<Position> {
557 let mut positions: Vec<Position> = Vec::new();
558 let mut rank = piece_pos.rank;
559 let mut file = piece_pos.file;
560
561 if rank > 0 && file > 0 {
562 loop {
563 if board.get(rank - 1, file - 1) != '-' {
564 if is_enemy(board, piece_pos, &Position::create(rank - 1, file - 1)) {
565 positions.push(Position::create(rank - 1, file - 1));
566 }
567
568 rank = piece_pos.rank;
569 file = piece_pos.file;
570 break;
571 }
572
573 positions.push(Position::create(rank - 1, file - 1));
574
575 rank = rank - 1;
576 file = file - 1;
577
578 if rank == 0 || file == 0 {
579 rank = piece_pos.rank;
580 file = piece_pos.file;
581 break;
582 }
583 }
584 }
585
586 if rank > 0 && file < 7 {
587 loop {
588 if board.get(rank - 1, file + 1) != '-' {
589 if is_enemy(board, piece_pos, &Position::create(rank - 1, file + 1)) {
590 positions.push(Position::create(rank - 1, file + 1));
591 }
592
593 rank = piece_pos.rank;
594 file = piece_pos.file;
595 break;
596 }
597
598 positions.push(Position::create(rank - 1, file + 1));
599
600 rank = rank - 1;
601 file = file + 1;
602
603 if rank == 0 || file == 7 {
604 rank = piece_pos.rank;
605 file = piece_pos.file;
606 break;
607 }
608 }
609 }
610
611 if rank < 7 && file > 0 {
612 loop {
613 if board.get(rank + 1, file - 1) != '-' {
614 if is_enemy(board, piece_pos, &Position::create(rank + 1, file - 1)) {
615 positions.push(Position::create(rank + 1, file - 1));
616 }
617
618 rank = piece_pos.rank;
619 file = piece_pos.file;
620 break;
621 }
622
623 positions.push(Position::create(rank + 1, file - 1));
624
625 rank = rank + 1;
626 file = file - 1;
627
628 if rank == 7 || file == 0 {
629 rank = piece_pos.rank;
630 file = piece_pos.file;
631 break;
632 }
633 }
634 }
635
636 if rank < 7 && file < 7 {
637 loop {
638 if board.get(rank + 1, file + 1) != '-' {
639 if is_enemy(board, piece_pos, &Position::create(rank + 1, file + 1)) {
640 positions.push(Position::create(rank + 1, file + 1));
641 }
642
643 rank = piece_pos.rank;
644 file = piece_pos.file;
645 break;
646 }
647
648 positions.push(Position::create(rank + 1, file + 1));
649
650 rank = rank + 1;
651 file = file + 1;
652
653 if rank == 7 || file == 7 {
654 rank = piece_pos.rank;
655 file = piece_pos.file;
656 break;
657 }
658 }
659 }
660
661 positions
662}
663
664pub fn get_pawn_moves(game: &Game, pawn_pos: &Position) -> Vec<Position> {
667 let piece = get_piece_from_position(&game.board, &pawn_pos);
668
669 let mut positions: Vec<Position> = Vec::new();
670 println!("{:?}", pawn_pos);
671 if piece == 'p' {
672 if (game.board.get(pawn_pos.rank + 1,pawn_pos.file) == '-') {
673 if pawn_pos.rank == 1 && game.board.get(3,pawn_pos.file) == '-' {
674 positions.push(Position::create(3, pawn_pos.file)); }
676
677 positions.push(Position::create(pawn_pos.rank + 1, pawn_pos.file)); let capture_positions = get_pawn_capture_pos(&game.board, pawn_pos, 'p');
680
681 for capture in capture_positions {
682 positions.push(capture);
683 }
684
685 return positions;
686 }
687 } else if piece == 'P' {
688 if game.board.get(pawn_pos.rank - 1, pawn_pos.file) == '-' {
689 if pawn_pos.rank == 6 && game.board.get(4, pawn_pos.rank) == '-' {
690 positions.push(Position::create(4, pawn_pos.file)); }
692
693 positions.push(Position::create(pawn_pos.rank - 1, pawn_pos.file)); let capture_positions = get_pawn_capture_pos(&game.board, pawn_pos, 'P');
696
697 for capture in capture_positions {
698 positions.push(capture);
699 }
700
701 return positions;
702 }
703 }
704
705 positions
706}
707pub fn get_knight_moves(board: &Board, knight_pos: &Position) -> Vec<Position> {
708 let piece = get_piece_from_position(&board, &knight_pos);
709 let mut positions: Vec<Position> = Vec::new();
710 let rank = knight_pos.rank;
711 let file = knight_pos.file;
712
713 if piece == 'n' || piece == 'N' {
714 if rank > 1 {
715 if file > 0 {
716 positions.push(Position::create(rank - 2, file - 1));
717 }
718
719 if file < 7 {
720 positions.push(Position::create(rank - 2, file + 1));
721 }
722 }
723
724 if rank < 6 {
725 if file > 0 {
726 positions.push(Position::create(rank + 2, file - 1));
727 }
728
729 if file < 7 {
730 positions.push(Position::create(rank + 2, file + 1));
731 }
732 }
733
734 if file > 1 {
735 if rank > 0 {
736 positions.push(Position::create(rank - 1, file - 2));
737 }
738
739 if rank < 7 {
740 positions.push(Position::create(rank + 1, file - 2));
741 }
742 }
743
744 if file < 6 {
745 if rank > 0 {
746 positions.push(Position::create(rank - 1, file + 2));
747 }
748
749 if rank < 7 {
750 positions.push(Position::create(rank + 1, file + 2));
751 }
752 }
753
754 let mut valid_moves = Vec::new();
755
756 for position in positions {
757 let newRank = position.rank;
758 let newFile = position.file;
759
760 if (board.get(position.rank, position.file) == '-') {
761 valid_moves.push(position);
762 }
763
764 else if board.get(position.rank, position.file).is_lowercase() && board.get(knight_pos.rank, knight_pos.file).is_uppercase() {
765 valid_moves.push(position);
766 }
767
768 else if board.get(position.rank, position.file).is_uppercase() && board.get(knight_pos.rank, knight_pos.file).is_lowercase() {
769 valid_moves.push(position);
770 }
771 }
772
773 return valid_moves;
774 }
775
776 positions
777}
778
779pub fn get_bishop_moves(board: &Board, bishop_pos: &Position) -> Vec<Position> {
780 let piece = get_piece_from_position(&board, &bishop_pos);
781
782 if piece == 'b' || piece == 'B' {
783 return get_diagonal_moves(&board, &bishop_pos);
784 }
785
786 Vec::new()
787}
788
789pub fn get_queen_moves(board: &Board, queen_pos: &Position) -> Vec<Position> {
790 let piece = get_piece_from_position(&board, &queen_pos);
791 let mut positions = get_diagonal_moves(&board, &queen_pos);
792
793 if piece == 'q' || piece == 'Q' {
794 let horizontal_moves = get_horizontal_moves(&board, &queen_pos);
795 let vertical_moves = get_vertical_moves(&board, &queen_pos);
796
797 for h_move in horizontal_moves {
798 positions.push(h_move);
799 }
800
801 for v_move in vertical_moves {
802 positions.push(v_move);
803 }
804
805 return positions;
806 }
807
808 positions
809}
810
811pub fn get_king_moves(game: &Game, king_pos: &Position) -> Vec<Position> {
812 let piece = get_piece_from_position(&game.board, &king_pos);
813
814 if piece == 'k' || piece == 'K' {
815 let king_rank = king_pos.rank;
816 let king_file = king_pos.file;
817 let mut positions:Vec<Position> = Vec::new();
818
819 if king_rank > 0 {
820 if king_file > 0 {
821 positions.push(Position::create(king_rank - 1, king_file - 1));
822 }
823
824 if king_file < 7 {
825 positions.push(Position::create(king_rank - 1, king_file + 1));
826 }
827
828 positions.push(Position::create(king_rank - 1, king_file ));
829 }
830
831 if king_rank < 7 {
832 if king_file > 0 {
833 positions.push(Position::create(king_rank + 1, king_file - 1));
834 }
835
836 if king_file < 7 {
837 positions.push(Position::create(king_rank + 1, king_file + 1));
838 }
839
840 positions.push(Position::create(king_rank + 1, king_file ));
841 }
842
843 if king_file > 0 {
844 positions.push(Position::create(king_rank, king_file - 1));
845 }
846
847 if king_file < 7 {
848 positions.push(Position::create(king_rank, king_file + 1));
849 }
850
851 let mut valid_positions: Vec<Position>= Vec::new();
852 let castle_positions = get_castling_position(game);
853
854 for position in positions {
855 if game.board.get(position.rank, position.file) == '-' {
856 valid_positions.push(position)
857 }
858 }
859
860 for castle_position in castle_positions {
861 valid_positions.push(castle_position);
862 }
863
864 return valid_positions;
865 }
866
867 Vec::new()
868}
869
870pub fn get_rook_moves(board: &Board, rook_pos: &Position) -> Vec<Position> {
871 let piece = get_piece_from_position(&board, &rook_pos);
872 let mut positions = Vec::new();
873
874 if piece == 'r' || piece == 'R' {
875 let horizontal_moves = get_horizontal_moves(&board, &rook_pos);
876 let vertical_moves = get_vertical_moves(&board, &rook_pos);
877
878 for h_move in horizontal_moves {
879 positions.push(h_move);
880 }
881
882 for v_move in vertical_moves {
883 positions.push(v_move);
884 }
885
886 return positions;
887 }
888
889 positions
890}
891
892pub fn get_moves(game: &Game, position: &Position) -> Option<Vec<Position>> {
893 let square: char = game.board.get(position.rank, position.file);
894
895 if (square != '-') {
896 let moves = match square.to_ascii_lowercase() {
897 'p' => get_pawn_moves(&game, &position),
898 'r' => get_rook_moves(&game.board, &position),
899 'n' => get_knight_moves(&game.board, &position),
900 'b' => get_bishop_moves(&game.board, &position),
901 'q' => get_queen_moves(&game.board, &position),
902 'k' => get_king_moves(&game, &position),
903 _ => Vec::new(),
904 };
905
906 return Some(moves);
907 }
908
909 None
910}
911
912pub fn get_all_moves(game: Game) -> HashMap<Position, Vec<Position>> {
913 let mut legal_moves: HashMap<Position, Vec<Position>> = HashMap::new();
914
915 let mut row_index = 0;
916
917 for row in &game.board.board {
918 for (col, square) in row.iter().enumerate() {
919 if *square != '-' {
920 let piece = *square;
921 let position = Position::create(row_index, col);
922
923 let is_white = game.status == WHITE_TO_MOVE && piece.is_ascii_lowercase();
924 let is_black = game.status == BLACK_TO_MOVE && piece.is_ascii_uppercase();
925
926 if is_white || is_black {
927 let moves = get_moves(&game, &position).unwrap();
928
929 legal_moves.insert(Position::create(row_index, col), moves);
930 }
931 }
932 }
933
934 row_index += 1;
935 }
936
937 legal_moves
938}
939
940fn validate_moves(current_move: &Position, legal_moves: Vec<Position>) -> bool {
941 for legal_move in legal_moves {
942 if current_move.file == legal_move.file && current_move.rank == legal_move.rank {
943 return true;
944 }
945 }
946
947 false
948}
949
950pub fn make_move(game: &mut Game, start: &Position, end: &Position) -> Result<bool> {
951 let piece: char = game.board.get(start.rank, start.file);
952
953 if game.status == WHITE_TO_MOVE && piece.is_lowercase() {
954 return Err(Error::new(ErrorKind::Other, "Error: Cannot move black piece on white turn"))
955 }
956
957 if game.status == BLACK_TO_MOVE && piece.is_uppercase() {
958 return Err(Error::new(ErrorKind::Other, "Error: Cannot move white piece on black turn"))
959 }
960
961 let positions = get_moves(game, start).unwrap();
962
963 if validate_moves(end, positions) {
964 let isTargetEnemy = is_enemy(&game.board, start, end);
965
966 game.board.board[start.rank][start.file] = '-';
967 game.board.board[end.rank][end.file] = piece;
968
969 if isTargetEnemy {
970 game.current_move = CAPTURE;
971 }
972
973 else {
974 game.current_move = REGULAR;
975 }
976
977 if game.status == WHITE_TO_MOVE {
978 game.status = BLACK_TO_MOVE;
979 }
980
981 else if game.status == BLACK_TO_MOVE {
982 game.status = WHITE_TO_MOVE;
983 }
984
985 return Ok(true);
986 }
987
988 Err(Error::new(ErrorKind::Other, "Error: Move is invalid"))
989}
990
991pub fn run() {
992 let mut game = Game::new();
993
994 loop {
995 game.board.print();
996 println!("");
997
998 let mut input = String::new();
999
1000 println!("Enter position");
1001 stdin().read_line(&mut input).unwrap();
1002
1003 let nums: Vec<usize> = input.split_whitespace().map(|w| w.parse().unwrap()).collect();
1004 let startRow = nums[0];
1005 let startFile = nums[1];
1006
1007 let endRow = nums[2];
1008 let endFile = nums[3];
1009
1010 match make_move(&mut game, &Position::create(startRow, startFile), &Position::create(endRow, endFile)) {
1011 Ok(v) => println!("Success"),
1012 Err(e) => println!("{}", e)
1013 }
1014 }
1015}
1016
1017#[cfg(test)]
1018mod tests {
1019 use super::*;
1020
1021 #[test]
1022 fn test_perft_depth_1() {
1023 let board = Board::create();
1024 let nodes = board.perft(1, WHITE);
1025 assert_eq!(nodes, 20, "Perft Depth 1 failed: Expected 20 nodes, got {}", nodes);
1026 }
1027
1028 #[test]
1029 fn test_perft_depth_2() {
1030 let board = Board::create();
1031 let nodes = board.perft(2, WHITE);
1032 assert_eq!(nodes, 400, "Perft Depth 2 failed: Expected 400 nodes, got {}", nodes);
1033 }
1034
1035 #[test]
1036 fn test_perft_depth_3() {
1037 let board = Board::create();
1038 let nodes = board.perft(3, WHITE);
1039 assert_eq!(nodes, 8902, "Perft Depth 3 failed: Expected 8902 nodes, got {}", nodes);
1040 }
1041}