stellar

Stellar - Chess engine written in C
Log | Files | Refs

engine.c (15673B)


      1 #include <ctype.h>
      2 #include <stdio.h>
      3 #include <stdlib.h>
      4 #include <string.h>
      5 
      6 #include "attacks.h"
      7 #include "board.h"
      8 #include "moves.h"
      9 #include "perft.h"
     10 #include "score.h"
     11 #include "stats.h"
     12 #include "transposition.h"
     13 #include "utils.h"
     14 #include "zobrist.h"
     15 
     16 #include <cul/assert.h>
     17 #include <cul/mem.h>
     18 #include <cul/utils.h>
     19 
     20 #define FULL_DEPTH 4
     21 #define REDUCTION_LIMIT 3
     22 #define REDUCTION_MOVE 2
     23 
     24 #define WINDOW 50
     25 
     26 void move_list_sort_pv(MoveList *list, Stats *stats, Move best) {
     27     assert(list && stats);
     28 
     29     for (int i = 0; i < move_list_size(list); i++) {
     30         const Move move = move_list_index_move(list, i);
     31         if (move_cmp(best, move)) {
     32             move_list_index_score_set(list, i, 30000);
     33             return;
     34         }
     35     }
     36 
     37     if (stats->ply && stats->follow_pv) {
     38         stats->follow_pv = 0;
     39         for (int i = 0; i < move_list_size(list); i++) {
     40             const Move move = move_list_index_move(list, i);
     41             if (move_cmp(stats->pv_table[0][stats->ply], move)) {
     42                 move_list_index_score_set(list, i, 20000);
     43                 stats->follow_pv = 1;
     44                 break;
     45             }
     46         }
     47     }
     48 }
     49 
     50 /* SEARCHING */
     51 
     52 int evaluate(const Board *board) {
     53     assert(board);
     54 
     55     Square square;
     56     eColor side = board_side(board);
     57     U64 occupancy = board_color(board, side);
     58 
     59     int score = 0;
     60     for (int i = 0; i < 6; i++) {
     61         U64 bitboard = board_piece(board, i);
     62         bitboard_for_each_bit(square, bitboard) {
     63             if (bit_get(occupancy, square)) {
     64                 score += Score_value(i);
     65                 score += Score_position(i, side, square);
     66             } else {
     67                 score -= Score_value(i);
     68                 score -= Score_position(i, !side, square);
     69             }
     70         }
     71     }
     72 
     73     return score;
     74 }
     75 
     76 int is_repetition() { return 0; }
     77 
     78 int stats_move_make(Stats *self, Board *copy, Move move, int flag) {
     79     assert(self && copy);
     80 
     81     board_copy(self->board, copy);
     82     if (!move_make(move, self->board, flag)) {
     83         board_copy(copy, self->board);
     84         return 0;
     85     }
     86     self->ply++;
     87     return 1;
     88 }
     89 
     90 void stats_move_make_pruning(Stats *self, Board *copy) {
     91     assert(self && copy);
     92 
     93     board_copy(self->board, copy);
     94     board_side_switch(self->board);
     95     board_enpassant_set(self->board, no_sq);
     96     self->ply++;
     97 }
     98 
     99 void stats_move_unmake(Stats *self, Board *copy) {
    100     assert(self && copy);
    101 
    102     board_copy(copy, self->board);
    103     self->ply--;
    104 }
    105 
    106 void stats_pv_store(Stats *self, Move move) {
    107     assert(self);
    108 
    109     const int ply = self->ply;
    110     self->pv_table[ply][ply] = move;
    111     for (int i = ply + 1; i < self->pv_length[ply + 1]; i++) {
    112         self->pv_table[ply][i] = self->pv_table[ply + 1][i];
    113     }
    114     self->pv_length[ply] = self->pv_length[ply + 1];
    115 }
    116 
    117 int quiescence(Stats *stats, int alpha, int beta) {
    118     assert(stats);
    119 
    120     stats->pv_length[stats->ply] = stats->ply;
    121     stats->nodes++;
    122 
    123     int score = evaluate(stats->board);
    124     if (stats->ply > MAX_PLY - 1) return score;
    125     if (score >= beta) return beta;
    126     if (score > alpha) alpha = score;
    127 
    128     Board copy;
    129     MoveList list;
    130     move_list_generate(&list, stats->board);
    131     Score_move_list(stats, &list);
    132     move_list_sort_pv(&list, stats, (Move){0});
    133     move_list_sort(&list);
    134 
    135     for (int i = 0; i < move_list_size(&list); i++) {
    136         Move move = move_list_index_move(&list, i);
    137         if (!stats_move_make(stats, &copy, move, 1)) continue;
    138         score = -quiescence(stats, -beta, -alpha);
    139         stats_move_unmake(stats, &copy);
    140 
    141         if (score > alpha) {
    142             alpha = score;
    143             stats_pv_store(stats, move);
    144             if (score >= beta) return beta;
    145         }
    146     }
    147 
    148     return alpha;
    149 }
    150 
    151 int negamax(Stats *stats, int alpha, int beta, int depth, bool null) {
    152     assert(stats);
    153 
    154     int pv_node = (beta - alpha) > 1;
    155     HasheFlag flag = flagAlpha;
    156     Move bestMove = {0};
    157     int futility = 0;
    158     Board copy;
    159 
    160     stats->pv_length[stats->ply] = stats->ply;
    161 
    162     int score = ttable_read(stats, &bestMove, alpha, beta, depth);
    163     if (stats->ply && score != TTABLE_UNKNOWN && !pv_node) return score;
    164 
    165     // && fifty >= 100
    166     if (stats->ply && is_repetition()) return 0;
    167     if (depth == 0) {
    168         stats->nodes++;
    169         int score = quiescence(stats, alpha, beta);
    170         // ttable_write(stats, bestMove, score, depth, flagExact);
    171         return score;
    172     }
    173 
    174     if (alpha < -MATE_VALUE) alpha = -MATE_VALUE;
    175     if (beta > MATE_VALUE - 1) beta = MATE_VALUE - 1;
    176     if (alpha >= beta) return alpha;
    177     // if (stats->ply > MAX_PLY - 1) return evaluate(stats->board);
    178 
    179     int isCheck = board_isCheck(stats->board);
    180     if (isCheck) depth++;
    181 
    182     if (!pv_node && !isCheck) {
    183         int staticEval = evaluate(stats->board);
    184 
    185         // evaluation pruning
    186         if (depth < 3 && abs(beta - 1) > -MATE_VALUE + 100) {
    187             int marginEval = Score_value(PAWN) * depth;
    188             if (staticEval - marginEval >= beta) return staticEval - marginEval;
    189         }
    190 
    191         if (null) {
    192             // null move pruning
    193             if (stats->ply && depth > 2 && staticEval >= beta) {
    194                 stats_move_make_pruning(stats, &copy);
    195                 score = -negamax(stats, -beta, -beta + 1,
    196                                  depth - 1 - REDUCTION_MOVE, false);
    197                 stats_move_unmake(stats, &copy);
    198                 if (score >= beta) return beta;
    199             }
    200 
    201             // razoring
    202             score = staticEval + Score_value(PAWN);
    203             int scoreNew = quiescence(stats, alpha, beta);
    204 
    205             if (score < beta && depth == 1) {
    206                 return (scoreNew > score) ? scoreNew : score;
    207             }
    208 
    209             score += Score_value(PAWN);
    210             if (score < beta && depth < 4) {
    211                 if (scoreNew < beta)
    212                     return (scoreNew > score) ? scoreNew : score;
    213             }
    214         }
    215 
    216         // futility pruning condition
    217         static const int margin[] = {0, 100, 300, 500};
    218         if (depth < 4 && abs(alpha) < MATE_SCORE &&
    219             staticEval + margin[depth] <= alpha)
    220             futility = 1;
    221     }
    222 
    223     MoveList list;
    224     move_list_generate(&list, stats->board);
    225     Score_move_list(stats, &list);
    226     move_list_sort_pv(&list, stats, bestMove);
    227     move_list_sort(&list);
    228 
    229     int legal_moves = 0;
    230     int searched = 0;
    231     for (int i = 0; i < move_list_size(&list); i++) {
    232         const Move move = move_list_index_move(&list, i);
    233         if (!stats_move_make(stats, &copy, move, 0)) continue;
    234         legal_moves++;
    235 
    236         // futility pruning
    237         if (futility && searched && !move_capture(move) &&
    238             !move_promote(move) && !board_isCheck(stats->board)) {
    239             stats_move_unmake(stats, &copy);
    240             continue;
    241         }
    242 
    243         if (!searched) {
    244             score = -negamax(stats, -beta, -alpha, depth - 1, true);
    245         } else {
    246             // Late Move Reduction
    247             if (!pv_node && searched >= FULL_DEPTH &&
    248                 depth >= REDUCTION_LIMIT && !isCheck && !move_capture(move) &&
    249                 !move_promote(move) &&
    250                 (move_source(move) !=
    251                      move_source(stats->killer[0][stats->ply]) ||
    252                  move_target(move) !=
    253                      move_target(stats->killer[0][stats->ply])) &&
    254                 (move_source(move) !=
    255                      move_source(stats->killer[1][stats->ply]) ||
    256                  move_target(move) !=
    257                      move_target(stats->killer[1][stats->ply]))) {
    258                 score = -negamax(stats, -alpha - 1, -alpha, depth - 2, true);
    259             } else
    260                 score = alpha + 1;
    261 
    262             // found better move
    263             // Principal Variation Search
    264             if (score > alpha) {
    265                 score = -negamax(stats, -alpha - 1, -alpha, depth - 1, true);
    266 
    267                 // if fail research
    268                 if ((score > alpha) && (score < beta))
    269                     score = -negamax(stats, -beta, -alpha, depth - 1, true);
    270             }
    271         }
    272 
    273         stats_move_unmake(stats, &copy);
    274         searched++;
    275 
    276         if (score > alpha) {
    277             if (!move_capture(move)) {
    278                 stats->history[piece_index(move_piece(move))]
    279                               [move_target(move)] += depth;
    280             }
    281 
    282             alpha = score;
    283             flag = flagExact;
    284             bestMove = move;
    285             stats_pv_store(stats, move);
    286 
    287             if (score >= beta) {
    288                 ttable_write(stats, bestMove, beta, depth, flagBeta);
    289 
    290                 if (!move_capture(move)) {
    291                     stats->killer[1][stats->ply] = stats->killer[0][stats->ply];
    292                     stats->killer[0][stats->ply] = move;
    293                 }
    294 
    295                 return beta;
    296             }
    297         }
    298     }
    299 
    300     if (legal_moves == 0) {
    301         if (isCheck)
    302             return -MATE_VALUE + stats->ply;
    303         else
    304             return 0;
    305     }
    306 
    307     ttable_write(stats, bestMove, alpha, depth, flag);
    308     return alpha;
    309 }
    310 
    311 void move_print_UCI(Move move) {
    312     printf("%s%s", square_to_coordinates[move_source(move)],
    313            square_to_coordinates[move_target(move)]);
    314     if (move_promote(move)) printf("%c", piece_asci(move_piece_promote(move)));
    315 }
    316 
    317 TTable *ttable = NULL;
    318 void search_position(Board *board, int depth) {
    319     assert(board);
    320 
    321     Stats stats = {.ttable = ttable, .board = board, 0};
    322 
    323     int alpha = -INFINITY, beta = INFINITY;
    324     for (int crnt = 1; crnt <= depth;) {
    325         stats.follow_pv = 1;
    326 
    327         int score = negamax(&stats, alpha, beta, crnt, true);
    328         if ((score <= alpha) || (score >= beta)) {
    329             alpha = -INFINITY;
    330             beta = INFINITY;
    331             continue;
    332         }
    333         alpha = score - WINDOW;
    334         beta = score + WINDOW;
    335 
    336         if (stats.pv_length[0]) {
    337             if (score > -MATE_VALUE && score < -MATE_SCORE) {
    338                 printf("info score mate %d depth %d nodes %ld pv ",
    339                        -(score + MATE_VALUE) / 2 - 1, crnt, stats.nodes);
    340             } else if (score > MATE_SCORE && score < MATE_VALUE) {
    341                 printf("info score mate %d depth %d nodes %ld pv ",
    342                        (MATE_VALUE - score) / 2 + 1, crnt, stats.nodes);
    343             } else {
    344                 printf("info score cp %d depth %d nodes %ld pv ", score, crnt,
    345                        stats.nodes);
    346             }
    347 
    348             for (int i = 0; i < stats.pv_length[0]; i++) {
    349                 move_print_UCI(stats.pv_table[0][i]);
    350                 printf(" ");
    351             }
    352             printf("\n");
    353         }
    354         crnt++;
    355     }
    356 
    357     printf("bestmove ");
    358     move_print_UCI(stats.pv_table[0][0]);
    359     printf("\n");
    360 }
    361 
    362 void print_info(void) {
    363     printf("id name Stellar\n");
    364     printf("id author Dimitrije Dobrota\n");
    365     printf("uciok\n");
    366 }
    367 
    368 typedef struct Instruction Instruction;
    369 struct Instruction {
    370     char *command;
    371     char *token;
    372     char *crnt;
    373 };
    374 
    375 char *Instruction_token_next(Instruction *self);
    376 
    377 Instruction *Instruction_new(char *command) {
    378     Instruction *p;
    379     NEW0(p);
    380     p->command = ALLOC(strlen(command) + 1);
    381     p->token = ALLOC(strlen(command) + 1);
    382     strcpy(p->command, command);
    383     p->crnt = command;
    384     Instruction_token_next(p);
    385     return p;
    386 }
    387 
    388 void Instruction_free(Instruction **p) {
    389     FREE((*p)->command);
    390     FREE((*p)->token);
    391     FREE(*p);
    392 }
    393 
    394 char *Instruction_token(Instruction *self) { return self->token; }
    395 char *Instruction_token_n(Instruction *self, int n) {
    396     while (isspace(*self->crnt) && *self->crnt != '\0')
    397         self->crnt++;
    398 
    399     if (*self->crnt == '\0') {
    400         *self->token = '\0';
    401         return NULL;
    402     }
    403 
    404     char *p = self->token;
    405     while (n--) {
    406         while (!isspace(*self->crnt) && *self->crnt != '\0' &&
    407                *self->crnt != ';')
    408             *p++ = *self->crnt++;
    409         if (*self->crnt == '\0') {
    410             p++;
    411             break;
    412         }
    413         self->crnt++;
    414         *p++ = ' ';
    415     }
    416     *--p = '\0';
    417 
    418     return self->token;
    419 }
    420 
    421 char *Instruction_token_next(Instruction *self) {
    422     return Instruction_token_n(self, 1);
    423 }
    424 
    425 Move parse_move(Board *board, char *move_string) {
    426     Move result = {0};
    427     MoveList *moves;
    428     Square source, target;
    429 
    430     source = coordinates_to_square(move_string);
    431     target = coordinates_to_square(move_string + 2);
    432 
    433     moves = move_list_generate(NULL, board);
    434     for (int i = 0; i < moves->count; i++) {
    435         Move move = move_list_index_move(moves, i);
    436         if (move_source(move) == source && move_target(move) == target) {
    437             if (move_string[4]) {
    438                 if (tolower(piece_code(move_piece_promote(move))) !=
    439                     move_string[4])
    440                     continue;
    441             }
    442             result = move;
    443             break;
    444         }
    445     }
    446 
    447     move_list_free(&moves);
    448     return result;
    449 }
    450 
    451 Board *Instruction_parse(Instruction *self, Board *board) {
    452     char *token = Instruction_token(self);
    453 
    454     if (!board) board = board_new();
    455 
    456     do {
    457         if (strcmp(token, "ucinewgame") == 0) {
    458             board = board_from_FEN(board, start_position);
    459             continue;
    460         }
    461 
    462         if (strcmp(token, "quit") == 0) return NULL;
    463 
    464         if (strcmp(token, "position") == 0) {
    465             token = Instruction_token_next(self);
    466             if (token && strcmp(token, "startpos") == 0) {
    467                 board = board_from_FEN(board, start_position);
    468             } else if (token && strcmp(token, "fen") == 0) {
    469                 token = Instruction_token_n(self, 6);
    470                 board = board_from_FEN(board, token);
    471             } else {
    472                 printf("Unknown argument after position\n");
    473             }
    474             // board_print(board);
    475             continue;
    476         }
    477 
    478         if (strcmp(token, "moves") == 0) {
    479             while ((token = Instruction_token_next(self))) {
    480                 Move move = parse_move(board, token);
    481                 if (!move_cmp(move, (Move){0})) {
    482                     move_make(move, board, 0);
    483                 } else {
    484                     printf("Invalid move %s!\n", token);
    485                 }
    486             }
    487             // board_print(board);
    488             return board;
    489         }
    490 
    491         if (strcmp(token, "go") == 0) {
    492             int depth = 6;
    493             for (token = Instruction_token_next(self); token;
    494                  token = Instruction_token_next(self)) {
    495 
    496                 if (token && strcmp(token, "depth") == 0) {
    497                     token = Instruction_token_next(self);
    498                     depth = atoi(token);
    499                 } else {
    500                     // printf("Unknown argument %s after go\n", token);
    501                 }
    502             }
    503             search_position(board, depth);
    504             continue;
    505         }
    506 
    507         if (strcmp(token, "isready") == 0) {
    508             printf("readyok\n");
    509             continue;
    510         }
    511 
    512         if (strcmp(token, "uci") == 0) {
    513             print_info();
    514             continue;
    515         }
    516     } while ((token = Instruction_token_next(self)));
    517 
    518     return board;
    519 }
    520 
    521 void uci_loop(void) {
    522     Board board;
    523     Instruction *instruction;
    524     char input[200000];
    525 
    526     setbuf(stdin, NULL);
    527     setbuf(stdout, NULL);
    528 
    529     print_info();
    530     while (1) {
    531         memset(input, 0, sizeof(input));
    532         fflush(stdout);
    533         if (!fgets(input, sizeof(input), stdin)) continue;
    534 
    535         instruction = Instruction_new(input);
    536         if (!Instruction_parse(instruction, &board)) break;
    537         Instruction_free(&instruction);
    538     }
    539 
    540     Instruction_free(&instruction);
    541 }
    542 
    543 /* MAIN */
    544 
    545 void init(void) {
    546     attacks_init();
    547     zobrist_init();
    548     ttable = ttable_new(C64(0x400000));
    549 }
    550 
    551 int main(void) {
    552     init();
    553     uci_loop();
    554     ttable_free(&ttable);
    555     return 0;
    556 }