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 "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--;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
100
src/main.c
100
src/main.c
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
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 <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;
|
||||||
|
|
|
||||||
13
src/types.h
13
src/types.h
|
|
@ -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
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