#include #include #include #include #include "config.h" #include "pieces.h" #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #define MIN(x, y) (((x) < (y)) ? (x) : (y)) void print_board(int board[128]) { for (int rank = 7; rank >= 0; rank--) { printf("%i|", rank + 1); for (int file = 0; file < 8; file++) { int piece = board[rank * 16 + file]; printf("%s ", pieces[piece]); } printf("\n"); } printf(" a b c d e f g h\n"); } void parse_FEN(char* FEN, int board[128]) { // TODO: parse everything, not position only int rank = 7; int file = 0; size_t lenght = strlen(FEN); for (int k = 0; k < lenght; k++) { if (FEN[k] > '0' && FEN[k] <= '8') { int last_file = file + FEN[k] - '0'; for (file; file < last_file; file++) { board[rank * 16 + file] = EMPTY; } } else { switch (FEN[k]) { case 'r': board[rank * 16 + file] = BLACK | ROOK; break; case 'R': board[rank * 16 + file] = ROOK; break; case 'n': board[rank * 16 + file] = BLACK | KNIGHT; break; case 'N': board[rank * 16 + file] = KNIGHT; break; case 'b': board[rank * 16 + file] = BLACK | BISHOP; break; case 'B': board[rank * 16 + file] = BISHOP; break; case 'q': board[rank * 16 + file] = BLACK | QUEEN; break; case 'Q': board[rank * 16 + file] = QUEEN; break; case 'k': board[rank * 16 + file] = BLACK | KING; break; case 'K': board[rank * 16 + file] = KING; break; case 'p': board[rank * 16 + file] = BLACK | PAWN; break; case 'P': board[rank * 16 + file] = PAWN; break; case '/': rank--; file = -1; // so that it becomes 0 break; case ' ': return; default: board[rank * 16 + file] = KNIGHT; } file++; } } } int notation_to_index(int file, int rank) { return (rank - 1) * 16 + (file - 'a'); } void index_to_notation(int index, char notation[2]) { notation[0] = (index & FILE_MASK) + 'a'; notation[1] = (index >> 4) + '1'; } void print_move(int move[2]) { char move_in_notation[] = "xy XY"; index_to_notation(move[0], move_in_notation); index_to_notation(move[1], move_in_notation + 3); printf("%s\n", move_in_notation); }; void input_move(int move[2]) { char file; int rank; char file_destination; int rank_destination; scanf(" %c%i %c%i", &file, &rank, &file_destination, &rank_destination); move[0] = notation_to_index(file, rank); move[1] = notation_to_index(file_destination, rank_destination); }; int validate_move(int move[2], int color, int* board) { int origin = move[0]; int destination = move[1]; if ((origin & 0x88) || (destination & 0x88)) return -1; int piece = board[origin]; int* legal_move; if (piece % 2 != color) return -3; switch (piece & NO_COLOR) { case KNIGHT: legal_move = knightMoves; break; case BISHOP: legal_move = bishopMoves; break; case ROOK: legal_move = rookMoves; break; case QUEEN: legal_move = queenMoves; break; case KING: legal_move = kingMoves; break; case PAWN: if (piece & BLACK) { if (board[destination] != EMPTY) legal_move = blackPawnAttackMoves; else if (origin >> 4 == 6) legal_move = newBlackPawnMoves; else legal_move = blackPawnMoves; } else { if (board[destination] != EMPTY) legal_move = pawnAttackMoves; else if (origin >> 4 == 1) legal_move = newPawnMoves; else legal_move = pawnMoves; } break; default: return -2; } while(*legal_move) { for (int square = origin + *legal_move; !(square & 0x88); square += *legal_move) { int target_piece = board[square]; if (square == destination) { if (target_piece != EMPTY && target_piece % 2 == color) return -4; return target_piece; } if (target_piece != EMPTY) break; if ((piece & NO_COLOR) == PAWN || (piece & NO_COLOR) == KNIGHT || (piece & NO_COLOR) == KING) break; } legal_move++; } return -5; } char* VALIDATION_ERRORS[] = { "trying to access off-board square", "no piece on origin square", "opponent piece on origin square", "trying to capture your own piece", "move is not allowed", }; int apply_move(int move[2], int* board) { int origin = move[0]; int destination = move[1]; int piece = board[origin]; int target_piece = board[destination]; board[destination] = piece; board[origin] = EMPTY; if (target_piece != EMPTY) { // printf("Captured %s\n", pieces[target_piece]); } return board[destination]; } int evaluate_pawn(int piece, int rank) { int color = piece % 2; if (color == 0) { switch (rank) { case 7: return 9; case 6: return 5; case 5: return 3; default: return 1; } } else { switch (rank) { case 0: return 9; case 1: return 5; case 2: return 3; default: return 1; } } } int compute_material_advantage(int* board, int color) { int counter = 0; for (int rank = 7; rank >= 0; rank--) { for (int file = 0; file < 8; file++) { int piece = board[rank * 16 + file]; if (piece != EMPTY) { int sign = (piece % 2 == color) ? 1 : -1; switch (piece & NO_COLOR) { case KING: counter += 1000 * sign; break; case PAWN: counter += evaluate_pawn(piece, rank) * sign; break; case KNIGHT: counter += 3 * sign; break; case BISHOP: counter += 3 * sign; break; case ROOK: counter += 4 * sign; break; case QUEEN: counter += 9 * sign; break; default: counter += 1 * sign; break; } } } } return counter; } int compute_coverage(int* board, int color) { int counter = 0; for (int rank = 7; rank >= 0; rank--) { for (int file = 0; file < 8; file++) { int index = rank * 16 + file; int piece = board[index]; if (piece != EMPTY && (piece % 2) == color) { for (int rank_destination = 7; rank_destination >= 0; rank_destination--) { for (int file_destination = 0; file_destination < 8; file_destination++) { int index_destination = rank_destination * 16 + file_destination; int move[2] = { index, index_destination }; if (validate_move(move, color, board) >= 0) { counter++; } } } } } } return counter; } int compute_score(int* board) { int coverage_score = compute_coverage(board, WHITE) - compute_coverage(board, BLACK); return compute_material_advantage(board, WHITE) * 2 + coverage_score; } // Alpha-beta pruning // Value here is white material advantage over black // Alpha is the best value for maximizer (white) // Beta is the best value for minimizer (black) int find_best_move(int best_move[2], int* board, int color, int depth, int alpha, int beta, int* metrics) { int fake_board[128]; int is_maximizer = (color == 0); int best_value = is_maximizer ? -INFINITY : INFINITY; for (int rank = 7; rank >= 0; rank--) { for (int file = 0; file < 8; file++) { int index = rank * 16 + file; int piece = board[index]; if (piece != EMPTY && (piece % 2) == color) { for (int rank_destination = 7; rank_destination >= 0; rank_destination--) { for (int file_destination = 0; file_destination < 8; file_destination++) { int index_destination = rank_destination * 16 + file_destination; int move[2] = { index, index_destination }; if (validate_move(move, color, board) >= 0) { *metrics += 1; // Generate fake board for (int r = 7; r >= 0; r--) { for (int f = 0; f < 8; f++) { fake_board[r * 16 + f] = board[r * 16 + f]; } } apply_move(move, fake_board); int dummy[2]; int value = depth < MAX_DEPTH ? find_best_move(dummy, fake_board, 1 - color, depth + 1, alpha, beta, metrics) : compute_score(fake_board); if (is_maximizer) { if (value > best_value) { best_value = value; best_move[0] = move[0]; best_move[1] = move[1]; } alpha = MAX(best_value, alpha); } else { if (value < best_value) { best_value = value; best_move[0] = move[0]; best_move[1] = move[1]; } beta = MIN(best_value, beta); } if (beta <= alpha) { return best_value; } } } } } } } return best_value; } int main() { int board[128]; parse_FEN(DEFAULT_FEN, board); print_board(board); int move[2]; int color = WHITE; while (1) { if (color == WHITE) { printf("Current score is %i\n", compute_score(board)); printf("Enter a move for %s:\n", COLORS[color]); input_move(move); } else { printf("Evaluating move for black...\n"); clock_t start, end; int metrics = 0; start = clock(); int value = find_best_move(move, board, color, 0, -INFINITY, +INFINITY, &metrics); end = clock(); char move_in_notation[] = "xy XY"; index_to_notation(move[0], move_in_notation); index_to_notation(move[1], move_in_notation + 3); printf("[%i positions analyzed in %f seconds]\n", metrics, (double)(end - start) / CLOCKS_PER_SEC); printf("Best move for black is %s with score %i in #%i moves\n", move_in_notation, value, MAX_DEPTH); } int error = validate_move(move, color, board); if (error < 0) { printf("Invalid move: %s!\n", VALIDATION_ERRORS[-1 - error]); } else { apply_move(move, board); print_board(board); color ^= 1; } } return 0; }