alphaBeta3.cc
Go to the documentation of this file.
00001 /* alphaBeta3.cc
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     // todo: open hash
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 // TODO: make shared? object
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& /*checker*/,
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 /*step*/, int initial_limit, 
00169                            size_t /*node_limit*/, 
00170                            const TimeAssigned& assign,
00171                            MoveWithComment */*additional_info*/)
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   // todo: iteration
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     // alpha_update_type.show(std::cerr);
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 /*move*/, int /*pawn_sacrifice*/)
00243 {
00244   return true;
00245 }
00246 
00247 void osl::search::AlphaBeta3::
00248 setRootIgnoreMoves(const MoveVector * /*rim*/, bool)
00249 {
00250 }
00251 void osl::search::AlphaBeta3::
00252 setHistory(const MoveStack& /*h*/)
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   // repetition_counter.push(node.hash_key, state);
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   // repetition_counter.pop();
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   // heuristic extension
00435 #if 0
00436   if (depth > 1 && tree[depth-1].in_check && tree[depth-1].moves.size() == 1) { // one reply
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   // null move pruning
00458   if (node.moved.isPass()) { // need verify?
00459     const int ext = (node.height >= 500) ? -200 : -100;
00460     node.height += ext;
00461     node.extended = ext;
00462   }
00463 
00464   // null window search
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)) // note: beta cut for opponent
00487       return;
00488     node.height = org_height;
00489     node.alpha = org_alpha;
00490     node.node_type = org_node_type;
00491     // verification search not in pv
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   // now node is pv
00503   assert(node.node_type == PvNode);
00504   if (node.height >= table_record_limit)
00505   {
00506     CompactRecord record = table.probe(node.hash_key);
00507     // iid if hash-move is not available
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   // main search
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   // best move ext --- ?
00528   if (best_move_extension_candidate
00529       && EvalTraits<P>::betterThan(node.search_value, node.beta)) // alpha value update for P
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     // history_table.setMove(depth, record.best_move);
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; // fall through
00682     } // fall through
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;      // fall through
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; // fall through
00695     node.move_index = 0;
00696     if (node.moved.isNormal()) {
00697       move_generator::GenerateCapture::generate(P,state, node.moved.to(), node.moves);
00698       // move_order::MoveSorter::sort(node.moves, move_order::CheapPtype());
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;   // fall through
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;    // fall through
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;        // fall through
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;       // fall through
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       // generation considering futility pruning
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) { // except for pawn
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   // promote
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   // pawn
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   } // end of in check
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   } // end of in check
00994 
00995   // leaf
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 // ;;; Local Variables:
01049 // ;;; mode:c++
01050 // ;;; c-basic-offset:2
01051 // ;;; End:
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines