std::tuple<Move, Value> AperyBook::probe(const Position &pos, const std::string &fname, bool pick_best) { AperyBookEntry entry; uint16_t best = 0; uint32_t sum = 0; Move move = kMoveNone; Key key = book_key(pos); Value min_book_score = static_cast<Value>(static_cast<int>(Options["Min_Book_Score"])); Value score = kValueZero; if (file_name_ != fname && !open(fname.c_str())) return std::make_tuple(kMoveNone, kValueZero); binary_search(key); while (read(reinterpret_cast<char*>(&entry), sizeof(entry)), entry.key == key && good()) { best = std::max(best, entry.count); sum += entry.count; if ( min_book_score <= entry.score && ( (random_() % sum < entry.count) || (pick_best && entry.count == best) ) ) { Square to = to_square(entry.from_to_pro & 0x007fU); int from_raw = (entry.from_to_pro >> 7) & 0x007fU; if (from_raw >= kBoardSquare) { move = move_init(to, to_drop_piece_type(static_cast<Square>(from_raw))); } else { Square from = to_square(from_raw); PieceType pt_from = type_of(pos.square(from)); if (entry.from_to_pro & kPromoted) move = move_init(from, to, pt_from, type_of(pos.square(to)), true); else move = move_init(from, to, pt_from, type_of(pos.square(to)), false); } score = entry.score; } }
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; } }
// Obtain a sorted move list. static int get_sortable_move_list(searchNode *node, sortable_move_t * move_list, int hash_table_move) { // number of moves in list int num_of_moves = generate_all_opt(&(node->position), move_list, false); color_t fake_color_to_move = color_to_move_of(&(node->position)); move_t killer_a = killer[KMT(node->ply, 0)]; move_t killer_b = killer[KMT(node->ply, 1)]; // sort special moves to the front for (int mv_index = 0; mv_index < num_of_moves; mv_index++) { move_t mv = get_move(move_list[mv_index]); if (mv == hash_table_move) { set_sort_key(&move_list[mv_index], SORT_MASK); } else if (mv == killer_a) { set_sort_key(&move_list[mv_index], SORT_MASK - 1); } else if (mv == killer_b) { set_sort_key(&move_list[mv_index], SORT_MASK - 2); } else { 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(node->position.board[fs]) + ro); square_t ts = to_square(mv); set_sort_key(&move_list[mv_index], best_move_history[BMH(fake_color_to_move, pce, ts, ot)]); } } return num_of_moves; }
// 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; } } }
void AperyBook::init() { for (int p = 0; p < kPieceMax; ++p) { for (int sq = 0; sq < kBoardSquare; ++sq) zob_piece_[p][to_square(sq)] = mt64bit_(); } for (int hp = 0; hp < 7; ++hp) { for (int num = 0; num < 19; ++num) zob_hand_[hp][num] = mt64bit_(); } zob_turn_ = mt64bit_(); }
// Obtain a sorted move list. static int get_sortable_move_list(searchNode *node, sortable_move_t * move_list, int hash_table_move) { // number of moves in list int num_of_moves = generate_all(&(node->position), move_list, false); color_t fake_color_to_move = color_to_move_of(&(node->position)); move_t killer_a = killer[KMT(node->ply, 0)]; move_t killer_b = killer[KMT(node->ply, 1)]; for (int mv_index = 0; mv_index < num_of_moves; mv_index++) { move_t mv = move_list[mv_index]; // don't use get_move. assumes generate_all doesn't bungle up high bits if (mv == hash_table_move) { set_sort_key(&move_list[mv_index], SORT_MASK); } else if (mv == killer_a) { set_sort_key(&move_list[mv_index], SORT_MASK - 1); } else if (mv == killer_b) { set_sort_key(&move_list[mv_index], SORT_MASK - 2); } else { 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(node->position.board[fs]) + ro); square_t ts = to_square(mv); set_sort_key(&move_list[mv_index], best_move_history[BMH(fake_color_to_move, pce, ts, ot)]); } sortable_move_t insert = move_list[mv_index]; // TODO: enable this optimization for final since node counts change // if (insert > SORT_MASK) { int hole = mv_index; while (hole > 0 && insert > move_list[hole-1]) { move_list[hole] = move_list[hole-1]; hole--; } move_list[hole] = insert; // } } return num_of_moves; }
int Search::alphaBeta(bool white_turn, int depth, int alpha, int beta, Board& board, Transposition *tt, bool null_move_in_branch, Move (&killers)[32][2], int (&history)[64][64], int ply) { // If, mate we do not need search at greater depths if (board.b[WHITE][KING] == 0) { return -10000; } else if (board.b[BLACK][KING] == 0) { return 10000; } if (depth == 0) { return capture_quiescence_eval_search(white_turn, alpha, beta, board); } if (depth == 1) { // futility pruning. we do not hope for improving a position more than 300 in one move... int static_eval = evaluate(board); if (white_turn && (static_eval + 300) < alpha) { return capture_quiescence_eval_search(white_turn, alpha, beta, board); } if (!white_turn && (static_eval - 300) > beta) { return capture_quiescence_eval_search(white_turn, alpha, beta, board); } } if (depth == 2) { // extended futility pruning. we do not hope for improving a position more than 500 in two plies... // not really proven to +ELO but does not worse performance at least int static_eval = evaluate(board); if ((white_turn && (static_eval + 500) < alpha) || (!white_turn && (static_eval - 500) > beta)) { return capture_quiescence_eval_search(white_turn, alpha, beta, board); } } // null move heuristic - we do this despite in check.. if (!null_move_in_branch && depth > 3) { // skip a turn and see if and see if we get a cut-off at shallower depth // it assumes: // 1. That the disadvantage of forfeiting one's turn is greater than the disadvantage of performing a shallower search. // 2. That the beta cut-offs prunes enough branches to be worth the time searching at reduced depth int R = 2; // depth reduction int res = alphaBeta(!white_turn, depth - 1 - R, alpha, beta, board, tt, true, killers, history, ply + 1); if (white_turn && res > alpha) { alpha = res; } else if (!white_turn && res < beta) { beta = res; } if (beta <= alpha) { if (white_turn) { return alpha; } return beta; } } MoveList moves = get_children(board, white_turn); if (moves.empty()) { return 0; } Transposition tt_pv = tt[board.hash_key % HASH_SIZE]; for (auto it = moves.begin(); it != moves.end(); ++it) { // sort pv moves first if (tt_pv.next_move != 0 && tt_pv.hash == board.hash_key && tt_pv.next_move == it->m) { it->sort_score += 1100000; } // ...then captures in MVVLVA order if (!is_capture(it->m)) { // killer moves are quite moves that has previously led to a cut-off if (killers[ply - 1][0].m == it->m) { it->sort_score += 999999; } else if (killers[ply - 1][1].m == it->m) { it->sort_score += 899999; } else { // "history heuristics" // the rest of the quite moves are sorted based on how often they increase score in the search tree it->sort_score += history[from_square(it->m)][to_square(it->m)]; } } } total_generated_moves += moves.size(); Transposition t; t.hash = board.hash_key; int next_move = 0; for (unsigned int i = 0; i < moves.size(); ++i) { // late move reduction. // we assume sort order is good enough to not search later moves as deep as the first 4 if (depth > 5 && i == 10) { depth -= 2; } pick_next_move(moves, i); Move child = moves[i]; node_count++; make_move(board, child); int res = alphaBeta(!white_turn, depth - 1, alpha, beta, board, tt, null_move_in_branch, killers, history, ply + 1); unmake_move(board, child); if (res > alpha && res < beta) { // only cache exact scores } if (res > alpha && white_turn) { next_move = child.m; alpha = res; //history heuristics if (!is_capture(child.m)) { history[from_square(child.m)][to_square(child.m)] += depth; } // update pv } if (res < beta && !white_turn) { next_move = child.m; beta = res; //history heuristics if (!is_capture(child.m)) { history[from_square(child.m)][to_square(child.m)] += depth; } // update pv } if (beta <= alpha || time_to_stop()) { if (!is_capture(child.m)) { if (killers[ply - 1][0].m != child.m) { killers[ply - 1][1] = killers[ply - 1][0]; killers[ply - 1][0] = child; } } break; } } t.next_move = next_move; tt[board.hash_key % HASH_SIZE] = t; if (white_turn) { return alpha; } return beta; }