add uci support

This commit is contained in:
srtk 2025-07-04 17:12:35 +05:30
parent e7af512d93
commit d649358c12
13 changed files with 618 additions and 127 deletions

View file

@ -4,6 +4,7 @@
#include "board.h" #include "board.h"
#include "bitboard.h" #include "bitboard.h"
#include "movegen.h" #include "movegen.h"
#include "repetition.h"
// Update occupancy bitboards // Update occupancy bitboards
void update_occupancy(GameState *state) { void update_occupancy(GameState *state) {
@ -48,6 +49,9 @@ void init_startpos(GameState *state) {
state->fullmove_number = 1; state->fullmove_number = 1;
update_occupancy(state); update_occupancy(state);
// Initialize history
clear_game_history(state);
} }
// Make a move // 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); SET_BIT(state->bitboards[pc], from);
// Handle en passant capture // 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); int cap_sq = to + (side == 0 ? -8 : 8);
SET_BIT(state->bitboards[captured_pc], cap_sq); SET_BIT(state->bitboards[captured_pc], cap_sq);
} }
// Restore captured piece // Restore captured piece
else if (captured_pc != NO_PIECE) { else if (captured_pc != NO_PIECE) {
SET_BIT(state->bitboards[captured_pc], to); SET_BIT(state->bitboards[captured_pc], to);
@ -232,6 +237,12 @@ void undo_move(GameState *state, Move move, int captured_pc, int old_castling, i
// Restore en passant square // Restore en passant square
state->ep_square = old_ep; 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 // Adjust fullmove number
if (side == 1) { if (side == 1) {
state->fullmove_number--; state->fullmove_number--;

View file

@ -1,5 +1,6 @@
#include "evaluation.h" #include "evaluation.h"
#include "movegen.h" #include "movegen.h"
#include "repetition.h"
// Material values // Material values
const int piece_value[12] = {100, 320, 330, 500, 900, 20000, 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]; 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; return score;
} }
@ -204,9 +221,21 @@ int evaluate_position(const GameState *state) {
int evaluate(const GameState *state) { int evaluate(const GameState *state) {
int score = evaluate_material(state) + evaluate_position(state); int score = evaluate_material(state) + evaluate_position(state);
// Mobility bonus (simplified) // Mobility bonus
GameState temp_state = *state; GameState temp_state = *state;
Move moves[256]; 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 temp_state.side_to_move = 0; // White
int white_mobility = generate_moves(&temp_state, moves); int white_mobility = generate_moves(&temp_state, moves);

View file

@ -10,100 +10,14 @@
#include "evaluation.h" #include "evaluation.h"
#include "search.h" #include "search.h"
#include "notation.h" #include "notation.h"
#include "pgn.h"
#include "repetition.h"
#include "uci.h"
// Function to clear the terminal screen // Main loop
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
int main() { int main() {
// Initialize attack tables // Default to UCI mode
init_attack_tables(); printf("Launching BitChess in UCI mode...\n");
uci_loop();
// 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
}
return 0; return 0;
} }

View file

@ -5,6 +5,15 @@
#include "board.h" #include "board.h"
#include "movegen.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 // Convert a move to SAN notation
char* move_to_san(const GameState *state, Move move) { char* move_to_san(const GameState *state, Move move) {
static char san[10]; static char san[10];
@ -34,64 +43,92 @@ char* move_to_san(const GameState *state, Move move) {
return san; return san;
} }
// Normal moves // Get piece character for non-pawns
char piece_char = ' '; char piece_char = ' ';
if (pc != P && pc != p) { 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 is_capture = 0;
int moving_side = (pc < 6) ? 0 : 1;
// Check for regular capture
for (int i = 0; i < 12; i++) { for (int i = 0; i < 12; i++) {
if (i != pc && GET_BIT(state->bitboards[i], move.to)) { if (GET_BIT(state->bitboards[i], move.to)) {
is_capture = 1; int captured_side = (i < 6) ? 0 : 1;
break; if (captured_side != moving_side) {
is_capture = 1;
break;
}
} }
} }
// En passant capture // Check for en passant capture
if ((pc == P || pc == p) && move.to == state->ep_square) { if ((pc == P || pc == p) && move.to == state->ep_square && state->ep_square != -1) {
is_capture = 1; is_capture = 1;
} }
// Check if we need file/rank disambiguation // Determine disambiguation for non-pawn pieces
int need_file = 0, need_rank = 0; int need_file = 0, need_rank = 0;
if (pc != P && pc != p) { if (pc != P && pc != p) {
// Find if other pieces of same type can move to same square Move legal_moves[256];
GameState test_state = *state; int num_moves = generate_legal_moves((GameState*)state, legal_moves);
Move test_moves[256];
int num_moves = generate_legal_moves(&test_state, test_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++) { for (int i = 0; i < num_moves; i++) {
if (test_moves[i].to == move.to && test_moves[i].from != move.from) { if (legal_moves[i].to == move.to && legal_moves[i].from != move.from) {
// See if it's the same piece type // Check if it's the same piece type
for (int p = 0; p < 12; p++) { for (int p = 0; p < 12; p++) {
if (GET_BIT(state->bitboards[p], test_moves[i].from) && p == pc) { if (GET_BIT(state->bitboards[p], legal_moves[i].from) && p == pc) {
int other_file = test_moves[i].from % 8; total_ambiguous++;
int other_rank = test_moves[i].from / 8; int other_file = legal_moves[i].from % 8;
int other_rank = legal_moves[i].from / 8;
if (other_file == from_file) { if (other_file == from_file) {
need_rank = 1; same_file_count++;
} else if (other_rank == from_rank) {
need_file = 1;
} else {
need_file = 1;
} }
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 // Build the SAN string
int idx = 0; int idx = 0;
// Add piece letter except for pawns // Add piece letter for non-pawns
if (piece_char != ' ') { if (piece_char != ' ') {
san[idx++] = piece_char; san[idx++] = piece_char;
} }
// Add disambiguation if needed // Add disambiguation
if (need_file) { if (need_file) {
san[idx++] = 'a' + from_file; san[idx++] = 'a' + from_file;
} }
@ -99,7 +136,7 @@ char* move_to_san(const GameState *state, Move move) {
san[idx++] = '1' + from_rank; 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) { if ((pc == P || pc == p) && is_capture) {
san[idx++] = 'a' + from_file; san[idx++] = 'a' + from_file;
} }

View file

@ -3,6 +3,9 @@
#include "types.h" #include "types.h"
// Convert a move to chessboard coordinates
char* move_to_coordinates(Move move);
// Convert a move to SAN notation // Convert a move to SAN notation
char* move_to_san(const GameState *state, Move move); char* move_to_san(const GameState *state, Move move);

127
src/pgn.c Normal file
View file

@ -0,0 +1,127 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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));
}

40
src/pgn.h Normal file
View file

@ -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

66
src/repetition.c Normal file
View file

@ -0,0 +1,66 @@
#include <stdio.h>
#include <string.h>
#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));
}

12
src/repetition.h Normal file
View file

@ -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

View file

@ -1,8 +1,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include "search.h" #include "search.h"
#include "evaluation.h" #include "evaluation.h"
#include "movegen.h" #include "movegen.h"
#include "board.h" #include "board.h"
#include "uci.h"
// Order moves to improve alpha-beta pruning // Order moves to improve alpha-beta pruning
void order_moves(Move *moves, int num_moves, const GameState *state) { 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); int num_moves = generate_legal_moves(state, moves);
if (num_moves == 0) { if (num_moves == 0) {
// No legal moves
return (Move){0, 0, 0, 0}; return (Move){0, 0, 0, 0};
} }
// Order moves to search promising moves first // Order moves to search promising moves first
order_moves(moves, num_moves, state); 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 // Set up threads
pthread_t threads[256]; pthread_t threads[256];
ThreadArg thread_args[256]; ThreadArg thread_args[256];
@ -175,11 +180,10 @@ Move find_best_move(GameState *state, int depth) {
// Shared variables for best move // Shared variables for best move
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int best_eval = -32000; int best_eval = -32000;
Move best_move = moves[0]; // Default to first move Move best_move = moves[0];
// Start a thread for each move // Start a thread for each move
for (int i = 0; i < num_moves; i++) { 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].state = *state;
thread_args[i].move = moves[i]; thread_args[i].move = moves[i];
thread_args[i].depth = depth; thread_args[i].depth = depth;
@ -195,7 +199,11 @@ Move find_best_move(GameState *state, int depth) {
pthread_join(threads[i], NULL); 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); pthread_mutex_destroy(&mutex);
return best_move; return best_move;

View file

@ -35,6 +35,18 @@ typedef struct {
int score; // for move ordering int score; // for move ordering
} Move; } 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 // Game state
typedef struct { typedef struct {
U64 bitboards[12]; U64 bitboards[12];
@ -44,6 +56,7 @@ typedef struct {
int ep_square; // en passant target square int ep_square; // en passant target square
int halfmove_clock; // for 50-move rule int halfmove_clock; // for 50-move rule
int fullmove_number; // starts at 1, increments after black's move int fullmove_number; // starts at 1, increments after black's move
GameHistory history; // Add this line
} GameState; } GameState;
// Thread argument structure // Thread argument structure

217
src/uci.c Normal file
View file

@ -0,0 +1,217 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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);
}
}
}

14
src/uci.h Normal file
View file

@ -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