diff --git a/src/board.h b/src/board.h index 559a40a..7768ce2 100644 --- a/src/board.h +++ b/src/board.h @@ -3,22 +3,11 @@ #include "types.h" -// Update occupancy bitboards void update_occupancy(GameState *state); - -// Initialize a standard chess starting position void init_startpos(GameState *state); - -// Make a move void make_move(GameState *state, Move move, int *captured_pc, int *old_castling, int *old_ep); - -// Undo a move void undo_move(GameState *state, Move move, int captured_pc, int old_castling, int old_ep); - -// Check if king is in check int is_king_in_check(const GameState *state, int side); - -// Print board representation void print_board(const GameState *state); #endif // BOARD_H diff --git a/src/evaluation.c b/src/evaluation.c index 638983a..f743bce 100644 --- a/src/evaluation.c +++ b/src/evaluation.c @@ -201,10 +201,6 @@ int evaluate_position(const GameState *state) { // 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 diff --git a/src/evaluation.h b/src/evaluation.h index afe04da..fe53399 100644 --- a/src/evaluation.h +++ b/src/evaluation.h @@ -15,13 +15,8 @@ extern const int pst_queen[64]; extern const int pst_king_mid[64]; extern const int pst_king_end[64]; -// Evaluate material balance int evaluate_material(const GameState *state); - -// Evaluate positional factors int evaluate_position(const GameState *state); - -// Complete evaluation function int evaluate(const GameState *state); #endif // EVALUATION_H diff --git a/src/movegen.h b/src/movegen.h index d42e880..b21cc2e 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -3,34 +3,15 @@ #include "types.h" -// Check if a square is attacked by a given side int is_square_attacked(const GameState *state, int sq, int side); - -// Generate pawn moves int generate_pawn_moves(const GameState *state, Move *moves, int index); - -// Generate knight moves int generate_knight_moves(const GameState *state, Move *moves, int index); - -// Generate bishop moves int generate_bishop_moves(const GameState *state, Move *moves, int index); - -// Generate rook moves int generate_rook_moves(const GameState *state, Move *moves, int index); - -// Generate queen moves int generate_queen_moves(const GameState *state, Move *moves, int index); - -// Generate king moves int generate_king_moves(const GameState *state, Move *moves, int index); - -// Generate all pseudo-legal moves int generate_moves(const GameState *state, Move *moves); - -// Check if a move is legal (doesn't leave king in check) int is_move_legal(GameState *state, Move move); - -// Generate all legal moves int generate_legal_moves(GameState *state, Move *moves); #endif // MOVEGEN_H diff --git a/src/notation.c b/src/notation.c index 468044d..0144ea1 100644 --- a/src/notation.c +++ b/src/notation.c @@ -6,11 +6,26 @@ #include "movegen.h" char* move_to_coordinates(Move move) { - char *buf = malloc(5); // 4 characters + null terminator + char *buf = malloc(6); 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)); + + // Move coordinates should be within valid chess board range + int from_file = move.from % 8; + int from_rank = move.from / 8; + int to_file = move.to % 8; + int to_rank = move.to / 8; + + // Validate ranges (0-7 for files and ranks) + if (from_file < 0 || from_file > 7 || from_rank < 0 || from_rank > 7 || + to_file < 0 || to_file > 7 || to_rank < 0 || to_rank > 7) { + free(buf); + return NULL; + } + + snprintf(buf, 6, "%c%d%c%d", + 'a' + from_file, 1 + from_rank, + 'a' + to_file, 1 + to_rank); + return buf; } diff --git a/src/notation.h b/src/notation.h index aedf0b2..f2f7584 100644 --- a/src/notation.h +++ b/src/notation.h @@ -3,16 +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); - -// Parse SAN notation into a Move Move parse_san(const GameState *state, const char *san); - -// Print move in algebraic notation void print_move(const GameState *state, Move move); #endif // NOTATION_H diff --git a/src/pgn.h b/src/pgn.h index 4dc265a..e8bbb2f 100644 --- a/src/pgn.h +++ b/src/pgn.h @@ -3,31 +3,6 @@ #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); diff --git a/src/repetition.h b/src/repetition.h index ed8e93d..b3ad352 100644 --- a/src/repetition.h +++ b/src/repetition.h @@ -3,7 +3,6 @@ #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); diff --git a/src/search.c b/src/search.c index 4e13d7a..97b64dc 100644 --- a/src/search.c +++ b/src/search.c @@ -1,19 +1,71 @@ #include #include +#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) { - // Simple move ordering: - // 1. Captures (with MVV-LVA: Most Valuable Victim - Least Valuable Aggressor) - // 2. Promotions - // 3. Checks - // 4. Other moves +// Global search control +volatile int stop_search = 0; +SearchInfo global_search_info; + +void *info_thread(void *arg) { + SearchInfo *info = (SearchInfo *)arg; + while (!stop_search) { + usleep(100000); // Report every 100ms + if (info->nodes > 0) { + print_search_info(info, 0); + } + } + + return NULL; +} + +// Get current time in milliseconds +long get_time_ms() { + struct timeval tv; + gettimeofday(&tv, NULL); + return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); +} + +// Print search information +void print_search_info(SearchInfo *info, int multipv_index) { + long elapsed = get_time_ms() - info->start_time; + if (elapsed == 0) elapsed = 1; + + int nps = (info->nodes * 1000) / elapsed; + + printf("info depth %d seldepth %d nodes %d time %ld nps %d", + info->depth, info->seldepth, info->nodes, elapsed, nps); + + if (multipv_index > 0) { + printf(" multipv %d", multipv_index); + } + + if (info->is_mate) { + printf(" score mate %d", info->mate_score); + } else { + printf(" score cp %d", info->eval); + } + + // Print principal variation + if (info->pv_length > 0) { + printf(" pv"); + for (int i = 0; i < info->pv_length; i++) { + printf(" %s", move_to_uci(info->pv[i])); + } + } + + printf("\n"); + fflush(stdout); +} + +// Enhanced move ordering +void order_moves(Move *moves, int num_moves, const GameState *state) { int scores[256] = {0}; for (int i = 0; i < num_moves; i++) { @@ -44,9 +96,8 @@ void order_moves(Move *moves, int num_moves, const GameState *state) { captured_piece = state->side_to_move == 0 ? p : P; } - // Base score + // MVV-LVA scoring if (captured_piece != NO_PIECE) { - // MVV-LVA: score = 10 * victim_value - attacker_value int victim_value = abs(piece_value[captured_piece]) / 100; int attacker_value = abs(piece_value[moving_piece]) / 100; scores[i] = 10 * victim_value - attacker_value + 1000; @@ -54,19 +105,27 @@ void order_moves(Move *moves, int num_moves, const GameState *state) { // Promotion bonus if (moves[i].promo) { - scores[i] += 800 + moves[i].promo * 10; // Queen promo gets highest score + scores[i] += 800 + moves[i].promo * 10; } - // Check if move gives check (simplified, could be more efficient) - GameState check_state = *state; - int captured, old_castling, old_ep; - make_move(&check_state, moves[i], &captured, &old_castling, &old_ep); - if (is_king_in_check(&check_state, check_state.side_to_move)) { - scores[i] += 500; + // Center control bonus + if ((to >= 27 && to <= 28) || (to >= 35 && to <= 36)) { // d4, e4, d5, e5 + scores[i] += 50; + } + + // Piece development bonus + if (moving_piece == N || moving_piece == n || + moving_piece == B || moving_piece == b) { + if ((moving_piece == N || moving_piece == B) && from >= 8) { + scores[i] += 30; + } + if ((moving_piece == n || moving_piece == b) && from < 56) { + scores[i] += 30; + } } } - // Sort moves based on scores (simple insertion sort) + // Sort moves based on scores for (int i = 1; i < num_moves; i++) { Move temp_move = moves[i]; int temp_score = scores[i]; @@ -83,41 +142,75 @@ void order_moves(Move *moves, int num_moves, const GameState *state) { } } -// Negamax with alpha-beta pruning -int minimax(GameState *state, int depth, int alpha, int beta) { - // Check if we're at a leaf node +// Enhanced minimax with PV tracking +int minimax(GameState *state, int depth, int alpha, int beta, Move *pv, int *pv_length, SearchInfo *info) { + if (stop_search) return 0; + + info->nodes++; + *pv_length = 0; + + // Update selective depth + if (info->depth - depth > info->seldepth) { + info->seldepth = info->depth - depth; + } + + // Check for time and report info periodically + if (info->nodes % 10000 == 0) { + long elapsed = get_time_ms() - info->start_time; + if (elapsed > 0) { + // Update global info for periodic reporting + global_search_info = *info; + global_search_info.pv_length = *pv_length; + for (int i = 0; i < *pv_length; i++) { + global_search_info.pv[i] = pv[i]; + } + } + } + if (depth == 0) { return evaluate(state); } - // Generate and count legal moves Move moves[256]; int num_moves = generate_legal_moves(state, moves); - // Check for checkmate or stalemate if (num_moves == 0) { if (is_king_in_check(state, state->side_to_move)) { - return -30000 + (10 - depth); // Checkmate (prefer faster checkmate) + info->is_mate = 1; + info->mate_score = -(30000 - (info->depth - depth)); + return -30000 + (info->depth - depth); } else { - return 0; // Stalemate + return 0; } } - // Order moves to improve alpha-beta pruning order_moves(moves, num_moves, state); int best_score = -32000; + Move best_pv[64]; + int best_pv_length = 0; - // Try each move for (int i = 0; i < num_moves; i++) { + if (stop_search) break; + int captured, old_castling, old_ep; + Move current_pv[64]; + int current_pv_length = 0; make_move(state, moves[i], &captured, &old_castling, &old_ep); - int score = -minimax(state, depth - 1, -beta, -alpha); + int score = -minimax(state, depth - 1, -beta, -alpha, current_pv, ¤t_pv_length, info); undo_move(state, moves[i], captured, old_castling, old_ep); if (score > best_score) { best_score = score; + + // Update PV + best_pv[0] = moves[i]; + best_pv_length = 1; + for (int j = 0; j < current_pv_length; j++) { + best_pv[j + 1] = current_pv[j]; + best_pv_length++; + } } if (best_score > alpha) { @@ -125,40 +218,26 @@ int minimax(GameState *state, int depth, int alpha, int beta) { } if (alpha >= beta) { - break; // Beta cutoff + break; // Beta cutoff } } + // Copy best PV to output + *pv_length = best_pv_length; + for (int i = 0; i < best_pv_length; i++) { + pv[i] = best_pv[i]; + } + return best_score; } -// Thread function for search -void *thread_search(void *arg) { - ThreadArg *thread_arg = (ThreadArg *)arg; - GameState *state = &thread_arg->state; - int captured, old_castling, old_ep; - - // Make the move - make_move(state, thread_arg->move, &captured, &old_castling, &old_ep); - - // Search from this position - thread_arg->eval = -minimax(state, thread_arg->depth - 1, -30000, 30000); - - // Lock mutex before checking if this is the best move - pthread_mutex_lock(thread_arg->mutex); - - if (thread_arg->eval > *(thread_arg->best_eval)) { - *(thread_arg->best_eval) = thread_arg->eval; - *(thread_arg->best_move) = thread_arg->move; - } - - pthread_mutex_unlock(thread_arg->mutex); - - return NULL; +// Root search with multi-PV support +int minimax_root(GameState *state, int depth, int alpha, int beta, Move *pv, int *pv_length, SearchInfo *info) { + return minimax(state, depth, alpha, beta, pv, pv_length, info); } -// Find best move using multithreaded search -Move find_best_move(GameState *state, int depth) { +// Find best move with multi-PV support +Move find_best_move(GameState *state, int depth, int multipv) { Move moves[256]; int num_moves = generate_legal_moves(state, moves); @@ -166,45 +245,75 @@ Move find_best_move(GameState *state, int depth) { 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); + // Initialize search info + SearchInfo info = {0}; + info.start_time = get_time_ms(); + info.depth = depth; + info.seldepth = 0; - // Set up threads - pthread_t threads[256]; - ThreadArg thread_args[256]; + MultiPVEntry multipv_entries[256]; + int multipv_count = (multipv > num_moves) ? num_moves : multipv; - // Shared variables for best move - pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - int best_eval = -32000; - Move best_move = moves[0]; - - // Start a thread for each move - for (int i = 0; i < num_moves; i++) { - thread_args[i].state = *state; - thread_args[i].move = moves[i]; - thread_args[i].depth = depth; - thread_args[i].mutex = &mutex; - thread_args[i].best_eval = &best_eval; - thread_args[i].best_move = &best_move; + // Search each move for multi-PV + for (int pv_index = 0; pv_index < multipv_count; pv_index++) { + int best_score = -32000; + int best_move_index = -1; - pthread_create(&threads[i], NULL, thread_search, &thread_args[i]); + // Find the best move not already in multi-PV list + for (int i = 0; i < num_moves; i++) { + // Skip moves already in multi-PV + int already_searched = 0; + for (int j = 0; j < pv_index; j++) { + if (moves[i].from == multipv_entries[j].move.from && + moves[i].to == multipv_entries[j].move.to && + moves[i].promo == multipv_entries[j].move.promo) { + already_searched = 1; + break; + } + } + if (already_searched) continue; + + int captured, old_castling, old_ep; + Move current_pv[64]; + int current_pv_length = 0; + + make_move(state, moves[i], &captured, &old_castling, &old_ep); + int score = -minimax_root(state, depth - 1, -30000, 30000, current_pv, ¤t_pv_length, &info); + undo_move(state, moves[i], captured, old_castling, old_ep); + + if (score > best_score) { + best_score = score; + best_move_index = i; + + // Store in multi-PV entry + multipv_entries[pv_index].move = moves[i]; + multipv_entries[pv_index].eval = score; + multipv_entries[pv_index].is_mate = info.is_mate; + multipv_entries[pv_index].mate_score = info.mate_score; + multipv_entries[pv_index].pv[0] = moves[i]; + multipv_entries[pv_index].pv_length = current_pv_length + 1; + for (int k = 0; k < current_pv_length; k++) { + multipv_entries[pv_index].pv[k + 1] = current_pv[k]; + } + } + } + + if (best_move_index != -1) { + // Print info for this PV line + SearchInfo pv_info = info; + pv_info.eval = multipv_entries[pv_index].eval; + pv_info.is_mate = multipv_entries[pv_index].is_mate; + pv_info.mate_score = multipv_entries[pv_index].mate_score; + pv_info.pv_length = multipv_entries[pv_index].pv_length; + for (int k = 0; k < pv_info.pv_length; k++) { + pv_info.pv[k] = multipv_entries[pv_index].pv[k]; + } + + print_search_info(&pv_info, pv_index + 1); + } } - // Wait for all threads to finish - for (int i = 0; i < num_moves; i++) { - pthread_join(threads[i], NULL); - } - - // 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; + return multipv_entries[0].move; } \ No newline at end of file diff --git a/src/search.h b/src/search.h index 59991d2..936951f 100644 --- a/src/search.h +++ b/src/search.h @@ -4,16 +4,13 @@ #include #include "types.h" -// Order moves to improve alpha-beta pruning +extern volatile int stop_search; +extern SearchInfo global_search_info; + void order_moves(Move *moves, int num_moves, const GameState *state); - -// Negamax with alpha-beta pruning -int minimax(GameState *state, int depth, int alpha, int beta); - -// Thread function for search +int minimax(GameState *state, int depth, int alpha, int beta, Move *pv, int *pv_length, SearchInfo *info); void *thread_search(void *arg); - -// Find best move using multithreaded search -Move find_best_move(GameState *state, int depth); +Move find_best_move(GameState *state, int depth, int multipv); +void print_search_info(SearchInfo *info, int multipv_index); #endif // SEARCH_H diff --git a/src/types.h b/src/types.h index 68b72cd..5bd50b8 100644 --- a/src/types.h +++ b/src/types.h @@ -59,6 +59,29 @@ typedef struct { GameHistory history; // Add this line } GameState; +// Search statistics +typedef struct { + int nodes; + int depth; + int seldepth; + clock_t start_time; + Move pv[64]; + int pv_length; + int eval; + int mate_score; + int is_mate; +} SearchInfo; + +// Multi-PV entry +typedef struct { + Move move; + int eval; + int is_mate; + int mate_score; + Move pv[64]; + int pv_length; +} MultiPVEntry; + // Thread argument structure typedef struct { GameState state; @@ -68,8 +91,33 @@ typedef struct { pthread_mutex_t *mutex; int *best_eval; Move *best_move; + SearchInfo *search_info; } ThreadArg; +// 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; + // Helper macros #define GET_BIT(bb, sq) (((bb) >> (sq)) & 1ULL) #define SET_BIT(bb, sq) ((bb) |= (1ULL << (sq))) diff --git a/src/uci.c b/src/uci.c index 5d9c31a..869f1f0 100644 --- a/src/uci.c +++ b/src/uci.c @@ -12,6 +12,7 @@ static int uci_depth = 6; static int uci_hash = 64; static int uci_threads = 4; +static int uci_multipv = 3; // Convert move to UCI format (e.g., "e2e4", "e7e8q") char* move_to_uci(Move move) { @@ -36,7 +37,7 @@ char* move_to_uci(Move move) { } // Parse UCI move format (e.g., "e2e4", "e7e8q") -Move parse_uci_move(const GameState *state, const char *move_str) { +Move parse_uci_move(const char *move_str) { Move move = {0, 0, 0, 0}; if (strlen(move_str) < 4) return move; @@ -79,13 +80,12 @@ void uci_position(GameState *state, char *line) { 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); + Move move = parse_uci_move(token); // Verify move is legal Move legal_moves[256]; @@ -117,10 +117,11 @@ void uci_position(GameState *state, char *line) { // Handle UCI go command void uci_go(GameState *state, char *line) { - int depth = uci_depth; // Use UCI option default + int depth = uci_depth; int movetime = 0; + int use_movetime = 0; - // Parse go parameters (these override the UCI options) + // Parse go parameters char *token = strtok(line, " "); while (token) { if (strcmp(token, "depth") == 0) { @@ -128,15 +129,26 @@ void uci_go(GameState *state, char *line) { if (token) depth = atoi(token); } else if (strcmp(token, "movetime") == 0) { token = strtok(NULL, " "); - if (token) movetime = atoi(token); + if (token) { + movetime = atoi(token); + use_movetime = 1; + } } token = strtok(NULL, " "); } - // Find best move - Move best_move = find_best_move(state, depth); + // Reset search control + stop_search = 0; + + // TODO: Implement time control logic + if (use_movetime) { + printf("info string movetime %d ms\n", movetime); + fflush(stdout); + } + + // Find best move with multi-PV support + Move best_move = find_best_move(state, depth, uci_multipv); - // Output the move printf("bestmove %s\n", move_to_uci(best_move)); fflush(stdout); } @@ -164,6 +176,7 @@ void uci_loop() { 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("option name MultiPV type spin default 3 min 1 max 10\n"); printf("uciok\n"); fflush(stdout); @@ -206,6 +219,10 @@ void uci_loop() { } 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(option_name, "MultiPV") == 0) { + uci_multipv = atoi(option_value); + if (uci_multipv < 1) uci_multipv = 1; + if (uci_multipv > 10) uci_multipv = 10; } } } else if (strcmp(line, "ucinewgame") == 0) { diff --git a/src/uci.h b/src/uci.h index 5aabc8b..dd15fe1 100644 --- a/src/uci.h +++ b/src/uci.h @@ -8,7 +8,7 @@ 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); +Move parse_uci_move(const char *move_str); char* move_to_uci(Move move); #endif // UCI_H \ No newline at end of file