00001
00002
00003 #include "osl/search/alphaBeta3.h"
00004 #include "osl/search/searchRecorder.h"
00005 #include "osl/search/bigramKillerMove.h"
00006 #include "osl/search/killerMoveTable.h"
00007 #include "osl/search/simpleHashTable.h"
00008 #include "osl/search/simpleHashRecord.h"
00009 #include "osl/search/shouldPromoteCut.h"
00010 #include "osl/search/moveWithComment.h"
00011 #include "osl/checkmate/immediateCheckmate.h"
00012 #include "osl/eval/see.h"
00013 #include "osl/rating/featureSet.h"
00014 #include "osl/rating/ratingEnv.h"
00015 #include "osl/move_generator/legalMoves.h"
00016 #include "osl/move_generator/capture_.h"
00017 #include "osl/move_generator/escape_.h"
00018 #include "osl/move_generator/promote_.h"
00019 #include "osl/move_generator/addEffect_.h"
00020 #include "osl/move_generator/allMoves.h"
00021 #include "osl/move_classifier/directCheck.h"
00022 #include "osl/move_classifier/moveAdaptor.h"
00023 #include "osl/move_action/store.h"
00024 #include "osl/move_order/captureEstimation.h"
00025 #include "osl/move_order/moveSorter.h"
00026 #include "osl/move_order/captureSort.h"
00027 #include "osl/move_order/cheapPtype.h"
00028 #include "osl/record/csa.h"
00029 #include "osl/stl/hash_map.h"
00030 #include "osl/stat/average.h"
00031 #include "osl/stat/histogram.h"
00032 #include "osl/repetitionCounter.h"
00033 #include <boost/scoped_array.hpp>
00034 #include <boost/foreach.hpp>
00035 #include <algorithm>
00036 #include <iostream>
00037 #include <cstdio>
00038 #include <iomanip>
00039 const int extended_futility_margin = 256*16, futility_margin = 128*16, table_record_limit = 400;
00040 const int lmr_fullwidth = 4, lmr_reduce_limit = 200;
00041 const bool best_move_extension_enabled = false;
00042 const bool futility_pruning_enabled = true;
00043 const bool extended_futility_pruning_enabled = true;
00044 const bool cut_drop_move_in_frontier_node = true;
00045 const bool lmr_enabled = true, lmr_verify_enabled = true;
00046 const bool immediate_checkmate_enabled = true;
00047 const bool decorate_csa_in_pv = false, show_height_in_pv = false;
00048
00049 namespace osl
00050 {
00051 namespace search
00052 {
00053 inline Ptype promoteIf(Ptype ptype)
00054 {
00055 return canPromote(ptype) ? promote(ptype) : ptype;
00056 }
00057 struct CompactRecord
00058 {
00059 Move best_move;
00060 int value, limit;
00061 enum ValueType { Exact, UpperBound, LowerBound };
00062 ValueType type;
00063 CompactRecord() : limit(-1000000)
00064 {
00065 }
00066 template <Player P>
00067 bool highFail(int height, int threshold) const
00068 {
00069 return height <= limit && EvalTraits<P>::betterThan(value, threshold)
00070 && (type == Exact || type == LowerBound);
00071 }
00072 template <Player P>
00073 bool lowFail(int height, int threshold) const
00074 {
00075 return height <= limit && EvalTraits<P>::betterThan(threshold, value)
00076 && (type == Exact || type == UpperBound);
00077 }
00078 };
00079 struct CompactHashTable
00080 {
00081 typedef hash_map<HashKey, CompactRecord> table_t;
00082 table_t table;
00083 mutable int probe_success, probe_fail;
00084 CompactHashTable() : probe_success(0), probe_fail(0)
00085 {
00086 }
00087 ~CompactHashTable()
00088 {
00089 }
00090 const CompactRecord probe(const HashKey& key) const
00091 {
00092 table_t::const_iterator p = table.find(key);
00093 if (p != table.end()) {
00094 ++probe_success;
00095 return p->second;
00096 }
00097 ++probe_fail;
00098 return CompactRecord();
00099 }
00100 void store(const HashKey& key, const CompactRecord& value)
00101 {
00102 table[key] = value;
00103 }
00104 void clear()
00105 {
00106 table.clear();
00107 probe_success = probe_fail = 0;
00108 }
00109 };
00110 }
00111 }
00112
00113
00114 namespace
00115 {
00116 boost::scoped_array<osl::search::AlphaBeta3::SearchInfo> tree;
00117 osl::search::CompactHashTable table;
00118 osl::stat::Average mpn, mpn_cut, last_alpha_update;
00119 osl::stat::Histogram alpha_update_type(1,8);
00120 osl::search::BigramKillerMove bigram_killers;
00121 osl::search::KillerMoveTable killer_moves;
00122 int eval_count;
00123 int max_node_depth, total_node_count, depth_node_count[osl::search::AlphaBeta3::MaxDepth];
00124 void init_node_count()
00125 {
00126 max_node_depth = total_node_count = 0;
00127 std::fill(depth_node_count, depth_node_count+sizeof(depth_node_count)/sizeof(int), 0);
00128 }
00129 inline void add_node_count(int depth)
00130 {
00131 max_node_depth = std::max(max_node_depth, depth);
00132 ++depth_node_count[depth];
00133 ++total_node_count;
00134 }
00135 osl::Player root_player;
00136 osl::RepetitionCounter repetition_counter;
00137 }
00138
00139
00140
00141 osl::search::AlphaBeta3::
00142 AlphaBeta3(const NumEffectState& s, checkmate_t& ,
00143 SimpleHashTable *t, CountRecorder& r)
00144 : state(s), depth(0), recorder(r), table_common(t)
00145 {
00146 if (! tree) {
00147 rating::StandardFeatureSet::instance();
00148 tree.reset(new SearchInfo[MaxDepth]);
00149 }
00150 }
00151
00152 osl::search::AlphaBeta3::
00153 ~AlphaBeta3()
00154 {
00155 }
00156
00157 int osl::search::AlphaBeta3::
00158 evalValue() const
00159 {
00160 ++eval_count;
00161 if ((eval_count % (1<<18) == 0) || stop_by_alarm)
00162 if (this->timeAssigned().standard.toSeconds() - this->elapsed() < 0.3 || stop_by_alarm)
00163 throw misc::NoMoreTime();
00164 return tree[depth].eval.value();
00165 }
00166
00167 osl::Move osl::search::AlphaBeta3::
00168 computeBestMoveIteratively(int limit, int , int initial_limit,
00169 size_t ,
00170 const TimeAssigned& assign,
00171 MoveWithComment *)
00172 {
00173 this->setStartTime(MilliSeconds::now());
00174 this->setTimeAssign(assign);
00175
00176 mpn.clear();
00177 mpn_cut.clear();
00178 last_alpha_update.clear();
00179 bigram_killers.clear();
00180 table.clear();
00181 eval_count = 0;
00182 init_node_count();
00183
00184 initial_limit = std::min(initial_limit, limit);
00185
00186
00187 Move best_move;
00188 double consumed = 0;
00189
00190 try {
00191 for (int i=0; i<=limit; i+=100) {
00192 double new_consumed = this->elapsed(), diff = new_consumed - consumed;
00193 consumed = new_consumed;
00194 if (table_common->verboseLevel() > 1)
00195 std::cerr << i << " sec " << diff << " " << new_consumed
00196 << " mpn " << mpn.getAverage() << " " << mpn_cut.getAverage()
00197 << " " << last_alpha_update.getAverage() << "\n";
00198 best_move = searchRoot(i);
00199
00200 if (hasSchedule()) {
00201 const double current_time_left = this->timeAssigned().standard.toSeconds()-this->elapsed();
00202 const double coef = nextIterationCoefficient();
00203 if (current_time_left < new_consumed * coef) {
00204 if (table_common->verboseLevel() > 1)
00205 std::cerr << "expected timeover\n";
00206 break;
00207 }
00208 }
00209 }
00210 }
00211 catch (misc::NoMoreTime&) {
00212 if (table_common->verboseLevel() > 1)
00213 std::cerr << "timeover\n";
00214 }
00215 catch (NoMoreMemory&) {
00216 if (table_common->verboseLevel() > 1)
00217 std::cerr << "memory full\n";
00218 }
00219 double new_consumed = this->elapsed(), diff = new_consumed - consumed;
00220 consumed = new_consumed;
00221 if (table_common->verboseLevel() > 1) {
00222 std::cerr << "finish" << " sec " << diff << " " << new_consumed
00223 << " mpn " << mpn.getAverage() << " " << mpn_cut.getAverage()
00224 << " " << last_alpha_update.getAverage() << "\n";
00225 std::cerr << "table " << table.table.size() << " " << table.probe_success << " " << table.probe_fail
00226 << "\n";
00227 recorder.finishSearch(best_move, consumed, table_common->verboseLevel() > 1);
00228
00229 for (int i=0; i<=max_node_depth/4; ++i) {
00230 for (int j=0; j<4; ++j) {
00231 const int id = i + (max_node_depth/4)*j;
00232 fprintf(stderr, " depth %2d %5.2f%%",
00233 id, 100.0*depth_node_count[id] / (double)total_node_count);
00234 }
00235 fprintf(stderr, "\n");
00236 }
00237 }
00238 return best_move;
00239 }
00240
00241 bool osl::search::AlphaBeta3::
00242 isReasonableMove(Move , int )
00243 {
00244 return true;
00245 }
00246
00247 void osl::search::AlphaBeta3::
00248 setRootIgnoreMoves(const MoveVector * , bool)
00249 {
00250 }
00251 void osl::search::AlphaBeta3::
00252 setHistory(const MoveStack& )
00253 {
00254 }
00255
00256 void osl::search::AlphaBeta3::
00257 showNodeDepth(std::ostream&)
00258 {
00259 }
00260 void osl::search::AlphaBeta3::
00261 clearNodeDepth()
00262 {
00263 }
00264
00265
00266 osl::Move osl::search::AlphaBeta3::
00267 searchRoot(int limit)
00268 {
00269 depth = 0;
00270 SearchInfo& root = tree[0];
00271 root.moved = Move::PASS(alt(state.turn()));
00272 root.hash_key = HashKey(state);
00273 root.height = limit;
00274 root.path = PathEncoding(state.turn(), 0);
00275 root.eval = eval_t(state);
00276 root.moves.clear();
00277 recorder.resetNodeCount();
00278 root_player = state.turn();
00279 repetition_counter.clear();
00280 repetition_counter.push(root.hash_key, state);
00281 #if 1
00282 RatedMoveVector moves;
00283 {
00284 const rating::StandardFeatureSet& features = rating::StandardFeatureSet::instance();
00285 RatingEnv env;
00286 env.make(state);
00287 features.generateRating(state, env, 2000, moves);
00288 BOOST_FOREACH(const RatedMove& move, moves)
00289 root.moves.push_back(move.move());
00290 }
00291 #else
00292 LegalMoves::generate(state, root.moves);
00293 #endif
00294
00295 Move best_move;
00296 const Player turn = state.turn();
00297 int best_value = minusInfty(turn);
00298 root.alpha = best_value + eval::delta(turn);
00299 root.beta = -minusInfty(turn) - eval::delta(turn);
00300 root.node_type = PvNode;
00301
00302 CompactRecord record = table.probe(root.hash_key);
00303 if (record.best_move.isNormal()) {
00304 MoveVector::iterator p
00305 =std::find(root.moves.begin(), root.moves.end(), record.best_move);
00306 if (p != root.moves.end())
00307 std::swap(*root.moves.begin(), *p);
00308 }
00309
00310 BOOST_FOREACH(Move move, root.moves) {
00311 if (best_move.isNormal())
00312 root.node_type = AllNode;
00313 assert(!ShouldPromoteCut::canIgnoreAndNotDrop(move));
00314 if (best_move.isNormal())
00315 continue;
00316 const int value = (turn == BLACK)
00317 ? makeMoveAndSearch<BLACK>(move, 100)
00318 : makeMoveAndSearch<WHITE>(move, 100);
00319 if (eval::betterThan(turn, value, best_value)) {
00320 root.pv.setPV(move, root, tree[depth+1].pv);
00321 if (limit && table_common->verboseLevel()) {
00322 std::cerr << " " << record::csa::show(move) << " " << std::setw(6) << value << " " << std::setw(3) << root.pv.size() << " ";
00323 for (size_t i=1; i<root.pv.size(); ++i) {
00324 std::cerr << record::csa::show(root.pv[i].move);
00325 if (decorate_csa_in_pv) {
00326 if (i && root.pv[i-1].move.to() == root.pv[i].move.to()) std::cerr << '!';
00327 else if (root.pv[i].move.capturePtype()) std::cerr << 'x' << record::csa::show(root.pv[i].move.capturePtype());
00328 if (root.pv[i].move.isPromotion()) std::cerr << '*';
00329 if (root.pv[i].in_check) std::cerr << '#';
00330 if (show_height_in_pv) std::cerr << "(" << root.pv[i].height/10 << ")";
00331 }
00332 }
00333 std::cerr << std::endl;
00334 }
00335 best_value = value;
00336 best_move = move;
00337 root.alpha = best_value + eval::delta(turn);
00338 SimpleHashRecord *record = table_common->allocate(root.hash_key, limit);
00339 if (record)
00340 record->setLowerBound(turn, limit, MoveLogProb(best_move,100), best_value);
00341 }
00342 }
00343 record.best_move = best_move;
00344 record.value = best_value;
00345 record.type = CompactRecord::Exact;
00346 record.limit = root.height;
00347 table.store(root.hash_key, record);
00348 return best_move;
00349 }
00350
00351 template <osl::Player P>
00352 struct osl::search::AlphaBeta3::CallSearch
00353 {
00354 AlphaBeta3 *search;
00355 explicit CallSearch(AlphaBeta3 *s) : search(s) {}
00356 void operator()(Square) const { search->template presearch<P>(); }
00357 };
00358
00359 template <osl::Player P>
00360 struct osl::search::AlphaBeta3::CallQuiesce
00361 {
00362 AlphaBeta3 *search;
00363 explicit CallQuiesce(AlphaBeta3 *s) : search(s) {}
00364 void operator()(Square) const { search->template quiesce<PlayerTraits<P>::opponent>(); }
00365 };
00366
00367 template <osl::Player P>
00368 int osl::search::AlphaBeta3::
00369 makeMoveAndSearch(Move move, int consume)
00370 {
00371 ++depth;
00372 SearchInfo &node = tree[depth], &parent = tree[depth-1];
00373 node.moved = move;
00374 node.hash_key = tree[depth-1].hash_key.newHashWithMove(move);
00375 node.path = parent.path;
00376 node.height = parent.height - consume;
00377 node.alpha = parent.beta;
00378 node.beta = parent.alpha;
00379 node.node_type = (NodeType)-(parent.node_type);
00380 node.eval = parent.eval;
00381 node.pv.clear();
00382 node.extended = 0;
00383
00384
00385 if (0)
00386 {
00387 const Sennichite next_sennichite
00388 = repetition_counter.isAlmostSennichite(node.hash_key);
00389 if (node.moved.isNormal() && next_sennichite.isDraw())
00390 return this->drawValue();
00391 if (next_sennichite.hasWinner())
00392 return this->winByFoul(next_sennichite.winner());
00393 }
00394
00395
00396 CallSearch<P> f(this);
00397 node.path.pushMove(move);
00398 state.makeUnmakeMove(Player2Type<P>(), move, f);
00399 node.path.popMove(move);
00400
00401
00402 --depth;
00403
00404 return tree[depth+1].search_value;
00405 }
00406
00407 inline
00408 bool osl::search::AlphaBeta3::
00409 reductionOk() const
00410 {
00411 const SearchInfo& node = tree[depth];
00412 const Move m = node.moved;
00413 if (m.isCaptureOrPromotion())
00414 return false;
00415 if (node.in_check || (depth > 0 && tree[depth-1].in_check))
00416 return false;
00417 return true;
00418 }
00419
00420 template <osl::Player P>
00421 void osl::search::AlphaBeta3::
00422 presearch()
00423 {
00424 SearchInfo& node = tree[depth];
00425 assert(state.turn() == alt(P));
00426 const Player turn = alt(P);
00427 if (state.hasEffectAt(turn, state.kingSquare(alt(turn)))) {
00428 node.search_value = winByFoul(turn);
00429 return;
00430 }
00431 node.in_check = state.hasEffectAt(alt(turn), state.kingSquare(turn));
00432 node.eval.update(state, node.moved);
00433
00434
00435 #if 0
00436 if (depth > 1 && tree[depth-1].in_check && tree[depth-1].moves.size() == 1) {
00437 const int ext = 50;
00438 node.extended += ext;
00439 node.height += ext;
00440 }
00441 #endif
00442 if (node.in_check) {
00443 const int ext = (node.alpha != node.beta
00444 || (depth > 2 && tree[depth-1].moved.ptype() == KING))
00445 ? 100 : 100;
00446 node.extended += ext;
00447 node.height += ext;
00448 }
00449 else if (depth > 1 && node.moved.to() == tree[depth-1].moved.to() && ! node.moved.isPass()) {
00450 const int ext = (node.alpha != node.beta
00451 || tree[depth-1].moved.isCapture())
00452 ? 50 : 25;
00453 node.extended += ext;
00454 node.height += ext;
00455 }
00456
00457
00458 if (node.moved.isPass()) {
00459 const int ext = (node.height >= 500) ? -200 : -100;
00460 node.height += ext;
00461 node.extended = ext;
00462 }
00463
00464
00465 const int org_alpha = node.alpha, org_height = node.height;
00466 const NodeType org_node_type = node.node_type;
00467 const bool pv_in_pvs = node.node_type == CutNode && node.alpha != node.beta;
00468 int lmr_reduce = 0;
00469 if (pv_in_pvs)
00470 node.alpha = node.beta;
00471
00472 if (node.alpha == node.beta) {
00473 if (lmr_enabled && ! node.extended && reductionOk()
00474 && (!pv_in_pvs || node.height >= lmr_reduce_limit+100)
00475 && depth > 0) {
00476 if (pv_in_pvs)
00477 lmr_reduce = tree[depth-1].moves_tried / lmr_fullwidth * 50;
00478 else
00479 lmr_reduce = tree[depth-1].moves_tried / lmr_fullwidth * 75;
00480 lmr_reduce = std::min(400, lmr_reduce);
00481 node.height -= lmr_reduce;
00482 if (pv_in_pvs && node.height < lmr_reduce_limit)
00483 node.height = lmr_reduce_limit;
00484 }
00485 search<PlayerTraits<P>::opponent>();
00486 if (EvalTraits<P>::betterThan(node.beta, node.search_value))
00487 return;
00488 node.height = org_height;
00489 node.alpha = org_alpha;
00490 node.node_type = org_node_type;
00491
00492 if (! pv_in_pvs) {
00493 if (lmr_verify_enabled && lmr_reduce) {
00494 node.height -= lmr_reduce/2;
00495 if (lmr_reduce >= 100 || node.height >= 400)
00496 search<PlayerTraits<P>::opponent>();
00497 }
00498 return;
00499 }
00500 node.node_type = PvNode;
00501 }
00502
00503 assert(node.node_type == PvNode);
00504 if (node.height >= table_record_limit)
00505 {
00506 CompactRecord record = table.probe(node.hash_key);
00507
00508 if (! record.best_move.isNormal()) {
00509 const int height = node.height;
00510 for (int i=200; i+100<height; i+=200) {
00511 node.height = i;
00512 search<PlayerTraits<P>::opponent>();
00513 node.alpha = org_alpha;
00514 node.node_type = PvNode;
00515 }
00516 node.height = height;
00517 }
00518 }
00519
00520 const bool best_move_extension_candidate
00521 = best_move_extension_enabled && root_player == P
00522 && node.height >= 150 && node.extended < 50;
00523 const bool skip_main_search
00524 = best_move_extension_candidate && pv_in_pvs;
00525 if (! skip_main_search)
00526 search<PlayerTraits<P>::opponent>();
00527
00528 if (best_move_extension_candidate
00529 && EvalTraits<P>::betterThan(node.search_value, node.beta))
00530 {
00531 node.node_type = PvNode;
00532 node.alpha = org_alpha;
00533 const int ext = 50;
00534 node.height += ext; node.extended += ext;
00535 search<PlayerTraits<P>::opponent>();
00536 }
00537 }
00538
00539 template <osl::Player P>
00540 void osl::search::AlphaBeta3::
00541 search()
00542 {
00543 using namespace move_classifier;
00544 add_node_count(depth);
00545
00546 SearchInfo& node = tree[depth];
00547 assert(state.turn() == P);
00548 recorder.addNodeCount();
00549
00550 if (node.height < 0) {
00551 quiesceRoot<P>();
00552 return;
00553 }
00554
00555 CompactRecord record = node.height >= table_record_limit
00556 ? table.probe(node.hash_key)
00557 : CompactRecord();
00558 if (node.alpha == node.beta) {
00559 if (record.highFail<P>(node.height, node.beta)) {
00560 node.search_value = record.value;
00561 return;
00562 }
00563 if (record.lowFail<P>(node.height, node.alpha)) {
00564 node.search_value = record.value;
00565 return;
00566 }
00567 }
00568 const bool frontier_node = futility_pruning_enabled && node.height < 100;
00569 const bool extended_frontier_node = (! frontier_node) && extended_futility_pruning_enabled && node.height < 200;
00570 const bool in_pv = node.alpha != node.beta;
00571 node.move_type = Initial;
00572 node.moves_tried = 0;
00573 const int initial_value = minusInfty(P)+depth*EvalTraits<P>::delta*2;
00574 int best_value = initial_value, last_alpha_update=0;
00575 if (EvalTraits<P>::betterThan(best_value, node.alpha)) {
00576 node.alpha = best_value + EvalTraits<P>::delta;
00577 if (EvalTraits<P>::betterThan(best_value, node.beta)) {
00578 node.search_value = best_value;
00579 return;
00580 }
00581 }
00582 const int initial_alpha = node.alpha;
00583 if (record.best_move.isNormal()) {
00584 const Move move = record.best_move;
00585 int value = makeMoveAndSearch<P>(move, 100);
00586 if (EvalTraits<P>::betterThan(value, best_value)) {
00587 best_value = value;
00588 if (EvalTraits<P>::betterThan(value, node.alpha)) {
00589 if (in_pv)
00590 node.pv.setPV(move, node, tree[depth+1].pv);
00591 node.alpha = value + EvalTraits<P>::delta;
00592 last_alpha_update = node.moves_tried+1;
00593 alpha_update_type.add(node.move_type);
00594 if (EvalTraits<P>::betterThan(value, node.beta)) {
00595 mpn_cut.add(node.moves_tried+1);
00596 goto done;
00597 }
00598 }
00599 }
00600 node.moves_tried++;
00601 }
00602 if (immediate_checkmate_enabled && ! node.in_check && (frontier_node || extended_futility_pruning_enabled)
00603 && ImmediateCheckmate::hasCheckmateMove<P>(state)) {
00604 node.search_value = winByCheckmate(P);
00605 return;
00606 }
00607 for (Move move=nextMove<P>(); !move.isInvalid(); move=nextMove<P>(), node.moves_tried++) {
00608 if (node.moves_tried == 1)
00609 node.node_type = AllNode;
00610 if (move == record.best_move)
00611 continue;
00612 if (! node.in_check && node.node_type != PvNode) {
00613 if (frontier_node && node.move_type > Pass) {
00614 const int futility = evalValue()
00615 + (move.capturePtype() ? eval_t::captureValue(move.capturePtypeO()) : 0)
00616 + futility_margin*EvalTraits<P>::delta;
00617 if (EvalTraits<P>::betterThan(best_value, futility)
00618 && (!tree[depth-1].in_check || !PlayerMoveAdaptor<DirectCheck>::isMember(state,move)))
00619 continue;
00620 }
00621 else if (extended_frontier_node && node.move_type > Killer) {
00622 const int futility_base = evalValue()+ extended_futility_margin*EvalTraits<P>::delta;
00623 if ((move.capturePtype()
00624 && EvalTraits<P>::betterThan(best_value, futility_base+node.eval.captureValue(move.capturePtypeO())))
00625 || EvalTraits<P>::betterThan(best_value, futility_base+See::see(state, move)))
00626 if (!tree[depth-1].in_check || !PlayerMoveAdaptor<DirectCheck>::isMember(state,move))
00627 continue;
00628 }
00629 }
00630 int value = makeMoveAndSearch<P>(move, 100);
00631 if (EvalTraits<P>::betterThan(value, best_value)) {
00632 best_value = value;
00633 record.best_move = move;
00634 if (EvalTraits<P>::betterThan(value, node.alpha)) {
00635 if (in_pv)
00636 node.pv.setPV(move, node, tree[depth+1].pv);
00637 node.alpha = value + EvalTraits<P>::delta;
00638 last_alpha_update = node.moves_tried+1;
00639 alpha_update_type.add(node.move_type);
00640 if (EvalTraits<P>::betterThan(value, node.beta)) {
00641 mpn_cut.add(node.moves_tried+1);
00642 goto done;
00643 }
00644 }
00645 }
00646 }
00647 mpn.add(node.moves_tried);
00648 if (last_alpha_update)
00649 ::last_alpha_update.add(last_alpha_update);
00650 done:
00651 if (last_alpha_update && node.move_type > Killer) {
00652 bigram_killers.setMove(node.moved, record.best_move);
00653 killer_moves.setMove(depth, record.best_move);
00654
00655 }
00656 if (node.height >= table_record_limit) {
00657 record.value = best_value;
00658 record.limit = node.height;
00659 if (EvalTraits<P>::betterThan(initial_alpha, best_value))
00660 record.type = CompactRecord::UpperBound;
00661 else if (EvalTraits<P>::betterThan(node.beta, best_value))
00662 record.type = CompactRecord::Exact;
00663 else
00664 record.type = CompactRecord::LowerBound;
00665 table.store(node.hash_key, record);
00666 }
00667 node.search_value = best_value;
00668 }
00669
00670 template <osl::Player P>
00671 osl::Move osl::search::AlphaBeta3::nextMove()
00672 {
00673 SearchInfo& node = tree[depth];
00674 switch (node.move_type) {
00675 case Initial:
00676 node.move_index = 0;
00677 node.moves.clear();
00678 if (node.in_check) {
00679 move_generator::GenerateEscape<P>::
00680 generate(state,state.kingPiece<P>(),node.moves);
00681 node.move_type = KingEscape;
00682 }
00683 case KingEscape:
00684 if (! node.moves.empty()) {
00685 if (node.move_index < node.moves.size())
00686 return node.moves[node.move_index++];
00687 return Move();
00688 }
00689 node.move_type = Pass;
00690 node.move_index = 0;
00691 case Pass:
00692 if (node.move_index++ == 0 && node.node_type != PvNode && !node.in_check)
00693 return Move::PASS(P);
00694 node.move_type = TakeBack;
00695 node.move_index = 0;
00696 if (node.moved.isNormal()) {
00697 move_generator::GenerateCapture::generate(P,state, node.moved.to(), node.moves);
00698
00699 }
00700 case TakeBack:
00701 if (node.move_index == 0 && node.moves.size())
00702 return node.moves[node.move_index++];
00703 node.move_type = Capture;
00704 node.move_index = 0;
00705 generateCapture<P>(state, node);
00706 case Capture:
00707 if (node.move_index < node.moves.size())
00708 return node.moves[node.move_index++];
00709 node.move_type = Killer;
00710 node.move_index = 0;
00711 node.moves.clear();
00712 bigram_killers.getMove(state, node.moved, node.moves);
00713 killer_moves.getMove(state, depth, node.moves);
00714 case Killer:
00715 if (node.move_index < node.moves.size())
00716 return node.moves[node.move_index++];
00717 node.move_type = CaptureAll;
00718 node.move_index = 0;
00719 generateCaptureAll<P>(state, node);
00720 case CaptureAll:
00721 if (node.move_index < node.moves.size())
00722 return node.moves[node.move_index++];
00723 node.move_type = All;
00724 node.move_index = 0;
00725 generateAllMoves<P>(state, tree[depth-1], node);
00726 case All:
00727 if (node.move_index < node.moves.size())
00728 return node.moves[node.move_index++];
00729 }
00730 return Move();
00731 }
00732
00733 template <osl::Player P>
00734 void osl::search::AlphaBeta3::
00735 generateAllMoves(const NumEffectState& state, const SearchInfo& parent, SearchInfo& node)
00736 {
00737 node.moves.clear();
00738 if (cut_drop_move_in_frontier_node
00739 && ! parent.in_check
00740 && ! node.in_check && node.node_type != PvNode) {
00741 if ((futility_pruning_enabled && node.height < 100)
00742 || (extended_futility_pruning_enabled && node.height < 200
00743 && EvalTraits<P>::betterThan(node.alpha, node.eval.value() + extended_futility_margin*EvalTraits<P>::delta))) {
00744
00745 GenerateAllMoves::generateOnBoard<P>(state, node.moves);
00746 }
00747 }
00748 #if 1
00749 # if 1
00750 if (node.alpha != node.beta || node.height >= 800) {
00751 RatedMoveVector moves;
00752 const rating::StandardFeatureSet& features = rating::StandardFeatureSet::instance();
00753 RatingEnv env;
00754 env.make(state);
00755 features.generateRating(state, env, 2000, moves);
00756 BOOST_FOREACH(const RatedMove& move, moves)
00757 if (move.move().isDrop() || ! seePlusLight<P>(state, move.move()))
00758 node.moves.push_back(move.move());
00759 return;
00760 }
00761 # endif
00762 GenerateAllMoves::generate<P>(state, node.moves);
00763
00764 if (node.alpha != node.beta || node.height > 300)
00765 std::sort(node.moves.begin(), node.moves.end(), move_order::CaptureEstimation(state));
00766 #endif
00767 }
00768
00769 template <osl::Player P>
00770 void osl::search::AlphaBeta3::
00771 generateCapture(const NumEffectState& state, SearchInfo& node)
00772 {
00773 node.moves.clear();
00774 MoveVector all;
00775 for (size_t i=0; i+1<PieceStand::order.size(); ++i) {
00776 const Ptype ptype = PieceStand::order[i];
00777 all.clear();
00778 move_action::Store store(all);
00779 for (int j=Ptype_Table.getIndexMin(ptype); j<Ptype_Table.getIndexLimit(ptype); ++j) {
00780 const Piece p = state.pieceOf(j);
00781 if (! p.isOnBoardByOwner<PlayerTraits<P>::opponent>())
00782 continue;
00783 move_generator::GenerateCapture::generate(P,state, p.square(), store);
00784 }
00785 BOOST_FOREACH(Move move, all) {
00786 if (See::see(state, move) > 0) {
00787 node.moves.push_back(move);
00788 }
00789 }
00790 if (! node.moves.empty())
00791 return;
00792 }
00793
00794 all.clear();
00795 move_generator::Promote<P>::generate(state, all);
00796 BOOST_FOREACH(Move move, all) {
00797 if (See::see(state, move) > 0) {
00798 node.moves.push_back(move);
00799 }
00800 }
00801 if (! node.moves.empty())
00802 return;
00803
00804 all.clear();
00805 {
00806 move_action::Store store(all);
00807 for (int j=Ptype_Table.getIndexMin(PAWN); j<Ptype_Table.getIndexLimit(PAWN); ++j) {
00808 const Piece p = state.pieceOf(j);
00809 if (! p.isOnBoardByOwner<PlayerTraits<P>::opponent>())
00810 continue;
00811 move_generator::GenerateCapture::generate(P,state, p.square(), store);
00812 }
00813 }
00814 BOOST_FOREACH(Move move, all) {
00815 if (See::see(state, move) > 0) {
00816 node.moves.push_back(move);
00817 }
00818 }
00819 }
00820 template <osl::Player P>
00821 inline
00822 bool osl::search::AlphaBeta3::
00823 seePlusLight(const NumEffectState& state, Move m)
00824 {
00825 assert(P == m.player());
00826 assert(P == state.turn());
00827 assert(! m.isDrop());
00828 if (state.countEffect(P, m.to()) > state.countEffect(P, m.to()))
00829 return true;
00830 return eval::Ptype_Eval_Table.value(m.capturePtype()) >= eval::Ptype_Eval_Table.value(m.oldPtype());
00831 }
00832
00833 template <osl::Player P>
00834 void osl::search::AlphaBeta3::
00835 generateCaptureAll(const NumEffectState& state, SearchInfo& node)
00836 {
00837 node.moves.clear();
00838 MoveVector all;
00839 {
00840 move_action::Store store(all);
00841 for (size_t i=0; i+1<PieceStand::order.size(); ++i) {
00842 const Ptype ptype = PieceStand::order[i];
00843 for (int j=Ptype_Table.getIndexMin(ptype); j<Ptype_Table.getIndexLimit(ptype); ++j) {
00844 const Piece p = state.pieceOf(j);
00845 if (! p.isOnBoardByOwner<PlayerTraits<P>::opponent>())
00846 continue;
00847 move_generator::GenerateCapture::generate(P,state, p.square(), store);
00848 }
00849 }
00850 move_generator::Promote<P>::generateMoves(state, store);
00851 for (int j=PtypeTraits<PAWN>::indexMin; j<PtypeTraits<PAWN>::indexLimit; ++j) {
00852 const Piece p = state.pieceOf(j);
00853 if (! p.isOnBoardByOwner<PlayerTraits<P>::opponent>())
00854 continue;
00855 move_generator::GenerateCapture::generate(P,state, p.square(), store);
00856 }
00857 }
00858 BOOST_FOREACH(Move move, all)
00859 if (seePlusLight<P>(state, move))
00860 node.moves.push_back(move);
00861 std::sort(node.moves.begin(), node.moves.end(), move_order::CaptureEstimation(state));
00862 }
00863
00864 template <osl::Player P>
00865 void osl::search::AlphaBeta3::
00866 quiesceRoot()
00867 {
00868 SearchInfo& node = tree[depth];
00869 assert(! state.hasEffectAt(P, state.kingSquare(alt(P))));
00870 assert(node.in_check == state.hasEffectAt(alt(P), state.kingSquare(P)));
00871
00872 node.search_value = evalValue();
00873 const int static_value = node.search_value;
00874 int best_value = static_value;
00875 if (node.in_check) {
00876 node.moves.clear();
00877 move_generator::GenerateEscape<P>::
00878 generate(state,state.kingPiece<P>(),node.moves);
00879 best_value = threatmatePenalty(P)+depth*EvalTraits<P>::delta*2;
00880
00881 BOOST_FOREACH(Move move, node.moves) {
00882 int value = makeMoveAndQuiesce<P>(move);
00883 if (EvalTraits<P>::betterThan(value, best_value)) {
00884 best_value = value;
00885 if (EvalTraits<P>::betterThan(value, node.alpha)) {
00886 if (node.node_type == PvNode)
00887 node.pv.setPV(move, node, tree[depth+1].pv);
00888 node.alpha = value + EvalTraits<P>::delta;
00889 if (EvalTraits<P>::betterThan(value, node.beta))
00890 goto done;
00891 }
00892 }
00893 }
00894 goto done;
00895 }
00896 if (EvalTraits<P>::betterThan(best_value, node.beta))
00897 goto done;
00898 if (immediate_checkmate_enabled && ImmediateCheckmate::hasCheckmateMove<P>(state)) {
00899 node.search_value = winByCheckmate(P);
00900 return;
00901 }
00902 BOOST_FOREACH(Ptype ptype, PieceStand::order) {
00903 const int expected = static_value + node.eval.captureValue(newPtypeO(alt(P), promoteIf(ptype)));
00904 if (EvalTraits<P>::betterThan(node.alpha, expected))
00905 break;
00906 for (int j=Ptype_Table.getIndexMin(ptype); j<Ptype_Table.getIndexLimit(ptype); ++j) {
00907 const Piece p = state.pieceOf(j);
00908 if (! p.isOnBoardByOwner<PlayerTraits<P>::opponent>())
00909 continue;
00910 node.moves.clear();
00911 move_generator::GenerateCapture::generate(P,state, p.square(), node.moves);
00912 BOOST_FOREACH(Move move, node.moves) {
00913 if (See::see(state, move) < 0)
00914 continue;
00915 int value = makeMoveAndQuiesce<P>(move);
00916 if (EvalTraits<P>::betterThan(value, best_value)) {
00917 best_value = value;
00918 if (EvalTraits<P>::betterThan(value, node.alpha)) {
00919 if (node.node_type == PvNode)
00920 node.pv.setPV(move, node, tree[depth+1].pv);
00921 node.alpha = value + EvalTraits<P>::delta;
00922 if (EvalTraits<P>::betterThan(value, node.beta))
00923 goto done;
00924 }
00925 }
00926 }
00927 }
00928 }
00929 done:
00930 node.search_value = best_value;
00931 }
00932
00933 template <osl::Player P>
00934 int osl::search::AlphaBeta3::
00935 makeMoveAndQuiesce(Move move)
00936 {
00937 ++depth;
00938 tree[depth] = tree[depth-1];
00939 tree[depth].moved = move;
00940 tree[depth].hash_key = tree[depth-1].hash_key.newHashWithMove(move);
00941 tree[depth].height -= 1;
00942 std::swap(tree[depth].alpha, tree[depth].beta);
00943 tree[depth].pv.clear();
00944
00945 CallQuiesce<P> f(this);
00946 tree[depth].path.pushMove(move);
00947 state.makeUnmakeMove(move, f);
00948 tree[depth].path.popMove(move);
00949
00950 --depth;
00951
00952 return tree[depth+1].search_value;
00953 }
00954
00955 template <osl::Player P>
00956 void osl::search::AlphaBeta3::
00957 quiesce()
00958 {
00959 add_node_count(depth);
00960
00961 assert(state.turn() == P);
00962 recorder.addQuiescenceCount();
00963 SearchInfo& node = tree[depth];
00964 if (state.hasEffectAt(P, state.kingSquare(alt(P)))) {
00965 node.search_value = winByFoul(P);
00966 return;
00967 }
00968 node.eval.update(state, node.moved);
00969 node.in_check = state.hasEffectAt(alt(P), state.kingSquare(P));
00970
00971 const int static_value = evalValue();
00972 int best_value = static_value;
00973
00974 if (node.in_check) {
00975 node.moves.clear();
00976 move_generator::GenerateEscape<P>::
00977 generate(state,state.kingPiece<P>(),node.moves);
00978
00979 best_value = threatmatePenalty(P)+depth*EvalTraits<P>::delta*2;
00980
00981 BOOST_FOREACH(Move move, node.moves) {
00982 int value = makeMoveAndQuiesce<P>(move);
00983 if (EvalTraits<P>::betterThan(value, best_value)) {
00984 best_value = value;
00985 if (EvalTraits<P>::betterThan(value, node.alpha)) {
00986 node.alpha = value + EvalTraits<P>::delta;
00987 if (EvalTraits<P>::betterThan(value, node.beta))
00988 goto done;
00989 }
00990 }
00991 }
00992 goto done;
00993 }
00994
00995
00996 if (EvalTraits<P>::betterThan(best_value, node.beta))
00997 goto done;
00998 if (immediate_checkmate_enabled && node.alpha != node.beta && ImmediateCheckmate::hasCheckmateMove<P>(state)) {
00999 node.search_value = winByCheckmate(P);
01000 return;
01001 }
01002 for (size_t i=0; i<PieceStand::order.size(); ++i) {
01003 const Ptype ptype = PieceStand::order[i];
01004 const int expected = static_value + node.eval.captureValue(newPtypeO(alt(P), promoteIf(ptype)));
01005 if (EvalTraits<P>::betterThan(node.alpha, expected))
01006 break;
01007 for (int j=Ptype_Table.getIndexMin(ptype); j<Ptype_Table.getIndexLimit(ptype); ++j) {
01008 const Piece p = state.pieceOf(j);
01009 if (! p.isOnBoardByOwner<PlayerTraits<P>::opponent>())
01010 continue;
01011 node.moves.clear();
01012 move_generator::GenerateCapture::generate(P,state, p.square(), node.moves);
01013
01014 for (size_t k=0; k<std::min((size_t)1, node.moves.size()); ++k) {
01015 const Move move = node.moves[k];
01016 const int see = See::see(state, move);
01017 int value = static_value + see*eval_t::seeScale()*EvalTraits<P>::delta;
01018 if (EvalTraits<P>::betterThan(value, best_value)) {
01019 if (node.node_type == PvNode)
01020 node.pv.setPV(move, node, tree[depth+1].pv);
01021 best_value = value;
01022 if (i < 6 || EvalTraits<P>::betterThan(value, node.beta))
01023 goto done;
01024 }
01025 }
01026 }
01027 }
01028 done:
01029 node.search_value = best_value;
01030 }
01031
01032
01033 osl::search::AlphaBeta3::
01034 SearchInfo::SearchInfo() : eval((NumEffectState(SimpleState(HIRATE)))), pv()
01035 {
01036 }
01037
01038 void osl::search::AlphaBeta3::
01039 PVVector::setPV(Move m, const SearchInfo& node, const PVVector& child)
01040 {
01041 clear();
01042 const PVInfo info = { m, node.height, node.in_check, };
01043 push_back(info);
01044 push_back(child.begin(), child.end());
01045 }
01046
01047
01048
01049
01050
01051