// 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; }
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 score_t scout_search(searchNode *node, int depth, uint64_t *node_count_serial, full_board_t* board) { // 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, board); // 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, board); int num_of_special_moves = move_list[num_of_moves]; int num_of_nonzero_moves = move_list[num_of_moves + 1]; if (num_of_special_moves == 0) { selectionSort(move_list, num_of_moves, 1); num_of_special_moves = 1; } int number_of_moves_evaluated = 0; bool generated_early_cutoff = false; for (int mv_index = 0; mv_index < num_of_special_moves; mv_index++) { int local_index = number_of_moves_evaluated++; move_t mv = get_move(move_list[local_index]); // increase node count REDUCER_VIEW(node_count_reducer)++; smallMoveEvaluationResult result = evaluateMove(node, mv, killer_a, killer_b, SEARCH_SCOUT, node_count_serial); if (result.type == MOVE_ILLEGAL || result.type == MOVE_IGNORE) { continue; } if (abortf || parallel_parent_aborted(node)) { return 0; } // 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) { generated_early_cutoff = true; break; } } // ABOVE IS YOUNG BROTHER'S WAIT CODE. IT SHOULD RUN IN SERIAL // // BELOW IS PARALLELIZED SCOUT SEARCH. IT SHOULD RUN IN PARALLEL // if this happens, we generated an early cutoff in young brother's wait and can just skip to the end if (generated_early_cutoff) goto finish_scout_search; sort_incremental(move_list + num_of_special_moves, num_of_nonzero_moves - num_of_special_moves); // A simple mutex. See simple_mutex.h for implementation details. simple_mutex_t node_mutex; init_simple_mutex(&node_mutex); // TODO: see if changing this depth is better if (depth >= 2) { cilk_for (int mv_index = num_of_special_moves; mv_index < num_of_moves; mv_index++) { do { if (node->abort) continue; int local_index = __sync_fetch_and_add(&number_of_moves_evaluated, 1); move_t mv = get_move(move_list[local_index]); // increase node count REDUCER_VIEW(node_count_reducer)++; smallMoveEvaluationResult result = evaluateMove(node, mv, killer_a, killer_b, SEARCH_SCOUT, node_count_serial); // TODO: change this to break if we want to abort if (result.type == MOVE_ILLEGAL || result.type == MOVE_IGNORE) { continue; } if (abortf || parallel_parent_aborted(node)) { node->abort = true; 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) { __sync_fetch_and_add(&node->legal_move_count, 1); } bool cutoff = false; if (result.score > node->best_score) { simple_acquire(&node_mutex); // process the score. Note that this mutates fields in node. cutoff = search_process_score(node, mv, local_index, &result, SEARCH_SCOUT); simple_release(&node_mutex); } if (cutoff) { node->abort = true; continue; } } while (false); } } else {