use std::cmp::PartialEq;
use serde::Serialize;
use crate::Color::{BLACK, WHITE};
const STARTING_FEN: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
pub enum Status {
WHITE_TO_MOVE,
BLACK_TO_MOVE,
DRAW,
WHITE_HAS_CHECKMATE,
BLACK_HAS_CHECKMATE,
}
pub type ChessBoard = Vec<Vec<char>>;
#[derive(Debug)]
pub struct Board {
pub board: ChessBoard,
}
impl Board {
pub fn create() -> Board {
Board {
board: convert_fen_to_vector(STARTING_FEN),
}
}
pub fn create_from_fen(FEN: &str) -> Board {
Board {
board: convert_fen_to_vector(FEN),
}
}
pub fn get(&self, rank: usize, file: usize) -> char {
self.board[rank][file]
}
pub fn pushRow(&mut self, row: Vec<char>) {
self.board.push(row);
}
pub fn clone(&self) -> Self {
Board {
board: self.board.clone(), }
}
pub fn make_move(board: &Board, start: Position, end: &Position, piece: char) -> Board {
let mut new_board = board.clone();
new_board.board[start.rank][start.file] = '-';
new_board.board[end.rank][end.file] = piece;
new_board
}
pub fn generate_legal_moves(&self, currentTurn: &Color) -> Vec<Board> {
let mut boards: Vec<Board> = Vec::new();
for (rowIndex, row) in self.board.iter().enumerate() {
for (fileIndex, piece) in row.iter().enumerate() {
if *currentTurn == WHITE {
match piece {
&'P' => {
let legal_moves = get_legal_moves_for_pawn(self, &Position::create(rowIndex, fileIndex));
for legal_move in &legal_moves {
let new_board = Self::make_move(self, Position::create(rowIndex, fileIndex), legal_move, 'P');
boards.push(new_board);
}
}
&'R' => {
let legal_moves = get_legal_moves_for_rook(self, &Position::create(rowIndex, fileIndex));
for legal_move in legal_moves {
let new_board = Self::make_move(self, Position::create(rowIndex, fileIndex), &legal_move, 'R');
boards.push(new_board);
}
}
&'N' => {
let legal_moves = get_legal_moves_for_knight(self, &Position::create(rowIndex, fileIndex));
for legal_move in legal_moves {
let new_board = Self::make_move(self, Position::create(rowIndex, fileIndex), &legal_move, 'N');
boards.push(new_board);
}
}
&'B' => {
let legal_moves = get_legal_moves_for_bishop(self, &Position::create(rowIndex, fileIndex));
for legal_move in legal_moves {
let new_board = Self::make_move(self, Position::create(rowIndex, fileIndex), &legal_move, 'B');
boards.push(new_board);
}
}
&'Q' => {
let legal_moves = get_legal_moves_for_queen(self, &Position::create(rowIndex, fileIndex));
for legal_move in legal_moves {
let new_board = Self::make_move(self, Position::create(rowIndex, fileIndex), &legal_move, 'Q');
boards.push(new_board);
}
}
&'K' => {
let legal_moves = get_legal_moves_for_king(self, &Position::create(rowIndex, fileIndex), WHITE, Game::new());
for legal_move in legal_moves {
let new_board = Self::make_move(self, Position::create(rowIndex, fileIndex), &legal_move, 'K');
boards.push(new_board);
}
}
_ => {}
}
}
else {
match piece {
&'p' => {
let legal_moves = get_legal_moves_for_pawn(self, &Position::create(rowIndex, fileIndex));
for legal_move in &legal_moves {
let new_board = Self::make_move(self, Position::create(rowIndex, fileIndex), legal_move, 'p');
boards.push(new_board);
}
}
&'r' => {
let legal_moves = get_legal_moves_for_rook(self, &Position::create(rowIndex, fileIndex));
for legal_move in legal_moves {
let new_board = Self::make_move(self, Position::create(rowIndex, fileIndex), &legal_move, 'r');
boards.push(new_board);
}
}
&'n' => {
let legal_moves = get_legal_moves_for_knight(self, &Position::create(rowIndex, fileIndex));
for legal_move in legal_moves {
let new_board = Self::make_move(self, Position::create(rowIndex, fileIndex), &legal_move, 'n');
boards.push(new_board);
}
}
&'b' => {
let legal_moves = get_legal_moves_for_bishop(self, &Position::create(rowIndex, fileIndex));
for legal_move in legal_moves {
let new_board = Self::make_move(self, Position::create(rowIndex, fileIndex), &legal_move, 'b');
boards.push(new_board);
}
}
&'q' => {
let legal_moves = get_legal_moves_for_queen(self, &Position::create(rowIndex, fileIndex));
for legal_move in legal_moves {
let new_board = Self::make_move(self, Position::create(rowIndex, fileIndex), &legal_move, 'q');
boards.push(new_board);
}
}
&'k' => {
let legal_moves = get_legal_moves_for_king(self, &Position::create(rowIndex, fileIndex), BLACK, Game::new());
for legal_move in legal_moves {
let new_board = Self::make_move(self, Position::create(rowIndex, fileIndex), &legal_move, 'k');
boards.push(new_board);
}
}
_ => {}
}
}
}
}
boards
}
pub fn print(&self) {
for row in &self.board {
println!("{:?}", row);
}
}
pub fn perft(&self, depth: usize, currentColor: Color) -> u64 {
if depth == 0 {
return 1;
}
let mut nodes = 0;
let moves = self.generate_legal_moves(¤tColor);
for new_board in moves {
let newColor = if currentColor == WHITE {
BLACK
}
else {
WHITE
};
nodes += new_board.perft(depth - 1, newColor);
}
nodes
}
}
#[derive(Serialize)]
#[derive(Debug)]
pub struct Position {
rank: usize,
file: usize
}
impl Position {
pub fn create(rank: usize, file: usize) -> Position {
Position {
rank,
file
}
}
}
#[derive(PartialEq, Debug)]
pub enum Color {
WHITE,
BLACK
}
pub enum Move {
REGULAR,
CAPTURE,
CASTLE
}
pub struct Game {
status: Status,
current_move: Move,
white_castle_short: bool,
white_castle_long: bool,
black_castle_short: bool,
black_castle_long: bool,
}
impl Game {
pub fn new() -> Game {
Game {
status: Status::WHITE_TO_MOVE,
current_move: Move::REGULAR,
white_castle_short: true,
white_castle_long: true,
black_castle_short: true,
black_castle_long: true
}
}
}
pub fn convert_fen_to_vector(fen: &str) -> ChessBoard {
let mut board: ChessBoard = Vec::new();
let position = fen.split(" ").collect::<Vec<&str>>()[0];
for rank in position.split("/").collect::<Vec<&str>>() {
let mut row_vector = Vec::new();
for square in rank.chars() {
match square.to_digit(10) {
Some(num) => {
let num = num as i32;
for _i in 0..num {
row_vector.push('-');
}
}
None => row_vector.push(square)
}
}
board.push(row_vector);
}
board
}
fn get_piece_from_position(board: &Board, piece_pos: &Position) -> char {
board.get(piece_pos.rank, piece_pos.file)
}
fn get_castling_position(game: Game, board: &Board, color: Color) -> Vec<Position> {
let mut positons = Vec::new();
if color == WHITE {
if game.white_castle_short {
if board.get(7, 5) == '-' && board.get(7, 6) == '-' {
positons.push(Position {
rank: 7,
file: 6
})
}
}
if game.white_castle_short {
if board.get(7, 1) == '-' && board.get(7, 2) == '-' && board.get(7, 3) == '-' {
positons.push(Position {
rank: 7,
file: 2
})
}
}
}
else {
if game.black_castle_short {
if board.get(0, 5) == '-' && board.get(0, 6) == '-' {
positons.push(Position {
rank: 0,
file: 6
})
}
}
if game.black_castle_long {
if board.get(0, 1) == '-' && board.get(0, 2) == '-' && board.get(0, 3) == '-' {
positons.push(Position {
rank: 0,
file: 2
})
}
}
}
positons
}
fn has_enemy_piece(board: &Board, pos: &Position, current_piece: char) -> bool {
let piece = get_piece_from_position(&board, &pos);
if current_piece.is_uppercase() && piece.is_lowercase() {
return true;
}
if current_piece.is_lowercase() && piece.is_uppercase() {
return true;
}
false
}
fn get_pawn_capture_pos(board: &Board, pawn_pos: &Position, pawn: char) -> Vec<Position> {
let mut target: Vec<Position> = Vec::new();
let mut target_rank = 0;
if pawn.is_lowercase() {
target_rank = pawn_pos.rank + 1;
}
else {
target_rank = pawn_pos.rank - 1;
}
if (pawn_pos.file > 0) {
let square2 = pawn_pos.file - 1;
let target2 = Position::create(target_rank, square2);
let has_enemy2 = has_enemy_piece(&board, &target2, pawn);
if pawn_pos.file == 7 && has_enemy2 {
target.push(Position::create(target_rank, square2));
}
else if has_enemy2 {
target.push(Position::create(target_rank, square2))
}
}
if (pawn_pos.file < 7) {
let square1 = pawn_pos.file + 1;
let target1 = Position::create(target_rank, square1);
let has_enemy1 = has_enemy_piece(&board, &target1, pawn);
if pawn_pos.file == 0 && has_enemy1 {
target.push(Position::create(target_rank, square1));
}
else if has_enemy1 {
target.push(Position::create(target_rank, square1));
}
}
target
}
fn get_horizontal_moves(board: &Board, piece_pos: &Position) -> Vec<Position> {
let mut positions: Vec<Position> = Vec::new();
let rank = piece_pos.rank;
let mut file = piece_pos.file;
loop {
if file == 7 {
if rank != piece_pos.rank || file != piece_pos.file {
positions.push(Position::create(rank, 7));
}
file = piece_pos.file;
break;
}
if rank == piece_pos.rank && file == piece_pos.file {
file += 1;
continue;
}
if board.get(rank, file) != '-' {
if is_enemy(board, piece_pos, Position::create(rank, file)) {
positions.push(Position::create(rank, file));
}
file = piece_pos.file;
break;
}
positions.push(Position::create(rank, file));
file += 1;
}
loop {
if file == 0 {
if rank != piece_pos.rank || file != piece_pos.file {
positions.push(Position::create(rank, 0));
}
file = piece_pos.file;
break;
}
if rank == piece_pos.rank && file == piece_pos.file {
file -= 1;
continue;
}
if board.get(rank, file) != '-' {
if is_enemy(board, piece_pos, Position::create(rank, file)) {
positions.push(Position::create(rank, file));
}
file = piece_pos.file;
break;
}
positions.push(Position::create(rank, file));
file -= 1;
}
positions
}
fn is_enemy(board: &Board, current_pos: &Position, target_pos: Position) -> bool {
let current_piece = board.get(current_pos.rank, current_pos.file);
if current_piece.is_lowercase() {
if board.get(target_pos.rank, target_pos.file).is_uppercase() {
return true;
}
}
else {
if board.get(target_pos.rank, target_pos.file).is_lowercase() {
return true;
}
}
false
}
fn get_vertical_moves(board: &Board, piece_pos: &Position) -> Vec<Position> {
let mut positions: Vec<Position> = Vec::new();
let mut rank = piece_pos.rank;
let file = piece_pos.file;
loop {
if rank == 7 {
if file != piece_pos.file || rank != piece_pos.rank {
positions.push(Position::create(7, file));
}
rank = piece_pos.rank;
break;
}
if rank == piece_pos.rank && file == piece_pos.file {
rank += 1;
continue;
}
if board.get(rank, file) != '-' {
if is_enemy(board, piece_pos, Position::create(rank, file)) {
positions.push(Position::create(rank, file));
}
rank = piece_pos.rank;
break;
}
positions.push(Position::create(rank, file));
rank += 1;
}
loop {
if rank == 0 {
if file != piece_pos.file || rank != piece_pos.rank {
positions.push(Position::create(rank, file));
}
rank = piece_pos.rank;
break;
}
if rank == piece_pos.rank && file == piece_pos.file {
rank -= 1;
continue;
}
if board.get(rank, file) != '-' {
if is_enemy(board, piece_pos, Position::create(rank, file)) {
positions.push(Position::create(rank, file));
}
rank = piece_pos.rank;
break;
}
positions.push(Position::create(rank, file));
rank -= 1;
}
positions
}
fn get_diagonal_moves(board: &Board, piece_pos: &Position) -> Vec<Position> {
let mut positions: Vec<Position> = Vec::new();
let mut rank = piece_pos.rank;
let mut file = piece_pos.file;
if rank > 0 && file > 0 {
loop {
if board.get(rank - 1, file - 1) != '-' {
rank = piece_pos.rank;
file = piece_pos.file;
break;
}
positions.push(Position::create(rank - 1, file - 1));
rank = rank - 1;
file = file - 1;
if rank == 0 || file == 0 {
rank = piece_pos.rank;
file = piece_pos.file;
break;
}
}
}
if rank > 0 && file < 7 {
loop {
if board.get(rank - 1, file + 1) != '-' {
rank = piece_pos.rank;
file = piece_pos.file;
break;
}
positions.push(Position::create(rank - 1, file + 1));
rank = rank - 1;
file = file + 1;
if rank == 0 || file == 7 {
rank = piece_pos.rank;
file = piece_pos.file;
break;
}
}
}
if rank < 7 && file > 0 {
loop {
if board.get(rank + 1, file - 1) != '-' {
rank = piece_pos.rank;
file = piece_pos.file;
break;
}
positions.push(Position::create(rank + 1, file - 1));
rank = rank + 1;
file = file - 1;
if rank == 7 || file == 0 {
rank = piece_pos.rank;
file = piece_pos.file;
break;
}
}
}
if rank < 7 && file < 7 {
loop {
println!("{} {}", rank, file);
if board.get(rank + 1, file + 1) != '-' {
rank = piece_pos.rank;
file = piece_pos.file;
break;
}
positions.push(Position::create(rank + 1, file + 1));
rank = rank + 1;
file = file + 1;
if rank == 7 || file == 7 {
rank = piece_pos.rank;
file = piece_pos.file;
break;
}
}
}
positions
}
pub fn get_legal_moves_for_pawn(board: &Board, pawn_pos: &Position) -> Vec<Position> {
let piece = get_piece_from_position(&board, &pawn_pos);
let mut positions: Vec<Position> = Vec::new();
if piece == 'p' {
if (board.get(pawn_pos.rank + 1,pawn_pos.file) == '-') {
if pawn_pos.rank == 1 && board.get(3,pawn_pos.file) == '-' {
positions.push(Position::create(3, pawn_pos.file)); }
positions.push(Position::create(pawn_pos.rank + 1, pawn_pos.file)); let capture_positions = get_pawn_capture_pos(board, pawn_pos, 'p');
for capture in capture_positions {
positions.push(capture);
}
return positions;
}
} else if piece == 'P' {
if board.get(pawn_pos.rank - 1, pawn_pos.file) == '-' {
if pawn_pos.rank == 6 && board.get(4, pawn_pos.rank) == '-' {
positions.push(Position::create(4, pawn_pos.file)); }
positions.push(Position::create(pawn_pos.rank - 1, pawn_pos.file)); let capture_positions = get_pawn_capture_pos(board, pawn_pos, 'P');
for capture in capture_positions {
positions.push(capture);
}
return positions;
}
}
println!("No pawn found");
positions
}
pub fn get_legal_moves_for_knight(board: &Board, knight_pos: &Position) -> Vec<Position> {
let piece = get_piece_from_position(&board, &knight_pos);
let mut positions: Vec<Position> = Vec::new();
let rank = knight_pos.rank;
let file = knight_pos.file;
if piece == 'n' || piece == 'N' {
if rank > 1 {
if file > 0 {
positions.push(Position::create(rank - 2, file - 1));
}
if file < 7 {
positions.push(Position::create(rank - 2, file + 1));
}
}
if rank < 6 {
if file > 0 {
positions.push(Position::create(rank + 2, file - 1));
}
if file < 7 {
positions.push(Position::create(rank + 2, file + 1));
}
}
if file > 1 {
if rank > 0 {
positions.push(Position::create(rank - 1, file - 2));
}
if rank < 7 {
positions.push(Position::create(rank + 1, file - 2));
}
}
if file < 6 {
if rank > 0 {
positions.push(Position::create(rank - 1, file + 2));
}
if rank < 7 {
positions.push(Position::create(rank + 1, file + 2));
}
}
let mut valid_moves = Vec::new();
for position in positions {
if (board.get(position.rank, position.file) == '-') {
valid_moves.push(position);
}
}
return valid_moves;
}
positions
}
pub fn get_legal_moves_for_bishop(board: &Board, bishop_pos: &Position) -> Vec<Position> {
let piece = get_piece_from_position(&board, &bishop_pos);
if piece == 'b' || piece == 'B' {
return get_diagonal_moves(&board, &bishop_pos);
}
Vec::new()
}
pub fn get_legal_moves_for_queen(board: &Board, queen_pos: &Position) -> Vec<Position> {
let piece = get_piece_from_position(&board, &queen_pos);
let mut positions = get_diagonal_moves(&board, &queen_pos);
if piece == 'q' || piece == 'Q' {
let horizontal_moves = get_horizontal_moves(&board, &queen_pos);
let vertical_moves = get_vertical_moves(&board, &queen_pos);
for h_move in horizontal_moves {
positions.push(h_move);
}
for v_move in vertical_moves {
positions.push(v_move);
}
return positions;
}
positions
}
pub fn get_legal_moves_for_king(board: &Board, king_pos: &Position, color: Color, game: Game) -> Vec<Position> {
let piece = get_piece_from_position(&board, &king_pos);
if piece == 'k' || piece == 'K' {
let king_rank = king_pos.rank;
let king_file = king_pos.file;
let mut positions:Vec<Position> = Vec::new();
if king_rank > 0 {
if king_file > 0 {
positions.push(Position::create(king_rank - 1, king_file - 1));
}
if king_file < 7 {
positions.push(Position::create(king_rank - 1, king_file + 1));
}
positions.push(Position::create(king_rank - 1, king_file ));
}
if king_rank < 7 {
if king_file > 0 {
positions.push(Position::create(king_rank + 1, king_file - 1));
}
if king_file < 7 {
positions.push(Position::create(king_rank + 1, king_file + 1));
}
positions.push(Position::create(king_rank + 1, king_file ));
}
if king_file > 0 {
positions.push(Position::create(king_rank, king_file - 1));
}
if king_file < 7 {
positions.push(Position::create(king_rank, king_file + 1));
}
let mut valid_positions: Vec<Position>= Vec::new();
let castle_positions = get_castling_position(game, board, color);
for position in positions {
if board.get(position.rank, position.file) == '-' {
valid_positions.push(position)
}
}
for castle_position in castle_positions {
valid_positions.push(castle_position);
}
return valid_positions;
}
Vec::new()
}
pub fn get_legal_moves_for_rook(board: &Board, rook_pos: &Position) -> Vec<Position> {
let piece = get_piece_from_position(&board, &rook_pos);
let mut positions = Vec::new();
if piece == 'r' || piece == 'R' {
let horizontal_moves = get_horizontal_moves(&board, &rook_pos);
let vertical_moves = get_vertical_moves(&board, &rook_pos);
for h_move in horizontal_moves {
positions.push(h_move);
}
for v_move in vertical_moves {
positions.push(v_move);
}
return positions;
}
positions
}
pub fn run() {
let board = Board::create_from_fen("rnbqkbnr/pppp1ppp/8/4p3/Q1P2P2/3PN1PB/PP1BP2P/RN2K2R");
board.print();
println!("{}", board.get(4, 0));
let queen_moves = get_legal_moves_for_queen(&board, &Position::create(4, 0));
for legal_move in queen_moves {
println!("{:?}", legal_move);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_perft_depth_1() {
let board = Board::create();
let nodes = board.perft(1, WHITE);
assert_eq!(nodes, 20, "Perft Depth 1 failed: Expected 20 nodes, got {}", nodes);
}
#[test]
fn test_perft_depth_2() {
let board = Board::create();
let nodes = board.perft(2, WHITE);
assert_eq!(nodes, 400, "Perft Depth 2 failed: Expected 400 nodes, got {}", nodes);
}
#[test]
fn test_perft_depth_3() {
let board = Board::create();
let nodes = board.perft(3, WHITE);
assert_eq!(nodes, 8902, "Perft Depth 3 failed: Expected 8902 nodes, got {}", nodes);
}
}