add uci support
This commit is contained in:
parent
e7af512d93
commit
d649358c12
13 changed files with 618 additions and 127 deletions
13
src/board.c
13
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);
|
||||
|
|
@ -232,6 +237,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) {
|
||||
state->fullmove_number--;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
100
src/main.c
100
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;
|
||||
}
|
||||
|
|
@ -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,50 +43,78 @@ 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)) {
|
||||
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) {
|
||||
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 if (other_rank == from_rank) {
|
||||
need_file = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,12 +123,12 @@ char* move_to_san(const GameState *state, Move move) {
|
|||
// 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
127
src/pgn.c
Normal file
127
src/pgn.c
Normal 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
40
src/pgn.h
Normal 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
66
src/repetition.c
Normal 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
12
src/repetition.h
Normal 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
|
||||
16
src/search.c
16
src/search.c
|
|
@ -1,8 +1,10 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#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;
|
||||
|
|
|
|||
13
src/types.h
13
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
|
||||
|
|
|
|||
217
src/uci.c
Normal file
217
src/uci.c
Normal 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
14
src/uci.h
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue