More UCI features
This commit is contained in:
parent
d649358c12
commit
e1370eab54
13 changed files with 296 additions and 182 deletions
11
src/board.h
11
src/board.h
|
|
@ -3,22 +3,11 @@
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
// Update occupancy bitboards
|
|
||||||
void update_occupancy(GameState *state);
|
void update_occupancy(GameState *state);
|
||||||
|
|
||||||
// Initialize a standard chess starting position
|
|
||||||
void init_startpos(GameState *state);
|
void init_startpos(GameState *state);
|
||||||
|
|
||||||
// Make a move
|
|
||||||
void make_move(GameState *state, Move move, int *captured_pc, int *old_castling, int *old_ep);
|
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);
|
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);
|
int is_king_in_check(const GameState *state, int side);
|
||||||
|
|
||||||
// Print board representation
|
|
||||||
void print_board(const GameState *state);
|
void print_board(const GameState *state);
|
||||||
|
|
||||||
#endif // BOARD_H
|
#endif // BOARD_H
|
||||||
|
|
|
||||||
|
|
@ -201,10 +201,6 @@ int evaluate_position(const GameState *state) {
|
||||||
// Add randomness to break symmetry in equal positions
|
// Add randomness to break symmetry in equal positions
|
||||||
score += (int)(hash_position(state) % 10) - 5;
|
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
|
// Slight bonus for having pieces in the center
|
||||||
U64 center = 0x0000001818000000ULL; // e4, e5, d4, d5
|
U64 center = 0x0000001818000000ULL; // e4, e5, d4, d5
|
||||||
U64 extended_center = 0x00003C3C3C3C0000ULL; // c3-f3 to c6-f6
|
U64 extended_center = 0x00003C3C3C3C0000ULL; // c3-f3 to c6-f6
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,8 @@ extern const int pst_queen[64];
|
||||||
extern const int pst_king_mid[64];
|
extern const int pst_king_mid[64];
|
||||||
extern const int pst_king_end[64];
|
extern const int pst_king_end[64];
|
||||||
|
|
||||||
// Evaluate material balance
|
|
||||||
int evaluate_material(const GameState *state);
|
int evaluate_material(const GameState *state);
|
||||||
|
|
||||||
// Evaluate positional factors
|
|
||||||
int evaluate_position(const GameState *state);
|
int evaluate_position(const GameState *state);
|
||||||
|
|
||||||
// Complete evaluation function
|
|
||||||
int evaluate(const GameState *state);
|
int evaluate(const GameState *state);
|
||||||
|
|
||||||
#endif // EVALUATION_H
|
#endif // EVALUATION_H
|
||||||
|
|
|
||||||
|
|
@ -3,34 +3,15 @@
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
// Check if a square is attacked by a given side
|
|
||||||
int is_square_attacked(const GameState *state, int sq, int 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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
int generate_king_moves(const GameState *state, Move *moves, int index);
|
||||||
|
|
||||||
// Generate all pseudo-legal moves
|
|
||||||
int generate_moves(const GameState *state, Move *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);
|
int is_move_legal(GameState *state, Move move);
|
||||||
|
|
||||||
// Generate all legal moves
|
|
||||||
int generate_legal_moves(GameState *state, Move *moves);
|
int generate_legal_moves(GameState *state, Move *moves);
|
||||||
|
|
||||||
#endif // MOVEGEN_H
|
#endif // MOVEGEN_H
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,26 @@
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
|
|
||||||
char* move_to_coordinates(Move move) {
|
char* move_to_coordinates(Move move) {
|
||||||
char *buf = malloc(5); // 4 characters + null terminator
|
char *buf = malloc(6);
|
||||||
if (!buf) return NULL;
|
if (!buf) return NULL;
|
||||||
snprintf(buf, 5, "%c%d%c%d",
|
|
||||||
'a' + (move.from % 8), 1 + (move.from / 8),
|
// Move coordinates should be within valid chess board range
|
||||||
'a' + (move.to % 8), 1 + (move.to / 8));
|
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;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,9 @@
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
// Convert a move to chessboard coordinates
|
|
||||||
char* move_to_coordinates(Move move);
|
char* move_to_coordinates(Move move);
|
||||||
|
|
||||||
// Convert a move to SAN notation
|
|
||||||
char* move_to_san(const GameState *state, Move move);
|
char* move_to_san(const GameState *state, Move move);
|
||||||
|
|
||||||
// Parse SAN notation into a Move
|
|
||||||
Move parse_san(const GameState *state, const char *san);
|
Move parse_san(const GameState *state, const char *san);
|
||||||
|
|
||||||
// Print move in algebraic notation
|
|
||||||
void print_move(const GameState *state, Move move);
|
void print_move(const GameState *state, Move move);
|
||||||
|
|
||||||
#endif // NOTATION_H
|
#endif // NOTATION_H
|
||||||
|
|
|
||||||
25
src/pgn.h
25
src/pgn.h
|
|
@ -3,31 +3,6 @@
|
||||||
|
|
||||||
#include "types.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_init(PGN* pgn);
|
||||||
void pgn_set_header(PGN* pgn, const char* event, const char* site, const char* date,
|
void pgn_set_header(PGN* pgn, const char* event, const char* site, const char* date,
|
||||||
const char* round, const char* white, const char* black);
|
const char* round, const char* white, const char* black);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
// Function declarations
|
|
||||||
U64 hash_position(const GameState* state);
|
U64 hash_position(const GameState* state);
|
||||||
void add_position_to_history(GameState* state);
|
void add_position_to_history(GameState* state);
|
||||||
int check_threefold_repetition(const GameState* state);
|
int check_threefold_repetition(const GameState* state);
|
||||||
|
|
|
||||||
273
src/search.c
273
src/search.c
|
|
@ -1,19 +1,71 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <unistd.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"
|
#include "uci.h"
|
||||||
|
|
||||||
// Order moves to improve alpha-beta pruning
|
// Global search control
|
||||||
void order_moves(Move *moves, int num_moves, const GameState *state) {
|
volatile int stop_search = 0;
|
||||||
// Simple move ordering:
|
SearchInfo global_search_info;
|
||||||
// 1. Captures (with MVV-LVA: Most Valuable Victim - Least Valuable Aggressor)
|
|
||||||
// 2. Promotions
|
|
||||||
// 3. Checks
|
|
||||||
// 4. Other moves
|
|
||||||
|
|
||||||
|
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};
|
int scores[256] = {0};
|
||||||
|
|
||||||
for (int i = 0; i < num_moves; i++) {
|
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;
|
captured_piece = state->side_to_move == 0 ? p : P;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base score
|
// MVV-LVA scoring
|
||||||
if (captured_piece != NO_PIECE) {
|
if (captured_piece != NO_PIECE) {
|
||||||
// MVV-LVA: score = 10 * victim_value - attacker_value
|
|
||||||
int victim_value = abs(piece_value[captured_piece]) / 100;
|
int victim_value = abs(piece_value[captured_piece]) / 100;
|
||||||
int attacker_value = abs(piece_value[moving_piece]) / 100;
|
int attacker_value = abs(piece_value[moving_piece]) / 100;
|
||||||
scores[i] = 10 * victim_value - attacker_value + 1000;
|
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
|
// Promotion bonus
|
||||||
if (moves[i].promo) {
|
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)
|
// Center control bonus
|
||||||
GameState check_state = *state;
|
if ((to >= 27 && to <= 28) || (to >= 35 && to <= 36)) { // d4, e4, d5, e5
|
||||||
int captured, old_castling, old_ep;
|
scores[i] += 50;
|
||||||
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;
|
// 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++) {
|
for (int i = 1; i < num_moves; i++) {
|
||||||
Move temp_move = moves[i];
|
Move temp_move = moves[i];
|
||||||
int temp_score = scores[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
|
// Enhanced minimax with PV tracking
|
||||||
int minimax(GameState *state, int depth, int alpha, int beta) {
|
int minimax(GameState *state, int depth, int alpha, int beta, Move *pv, int *pv_length, SearchInfo *info) {
|
||||||
// Check if we're at a leaf node
|
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) {
|
if (depth == 0) {
|
||||||
return evaluate(state);
|
return evaluate(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate and count legal moves
|
|
||||||
Move moves[256];
|
Move moves[256];
|
||||||
int num_moves = generate_legal_moves(state, moves);
|
int num_moves = generate_legal_moves(state, moves);
|
||||||
|
|
||||||
// Check for checkmate or stalemate
|
|
||||||
if (num_moves == 0) {
|
if (num_moves == 0) {
|
||||||
if (is_king_in_check(state, state->side_to_move)) {
|
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 {
|
} else {
|
||||||
return 0; // Stalemate
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Order moves to improve alpha-beta pruning
|
|
||||||
order_moves(moves, num_moves, state);
|
order_moves(moves, num_moves, state);
|
||||||
|
|
||||||
int best_score = -32000;
|
int best_score = -32000;
|
||||||
|
Move best_pv[64];
|
||||||
|
int best_pv_length = 0;
|
||||||
|
|
||||||
// Try each move
|
|
||||||
for (int i = 0; i < num_moves; i++) {
|
for (int i = 0; i < num_moves; i++) {
|
||||||
|
if (stop_search) break;
|
||||||
|
|
||||||
int captured, old_castling, old_ep;
|
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);
|
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);
|
undo_move(state, moves[i], captured, old_castling, old_ep);
|
||||||
|
|
||||||
if (score > best_score) {
|
if (score > best_score) {
|
||||||
best_score = 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) {
|
if (best_score > alpha) {
|
||||||
|
|
@ -129,36 +222,22 @@ int minimax(GameState *state, int depth, int alpha, int beta) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
return best_score;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread function for search
|
// Root search with multi-PV support
|
||||||
void *thread_search(void *arg) {
|
int minimax_root(GameState *state, int depth, int alpha, int beta, Move *pv, int *pv_length, SearchInfo *info) {
|
||||||
ThreadArg *thread_arg = (ThreadArg *)arg;
|
return minimax(state, depth, alpha, beta, pv, pv_length, info);
|
||||||
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);
|
// Find best move with multi-PV support
|
||||||
|
Move find_best_move(GameState *state, int depth, int multipv) {
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find best move using multithreaded search
|
|
||||||
Move find_best_move(GameState *state, int depth) {
|
|
||||||
Move moves[256];
|
Move moves[256];
|
||||||
int num_moves = generate_legal_moves(state, moves);
|
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};
|
return (Move){0, 0, 0, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Order moves to search promising moves first
|
|
||||||
order_moves(moves, num_moves, state);
|
order_moves(moves, num_moves, state);
|
||||||
|
|
||||||
// Output initial search info
|
// Initialize search info
|
||||||
printf("info depth %d nodes 0 time 0\n", depth);
|
SearchInfo info = {0};
|
||||||
fflush(stdout);
|
info.start_time = get_time_ms();
|
||||||
|
info.depth = depth;
|
||||||
|
info.seldepth = 0;
|
||||||
|
|
||||||
// Set up threads
|
MultiPVEntry multipv_entries[256];
|
||||||
pthread_t threads[256];
|
int multipv_count = (multipv > num_moves) ? num_moves : multipv;
|
||||||
ThreadArg thread_args[256];
|
|
||||||
|
|
||||||
// Shared variables for best move
|
// Search each move for multi-PV
|
||||||
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
for (int pv_index = 0; pv_index < multipv_count; pv_index++) {
|
||||||
int best_eval = -32000;
|
int best_score = -32000;
|
||||||
Move best_move = moves[0];
|
int best_move_index = -1;
|
||||||
|
|
||||||
// Start a thread for each move
|
// Find the best move not already in multi-PV list
|
||||||
for (int i = 0; i < num_moves; i++) {
|
for (int i = 0; i < num_moves; i++) {
|
||||||
thread_args[i].state = *state;
|
// Skip moves already in multi-PV
|
||||||
thread_args[i].move = moves[i];
|
int already_searched = 0;
|
||||||
thread_args[i].depth = depth;
|
for (int j = 0; j < pv_index; j++) {
|
||||||
thread_args[i].mutex = &mutex;
|
if (moves[i].from == multipv_entries[j].move.from &&
|
||||||
thread_args[i].best_eval = &best_eval;
|
moves[i].to == multipv_entries[j].move.to &&
|
||||||
thread_args[i].best_move = &best_move;
|
moves[i].promo == multipv_entries[j].move.promo) {
|
||||||
|
already_searched = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (already_searched) continue;
|
||||||
|
|
||||||
pthread_create(&threads[i], NULL, thread_search, &thread_args[i]);
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all threads to finish
|
if (best_move_index != -1) {
|
||||||
for (int i = 0; i < num_moves; i++) {
|
// Print info for this PV line
|
||||||
pthread_join(threads[i], NULL);
|
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output final search info with evaluation
|
print_search_info(&pv_info, pv_index + 1);
|
||||||
printf("info depth %d score cp %d pv %s\n",
|
}
|
||||||
depth, best_eval, move_to_uci(best_move));
|
}
|
||||||
fflush(stdout);
|
|
||||||
|
return multipv_entries[0].move;
|
||||||
pthread_mutex_destroy(&mutex);
|
|
||||||
|
|
||||||
return best_move;
|
|
||||||
}
|
}
|
||||||
15
src/search.h
15
src/search.h
|
|
@ -4,16 +4,13 @@
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include "types.h"
|
#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);
|
void order_moves(Move *moves, int num_moves, const GameState *state);
|
||||||
|
int minimax(GameState *state, int depth, int alpha, int beta, Move *pv, int *pv_length, SearchInfo *info);
|
||||||
// Negamax with alpha-beta pruning
|
|
||||||
int minimax(GameState *state, int depth, int alpha, int beta);
|
|
||||||
|
|
||||||
// Thread function for search
|
|
||||||
void *thread_search(void *arg);
|
void *thread_search(void *arg);
|
||||||
|
Move find_best_move(GameState *state, int depth, int multipv);
|
||||||
// Find best move using multithreaded search
|
void print_search_info(SearchInfo *info, int multipv_index);
|
||||||
Move find_best_move(GameState *state, int depth);
|
|
||||||
|
|
||||||
#endif // SEARCH_H
|
#endif // SEARCH_H
|
||||||
|
|
|
||||||
48
src/types.h
48
src/types.h
|
|
@ -59,6 +59,29 @@ typedef struct {
|
||||||
GameHistory history; // Add this line
|
GameHistory history; // Add this line
|
||||||
} GameState;
|
} 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
|
// Thread argument structure
|
||||||
typedef struct {
|
typedef struct {
|
||||||
GameState state;
|
GameState state;
|
||||||
|
|
@ -68,8 +91,33 @@ typedef struct {
|
||||||
pthread_mutex_t *mutex;
|
pthread_mutex_t *mutex;
|
||||||
int *best_eval;
|
int *best_eval;
|
||||||
Move *best_move;
|
Move *best_move;
|
||||||
|
SearchInfo *search_info;
|
||||||
} ThreadArg;
|
} 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
|
// Helper macros
|
||||||
#define GET_BIT(bb, sq) (((bb) >> (sq)) & 1ULL)
|
#define GET_BIT(bb, sq) (((bb) >> (sq)) & 1ULL)
|
||||||
#define SET_BIT(bb, sq) ((bb) |= (1ULL << (sq)))
|
#define SET_BIT(bb, sq) ((bb) |= (1ULL << (sq)))
|
||||||
|
|
|
||||||
35
src/uci.c
35
src/uci.c
|
|
@ -12,6 +12,7 @@
|
||||||
static int uci_depth = 6;
|
static int uci_depth = 6;
|
||||||
static int uci_hash = 64;
|
static int uci_hash = 64;
|
||||||
static int uci_threads = 4;
|
static int uci_threads = 4;
|
||||||
|
static int uci_multipv = 3;
|
||||||
|
|
||||||
// Convert move to UCI format (e.g., "e2e4", "e7e8q")
|
// Convert move to UCI format (e.g., "e2e4", "e7e8q")
|
||||||
char* move_to_uci(Move move) {
|
char* move_to_uci(Move move) {
|
||||||
|
|
@ -36,7 +37,7 @@ char* move_to_uci(Move move) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse UCI move format (e.g., "e2e4", "e7e8q")
|
// 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};
|
Move move = {0, 0, 0, 0};
|
||||||
|
|
||||||
if (strlen(move_str) < 4) return move;
|
if (strlen(move_str) < 4) return move;
|
||||||
|
|
@ -79,13 +80,12 @@ void uci_position(GameState *state, char *line) {
|
||||||
|
|
||||||
token = strtok(NULL, " ");
|
token = strtok(NULL, " ");
|
||||||
}
|
}
|
||||||
// Add support for FEN later if needed
|
|
||||||
|
|
||||||
// Handle moves
|
// Handle moves
|
||||||
if (token && strcmp(token, "moves") == 0) {
|
if (token && strcmp(token, "moves") == 0) {
|
||||||
token = strtok(NULL, " ");
|
token = strtok(NULL, " ");
|
||||||
while (token) {
|
while (token) {
|
||||||
Move move = parse_uci_move(state, token);
|
Move move = parse_uci_move(token);
|
||||||
|
|
||||||
// Verify move is legal
|
// Verify move is legal
|
||||||
Move legal_moves[256];
|
Move legal_moves[256];
|
||||||
|
|
@ -117,10 +117,11 @@ void uci_position(GameState *state, char *line) {
|
||||||
|
|
||||||
// Handle UCI go command
|
// Handle UCI go command
|
||||||
void uci_go(GameState *state, char *line) {
|
void uci_go(GameState *state, char *line) {
|
||||||
int depth = uci_depth; // Use UCI option default
|
int depth = uci_depth;
|
||||||
int movetime = 0;
|
int movetime = 0;
|
||||||
|
int use_movetime = 0;
|
||||||
|
|
||||||
// Parse go parameters (these override the UCI options)
|
// Parse go parameters
|
||||||
char *token = strtok(line, " ");
|
char *token = strtok(line, " ");
|
||||||
while (token) {
|
while (token) {
|
||||||
if (strcmp(token, "depth") == 0) {
|
if (strcmp(token, "depth") == 0) {
|
||||||
|
|
@ -128,15 +129,26 @@ void uci_go(GameState *state, char *line) {
|
||||||
if (token) depth = atoi(token);
|
if (token) depth = atoi(token);
|
||||||
} else if (strcmp(token, "movetime") == 0) {
|
} else if (strcmp(token, "movetime") == 0) {
|
||||||
token = strtok(NULL, " ");
|
token = strtok(NULL, " ");
|
||||||
if (token) movetime = atoi(token);
|
if (token) {
|
||||||
|
movetime = atoi(token);
|
||||||
|
use_movetime = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
token = strtok(NULL, " ");
|
token = strtok(NULL, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find best move
|
// Reset search control
|
||||||
Move best_move = find_best_move(state, depth);
|
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));
|
printf("bestmove %s\n", move_to_uci(best_move));
|
||||||
fflush(stdout);
|
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 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 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 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");
|
printf("uciok\n");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
@ -206,6 +219,10 @@ void uci_loop() {
|
||||||
} else if (strcmp(option_name, "Threads") == 0) {
|
} else if (strcmp(option_name, "Threads") == 0) {
|
||||||
uci_threads = atoi(option_value);
|
uci_threads = atoi(option_value);
|
||||||
// We don't control thread count yet, but store the 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) {
|
} else if (strcmp(line, "ucinewgame") == 0) {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
void uci_loop();
|
void uci_loop();
|
||||||
void uci_position(GameState *state, char *line);
|
void uci_position(GameState *state, char *line);
|
||||||
void uci_go(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);
|
char* move_to_uci(Move move);
|
||||||
|
|
||||||
#endif // UCI_H
|
#endif // UCI_H
|
||||||
Loading…
Add table
Add a link
Reference in a new issue