// MOBILITY heuristic: safe squares around king of color color. int mobility(position_t *p, color_t color) { char* laser_map; if (color == WHITE) { laser_map = laser_map_black; } else { laser_map = laser_map_white; } int mobility = 0; square_t king_sq = p->kloc[color]; tbassert(ptype_of(p->board[king_sq]) == KING, "ptype: %d\n", ptype_of(p->board[king_sq])); tbassert(color_of(p->board[king_sq]) == color, "color: %d\n", color_of(p->board[king_sq])); if (laser_map[king_sq] == 0) { mobility++; } for (int d = 0; d < 8; ++d) { square_t sq = king_sq + dir_of(d); if (laser_map[sq] == 0) { mobility++; } } return mobility; }
static void update_best_move_history(position_t *p, int index_of_best, sortable_move_t* lst, int count) { tbassert(ENABLE_TABLES, "Tables weren't enabled.\n"); int color_to_move = color_to_move_of(p); for (int i = 0; i < count; i++) { move_t mv = get_move(lst[i]); ptype_t pce = ptype_mv_of(mv); rot_t ro = rot_of(mv); // rotation square_t fs = from_square(mv); int ot = ORI_MASK & (ori_of(p->board[fs]) + ro); square_t ts = to_square(mv); int s = best_move_history[BMH(color_to_move, pce, ts, ot)]; if (index_of_best == i) { s = s + 11200; // number will never exceed 1017 } s = s * 0.90; // decay score over time tbassert(s < 102000, "s = %d\n", s); // or else sorting will fail best_move_history[BMH(color_to_move, pce, ts, ot)] = s; } }
// Marks the path of the laser until it hits a piece or goes off the board. // Returns the number of unpinned pawns. // // p : current board state // laser_map : end result will be stored here. Every square on the // path of the laser is marked with mark_mask // c : color of king shooting laser // mark_mask: what each square is marked with int mark_laser_path(position_t *p, char *laser_map, color_t c, char mark_mask) { int pinned_pawns = 0; uint8_t total_pawns; color_t color = opp_color(c); square_t o_king_sq = p->kloc[color]; if (c == WHITE) { // opposing king pins our pawns total_pawns = p->pawn_count[BLACK]; } else { total_pawns = p->pawn_count[WHITE]; } // Fire laser, recording in laser_map square_t sq = p->kloc[c]; int bdir = ori_of(p->board[sq]); int beam = beam_of(bdir); tbassert(ptype_of(p->board[sq]) == KING, "ptype: %d\n", ptype_of(p->board[sq])); laser_map[sq] |= mark_mask; // we update h_attackable here h_attackable = h_dist(sq, o_king_sq); while (true) { sq += beam; laser_map[sq] |= mark_mask; tbassert(sq < ARR_SIZE && sq >= 0, "sq: %d\n", sq); switch (ptype_of(p->board[sq])) { case EMPTY: // empty square h_attackable += h_dist(sq, o_king_sq); break; case PAWN: // Pawn h_attackable += h_dist(sq, o_king_sq); if (color_of(p->board[sq]) == color) { pinned_pawns += 1; } bdir = reflect_of(bdir, ori_of(p->board[sq])); if (bdir < 0) { // Hit back of Pawn return total_pawns - pinned_pawns; } beam = beam_of(bdir); break; case KING: // King h_attackable += h_dist(sq, o_king_sq); return total_pawns - pinned_pawns; break; case INVALID: // Ran off edge of board return total_pawns - pinned_pawns; break; default: // Shouldna happen, man! tbassert(false, "Not cool, man. Not cool.\n"); break; } } }
// Translate a position struct into a fen string // NOTE: When you use the test framework in search.c, you should modify this // function to match your optimized board representation in move_gen.c // // Input: (populated) position struct // empty string where FEN characters will be written // Output: null int pos_to_fen(position_t *p, char *fen) { int pos = 0; int i; for (rnk_t r = BOARD_WIDTH - 1; r >=0 ; --r) { int empty_in_a_row = 0; for (fil_t f = 0; f < BOARD_WIDTH; ++f) { piece_t piece = get_piece(p, r, f, &base_board); if (ptype_of(piece) == INVALID) { // invalid square tbassert(false, "Bad news, yo.\n"); // This is bad! } if (ptype_of(piece) == EMPTY) { // empty square empty_in_a_row++; continue; } else { if (empty_in_a_row) fen[pos++] = '0' + empty_in_a_row; empty_in_a_row = 0; int ori = ori_of(piece); // orientation color_t c = color_of(piece); if (ptype_of(piece) == KING) { for (i = 0; i < 2; i++) fen[pos++] = king_ori_to_rep[c][ori][i]; continue; } if (ptype_of(piece) == PAWN) { for (i = 0; i < 2; i++) fen[pos++] = pawn_ori_to_rep[c][ori][i]; continue; } } } // assert: for larger boards, we need more general solns tbassert(BOARD_WIDTH <= 10, "BOARD_WIDTH = %d\n", BOARD_WIDTH); if (empty_in_a_row == 10) { fen[pos++] = '1'; fen[pos++] = '0'; } else if (empty_in_a_row) { fen[pos++] = '0' + empty_in_a_row; } if (r) fen[pos++] = '/'; } fen[pos++] = ' '; fen[pos++] = 'W'; fen[pos++] = '\0'; return pos; }
// converts a move to string notation for FEN void move_to_str(move_t mv, char *buf, size_t bufsize) { square_t f = from_square(mv); // from-square square_t t = to_square(mv); // to-square rot_t r = rot_of(mv); // rotation const char *orig_buf = buf; buf += square_to_str(f, buf, bufsize); if (f != t) { buf += square_to_str(t, buf, bufsize - (buf - orig_buf)); } else { switch (r) { case NONE: buf += square_to_str(t, buf, bufsize - (buf - orig_buf)); break; case RIGHT: buf += snprintf(buf, bufsize - (buf - orig_buf), "R"); break; case UTURN: buf += snprintf(buf, bufsize - (buf - orig_buf), "U"); break; case LEFT: buf += snprintf(buf, bufsize - (buf - orig_buf), "L"); break; default: tbassert(false, "Whoa, now. Whoa, I say.\n"); // Bad, bad, bad break; } } }
// KAGGRESSIVE heuristic: bonus for King with more space to back ev_score_t kaggressive_old(position_t *p, fil_t f, rnk_t r) { square_t sq = square_of(f, r); piece_t x = p->board[sq]; color_t c = color_of(x); tbassert(ptype_of(x) == KING, "ptype_of(x) = %d\n", ptype_of(x)); square_t opp_sq = p->kloc[opp_color(c)]; fil_t of = fil_of(opp_sq); rnk_t _or = (rnk_t) rnk_of(opp_sq); int delta_fil = of - f; int delta_rnk = _or - r; int bonus = 0; if (delta_fil >= 0 && delta_rnk >= 0) { bonus = (f + 1) * (r + 1); } else if (delta_fil <= 0 && delta_rnk >= 0) { bonus = (BOARD_WIDTH - f) * (r + 1); } else if (delta_fil <= 0 && delta_rnk <= 0) { bonus = (BOARD_WIDTH - f) * (BOARD_WIDTH - r); } else if (delta_fil >= 0 && delta_rnk <= 0) { bonus = (f + 1) * (BOARD_WIDTH - r); } return (KAGGRESSIVE * bonus) / (BOARD_WIDTH * BOARD_WIDTH); }
// KFACE heuristic: bonus (or penalty) for King facing toward the other King ev_score_t kface(position_t *p, rnk_t r, fil_t f, full_board_t* board) { piece_t x = get_piece(p, r, f, board); color_t c = color_of(x); square_t opp_sq = board->pieces[opp_color(c)][0]; int delta_fil = fil_of(opp_sq) - f; int delta_rnk = rnk_of(opp_sq) - r; int bonus; switch (ori_of(x)) { case NN: bonus = delta_rnk; break; case EE: bonus = delta_fil; break; case SS: bonus = -delta_rnk; break; case WW: bonus = -delta_fil; break; default: bonus = 0; tbassert(false, "Illegal King orientation.\n"); } return (bonus * KFACE) / (abs(delta_rnk) + abs(delta_fil)); }
// Helper method that finds all "small" primes -- primes in [0, 2^32). static sieve_t* find_small_primes(void) { int64_t upper_bound = (int64_t)((double)1.42 * ((int64_t)1 << 31)); sieve_t *sieve = create_sieve(upper_bound); if (NULL == sieve) { fprintf(stderr, "Failed to create SMALL_PRIMES sieve of length %"PRId64".\n"\ "This failure can occur if there is insufficient physical memory on the system.\n"\ "Aborting.\n", upper_bound); exit(1); } init_sieve(sieve); mark_composite(sieve, 0); mark_composite(sieve, 1); // Scan the entries of the sieve from 2 to UPPER_BOUND for (int64_t i = 2; i < upper_bound; ++i) { tbassert(trialdiv_prime_p(i) == prime_p(sieve, i), "Incorrect primality recorded for %"PRId64" (%d vs %d)\n", i, trialdiv_prime_p(i), prime_p(sieve, i)); // Skip any I marked as composite if (!prime_p(sieve, i)) { continue; } // At this point, I is prime. // Mark all multiples of I as composite. for (int64_t j = 2; i * j < upper_bound; ++j) { mark_composite(sieve, i * j); } } return sieve; }
// KAGGRESSIVE heuristic: bonus for King with more space to back ev_score_t kaggressive(position_t *p, rnk_t r, fil_t f, full_board_t* board) { piece_t x = get_piece(p, r, f, board); color_t c = color_of(x); tbassert(ptype_of(x) == KING, "ptype_of(x) = %d\n", ptype_of(x)); square_t opp_sq = board->pieces[opp_color(c)][0]; fil_t of = fil_of(opp_sq); rnk_t _or = (rnk_t) rnk_of(opp_sq); int delta_fil = of - f; int delta_rnk = _or - r; int bonus = 0; if (delta_fil >= 0 && delta_rnk >= 0) { bonus = (f + 1) * (r + 1); } else if (delta_fil <= 0 && delta_rnk >= 0) { bonus = (BOARD_WIDTH - f) * (r + 1); } else if (delta_fil <= 0 && delta_rnk <= 0) { bonus = (BOARD_WIDTH - f) * (BOARD_WIDTH - r); } else if (delta_fil >= 0 && delta_rnk <= 0) { bonus = (f + 1) * (BOARD_WIDTH - r); } return (KAGGRESSIVE * bonus) / (BOARD_WIDTH * BOARD_WIDTH); }
static void set_sort_key(sortable_move_t *mv, sort_key_t key) { // sort keys must not exceed SORT_MASK // assert ((0 <= key) && (key <= SORT_MASK)); tbassert(*mv <= SORT_MASK, "set_sort_key assumes high bits of mv are 0"); *mv |= ((uint64_t)key & SORT_MASK) << SORT_SHIFT; return; }
// check the victim pieces returned by the move to determine if it's a // game-over situation. If so, also calculate the score depending on // the pov (which player's point of view) static bool is_game_over(victims_t victims, int pov, int ply) { tbassert(ptype_of(victims.stomped) != KING, "Stomped a king.\n"); if (ptype_of(victims.zapped) == KING) { return true; } return false; }
// KFACE heuristic: bonus (or penalty) for King facing toward the other King ev_score_t kface(position_t *p, fil_t f, rnk_t r) { square_t sq = square_of(f, r); piece_t x = p->board[sq]; color_t c = color_of(x); square_t opp_sq = p->kloc[opp_color(c)]; int delta_fil = fil_of(opp_sq) - f; int delta_rnk = rnk_of(opp_sq) - r; int bonus; switch (ori_of(x)) { case NN: bonus = delta_rnk; break; case EE: bonus = delta_fil; break; case SS: bonus = -delta_rnk; break; case WW: bonus = -delta_fil; break; default: bonus = 0; tbassert(false, "Illegal King orientation.\n"); } return (bonus * KFACE) / (abs(delta_rnk) + abs(delta_fil)); }
// MOBILITY heuristic: safe squares around king of color color. int mobility(position_t *p, color_t color, char* laser_map, full_board_t* board) { int mobility = 0; square_t king_sq = board->pieces[color][0]; tbassert(ptype_of(board->board[king_sq]) == KING, "ptype: %d\n", ptype_of(board->board[king_sq])); tbassert(color_of(board->board[king_sq]) == color, "color: %d\n", color_of(board->board[king_sq])); if (laser_map[king_sq] == 0) { mobility++; } for (int d = 0; d < 8; ++d) { square_t sq = king_sq + dir_of(d); if (in_bounds(sq) && laser_map[sq] == 0) { mobility++; } } return mobility; }
static score_t get_game_over_score(victims_t victims, int pov, int ply) { tbassert(ptype_of(victims.stomped) != KING, "Stomped a king.\n"); score_t score; if (color_of(victims.zapped) == WHITE) { score = -WIN * pov; } else { score = WIN * pov; } if (score < 0) { score += ply; } else { score -= ply; } return score; }
// H_SQUARES_ATTACKABLE heuristic: for shooting the enemy king int h_squares_attackable(position_t *p, color_t c) { char* laser_map; if (c == WHITE) { laser_map = laser_map_white; } else { laser_map = laser_map_black; } square_t o_king_sq = p->kloc[opp_color(c)]; tbassert(ptype_of(p->board[o_king_sq]) == KING, "ptype: %d\n", ptype_of(p->board[o_king_sq])); tbassert(color_of(p->board[o_king_sq]) != c, "color: %d\n", color_of(p->board[o_king_sq])); float h_attackable_temp = 0; for (fil_t f = 0; f < BOARD_WIDTH; f++) { for (rnk_t r = 0; r < BOARD_WIDTH; r++) { square_t sq = square_of(f, r); if (laser_map[sq] != 0) { h_attackable_temp += h_dist(sq, o_king_sq); } } } return h_attackable_temp; }
//TODO: make euphoric by just subtracting 2 values in a sum table instead static inline float fast_h_dist(square_t start, square_t end, rnk_t k_rnk, fil_t k_fil) { tbassert(start >= 4, "olooloo\n"); rnk_t start_r = rnk_of(start); rnk_t end_r = rnk_of(end); fil_t start_f = fil_of(start); fil_t end_f = fil_of(end); if (start_r == end_r) { float length = abs(start_f - end_f); int firstDist = k_fil - start_f; int secondDist = k_fil - end_f; secondDist += ((firstDist > secondDist) - (secondDist > firstDist)); float partial_sum; if(firstDist*secondDist > 0){ //same sign, disjoint segment partial_sum = fabsf(harmonicSum[abs(firstDist)] - harmonicSum[abs(secondDist)]) + h_table[MIN(abs(firstDist), abs(secondDist))]; } else { partial_sum = harmonicSum[abs(firstDist)] + harmonicSum[abs(secondDist)] - 1; } return partial_sum + length * h_table[abs(start_r - k_rnk)]; } else { float length = abs(start_r - end_r); int firstDist = k_rnk - start_r; int secondDist = k_rnk - end_r; secondDist += ((firstDist > secondDist) - (secondDist > firstDist)); float partial_sum; if(firstDist*secondDist > 0){ //same sign, disjoint segment partial_sum = fabsf(harmonicSum[abs(firstDist)] - harmonicSum[abs(secondDist)]) + h_table[MIN(abs(firstDist), abs(secondDist))]; } else { partial_sum = harmonicSum[abs(firstDist)] + harmonicSum[abs(secondDist)] - 1; } return partial_sum + length * h_table[abs(start_f - k_fil)]; } }
// Static evaluation. Returns score score_t eval(position_t *p, bool verbose, full_board_t* board) { // seed rand_r with a value of 1, as per // http://linux.die.net/man/3/rand_r static __thread unsigned int seed = 1; // verbose = true: print out components of score ev_score_t score[2] = { 0, 0 }; // int corner[2][2] = { {INF, INF}, {INF, INF} }; ev_score_t bonus; color_t c = WHITE; while (true) { for (pnum_t pnum = 0; pnum <= board->pawn_count[c]; pnum++) { square_t sq = board->pieces[c][pnum]; rnk_t r = rnk_of(sq); fil_t f = fil_of(sq); piece_t x = board->board[sq]; switch (ptype_of(x)) { case EMPTY: tbassert(false, "Jose says: no way!\n"); // No way, Jose! case PAWN: // PBETWEEN heuristic bonus = pbetween(p, r, f, board); score[c] += bonus; // PCENTRAL heuristic bonus = pcentral(r, f); score[c] += bonus; break; case KING: // KFACE heuristic bonus = kface(p, r, f, board); score[c] += bonus; // KAGGRESSIVE heuristic bonus = kaggressive(p, r, f, board); score[c] += bonus; break; case INVALID: tbassert(false, "Jose says: no way!\n"); // No way, Jose! default: tbassert(false, "Jose says: no way!\n"); // No way, Jose! } } if (c == BLACK) break; c = BLACK; } score[WHITE] += board->pawn_count[WHITE] * PAWN_EV_VALUE; score[BLACK] += board->pawn_count[BLACK] * PAWN_EV_VALUE; square_t white_laser_list[MAX_NUM_PIECES]; square_t black_laser_list[MAX_NUM_PIECES]; int whitePathCount = get_laser_path_list(p, white_laser_list, WHITE, board); int blackPathCount = get_laser_path_list(p, black_laser_list, BLACK, board); ev_score_t w_hattackable = HATTACK * h_squares_attackable(p, WHITE, white_laser_list, whitePathCount, board); score[WHITE] += w_hattackable; ev_score_t b_hattackable = HATTACK * h_squares_attackable(p, BLACK, black_laser_list, blackPathCount, board); score[BLACK] += b_hattackable; int w_mobility_list = MOBILITY * mobility_list(p, WHITE, black_laser_list, blackPathCount, board); score[WHITE] += w_mobility_list; int b_mobility_list = MOBILITY * mobility_list(p, BLACK, white_laser_list, whitePathCount, board); score[BLACK] += b_mobility_list; // PAWNPIN Heuristic --- is a pawn immobilized by the enemy laser. int w_pawnpin = PAWNPIN * pawnpin(p, WHITE, black_laser_list, blackPathCount, board); //use other color's laser map score[WHITE] += w_pawnpin; int b_pawnpin = PAWNPIN * pawnpin(p, BLACK, white_laser_list, whitePathCount, board); //use other color's laser map score[BLACK] += b_pawnpin; // score from WHITE point of view ev_score_t tot = score[WHITE] - score[BLACK]; if (RANDOMIZE) { ev_score_t z = rand_r(&seed) % (RANDOMIZE*2+1); tot = tot + z - RANDOMIZE; } if (color_to_move_of(p) == BLACK) { tot = -tot; } return tot / EV_SCORE_RATIO; }
// Helper function for COUNT_PRIMES_IN_INTERVAL() to count the number // of primes in [START, START+LENGTH) where 0 < LENGTH <= // MAX_SIEVE_LENGTH. Returns the number of primes in [START, // START+LENGTH). // // START -- The low endpoint of the interval. // // LENGTH -- The length of the interval. // // SMALL_PRIMES -- Sieve recording all primes in [0,2^32). // static int64_t count_primes_in_interval_helper(int64_t start, int64_t length, const sieve_t* small_primes) { int64_t num_primes; // Create LARGE_PRIMES structures to record the primes in // [START,START+LENGTH), where index I in LARGE_PRIMES corresponds // to the integer LOW+I. sieve_t *large_primes = create_sieve(length); if (NULL == large_primes) { fprintf(stderr, "Failed to create LARGE_PRIMES sieve of length %"PRId64".\n"\ "This can happen if there is insufficient physical memory on the system.\n"\ "Aborting.\n", length); exit(1); } init_sieve(large_primes); // Scan all of the potentially prime entries in the SMALL_PRIMES // sieve. for (int64_t p = 2; p < small_primes->length; ++p) { tbassert(trialdiv_prime_p(p) == prime_p(small_primes, p), "Incorrect primality recorded for %"PRId64" in small_primes\n", p); // Skip any entry in SMALL_PRIMES marked as composite. if (!prime_p(small_primes, p)) { continue; } // At this point, P is a prime. /* DEBUG_EPRINTF("p = %ld\n", p); */ // Find index KP_INDEX of smallest multiple of <p> in range // [START,START+LENGTH). int64_t kp_index = start % p; if (0 != kp_index) { kp_index = p - kp_index; } if (start + kp_index == p) { kp_index += p; } /* DEBUG_EPRINTF("kp_index = %"PRId64"\n", kp_index); */ // Mark all multiples of P in [KP_INDEX, KP_INDEX+LENGTH) as // composite. for ( ; kp_index < length; kp_index += p) { mark_composite(large_primes, kp_index); } } // Count number of primes in LARGE_PRIMES. num_primes = 0; for (int64_t i = 0; i < length; ++i) { tbassert(trialdiv_prime_p(i + start) == prime_p(large_primes, i), "Incorrect primality recorded for %"PRId64" in large_primes (index %"PRId64")\n", i + start, i); if (prime_p(large_primes, i)) { ++num_primes; } } // Free LARGE_PRIMES structure destroy_sieve(large_primes); return num_primes; }
static score_t get_game_over_score(victims_t* victims, int pov, int ply) { tbassert(ptype_of(victims->stomped) != KING, "Stomped a king.\n"); // score negative when victims.zapped == WHITE score_t score = -1*(1 - 2*(color_of(victims->zapped)))*WIN*pov; return score + (1 - 2*(score >= 0))*ply; }
static score_t scout_search(searchNode *node, int depth, uint64_t *node_count_serial) { // Initialize the search node. initialize_scout_node(node, depth); // check whether we should abort if (should_abort_check() || parallel_parent_aborted(node)) { return 0; } // Pre-evaluate this position. leafEvalResult pre_evaluation_result = evaluate_as_leaf(node, SEARCH_SCOUT); // If we decide to stop searching, return the pre-evaluation score. if (pre_evaluation_result.type == MOVE_EVALUATED) { return pre_evaluation_result.score; } // Populate some of the fields of this search node, using some // of the information provided by the pre-evaluation. int hash_table_move = pre_evaluation_result.hash_table_move; node->best_score = pre_evaluation_result.score; node->quiescence = pre_evaluation_result.should_enter_quiescence; // Grab the killer-moves for later use. move_t killer_a = killer[KMT(node->ply, 0)]; move_t killer_b = killer[KMT(node->ply, 1)]; // Store the sorted move list on the stack. // MAX_NUM_MOVES is all that we need. sortable_move_t move_list[MAX_NUM_MOVES]; // Obtain the sorted move list. int num_of_moves = get_sortable_move_list(node, move_list, hash_table_move); int number_of_moves_evaluated = 0; // A simple mutex. See simple_mutex.h for implementation details. simple_mutex_t node_mutex; init_simple_mutex(&node_mutex); // Sort the move list. sort_incremental(move_list, num_of_moves, 0); moveEvaluationResult result; for (int mv_index = 0; mv_index < num_of_moves; mv_index++) { if (mv_index == 1) { sort_full(move_list, num_of_moves); // sortable_move_t new_move_list[MAX_NUM_MOVES]; //memcpy(new_move_list, move_list, num_of_moves*sizeof(sortable_move_t)); //: sort_incremental_full(move_list,num_of_moves); } // Get the next move from the move list. int local_index = number_of_moves_evaluated++; move_t mv = get_move(move_list[local_index]); if (TRACE_MOVES) { print_move_info(mv, node->ply); } // increase node count __sync_fetch_and_add(node_count_serial, 1); evaluateMove(&result, node, mv, killer_a, killer_b, SEARCH_SCOUT, node_count_serial); undo_move(&result.next_node, mv); if (result.type == MOVE_ILLEGAL || result.type == MOVE_IGNORE || abortf || parallel_parent_aborted(node)) { continue; } // A legal move is a move that's not KO, but when we are in quiescence // we only want to count moves that has a capture. if (result.type == MOVE_EVALUATED) { node->legal_move_count++; } // process the score. Note that this mutates fields in node. bool cutoff = search_process_score(node, mv, local_index, &result, SEARCH_SCOUT); if (cutoff) { node->abort = true; break; } } if (parallel_parent_aborted(node)) { return 0; } if (node->quiescence == false) { update_best_move_history(node->position, node->best_move_index, move_list, number_of_moves_evaluated); } tbassert(abs(node->best_score) != -INF, "best_score = %d\n", node->best_score); // Reads node->position->key, node->depth, node->best_score, and node->ply update_transposition_table(node); return node->best_score; }
// Static evaluation. Returns score score_t eval(position_t *p, bool verbose) { // seed rand_r with a value of 1, as per // http://linux.die.net/man/3/rand_r static __thread unsigned int seed = 1; // verbose = true: print out components of score ev_score_t score[2] = { 0, 0 }; // int corner[2][2] = { {INF, INF}, {INF, INF} }; ev_score_t bonus; //char buf[MAX_CHARS_IN_MOVE]; color_t c; for (fil_t f = 0; f < BOARD_WIDTH; f++) { for (rnk_t r = 0; r < BOARD_WIDTH; r++) { square_t sq = square_of(f, r); piece_t x = p->board[sq]; //if (verbose) { // square_to_str(sq, buf, MAX_CHARS_IN_MOVE); //} switch (ptype_of(x)) { case EMPTY: break; case PAWN: c = color_of(x); // MATERIAL heuristic: Bonus for each Pawn bonus = PAWN_EV_VALUE; // if (verbose) { // printf("MATERIAL bonus %d for %s Pawn on %s\n", bonus, color_to_str(c), buf); // } score[c] += bonus; // PBETWEEN heuristic bonus = pbetween(p, f, r); // if (verbose) { // printf("PBETWEEN bonus %d for %s Pawn on %s\n", bonus, color_to_str(c), buf); // } score[c] += bonus; // PCENTRAL heuristic bonus = pcentral(f, r); // if (verbose) { // printf("PCENTRAL bonus %d for %s Pawn on %s\n", bonus, color_to_str(c), buf); // } score[c] += bonus; break; case KING: c = color_of(x); // KFACE heuristic bonus = kface(p, f, r); // if (verbose) { // printf("KFACE bonus %d for %s King on %s\n", bonus, // color_to_str(c), buf); // } score[c] += bonus; // KAGGRESSIVE heuristic color_t othercolor = opp_color(c); square_t otherking = p->kloc[othercolor]; fil_t otherf = fil_of(otherking); rnk_t otherr = rnk_of(otherking); bonus = kaggressive(f, r, otherf, otherr); assert(bonus == kaggressive_old(p, f, r)); // if (verbose) { // printf("KAGGRESSIVE bonus %d for %s King on %s\n", bonus, color_to_str(c), buf); // } score[c] += bonus; break; case INVALID: break; default: tbassert(false, "Jose says: no way!\n"); // No way, Jose! } laser_map_black[sq] = 0; laser_map_white[sq] = 0; } } int black_pawns_unpinned = mark_laser_path(p, laser_map_white, WHITE, 1); // 1 = path of laser with no moves ev_score_t w_hattackable = HATTACK * (int) h_attackable; score[WHITE] += w_hattackable; // if (verbose) { // printf("HATTACK bonus %d for White\n", w_hattackable); // } // PAWNPIN Heuristic --- is a pawn immobilized by the enemy laser. int b_pawnpin = PAWNPIN * black_pawns_unpinned; score[BLACK] += b_pawnpin; int b_mobility = MOBILITY * mobility(p, BLACK); score[BLACK] += b_mobility; // if (verbose) { // printf("MOBILITY bonus %d for Black\n", b_mobility); // } int white_pawns_unpinned = mark_laser_path(p, laser_map_black, BLACK, 1); // 1 = path of laser with no moves ev_score_t b_hattackable = HATTACK * (int) h_attackable; score[BLACK] += b_hattackable; // if (verbose) { // printf("HATTACK bonus %d for Black\n", b_hattackable); // } int w_mobility = MOBILITY * mobility(p, WHITE); score[WHITE] += w_mobility; // if (verbose) { // printf("MOBILITY bonus %d for White\n", w_mobility); // } int w_pawnpin = PAWNPIN * white_pawns_unpinned; score[WHITE] += w_pawnpin; // score from WHITE point of view ev_score_t tot = score[WHITE] - score[BLACK]; if (RANDOMIZE) { ev_score_t z = rand_r(&seed) % (RANDOMIZE*2+1); tot = tot + z - RANDOMIZE; } if (color_to_move_of(p) == BLACK) { tot = -tot; } return tot / EV_SCORE_RATIO; }
int beam_of(int direction) { tbassert(direction >= 0 && direction < NUM_ORI, "dir: %d\n", direction); return beam[direction]; }
// check the victim pieces returned by the move to determine if it's a // game-over situation. If so, also calculate the score depending on // the pov (which player's point of view) static bool is_game_over(victims_t* victims, int pov, int ply) { tbassert(ptype_of(victims->stomped) != KING, "Stomped a king.\n"); return ptype_of(victims->zapped) == KING; }
int dir_of(int i) { tbassert(i >= 0 && i < 8, "i: %d\n", i); return dir[i]; }
// Evaluate the move by performing a search. void evaluateMove(searchNode *node, move_t mv, move_t killer_a, move_t killer_b, searchType_t type, uint64_t *node_count_serial, moveEvaluationResult* result) { int ext = 0; // extensions bool blunder = false; // shoot our own piece result->next_node.subpv[0] = 0; result->next_node.parent = node; // Make the move, and get any victim pieces. bool isko = make_move(&(node->position), &(result->next_node.position), mv); // Check whether this move changes the board state. // such moves are not legal. if (isko) { result->type = MOVE_ILLEGAL; return; } victims_t* victims = &result->next_node.position.victims; // Check whether the game is over. if (is_game_over(victims, node->pov, node->ply)) { // Compute the end-game score. result->type = MOVE_GAMEOVER; result->score = get_game_over_score(victims, node->pov, node->ply); return; } // Ignore noncapture moves when in quiescence. if (zero_victims(victims) && node->quiescence) { result->type = MOVE_IGNORE; return; } // Check whether the board state has been repeated, this results in a draw. if (is_repeated(&(result->next_node.position), node->ply)) { result->type = MOVE_GAMEOVER; result->score = get_draw_score(&(result->next_node.position), node->ply); return; } tbassert(victims->stomped == 0 || color_of(victims->stomped) != node->fake_color_to_move, "stomped = %d, color = %d, fake_color_to_move = %d\n", victims->stomped, color_of(victims->stomped), node->fake_color_to_move); // Check whether we caused our own piece to be zapped. This isn't considered // a blunder if we also managed to stomp an enemy piece in the process. if (victims->stomped == 0 && victims->zapped > 0 && color_of(victims->zapped) == node->fake_color_to_move) { blunder = true; } // Do not consider moves that are blunders while in quiescence. if (node->quiescence && blunder) { result->type = MOVE_IGNORE; return; } // Extend the search-depth by 1 if we captured a piece, since that means the // move was interesting. if (victim_exists(victims) && !blunder) { ext = 1; } // Late move reductions - or LMR. Only done in scout search. int next_reduction = 0; if (type == SEARCH_SCOUT && node->legal_move_count + 1 >= LMR_R1 && node->depth > 2 && zero_victims(victims) && mv != killer_a && mv != killer_b) { if (node->legal_move_count + 1 >= LMR_R2) { next_reduction = 2; } else { next_reduction = 1; } } result->type = MOVE_EVALUATED; int search_depth = ext + node->depth - 1; // Check if we need to perform a reduced-depth search. // // After a reduced-depth search, a full-depth search will be performed if the // reduced-depth search did not trigger a cut-off. if (next_reduction > 0) { search_depth -= next_reduction; int reduced_depth_score = -scout_search(&(result->next_node), search_depth, node_count_serial); if (reduced_depth_score < node->beta) { result->score = reduced_depth_score; return; } search_depth += next_reduction; } // Check if we should abort due to time control. if (abortf) { result->score = 0; result->type = MOVE_IGNORE; return; } if (type == SEARCH_SCOUT) { result->score = -scout_search(&(result->next_node), search_depth, node_count_serial); } else { if (node->legal_move_count == 0 || node->quiescence) { result->score = -searchPV(&(result->next_node), search_depth, node_count_serial); } else { result->score = -scout_search(&(result->next_node), search_depth, node_count_serial); if (result->score > node->alpha) { result->score = -searchPV(&(result->next_node), node->depth + ext - 1, node_count_serial); } } } }
score_t searchRoot(position_t *p, score_t alpha, score_t beta, int depth, int ply, move_t *pv, uint64_t *node_count_serial, FILE *OUT) { static int num_of_moves = 0; // number of moves in list // hopefully, more than we will need static sortable_move_t move_list[MAX_NUM_MOVES]; if (depth == 1) { // we are at depth 1; generate all possible moves num_of_moves = generate_all_opt(p, move_list, false); // shuffle the list of moves for (int i = 0; i < num_of_moves; i++) { int r = myrand() % num_of_moves; sortable_move_t tmp = move_list[i]; move_list[i] = move_list[r]; move_list[r] = tmp; } } searchNode rootNode; rootNode.parent = NULL; initialize_root_node(&rootNode, alpha, beta, depth, ply, p); assert(rootNode.best_score == alpha); // initial conditions searchNode next_node; next_node.subpv[0] = 0; next_node.parent = &rootNode; score_t score; for (int mv_index = 0; mv_index < num_of_moves; mv_index++) { move_t mv = get_move(move_list[mv_index]); if (TRACE_MOVES) { print_move_info(mv, ply); } (*node_count_serial)++; // make the move. victims_t x = make_move(&(rootNode.position), &(next_node.position), mv); if (is_KO(x)) { continue; // not a legal move } if (is_game_over(x, rootNode.pov, rootNode.ply)) { score = get_game_over_score(x, rootNode.pov, rootNode.ply); next_node.subpv[0] = 0; goto scored; } if (is_repeated(&(next_node.position), rootNode.ply)) { score = get_draw_score(&(next_node.position), rootNode.ply); next_node.subpv[0] = 0; goto scored; } if (mv_index == 0 || rootNode.depth == 1) { // We guess that the first move is the principle variation score = -searchPV(&next_node, rootNode.depth-1, node_count_serial); // Check if we should abort due to time control. if (abortf) { return 0; } } else { score = -scout_search(&next_node, rootNode.depth-1, node_count_serial); // Check if we should abort due to time control. if (abortf) { return 0; } // If its score exceeds the current best score, if (score > rootNode.alpha) { score = -searchPV(&next_node, rootNode.depth-1, node_count_serial); // Check if we should abort due to time control. if (abortf) { return 0; } } } scored: // only valid for the root node: tbassert((score > rootNode.best_score) == (score > rootNode.alpha), "score = %d, best = %d, alpha = %d\n", score, rootNode.best_score, rootNode.alpha); if (score > rootNode.best_score) { tbassert(score > rootNode.alpha, "score: %d, alpha: %d\n", score, rootNode.alpha); rootNode.best_score = score; pv[0] = mv; memcpy(pv+1, next_node.subpv, sizeof(move_t) * (MAX_PLY_IN_SEARCH - 1)); pv[MAX_PLY_IN_SEARCH - 1] = 0; // Print out based on UCI (universal chess interface) double et = elapsed_time(); char pvbuf[MAX_PLY_IN_SEARCH * MAX_CHARS_IN_MOVE]; getPV(pv, pvbuf, MAX_PLY_IN_SEARCH * MAX_CHARS_IN_MOVE); if (et < 0.00001) { et = 0.00001; // hack so that we don't divide by 0 } uint64_t nps = 1000 * *node_count_serial / et; fprintf(OUT, "info depth %d move_no %d time (microsec) %d nodes %" PRIu64 " nps %" PRIu64 "\n", depth, mv_index + 1, (int) (et * 1000), *node_count_serial, nps); fprintf(OUT, "info score cp %d pv %s\n", score, pvbuf); // Slide this move to the front of the move list for (int j = mv_index; j > 0; j--) { move_list[j] = move_list[j - 1]; } move_list[0] = mv; } // Normal alpha-beta logic: if the current score is better than what the // maximizer has been able to get so far, take that new value. Likewise, // score >= beta is the beta cutoff condition if (score > rootNode.alpha) { rootNode.alpha = score; } if (score >= rootNode.beta) { tbassert(0, "score: %d, beta: %d\n", score, rootNode.beta); break; } } return rootNode.best_score; }
int reflect_of(int beam_dir, int pawn_ori) { tbassert(beam_dir >= 0 && beam_dir < NUM_ORI, "beam-dir: %d\n", beam_dir); tbassert(pawn_ori >= 0 && pawn_ori < NUM_ORI, "pawn-ori: %d\n", pawn_ori); return reflect[beam_dir][pawn_ori]; }
// Perform a Principle Variation Search // https://chessprogramming.wikispaces.com/Principal+Variation+Search static score_t searchPV(searchNode *node, int depth, uint64_t *node_count_serial) { // Initialize the searchNode data structure. initialize_pv_node(node, depth); // Pre-evaluate the node to determine if we need to search further. leafEvalResult pre_evaluation_result = evaluate_as_leaf(node, SEARCH_PV); // use some information from the pre-evaluation int hash_table_move = pre_evaluation_result.hash_table_move; if (pre_evaluation_result.type == MOVE_EVALUATED) { return pre_evaluation_result.score; } if (pre_evaluation_result.score > node->best_score) { node->best_score = pre_evaluation_result.score; if (node->best_score > node->alpha) { node->alpha = node->best_score; } } // Get the killer moves at this node. move_t killer_a = killer[KMT(node->ply, 0)]; move_t killer_b = killer[KMT(node->ply, 1)]; // sortable_move_t move_list // // Contains a list of possible moves at this node. These moves are "sortable" // and can be compared as integers. This is accomplished by using high-order // bits to store a sort key. // // Keep track of the number of moves that we have considered at this node. // After we finish searching moves at this node the move_list array will // be organized in the following way: // // m0, m1, ... , m_k-1, m_k, ... , m_N-1 // // where k = num_moves_tried, and N = num_of_moves // // This will allow us to update the best_move_history table easily by // scanning move_list from index 0 to k such that we update the table // only for moves that we actually considered at this node. sortable_move_t move_list[MAX_NUM_MOVES]; int num_of_moves = get_sortable_move_list(node, move_list, hash_table_move); int num_moves_tried = 0; // Start searching moves. for (int mv_index = 0; mv_index < num_of_moves; mv_index++) { // Incrementally sort the move list. sort_incremental(move_list, num_of_moves, mv_index); move_t mv = get_move(move_list[mv_index]); num_moves_tried++; (*node_count_serial)++; moveEvaluationResult result = evaluateMove(node, mv, killer_a, killer_b, SEARCH_PV, node_count_serial); if (result.type == MOVE_ILLEGAL || result.type == MOVE_IGNORE) { continue; } // A legal move is a move that's not KO, but when we are in quiescence // we only want to count moves that has a capture. if (result.type == MOVE_EVALUATED) { node->legal_move_count++; } // Check if we should abort due to time control. if (abortf) { return 0; } bool cutoff = search_process_score(node, mv, mv_index, &result, SEARCH_PV); if (cutoff) { break; } } if (node->quiescence == false) { update_best_move_history(&(node->position), node->best_move_index, move_list, num_moves_tried); } tbassert(abs(node->best_score) != -INF, "best_score = %d\n", node->best_score); // Update the transposition table. // // Note: This function reads node->best_score, node->orig_alpha, // node->position.key, node->depth, node->ply, node->beta, // node->alpha, node->subpv update_transposition_table(node); return node->best_score; }