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, ©, move, 1)) continue; 138 score = -quiescence(stats, -beta, -alpha); 139 stats_move_unmake(stats, ©); 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, ©); 195 score = -negamax(stats, -beta, -beta + 1, 196 depth - 1 - REDUCTION_MOVE, false); 197 stats_move_unmake(stats, ©); 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, ©, 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, ©); 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, ©); 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 }