diff --git a/src/board.c b/src/board.c index 6ae0dd4..7f9c13c 100644 --- a/src/board.c +++ b/src/board.c @@ -4,6 +4,7 @@ #include "board.h" #include "bitboard.h" #include "movegen.h" +#include "repetition.h" // Update occupancy bitboards void update_occupancy(GameState *state) { @@ -48,6 +49,9 @@ void init_startpos(GameState *state) { state->fullmove_number = 1; update_occupancy(state); + + // Initialize history + clear_game_history(state); } // Make a move @@ -198,10 +202,11 @@ void undo_move(GameState *state, Move move, int captured_pc, int old_castling, i SET_BIT(state->bitboards[pc], from); // Handle en passant capture - if ((pc == P || pc == p) && to == state->ep_square) { + if ((pc == P || pc == p) && to == old_ep) { int cap_sq = to + (side == 0 ? -8 : 8); SET_BIT(state->bitboards[captured_pc], cap_sq); } + // Restore captured piece else if (captured_pc != NO_PIECE) { SET_BIT(state->bitboards[captured_pc], to); @@ -231,6 +236,12 @@ void undo_move(GameState *state, Move move, int captured_pc, int old_castling, i // Restore en passant square state->ep_square = old_ep; + + if (captured_pc != NO_PIECE || pc == P || pc == p) { + state->halfmove_clock = 0; + } else { + state->halfmove_clock--; // Undo the increment + } // Adjust fullmove number if (side == 1) { diff --git a/src/evaluation.c b/src/evaluation.c index a50c305..638983a 100644 --- a/src/evaluation.c +++ b/src/evaluation.c @@ -1,5 +1,6 @@ #include "evaluation.h" #include "movegen.h" +#include "repetition.h" // Material values const int piece_value[12] = {100, 320, 330, 500, 900, 20000, @@ -197,6 +198,22 @@ int evaluate_position(const GameState *state) { score -= pst_king_mid[63 - sq]; } + // Add randomness to break symmetry in equal positions + score += (int)(hash_position(state) % 10) - 5; + + // Encourage piece activity + int white_pieces = __builtin_popcountll(state->occ_white); + int black_pieces = __builtin_popcountll(state->occ_black); + + // Slight bonus for having pieces in the center + U64 center = 0x0000001818000000ULL; // e4, e5, d4, d5 + U64 extended_center = 0x00003C3C3C3C0000ULL; // c3-f3 to c6-f6 + + score += __builtin_popcountll(state->occ_white & center) * 5; + score -= __builtin_popcountll(state->occ_black & center) * 5; + score += __builtin_popcountll(state->occ_white & extended_center) * 2; + score -= __builtin_popcountll(state->occ_black & extended_center) * 2; + return score; } @@ -204,9 +221,21 @@ int evaluate_position(const GameState *state) { int evaluate(const GameState *state) { int score = evaluate_material(state) + evaluate_position(state); - // Mobility bonus (simplified) + // Mobility bonus GameState temp_state = *state; Move moves[256]; + + // Add repetition penalty to avoid draw by repetition everytime + U64 current_hash = hash_position(state); + for (int i = 0; i < state->history.position_count; i++) { + if (state->history.positions[i].hash == current_hash) { + if (state->history.positions[i].count >= 2) { + score -= 50; // Penalty for positions appearing twice + } + break; + } + } + temp_state.side_to_move = 0; // White int white_mobility = generate_moves(&temp_state, moves); diff --git a/src/main.c b/src/main.c index d25aa9e..8516490 100644 --- a/src/main.c +++ b/src/main.c @@ -10,100 +10,14 @@ #include "evaluation.h" #include "search.h" #include "notation.h" +#include "pgn.h" +#include "repetition.h" +#include "uci.h" -// Function to clear the terminal screen -void clear_screen() { - printf("\033[2J\033[H"); - fflush(stdout); -} - -// Function to clear from cursor to end of screen -void clear_to_end_of_screen() { - printf("\033[J"); - fflush(stdout); -} - -// Function to move cursor to the top-left corner -void move_cursor_to_top() { - printf("\033[H"); - fflush(stdout); -} - -// Function to print the game state -void print_game_state(GameState *state, int depth, int move_number, char* move_san, int pre_eval) { - print_board(state); - - if (move_number > 0) { - printf("Last move: %d%s: %s\nEvaluation: %+.2f\n", - move_number, - state->side_to_move == 1 ? "w" : "b", // opposite of current side - move_san, - pre_eval / 100.0); - } - - printf("Search depth: %d\n", depth); -} - -// Main game loop +// Main loop int main() { - // Initialize attack tables - init_attack_tables(); - - // Initialize game state - GameState state; - init_startpos(&state); - - // Search depth (can be adjusted) - int depth = 5; - char last_move_san[10] = ""; - - // Clear the screen at the beginning - clear_screen(); - print_game_state(&state, depth, 0, "", 0); - - // Game loop - while (1) { - // Generate legal moves - Move moves[256]; - int num_moves = generate_legal_moves(&state, moves); - - // Check for checkmate or stalemate - if (num_moves == 0) { - move_cursor_to_top(); - clear_to_end_of_screen(); - print_game_state(&state, depth, state.fullmove_number, last_move_san, 0); - - if (is_king_in_check(&state, state.side_to_move)) { - printf("Checkmate! %s wins.\n", state.side_to_move == 0 ? "Black" : "White"); - } else { - printf("Stalemate! The game is a draw.\n"); - } - break; - } - - // Engine finds the best move - fflush(stdout); - Move best_move = find_best_move(&state, depth); - - // Get SAN notation of the move - strcpy(last_move_san, move_to_san(&state, best_move)); - - // Get evaluation before making the move - int pre_eval = evaluate(&state); - int move_number = state.fullmove_number; - - // Make the best move - int captured, old_castling, old_ep; - make_move(&state, best_move, &captured, &old_castling, &old_ep); - - // Redraw the entire screen - move_cursor_to_top(); - clear_to_end_of_screen(); - print_game_state(&state, depth, move_number, last_move_san, pre_eval); - - // Small delay between moves - usleep(500000); // 0.5 second - } - + // Default to UCI mode + printf("Launching BitChess in UCI mode...\n"); + uci_loop(); return 0; -} +} \ No newline at end of file diff --git a/src/notation.c b/src/notation.c index 1c883a3..468044d 100644 --- a/src/notation.c +++ b/src/notation.c @@ -5,6 +5,15 @@ #include "board.h" #include "movegen.h" +char* move_to_coordinates(Move move) { + char *buf = malloc(5); // 4 characters + null terminator + if (!buf) return NULL; + snprintf(buf, 5, "%c%d%c%d", + 'a' + (move.from % 8), 1 + (move.from / 8), + 'a' + (move.to % 8), 1 + (move.to / 8)); + return buf; +} + // Convert a move to SAN notation char* move_to_san(const GameState *state, Move move) { static char san[10]; @@ -34,64 +43,92 @@ char* move_to_san(const GameState *state, Move move) { return san; } - // Normal moves + // Get piece character for non-pawns char piece_char = ' '; if (pc != P && pc != p) { - piece_char = "NBRQK"[pc % 6]; + piece_char = "NBRQK"[pc % 6 - 1]; } - // Check if capture + // Check if capture - must be enemy piece or en passant int is_capture = 0; + int moving_side = (pc < 6) ? 0 : 1; + + // Check for regular capture for (int i = 0; i < 12; i++) { - if (i != pc && GET_BIT(state->bitboards[i], move.to)) { - is_capture = 1; - break; + if (GET_BIT(state->bitboards[i], move.to)) { + int captured_side = (i < 6) ? 0 : 1; + if (captured_side != moving_side) { + is_capture = 1; + break; + } } } - // En passant capture - if ((pc == P || pc == p) && move.to == state->ep_square) { + // Check for en passant capture + if ((pc == P || pc == p) && move.to == state->ep_square && state->ep_square != -1) { is_capture = 1; } - // Check if we need file/rank disambiguation + // Determine disambiguation for non-pawn pieces int need_file = 0, need_rank = 0; if (pc != P && pc != p) { - // Find if other pieces of same type can move to same square - GameState test_state = *state; - Move test_moves[256]; - int num_moves = generate_legal_moves(&test_state, test_moves); + Move legal_moves[256]; + int num_moves = generate_legal_moves((GameState*)state, legal_moves); + + // Find all other pieces of same type that can move to same destination + int same_file_count = 0; + int same_rank_count = 0; + int total_ambiguous = 0; for (int i = 0; i < num_moves; i++) { - if (test_moves[i].to == move.to && test_moves[i].from != move.from) { - // See if it's the same piece type + if (legal_moves[i].to == move.to && legal_moves[i].from != move.from) { + // Check if it's the same piece type for (int p = 0; p < 12; p++) { - if (GET_BIT(state->bitboards[p], test_moves[i].from) && p == pc) { - int other_file = test_moves[i].from % 8; - int other_rank = test_moves[i].from / 8; + if (GET_BIT(state->bitboards[p], legal_moves[i].from) && p == pc) { + total_ambiguous++; + int other_file = legal_moves[i].from % 8; + int other_rank = legal_moves[i].from / 8; if (other_file == from_file) { - need_rank = 1; - } else if (other_rank == from_rank) { - need_file = 1; - } else { - need_file = 1; + same_file_count++; } + if (other_rank == from_rank) { + same_rank_count++; + } + break; } } } } + + // Determine what disambiguation is needed + if (total_ambiguous > 0) { + if (same_file_count == 0) { + // No other piece shares the same file, so file is sufficient + need_file = 1; + } else if (same_rank_count == 0) { + // Other pieces share file but not rank, so rank is sufficient + need_rank = 1; + } else { + // Both file and rank are shared by some pieces, use file as preference + need_file = 1; + // But if file alone isn't sufficient, we need rank too + if (same_file_count > 0) { + need_rank = 1; + } + } + } } // Build the SAN string int idx = 0; - // Add piece letter except for pawns + // Add piece letter for non-pawns if (piece_char != ' ') { san[idx++] = piece_char; } - // Add disambiguation if needed + // Add disambiguation if (need_file) { san[idx++] = 'a' + from_file; } @@ -99,7 +136,7 @@ char* move_to_san(const GameState *state, Move move) { san[idx++] = '1' + from_rank; } - // For pawns capturing, need to specify the file + // For pawn captures, add the departure file if ((pc == P || pc == p) && is_capture) { san[idx++] = 'a' + from_file; } @@ -270,4 +307,4 @@ Move parse_san(const GameState *state, const char *san) { // Print move in algebraic notation void print_move(const GameState *state, Move move) { printf("%s", move_to_san(state, move)); -} +} \ No newline at end of file diff --git a/src/notation.h b/src/notation.h index 971615e..aedf0b2 100644 --- a/src/notation.h +++ b/src/notation.h @@ -3,6 +3,9 @@ #include "types.h" +// Convert a move to chessboard coordinates +char* move_to_coordinates(Move move); + // Convert a move to SAN notation char* move_to_san(const GameState *state, Move move); diff --git a/src/pgn.c b/src/pgn.c new file mode 100644 index 0000000..167eabb --- /dev/null +++ b/src/pgn.c @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include "pgn.h" + +// Initialize PGN structure with default values +void pgn_init(PGN* pgn) { + strcpy(pgn->event, "Casual Game"); + strcpy(pgn->site, "Local Engine"); + + // Get current date + time_t now; + struct tm* timeinfo; + time(&now); + timeinfo = localtime(&now); + strftime(pgn->date, sizeof(pgn->date), "%Y.%m.%d", timeinfo); + + strcpy(pgn->round, "1"); + strcpy(pgn->white, "Engine"); + strcpy(pgn->black, "Engine"); + pgn->result = PGN_RESULT_ONGOING; + + pgn->moves[0] = '\0'; + pgn->move_count = 0; + pgn->current_move_number = 1; + pgn->white_to_move = 1; +} + +// Set PGN headers +void pgn_set_header(PGN* pgn, const char* event, const char* site, const char* date, + const char* round, const char* white, const char* black) { + if (event) strncpy(pgn->event, event, sizeof(pgn->event) - 1); + if (site) strncpy(pgn->site, site, sizeof(pgn->site) - 1); + if (date) strncpy(pgn->date, date, sizeof(pgn->date) - 1); + if (round) strncpy(pgn->round, round, sizeof(pgn->round) - 1); + if (white) strncpy(pgn->white, white, sizeof(pgn->white) - 1); + if (black) strncpy(pgn->black, black, sizeof(pgn->black) - 1); +} + +// Add a move to the PGN +void pgn_add_move(PGN* pgn, const char* san_move) { + char move_str[32]; + + // Format the move with proper numbering + if (pgn->white_to_move) { + // White's move - add move number + snprintf(move_str, sizeof(move_str), "%d. %s", pgn->current_move_number, san_move); + } else { + // Black's move - just add the move + snprintf(move_str, sizeof(move_str), " %s", san_move); + pgn->current_move_number++; + } + + // Add to moves string + strcat(pgn->moves, move_str); + + // Add space or newline for formatting (every 8 moves on new line) + if (pgn->move_count % 8 == 7) { + strcat(pgn->moves, "\n"); + } else { + strcat(pgn->moves, " "); + } + + pgn->move_count++; + pgn->white_to_move = !pgn->white_to_move; +} + +// Set the game result +void pgn_set_result(PGN* pgn, PGNResult result) { + pgn->result = result; +} + +// Get formatted PGN string +char* pgn_get_formatted(PGN* pgn) { + static char formatted_pgn[8192]; + + // Build the PGN headers + snprintf(formatted_pgn, sizeof(formatted_pgn), + "[Event \"%s\"]\n" + "[Site \"%s\"]\n" + "[Date \"%s\"]\n" + "[Round \"%s\"]\n" + "[White \"%s\"]\n" + "[Black \"%s\"]\n" + "[Result \"%s\"]\n" + "\n" + "%s", + pgn->event, + pgn->site, + pgn->date, + pgn->round, + pgn->white, + pgn->black, + pgn->result == PGN_RESULT_WHITE_WINS ? "1-0" : + pgn->result == PGN_RESULT_BLACK_WINS ? "0-1" : + pgn->result == PGN_RESULT_DRAW ? "1/2-1/2" : "*", + pgn->moves + ); + + // Add result at the end if game is finished + if (pgn->result != PGN_RESULT_ONGOING) { + strcat(formatted_pgn, " "); + strcat(formatted_pgn, pgn->result == PGN_RESULT_WHITE_WINS ? "1-0" : + pgn->result == PGN_RESULT_BLACK_WINS ? "0-1" : + pgn->result == PGN_RESULT_DRAW ? "1/2-1/2" : "*"); + } + + return formatted_pgn; +} + +// Save PGN to file +int pgn_save_to_file(PGN* pgn, const char* filename) { + FILE* file = fopen(filename, "w"); + if (!file) { + return 0; // Failed to open file + } + + fprintf(file, "%s\n", pgn_get_formatted(pgn)); + fclose(file); + return 1; // Success +} + +// Print PGN to console +void pgn_print(PGN* pgn) { + printf("%s\n", pgn_get_formatted(pgn)); +} \ No newline at end of file diff --git a/src/pgn.h b/src/pgn.h new file mode 100644 index 0000000..4dc265a --- /dev/null +++ b/src/pgn.h @@ -0,0 +1,40 @@ +#ifndef PGN_H +#define PGN_H + +#include "types.h" + +// PGN result types +typedef enum { + PGN_RESULT_ONGOING = 0, + PGN_RESULT_WHITE_WINS, + PGN_RESULT_BLACK_WINS, + PGN_RESULT_DRAW +} PGNResult; + +// PGN structure to hold game data +typedef struct { + char event[64]; + char site[64]; + char date[32]; + char round[16]; + char white[64]; + char black[64]; + PGNResult result; + + char moves[4096]; // String to store all moves + int move_count; + int current_move_number; + int white_to_move; +} PGN; + +// Function declarations +void pgn_init(PGN* pgn); +void pgn_set_header(PGN* pgn, const char* event, const char* site, const char* date, + const char* round, const char* white, const char* black); +void pgn_add_move(PGN* pgn, const char* san_move); +void pgn_set_result(PGN* pgn, PGNResult result); +char* pgn_get_formatted(PGN* pgn); +int pgn_save_to_file(PGN* pgn, const char* filename); +void pgn_print(PGN* pgn); + +#endif // PGN_H \ No newline at end of file diff --git a/src/repetition.c b/src/repetition.c new file mode 100644 index 0000000..4435ce7 --- /dev/null +++ b/src/repetition.c @@ -0,0 +1,66 @@ +#include +#include +#include "repetition.h" + +// Simple hash function for position +U64 hash_position(const GameState* state) { + U64 hash = 0; + + // Hash all bitboards + for (int i = 0; i < 12; i++) { + hash ^= state->bitboards[i] * (i + 1); + } + + // Hash side to move + hash ^= (U64)state->side_to_move * 0x9E3779B97F4A7C15ULL; + + // Hash castling rights + hash ^= (U64)state->castling * 0x85EBCA6B7C1DE10BULL; + + // Hash en passant square + if (state->ep_square != -1) { + hash ^= (U64)state->ep_square * 0xC2B2AE3D27D4EB4FULL; + } + + return hash; +} + +// Add current position to history +void add_position_to_history(GameState* state) { + U64 current_hash = hash_position(state); + + // Check if this position already exists in history + for (int i = 0; i < state->history.position_count; i++) { + if (state->history.positions[i].hash == current_hash) { + state->history.positions[i].count++; + return; + } + } + + // Add new position if not found + if (state->history.position_count < 1000) { + state->history.positions[state->history.position_count].hash = current_hash; + state->history.positions[state->history.position_count].count = 1; + state->history.position_count++; + } +} + +// Check if current position has occurred 3 times (threefold repetition) +int check_threefold_repetition(const GameState* state) { + U64 current_hash = hash_position(state); + + // Count occurrences of current position + for (int i = 0; i < state->history.position_count; i++) { + if (state->history.positions[i].hash == current_hash) { + return state->history.positions[i].count >= 3; + } + } + + return 0; // Position not found or less than 3 occurrences +} + +// Clear game history (for new game) +void clear_game_history(GameState* state) { + state->history.position_count = 0; + memset(state->history.positions, 0, sizeof(state->history.positions)); +} \ No newline at end of file diff --git a/src/repetition.h b/src/repetition.h new file mode 100644 index 0000000..ed8e93d --- /dev/null +++ b/src/repetition.h @@ -0,0 +1,12 @@ +#ifndef REPETITION_H +#define REPETITION_H + +#include "types.h" + +// Function declarations +U64 hash_position(const GameState* state); +void add_position_to_history(GameState* state); +int check_threefold_repetition(const GameState* state); +void clear_game_history(GameState* state); + +#endif // REPETITION_H \ No newline at end of file diff --git a/src/search.c b/src/search.c index 2724fe8..4e13d7a 100644 --- a/src/search.c +++ b/src/search.c @@ -1,8 +1,10 @@ #include +#include #include "search.h" #include "evaluation.h" #include "movegen.h" #include "board.h" +#include "uci.h" // Order moves to improve alpha-beta pruning void order_moves(Move *moves, int num_moves, const GameState *state) { @@ -161,13 +163,16 @@ Move find_best_move(GameState *state, int depth) { int num_moves = generate_legal_moves(state, moves); if (num_moves == 0) { - // No legal moves return (Move){0, 0, 0, 0}; } // Order moves to search promising moves first order_moves(moves, num_moves, state); + // Output initial search info + printf("info depth %d nodes 0 time 0\n", depth); + fflush(stdout); + // Set up threads pthread_t threads[256]; ThreadArg thread_args[256]; @@ -175,11 +180,10 @@ Move find_best_move(GameState *state, int depth) { // Shared variables for best move pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int best_eval = -32000; - Move best_move = moves[0]; // Default to first move + Move best_move = moves[0]; // Start a thread for each move for (int i = 0; i < num_moves; i++) { - // Make a copy of the game state for each thread thread_args[i].state = *state; thread_args[i].move = moves[i]; thread_args[i].depth = depth; @@ -195,7 +199,11 @@ Move find_best_move(GameState *state, int depth) { pthread_join(threads[i], NULL); } - // Clean up mutex + // Output final search info with evaluation + printf("info depth %d score cp %d pv %s\n", + depth, best_eval, move_to_uci(best_move)); + fflush(stdout); + pthread_mutex_destroy(&mutex); return best_move; diff --git a/src/types.h b/src/types.h index 4a842ed..68b72cd 100644 --- a/src/types.h +++ b/src/types.h @@ -35,6 +35,18 @@ typedef struct { int score; // for move ordering } Move; +// Position hash for repetition detection +typedef struct { + U64 hash; + int count; +} PositionHash; + +// Game history for repetition detection +typedef struct { + PositionHash positions[1000]; // Store up to 1000 positions + int position_count; +} GameHistory; + // Game state typedef struct { U64 bitboards[12]; @@ -44,6 +56,7 @@ typedef struct { int ep_square; // en passant target square int halfmove_clock; // for 50-move rule int fullmove_number; // starts at 1, increments after black's move + GameHistory history; // Add this line } GameState; // Thread argument structure diff --git a/src/uci.c b/src/uci.c new file mode 100644 index 0000000..5d9c31a --- /dev/null +++ b/src/uci.c @@ -0,0 +1,217 @@ +#include +#include +#include +#include "uci.h" +#include "board.h" +#include "movegen.h" +#include "search.h" +#include "bitboard.h" +#include "repetition.h" + +// UCI options +static int uci_depth = 6; +static int uci_hash = 64; +static int uci_threads = 4; + +// Convert move to UCI format (e.g., "e2e4", "e7e8q") +char* move_to_uci(Move move) { + static char uci_move[6]; + char files[] = "abcdefgh"; + char ranks[] = "12345678"; + + uci_move[0] = files[move.from % 8]; + uci_move[1] = ranks[move.from / 8]; + uci_move[2] = files[move.to % 8]; + uci_move[3] = ranks[move.to / 8]; + uci_move[4] = '\0'; + + // Add promotion + if (move.promo) { + char promo_chars[] = "nbrq"; + uci_move[4] = promo_chars[move.promo - 1]; + uci_move[5] = '\0'; + } + + return uci_move; +} + +// Parse UCI move format (e.g., "e2e4", "e7e8q") +Move parse_uci_move(const GameState *state, const char *move_str) { + Move move = {0, 0, 0, 0}; + + if (strlen(move_str) < 4) return move; + + // Parse from square + int from_file = move_str[0] - 'a'; + int from_rank = move_str[1] - '1'; + move.from = from_rank * 8 + from_file; + + // Parse to square + int to_file = move_str[2] - 'a'; + int to_rank = move_str[3] - '1'; + move.to = to_rank * 8 + to_file; + + // Parse promotion + if (strlen(move_str) == 5) { + switch (move_str[4]) { + case 'n': move.promo = 1; break; + case 'b': move.promo = 2; break; + case 'r': move.promo = 3; break; + case 'q': move.promo = 4; break; + } + } + + return move; +} + +// Handle UCI position command +void uci_position(GameState *state, char *line) { + char *token = strtok(line, " "); + + if (token && strcmp(token, "position") == 0) { + token = strtok(NULL, " "); + + if (token && strcmp(token, "startpos") == 0) { + // Initialize starting position + init_startpos(state); + clear_game_history(state); + add_position_to_history(state); + + token = strtok(NULL, " "); + } + // Add support for FEN later if needed + + // Handle moves + if (token && strcmp(token, "moves") == 0) { + token = strtok(NULL, " "); + while (token) { + Move move = parse_uci_move(state, token); + + // Verify move is legal + Move legal_moves[256]; + int num_moves = generate_legal_moves(state, legal_moves); + int legal = 0; + + for (int i = 0; i < num_moves; i++) { + if (legal_moves[i].from == move.from && + legal_moves[i].to == move.to && + legal_moves[i].promo == move.promo) { + legal = 1; + break; + } + } + + if (legal) { + int captured, old_castling, old_ep; + make_move(state, move, &captured, &old_castling, &old_ep); + add_position_to_history(state); + } else { + printf("info string Illegal move: %s\n", token); + } + + token = strtok(NULL, " "); + } + } + } +} + +// Handle UCI go command +void uci_go(GameState *state, char *line) { + int depth = uci_depth; // Use UCI option default + int movetime = 0; + + // Parse go parameters (these override the UCI options) + char *token = strtok(line, " "); + while (token) { + if (strcmp(token, "depth") == 0) { + token = strtok(NULL, " "); + if (token) depth = atoi(token); + } else if (strcmp(token, "movetime") == 0) { + token = strtok(NULL, " "); + if (token) movetime = atoi(token); + } + token = strtok(NULL, " "); + } + + // Find best move + Move best_move = find_best_move(state, depth); + + // Output the move + printf("bestmove %s\n", move_to_uci(best_move)); + fflush(stdout); +} + +// Main UCI loop +void uci_loop() { + char line[8192]; + GameState state; + + // Initialize + init_attack_tables(); + init_startpos(&state); + clear_game_history(&state); + add_position_to_history(&state); + + while (fgets(line, sizeof(line), stdin)) { + // Remove newline + line[strcspn(line, "\n")] = 0; + + if (strcmp(line, "uci") == 0) { + printf("id name BitChess\n"); + printf("id author Sarthak\n"); + + // Add UCI options + printf("option name Hash type spin default 64 min 1 max 1024\n"); + printf("option name Threads type spin default 1 min 1 max 64\n"); + printf("option name Depth type spin default 6 min 1 max 20\n"); + + printf("uciok\n"); + fflush(stdout); + + } else if (strcmp(line, "isready") == 0) { + printf("readyok\n"); + fflush(stdout); + + } else if (strcmp(line, "quit") == 0) { + break; + + } else if (strncmp(line, "position", 8) == 0) { + uci_position(&state, line); + + } else if (strncmp(line, "go", 2) == 0) { + uci_go(&state, line); + + } else if (strncmp(line, "setoption", 9) == 0) { + char *name_ptr = strstr(line, "name"); + char *value_ptr = strstr(line, "value"); + + if (name_ptr && value_ptr) { + char option_name[100] = {0}; + char option_value[100] = {0}; + + // Extract option name + sscanf(name_ptr + 5, "%s", option_name); + + // Extract option value + sscanf(value_ptr + 6, "%s", option_value); + + // Handle different options + if (strcmp(option_name, "Depth") == 0) { + uci_depth = atoi(option_value); + if (uci_depth < 1) uci_depth = 1; + if (uci_depth > 20) uci_depth = 20; + } else if (strcmp(option_name, "Hash") == 0) { + uci_hash = atoi(option_value); + // We don't actually use hash tables yet, but store the value + } else if (strcmp(option_name, "Threads") == 0) { + uci_threads = atoi(option_value); + // We don't control thread count yet, but store the value + } + } + } else if (strcmp(line, "ucinewgame") == 0) { + init_startpos(&state); + clear_game_history(&state); + add_position_to_history(&state); + } + } +} \ No newline at end of file diff --git a/src/uci.h b/src/uci.h new file mode 100644 index 0000000..5aabc8b --- /dev/null +++ b/src/uci.h @@ -0,0 +1,14 @@ +#ifndef UCI_H +#define UCI_H + +#include "types.h" +#include "board.h" + +// UCI command handling +void uci_loop(); +void uci_position(GameState *state, char *line); +void uci_go(GameState *state, char *line); +Move parse_uci_move(const GameState *state, const char *move_str); +char* move_to_uci(Move move); + +#endif // UCI_H \ No newline at end of file