Index   Main   Namespaces   Classes   Hierarchy   Annotated   Files   Compound   Global   Pages  

GoGtpEngine.cpp

Go to the documentation of this file.
00001 //----------------------------------------------------------------------------
00002 /** @file GoGtpEngine.cpp
00003     See GoGtpEngine.h
00004 */
00005 //----------------------------------------------------------------------------
00006 
00007 #include "SgSystem.h"
00008 #include "GoGtpEngine.h"
00009 
00010 #include <algorithm>
00011 #include <cmath>
00012 #include <exception>
00013 #include <fstream>
00014 #include <iomanip>
00015 #include <limits>
00016 #include <time.h>
00017 #include <boost/filesystem/operations.hpp>
00018 #include "GoEyeUtil.h"
00019 #include "GoGtpCommandUtil.h"
00020 #include "GoNodeUtil.h"
00021 #include "GoPlayer.h"
00022 #include "GoTimeControl.h"
00023 #include "GoUtil.h"
00024 #include "SgDebug.h"
00025 #include "SgEBWArray.h"
00026 #include "SgException.h"
00027 #include "SgGameReader.h"
00028 #include "SgGameWriter.h"
00029 #include "SgPointSetUtil.h"
00030 #include "SgTime.h"
00031 #include "SgWrite.h"
00032 
00033 #if GTPENGINE_PONDER
00034 #include <boost/thread/thread.hpp>
00035 #include <boost/thread/xtime.hpp>
00036 #endif
00037 
00038 using namespace std;
00039 using boost::filesystem::exists;
00040 using boost::filesystem::path;
00041 using boost::filesystem::remove;
00042 using SgPointUtil::Pt;
00043 
00044 //----------------------------------------------------------------------------
00045 
00046 namespace {
00047 
00048 GoRules::KoRule KoRuleArg(GtpCommand& cmd, size_t number)
00049 {
00050     string arg = cmd.ArgToLower(number);
00051     if (arg == "simple")
00052         return GoRules::SIMPLEKO;
00053     if (arg == "superko")
00054         return GoRules::SUPERKO;
00055     if (arg == "pos_superko")
00056         return GoRules::POS_SUPERKO;
00057     throw GtpFailure() << "unknown ko rule \"" << arg << '"';
00058 }
00059 
00060 string KoRuleToString(GoRules::KoRule rule)
00061 {
00062     switch (rule)
00063     {
00064     case GoRules::SIMPLEKO:
00065         return "simple";
00066     case GoRules::SUPERKO:
00067         return "superko";
00068     case GoRules::POS_SUPERKO:
00069         return "pos_superko";
00070     default:
00071         SG_ASSERT(false);
00072         return "?";
00073     }
00074 }
00075 
00076 } // namespace
00077 
00078 //----------------------------------------------------------------------------
00079 
00080 GoGtpEngine::GoGtpEngine(GtpInputStream& in, GtpOutputStream& out, int fixedBoardSize,
00081                          const char* programPath, bool noPlayer,
00082                          bool noHandicap)
00083     : GtpEngine(in, out),
00084       m_player(0),
00085       m_autoBook(0),
00086       m_noPlayer(noPlayer),
00087       m_acceptIllegal(false),
00088       m_autoSave(false),
00089       m_autoShowBoard(false),
00090       m_debugToComment(false),
00091       m_fixedBoardSize(fixedBoardSize),
00092       m_maxClearBoard(-1),
00093       m_numberClearBoard(0),
00094       m_timeLastMove(0),
00095       m_timeLimit(10),
00096       m_overhead(0),
00097       m_board(fixedBoardSize > 0 ? fixedBoardSize : GO_DEFAULT_SIZE),
00098       m_game(m_board),
00099       m_sgCommands(*this, programPath),
00100       m_bookCommands(*this, m_board, m_book),
00101       m_mpiSynchronizer(SgMpiNullSynchronizer::Create())
00102 {
00103     Register("all_legal", &GoGtpEngine::CmdAllLegal, this);
00104     Register("boardsize", &GoGtpEngine::CmdBoardSize, this);
00105     Register("clear_board", &GoGtpEngine::CmdClearBoard, this);
00106     Register("get_komi", &GoGtpEngine::CmdGetKomi, this);
00107     Register("gg-undo", &GoGtpEngine::CmdGGUndo, this);
00108     Register("go_board", &GoGtpEngine::CmdBoard, this);
00109     Register("go_param", &GoGtpEngine::CmdParam, this);
00110     Register("go_param_rules", &GoGtpEngine::CmdParamRules, this);
00111     Register("go_player_board", &GoGtpEngine::CmdPlayerBoard, this);
00112     Register("go_point_info", &GoGtpEngine::CmdPointInfo, this);
00113     Register("go_point_numbers", &GoGtpEngine::CmdPointNumbers, this);
00114     Register("go_rules", &GoGtpEngine::CmdRules, this);
00115     Register("go_sentinel_file", &GoGtpEngine::CmdSentinelFile, this);
00116     Register("go_set_info", &GoGtpEngine::CmdSetInfo, this);
00117     Register("gogui-analyze_commands", &GoGtpEngine::CmdAnalyzeCommands,
00118              this);
00119     Register("gogui-interrupt", &GoGtpEngine::CmdInterrupt, this);
00120     Register("gogui-play_sequence", &GoGtpEngine::CmdPlaySequence, this);
00121     Register("gogui-setup", &GoGtpEngine::CmdSetup, this);
00122     Register("gogui-setup_player", &GoGtpEngine::CmdSetupPlayer, this);
00123     Register("is_legal", &GoGtpEngine::CmdIsLegal, this);
00124     Register("kgs-genmove_cleanup", &GoGtpEngine::CmdGenMoveCleanup, this);
00125     Register("kgs-time_settings", &GoGtpEngine::CmdKgsTimeSettings, this);
00126     Register("komi", &GoGtpEngine::CmdKomi, this);
00127     Register("list_stones", &GoGtpEngine::CmdListStones, this);
00128     Register("loadsgf", &GoGtpEngine::CmdLoadSgf, this);
00129     Register("play", &GoGtpEngine::CmdPlay, this);
00130     Register("savesgf", &GoGtpEngine::CmdSaveSgf, this);
00131     Register("showboard", &GoGtpEngine::CmdShowBoard, this);
00132     Register("time_left", &GoGtpEngine::CmdTimeLeft, this);
00133     Register("time_settings", &GoGtpEngine::CmdTimeSettings, this);
00134     Register("undo", &GoGtpEngine::CmdUndo, this);
00135     m_sgCommands.Register(*this);
00136     if (! noPlayer)
00137     {
00138         Register("all_move_values", &GoGtpEngine::CmdAllMoveValues, this);
00139         Register("final_score", &GoGtpEngine::CmdFinalScore, this);
00140         Register("genmove", &GoGtpEngine::CmdGenMove, this);
00141         Register("go_clock", &GoGtpEngine::CmdClock, this);
00142         Register("go_param_timecontrol", &GoGtpEngine::CmdParamTimecontrol,
00143                  this);
00144         Register("reg_genmove", &GoGtpEngine::CmdRegGenMove, this);
00145         Register("reg_genmove_toplay", &GoGtpEngine::CmdRegGenMoveToPlay,
00146                  this);
00147         Register("time_lastmove", &GoGtpEngine::CmdTimeLastMove, this);
00148         m_bookCommands.Register(*this);
00149     }
00150     if (! noHandicap)
00151     {
00152         Register("fixed_handicap", &GoGtpEngine::CmdFixedHandicap, this);
00153         Register("place_free_handicap", &GoGtpEngine::CmdPlaceFreeHandicap,
00154                  this);
00155         Register("set_free_handicap", &GoGtpEngine::CmdSetFreeHandicap, this);
00156     }
00157 }
00158 
00159 GoGtpEngine::~GoGtpEngine()
00160 {
00161 #ifndef NDEBUG
00162     m_player = 0;
00163 #endif
00164 }
00165 
00166 /** Add player name property to root node. */
00167 void GoGtpEngine::AddPlayerProp(SgBlackWhite color, const string& name,
00168                                 bool overwrite)
00169 {
00170     SgNode& root = GetGame().Root();
00171     SgPropID SG_PROP_PLAYER =
00172         (color == SG_BLACK ? SG_PROP_PLAYER_BLACK : SG_PROP_PLAYER_WHITE);
00173     if (overwrite || ! root.HasProp(SG_PROP_PLAYER))
00174         root.SetStringProp(SG_PROP_PLAYER, name);
00175 }
00176 
00177 void GoGtpEngine::AddPlayStatistics()
00178 {
00179     // Default implementation does nothing
00180 }
00181 
00182 void GoGtpEngine::AddStatistics(const std::string& key,
00183                                 const std::string& value)
00184 {
00185     SG_ASSERT(m_statisticsValues.size() == m_statisticsSlots.size());
00186     if (value.find('\t') != string::npos)
00187         throw SgException("GoGtpEngine::AddStatistics: value contains tab: '"
00188                           + value + "'");
00189     for (size_t i = 0; i < m_statisticsSlots.size(); ++i)
00190         if (m_statisticsSlots[i] == key)
00191         {
00192             m_statisticsValues[i] = value;
00193             return;
00194         }
00195     throw SgException("GoGtpEngine::AddStatistics: invalid key '" + key
00196                       + "'");
00197 }
00198 
00199 void GoGtpEngine::ApplyTimeSettings()
00200 {
00201     SG_ASSERT(Board().MoveNumber() == 0);
00202     if (m_timeSettings.NoTimeLimits())
00203         return;
00204     GoGame& game = GetGame();
00205     SgTimeRecord& time = game.Time();
00206     SgNode& node = *game.CurrentNode();
00207     int mainTime = m_timeSettings.MainTime();
00208     time.SetOTPeriod(m_timeSettings.ByoYomiTime());
00209     time.SetOTNumMoves(m_timeSettings.ByoYomiStones());
00210     time.SetOverhead(m_overhead);
00211     time.SetClock(node, SG_BLACK, mainTime);
00212     time.SetClock(node, SG_WHITE, mainTime);
00213     SgNode& root = game.Root();
00214     if (mainTime > 0)
00215         root.Add(new SgPropTime(SG_PROP_TIME, mainTime));
00216     root.SetIntProp(SG_PROP_OT_NU_MOVES, m_timeSettings.ByoYomiStones());
00217     root.Add(new SgPropTime(SG_PROP_OT_PERIOD, m_timeSettings.ByoYomiTime()));
00218     game.TurnClockOn(true);
00219 }
00220 
00221 void GoGtpEngine::AutoSave() const
00222 {
00223     if (! m_autoSave)
00224         return;
00225     try
00226     {
00227         SaveGame(m_autoSaveFileName);
00228     }
00229     catch (const GtpFailure& failure)
00230     {
00231         SgWarning() << failure.Response() << '\n';
00232     }
00233 }
00234 
00235 void GoGtpEngine::BoardChanged()
00236 {
00237     GoBoard& bd = Board();
00238     if (m_autoShowBoard)
00239         SgDebug() << bd;
00240     AutoSave();
00241 }
00242 
00243 void GoGtpEngine::BeforeHandleCommand()
00244 {
00245     SgSetUserAbort(false);
00246     SgDebug() << flush;
00247 }
00248 
00249 void GoGtpEngine::BeforeWritingResponse()
00250 {
00251     SgDebug() << flush;
00252 }
00253 
00254 void GoGtpEngine::CheckBoardEmpty() const
00255 {
00256     const GoBoard& bd = Board();
00257     if (bd.TotalNumStones(SG_BLACK) + bd.TotalNumStones(SG_WHITE) > 0)
00258         throw GtpFailure("board is not empty");
00259 }
00260 
00261 /** Check if move is legal.
00262     @param message Prefix for error message; move and reason will be appended
00263     @param color Player of move
00264     @param move The move
00265     @param checkOnlyOccupied Only check if point is empty (accepts moves that
00266     are illegal, because of the ko or suicide rules used)
00267     @throws GtpFailure if not legal.
00268 */
00269 void GoGtpEngine::CheckLegal(string message, SgBlackWhite color, SgPoint move,
00270                              bool checkOnlyOccupied)
00271 {
00272     GoBoard& bd = Board();
00273     bool illegal = false;
00274     string reason = "";
00275     if (move != SG_PASS)
00276     {
00277         if (bd.Occupied(move))
00278         {
00279             illegal = true;
00280             reason = " (occupied)";
00281         }
00282         else if (! checkOnlyOccupied)
00283         {
00284             bd.Play(move, color);
00285             GoMoveInfo moveInfo = bd.GetLastMoveInfo();
00286             bd.Undo();
00287             if (moveInfo.test(GO_MOVEFLAG_ILLEGAL))
00288             {
00289                 illegal = true;
00290                 if (moveInfo.test(GO_MOVEFLAG_SUICIDE))
00291                     reason = " (suicide)";
00292                 else if (moveInfo.test(GO_MOVEFLAG_REPETITION))
00293                 {
00294                     reason =
00295                         " (" + KoRuleToString(bd.Rules().GetKoRule()) + ")";
00296                 }
00297             }
00298         }
00299     }
00300     if (illegal)
00301     {
00302         int moveNumber = GetGame().CurrentMoveNumber() + 1;
00303         throw GtpFailure() << message << moveNumber << ' ' << SgBW(color)
00304                            << ' ' << SgWritePoint(move) << reason;
00305     }
00306 }
00307 
00308 void GoGtpEngine::CheckMaxClearBoard()
00309 {
00310     if (m_maxClearBoard >= 0 && m_numberClearBoard > m_maxClearBoard - 1)
00311         throw GtpFailure() << "maximum number of " << m_maxClearBoard
00312                            << " reached";
00313     ++m_numberClearBoard;
00314 }
00315 
00316 /** Return all legal move points.
00317     Compatible with GNU Go's all_legal command.<br>
00318     Arguments: color
00319 */
00320 void GoGtpEngine::CmdAllLegal(GtpCommand& cmd)
00321 {
00322     cmd.CheckNuArg(1);
00323     SgBlackWhite color = BlackWhiteArg(cmd, 0);
00324     SgVector<SgPoint> allLegal;
00325     for (GoBoard::Iterator p(Board()); p; ++p)
00326         if (Board().IsLegal(*p, color))
00327             allLegal.PushBack(*p);
00328     cmd << SgWritePointList(allLegal, "", false);
00329 }
00330 
00331 /** Like GNU Go's all_move_values */
00332 void GoGtpEngine::CmdAllMoveValues(GtpCommand& cmd)
00333 {
00334     cmd.CheckArgNone();
00335     GoBoard& bd = Board();
00336     GoPlayer& player = Player();
00337     for (GoBoard::Iterator it(bd); it; ++it)
00338         if (! bd.Occupied(*it))
00339         {
00340             int value = player.MoveValue(*it);
00341             if (value > numeric_limits<int>::min())
00342                 cmd << SgWritePoint(*it) << ' ' << value << '\n';
00343         }
00344 }
00345 
00346 /** Return configuration for GoGui analyze commands.
00347     See the GoGui documentation http://gogui.sf.net
00348 */
00349 void GoGtpEngine::CmdAnalyzeCommands(GtpCommand& cmd)
00350 {
00351     cmd.CheckArgNone();
00352     cmd <<
00353         "hpstring/Go Board/go_board\n"
00354         "param/Go Param/go_param\n"
00355         "param/Go Param Rules/go_param_rules\n"
00356         "hpstring/Go Point Info/go_point_info %p\n"
00357         "sboard/Go Point Numbers/go_point_numbers\n"
00358         "none/Go Rules/go_rules %s\n"
00359         "plist/All Legal/all_legal %c\n"
00360         "string/ShowBoard/showboard\n"
00361         "string/CpuTime/cputime\n"
00362         "string/Get Komi/get_komi\n"
00363         "string/Get Random Seed/get_random_seed\n"
00364         "plist/List Stones/list_stones %c\n"
00365         "none/Set Random Seed/set_random_seed %s\n"
00366         "none/SaveSgf/savesgf %w\n";
00367     m_sgCommands.AddGoGuiAnalyzeCommands(cmd);
00368     if (! m_noPlayer)
00369     {
00370         m_bookCommands.AddGoGuiAnalyzeCommands(cmd);
00371         cmd <<
00372             "pspairs/All Move Values/all_move_values\n"
00373             "string/Final Score/final_score\n"
00374             "param/Go Param TimeControl/go_param_timecontrol\n"
00375             "varc/Reg GenMove/reg_genmove %c\n";
00376     }
00377 }
00378 
00379 /** Print some information about game board.
00380     See WriteBoardInfo for optional arguments.
00381 */
00382 void GoGtpEngine::CmdBoard(GtpCommand& cmd)
00383 {
00384     WriteBoardInfo(cmd, Board());
00385 }
00386 
00387 /** Init new game with given board size. */
00388 void GoGtpEngine::CmdBoardSize(GtpCommand& cmd)
00389 {
00390     cmd.CheckNuArg(1);
00391     int size = cmd.IntArg(0, SG_MIN_SIZE, SG_MAX_SIZE);
00392     if (m_fixedBoardSize > 0 && size != m_fixedBoardSize)
00393         throw GtpFailure() << "Boardsize " << m_fixedBoardSize << " fixed";
00394     if (Board().MoveNumber() > 0)
00395         GameFinished();
00396     Init(size);
00397 }
00398 
00399 /** Init new game.
00400     @see SetMaxGames()
00401 */
00402 void GoGtpEngine::CmdClearBoard(GtpCommand& cmd)
00403 {
00404     cmd.CheckArgNone();
00405     CheckMaxClearBoard();
00406     if (! m_sentinelFile.empty() && exists(m_sentinelFile))
00407         throw GtpFailure() << "Detected sentinel file '"
00408                            << m_sentinelFile.native_file_string() << "'";
00409     if (Board().MoveNumber() > 0)
00410         GameFinished();
00411     Init(Board().Size());
00412     if (m_player != 0)
00413         m_player->OnNewGame();
00414     BoardChanged();
00415 }
00416 
00417 /** Show clock info from GoGameRecord::Time() */
00418 void GoGtpEngine::CmdClock(GtpCommand& cmd)
00419 {
00420     cmd.CheckArgNone();
00421     cmd << '\n' << GetGame().Time();
00422 }
00423 
00424 /** Compute final score.
00425     Computes a final score only, if Tromp-Taylor rules are used
00426     (GoRules::CaptureDead() == true and GoRules::JapaneseScoring() == false).
00427     Otherwise it returns an error. Override this function for players that
00428     have enough knowledge to do a better scoring.
00429 */
00430 void GoGtpEngine::CmdFinalScore(GtpCommand& cmd)
00431 {
00432     cmd.CheckArgNone();
00433     const GoBoard& bd = Board();
00434     if (! bd.Rules().CaptureDead() || bd.Rules().JapaneseScoring())
00435         throw GtpFailure("can only score if Tromp-Taylor rules");
00436     float komi = bd.Rules().Komi().ToFloat();
00437     float score = GoBoardUtil::TrompTaylorScore(bd, komi);
00438     cmd << GoUtil::ScoreToString(score);
00439 }
00440 
00441 /** Standard GTP command fixed_handicap. */
00442 void GoGtpEngine::CmdFixedHandicap(GtpCommand& cmd)
00443 {
00444     int n = cmd.IntArg(0, 2);
00445     int size = Board().Size();
00446     SgVector<SgPoint> stones = GoGtpCommandUtil::GetHandicapStones(size, n);
00447     PlaceHandicap(stones);
00448 }
00449 
00450 /** Generate and play a move. */
00451 void GoGtpEngine::CmdGenMove(GtpCommand& cmd)
00452 {
00453     cmd.CheckNuArg(1);
00454     SgBlackWhite color = BlackWhiteArg(cmd, 0);
00455     GoGame& game = GetGame();
00456     auto_ptr<SgDebugToString> debugStrToString;
00457     if (m_debugToComment)
00458         debugStrToString.reset(new SgDebugToString(true));
00459     SgPoint move = GenMove(color, false);
00460     if (move == SG_RESIGN)
00461     {
00462         cmd << "resign";
00463         SgNode* node = game.AddResignNode(color);
00464         if (debugStrToString.get() != 0)
00465         {
00466             node->AddComment("\n\n");
00467             node->AddComment(debugStrToString->GetString());
00468         }
00469         AutoSave();
00470     }
00471     else
00472     {
00473         SgNode* node = game.AddMove(move, color);
00474         if (debugStrToString.get() != 0)
00475             node->AddComment(debugStrToString->GetString());
00476         game.GoToNode(node);
00477         BoardChanged();
00478         cmd << SgWritePoint(move);
00479     }
00480     AddPlayerProp(color, Player().Name(), false);
00481 }
00482 
00483 /** Generate cleanup move.
00484     As defined in the kgsGtp interface to the KGS Go server.
00485     Should not return pass, before all enemy dead stones are captured.<br>
00486     Arguments: color
00487     @bug This does not work, if the opponent passes, before he captured all
00488     of our dead stones, because then we could also pass without capturing all
00489     of his dead stones (if it is a win accosing to Tromp-Taylor counting),
00490     but KGS will not use Tromp-Taylor counting in the cleanup phase, but
00491     send another <tt>final_status_list dead</tt> command. See also bug
00492     2157832 int the bug tracker.
00493 */
00494 void GoGtpEngine::CmdGenMoveCleanup(GtpCommand& cmd)
00495 {
00496     GoRules& rules = Board().Rules();
00497     bool oldCaptureDead = rules.CaptureDead();
00498     rules.SetCaptureDead(true);
00499     RulesChanged();
00500     try
00501     {
00502         CmdGenMove(cmd);
00503     }
00504     catch (const GtpFailure& failure)
00505     {
00506         rules.SetCaptureDead(oldCaptureDead);
00507         RulesChanged();
00508         throw failure;
00509     }
00510     rules.SetCaptureDead(oldCaptureDead);
00511     RulesChanged();
00512 }
00513 
00514 /** Get the komi.
00515     Compatible to GNU Go's get_komi.
00516 */
00517 void GoGtpEngine::CmdGetKomi(GtpCommand& cmd)
00518 {
00519     cmd.CheckArgNone();
00520     cmd << Board().Rules().Komi();
00521 }
00522 
00523 /** Undo multiple moves.
00524     Extension command introduced by GNU Go to undo multiple moves.<br>
00525     Arguments: optional int<br>
00526     Fails: if move history is too short<br>
00527     Returns: nothing
00528  */
00529 void GoGtpEngine::CmdGGUndo(GtpCommand& cmd)
00530 {
00531     cmd.CheckNuArgLessEqual(1);
00532     Undo(cmd.NuArg() == 0 ? 0 : cmd.IntArg(0, 0));
00533     BoardChanged();
00534 }
00535 
00536 /** Sets time settings on kgs.
00537     Handles the four different kinds of time control on kgs, "none",
00538     "absolute", "byoyomi" (which is not currently fully supported),
00539     and "canadian".
00540  */
00541 void GoGtpEngine::CmdKgsTimeSettings(GtpCommand& cmd)
00542 {
00543     if (cmd.NuArg() < 1)
00544         throw GtpFailure("Need at least one argument!");
00545     if (Board().MoveNumber() > 0)
00546         throw GtpFailure("cannot change time settings during game");
00547     std::string type = cmd.Arg(0);
00548     if (type == "none")
00549     {
00550         cmd.CheckNuArg(1);
00551         // This will make m_timeSetings.NoTimeLimits() to true.
00552         m_timeSettings = GoGtpTimeSettings(0, 1, 0);
00553         SG_ASSERT(m_timeSettings.NoTimeLimits());
00554     }
00555     else if (type == "absolute")
00556     {
00557         cmd.CheckNuArg(2);
00558         int mainTime = cmd.IntArg(1, 0);
00559         GoGtpTimeSettings timeSettings(mainTime, 0, 0);
00560         if (m_timeSettings == timeSettings)
00561             return;
00562         m_timeSettings = timeSettings;
00563         ApplyTimeSettings();
00564     }
00565     else if (type == "byoyomi")
00566     {
00567         cmd.CheckNuArg(4);
00568         // FIXME: not fully supported yet!
00569         int mainTime = cmd.IntArg(1, 0);
00570         int byoYomiTime = cmd.IntArg(2, 0);
00571         //int byoYomiPeriods = cmd.IntArg(3, 0);
00572         GoGtpTimeSettings timeSettings(mainTime, byoYomiTime, 1);
00573         if (m_timeSettings == timeSettings)
00574             return;
00575         m_timeSettings = timeSettings;
00576         ApplyTimeSettings();
00577     }    
00578     else if (type == "canadian")
00579     {
00580         cmd.CheckNuArg(4);
00581         int mainTime = cmd.IntArg(1, 0);
00582         int byoYomiTime = cmd.IntArg(2, 0);
00583         int byoYomiStones = cmd.IntArg(3, 0);
00584         GoGtpTimeSettings timeSettings(mainTime, byoYomiTime, byoYomiStones);
00585         if (m_timeSettings == timeSettings)
00586             return;
00587         m_timeSettings = timeSettings;
00588         ApplyTimeSettings();
00589     }
00590     else
00591         throw GtpFailure("Unknown type of time control");
00592 }
00593 
00594 /** This command indicates that commands can be interrupted using the GoGui
00595     convention.
00596     The command does nothing but indicate the ability to handle the
00597     special comment line <tt># interrupt</tt> used by GoGui.
00598     It is registered as a handler for @c gogui-interrupt.
00599 */
00600 void GoGtpEngine::CmdInterrupt(GtpCommand& cmd)
00601 {
00602     cmd.CheckArgNone();
00603 }
00604 
00605 /** Check if move is legal.
00606     Compatible with GNU Go's is_legal.
00607     Arguments: color move<br>
00608     Returns: 0/1
00609 */
00610 void GoGtpEngine::CmdIsLegal(GtpCommand& cmd)
00611 {
00612     cmd.CheckNuArg(2);
00613     SgBlackWhite color = BlackWhiteArg(cmd, 0);
00614     SgPoint move = MoveArg(cmd, 1);
00615     cmd << SgWriteBoolAsInt(Board().IsLegal(move, color));
00616 }
00617 
00618 /** Set the komi.
00619     GTP standard command.
00620 */
00621 void GoGtpEngine::CmdKomi(GtpCommand& cmd)
00622 {
00623     cmd.CheckNuArg(1);
00624     try
00625     {
00626         GoKomi komi(cmd.Arg(0));
00627         GetGame().Root().SetRealProp(SG_PROP_KOMI, komi.ToFloat(), 1);
00628         m_defaultRules.SetKomi(komi);
00629         Board().Rules().SetKomi(komi);
00630         RulesChanged();
00631     }
00632     catch (const GoKomi::InvalidKomi& e)
00633     {
00634         throw GtpFailure(e.what());
00635     }
00636 }
00637 
00638 /** List stones on board.
00639     Mainly useful for regression tests to verify the board position.
00640     For compatibility with GNU Go's list_stones command, the points are
00641     returned in a single line in the same order that is used by GNU Go 3.6
00642     (A19, B19, ..., A18, B18, ...)
00643 
00644     Arguments: color<br>
00645     Returns: List of stones<br>
00646 */
00647 void GoGtpEngine::CmdListStones(GtpCommand& cmd)
00648 {
00649     cmd.CheckNuArg(1);
00650     SgBlackWhite color = BlackWhiteArg(cmd, 0);
00651     const GoBoard& bd = Board();
00652     const SgPointSet& points = bd.All(color);
00653     bool isFirst = true;
00654     for (int row = bd.Size(); row >= 1; --row)
00655         for (int col = 1; col <= bd.Size();++col)
00656         {
00657             SgPoint p = Pt(col, row);
00658             if (points.Contains(p))
00659             {
00660                 if (! isFirst)
00661                     cmd << ' ';
00662                 cmd << SgWritePoint(p);
00663                 isFirst = false;
00664             }
00665         }
00666 }
00667 
00668 /** Load a position from a SGF file. */
00669 void GoGtpEngine::CmdLoadSgf(GtpCommand& cmd)
00670 {
00671     cmd.CheckNuArgLessEqual(2);
00672     string fileName = cmd.Arg(0);
00673     int moveNumber = -1;
00674     if (cmd.NuArg() == 2)
00675         moveNumber = cmd.IntArg(1, 1);
00676     ifstream in(fileName.c_str());
00677     if (! in)
00678         throw GtpFailure("could not open file");
00679     SgGameReader reader(in);
00680     SgNode* root = reader.ReadGame();
00681     if (root == 0)
00682         throw GtpFailure("no games in file");
00683     if (reader.GetWarnings().any())
00684     {
00685         SgWarning() << fileName << ":\n";
00686         reader.PrintWarnings(SgDebug());
00687     }
00688     if (Board().MoveNumber() > 0)
00689         GameFinished();
00690     GoGame& game = GetGame();
00691     game.Init(root, true, false);
00692     if (! GoGameUtil::GotoBeforeMove(&game, moveNumber))
00693         throw GtpFailure("invalid move number");
00694     GoRules& rules = Board().Rules();
00695     rules = m_defaultRules;
00696     rules.SetKomi(GoNodeUtil::GetKomi(game.CurrentNode()));
00697     rules.SetHandicap(GoNodeUtil::GetHandicap(game.CurrentNode()));
00698     RulesChanged();
00699     if (m_player != 0)
00700         m_player->OnNewGame();
00701     BoardChanged();
00702 }
00703 
00704 /** Return name of player, if set, GtpEngine::Name otherwise. */
00705 void GoGtpEngine::CmdName(GtpCommand& cmd)
00706 {
00707     cmd.CheckArgNone();
00708     if (m_player == 0)
00709         GtpEngine::CmdName(cmd);
00710     else
00711         cmd << m_player->Name();
00712 }
00713 
00714 /** Get and set GoGtpEngine parameters.
00715     Parameters:
00716     @arg @c auto_save See SetAutoSave()
00717     @arg @c accept_illegal Accept illegal ko or suicide moves in CmdPlay()
00718     @arg @c debug_to_comment See SetDebugToComment()
00719     @arg @c overhead See SgTimeRecord::SetOverhead()
00720     @arg @c statistics_file See SetStatisticsFile()
00721     @arg @c timelimit See TimeLimit()
00722 */
00723 void GoGtpEngine::CmdParam(GtpCommand& cmd)
00724 {
00725     cmd.CheckNuArgLessEqual(2);
00726     if (cmd.NuArg() == 0)
00727     {
00728         cmd << "[bool] accept_illegal " << m_acceptIllegal << '\n'
00729             << "[bool] debug_to_comment " << m_debugToComment << '\n'
00730             << "[string] auto_save " << (m_autoSave ? m_autoSavePrefix : "")
00731             << '\n'
00732             << "[string] overhead " << m_overhead << '\n'
00733             << "[string] statistics_file " << m_statisticsFile << '\n'
00734             << "[string] timelimit " << m_timeLimit << '\n';
00735     }
00736     else if (cmd.NuArg() >= 1 && cmd.NuArg() <= 2)
00737     {
00738         string name = cmd.Arg(0);
00739         if (name == "accept_illegal")
00740             m_acceptIllegal = cmd.BoolArg(1);
00741         else if (name == "debug_to_comment")
00742             m_debugToComment = cmd.BoolArg(1);
00743         else if (name == "auto_save")
00744         {
00745             string prefix = cmd.RemainingLine(0);
00746             if (prefix == "")
00747                 m_autoSave = false;
00748             else
00749                 SetAutoSave(prefix);
00750         }
00751         else if (name == "overhead")
00752         {
00753             m_overhead = cmd.FloatArg(1);
00754             GetGame().Time().SetOverhead(m_overhead);
00755         }
00756         else if (name == "statistics_file")
00757             SetStatisticsFile(cmd.RemainingLine(0));
00758         else if (name == "timelimit")
00759             m_timeLimit = cmd.FloatArg(1);
00760         else
00761             throw GtpFailure() << "unknown parameter: " << name;
00762     }
00763     else
00764         throw GtpFailure() << "need 0 or 2 arguments";
00765 }
00766 
00767 /** Get and set detailed rule parameters.
00768     Changes the rules in the current game as well as the default rule.
00769 
00770     Parameters:
00771     @arg @c allow_suicde See GoRules:AllowSuicide()
00772     @arg @c capture_dead See GoRules:CaptureDead()
00773     @arg @c extra_handicap_komi See GoRules:ExtraHandicapKomi()
00774     @arg @c japanese_scoring See GoRules:JapaneseScoring()
00775     @arg @c two_passes_end_game See GoRules:TwoPassesEndGame()
00776     @arg @c ko_rule (simple, superko, pos_superko) See GoRules:KoRule()
00777 */
00778 void GoGtpEngine::CmdParamRules(GtpCommand& cmd)
00779 {
00780     cmd.CheckNuArgLessEqual(2);
00781     GoRules& r = Board().Rules();
00782     if (cmd.NuArg() == 0)
00783     {
00784         cmd << "[bool] allow_suicide "
00785             << SgWriteBoolAsInt(r.AllowSuicide()) << '\n'
00786             << "[bool] capture_dead "
00787             << SgWriteBoolAsInt(r.CaptureDead()) << '\n'
00788             << "[bool] extra_handicap_komi "
00789             << SgWriteBoolAsInt(r.ExtraHandicapKomi()) << '\n'
00790             << "[bool] japanese_scoring "
00791             << SgWriteBoolAsInt(r.JapaneseScoring()) << '\n'
00792             << "[bool] two_passes_end_game "
00793             << SgWriteBoolAsInt(r.TwoPassesEndGame()) << '\n'
00794             << "[list/simple/superko/pos_superko] ko_rule "
00795             << KoRuleToString(r.GetKoRule()) << '\n';
00796     }
00797     else if (cmd.NuArg() == 2)
00798     {
00799         string name = cmd.Arg(0);
00800         if (name == "allow_suicide")
00801         {
00802             r.SetAllowSuicide(cmd.BoolArg(1));
00803             m_defaultRules.SetAllowSuicide(cmd.BoolArg(1));
00804         }
00805         else if (name == "capture_dead")
00806         {
00807             r.SetCaptureDead(cmd.BoolArg(1));
00808             m_defaultRules.SetCaptureDead(cmd.BoolArg(1));
00809         }
00810         else if (name == "extra_handicap_komi")
00811         {
00812             r.SetExtraHandicapKomi(cmd.BoolArg(1));
00813             m_defaultRules.SetExtraHandicapKomi(cmd.BoolArg(1));
00814         }
00815         else if (name == "japanese_scoring")
00816         {
00817             r.SetJapaneseScoring(cmd.BoolArg(1));
00818             m_defaultRules.SetJapaneseScoring(cmd.BoolArg(1));
00819         }
00820         else if (name == "two_passes_end_game")
00821         {
00822             r.SetTwoPassesEndGame(cmd.BoolArg(1));
00823             m_defaultRules.SetTwoPassesEndGame(cmd.BoolArg(1));
00824         }
00825         else if (name == "ko_rule")
00826         {
00827             r.SetKoRule(KoRuleArg(cmd, 1));
00828             m_defaultRules.SetKoRule(KoRuleArg(cmd, 1));
00829         }
00830         else
00831             throw GtpFailure() << "unknown parameter: " << name;
00832         RulesChanged();
00833     }
00834     else
00835         throw GtpFailure() << "need 0 or 2 arguments";
00836 }
00837 
00838 /** Get and set time control parameters.
00839     Fails if the current player is not a SgObjectWithDefaultTimeControl
00840     or the time control is not a GoTimeControl.
00841 
00842     Parameters:
00843     @arg @c fast_open_factor See SgDefaultTimeControl::FastOpenFactor()
00844     @arg @c fast_open_moves See SgDefaultTimeControl::FastOpenMoves()
00845     @arg @c final_space See GoTimeControl::FinalSpace()
00846     @arg @c remaining_constant See SgDefaultTimeControl::RemainingConstant()
00847 */
00848 void GoGtpEngine::CmdParamTimecontrol(GtpCommand& cmd)
00849 {
00850     SgObjectWithDefaultTimeControl* object =
00851         dynamic_cast<SgObjectWithDefaultTimeControl*>(&Player());
00852     if (object == 0)
00853         throw GtpFailure("current player is not a "
00854                          "SgObjectWithDefaultTimeControl");
00855     GoTimeControl* c = dynamic_cast<GoTimeControl*>(&object->TimeControl());
00856     if (c == 0)
00857         throw GtpFailure("current player does not have a GoTimeControl");
00858     cmd.CheckNuArgLessEqual(2);
00859     if (cmd.NuArg() == 0)
00860     {
00861         cmd << "fast_open_factor " << c->FastOpenFactor() << '\n'
00862             << "fast_open_moves " << c->FastOpenMoves() << '\n'
00863             << "final_space " << c->FinalSpace() << '\n'
00864             << "remaining_constant " << c->RemainingConstant() << '\n';
00865     }
00866     else if (cmd.NuArg() == 2)
00867     {
00868         string name = cmd.Arg(0);
00869         if (name == "fast_open_factor")
00870             c->SetFastOpenFactor(cmd.FloatArg(1));
00871         else if (name == "fast_open_moves")
00872             c->SetFastOpenMoves(cmd.IntArg(1, 0));
00873         else if (name == "final_space")
00874             c->SetFinalSpace(max(cmd.FloatArg(1), 0.));
00875         else if (name == "remaining_constant")
00876             c->SetRemainingConstant(max(cmd.FloatArg(1), 0.));
00877         else
00878             throw GtpFailure() << "unknown parameter: " << name;
00879     }
00880     else
00881         throw GtpFailure() << "need 0 or 2 arguments";
00882 }
00883 
00884 /** Standard GTP command place_free_handicap.
00885     The current implementation uses the same locations as for fixed_handicap,
00886     if defined, and generates additional handicap locations by making the
00887     player play moves.
00888 
00889     Arguments: number of handicap stones <br>
00890     Effect: Places handicap stones at chosen locations <br>
00891     Returns: Handicap stone locations <br>
00892 */
00893 void GoGtpEngine::CmdPlaceFreeHandicap(GtpCommand& cmd)
00894 {
00895     CheckBoardEmpty();
00896     int n = cmd.IntArg(0, 2);
00897     int size = Board().Size();
00898     SgVector<SgPoint> stones;
00899     try
00900     {
00901         stones = GoGtpCommandUtil::GetHandicapStones(size, n);
00902     }
00903     catch (const GtpFailure&)
00904     {
00905     }
00906     if (stones.Length() < n && m_player != 0)
00907     {
00908         // 9 handicap are always defined for odd sizes >= 9
00909         if (n >= 9 && size % 2 != 0 && size >= 9 && size <= 25)
00910             stones = GoGtpCommandUtil::GetHandicapStones(size, 9);
00911         // 4 handicap are always defined for even sizes >= 8 and size 7
00912         else if (n >= 4 && size % 2 == 0 && (size >= 8 || size == 7)
00913                  && size <= 25)
00914             stones = GoGtpCommandUtil::GetHandicapStones(size, 4);
00915         SgDebug() << "GoGtpEngine: Generating missing handicap\n";
00916         GoSetup setup;
00917         for (SgVectorIterator<SgPoint> it(stones); it; ++it)
00918             setup.AddBlack(*it);
00919         GoBoard& playerBd = m_player->Board();
00920         playerBd.Init(playerBd.Size(), setup);
00921         for (int i = stones.Length(); i < n; ++i)
00922         {
00923             SgPoint p = GenMove(SG_BLACK, true);
00924             SgDebug() << "GoGtpEngine: " << i << ' ' << SgWritePoint(p)
00925                       << '\n';
00926             if (p == SG_PASS)
00927                 break;
00928             playerBd.Play(p, SG_BLACK);
00929             stones.PushBack(p);
00930         }
00931     }
00932     SG_ASSERT(stones.Length() <= n); // less than n is allowed by GTP standard
00933     PlaceHandicap(stones);
00934     cmd << SgWritePointList(stones, "", false);
00935 }
00936 
00937 /** Play a move. */
00938 void GoGtpEngine::CmdPlay(GtpCommand& cmd)
00939 {
00940     cmd.CheckNuArg(2);
00941     SgBlackWhite color = BlackWhiteArg(cmd, 0);
00942     SgPoint move = MoveArg(cmd, 1);
00943     Play(color, move);
00944     BoardChanged();
00945 }
00946 
00947 /** Play a sequence of moves.
00948     Extension to standard play command used by GoGui.
00949 
00950     This command is registered with the command name @c gogui-play_sequence
00951     as used in newer versions of GoGui and for a transition period also
00952     with @c play_sequence as used by older versions of GoGui
00953 */
00954 void GoGtpEngine::CmdPlaySequence(GtpCommand& cmd)
00955 {
00956     SgNode* oldCurrentNode = GetGame().CurrentNode();
00957     try
00958     {
00959         for (size_t i = 0; i < cmd.NuArg(); i += 2)
00960             Play(BlackWhiteArg(cmd, i), MoveArg(cmd, i + 1));
00961     }
00962     catch (GtpFailure fail)
00963     {
00964         GetGame().GoToNode(oldCurrentNode);
00965         throw fail;
00966     }
00967     BoardChanged();
00968 }
00969 
00970 /** Print some information about point. */
00971 void GoGtpEngine::CmdPointInfo(GtpCommand& cmd)
00972 {
00973     SgPoint p = PointArg(cmd);
00974     const GoBoard& bd = Board();
00975     cmd << "Point:\n"
00976         << SgWriteLabel("Color") << SgEBW(bd.GetColor(p)) << '\n'
00977         << SgWriteLabel("InCenter") << bd.InCenter(p) << '\n'
00978         << SgWriteLabel("InCorner") << bd.InCorner(p) << '\n'
00979         << SgWriteLabel("Line") << bd.Line(p) << '\n'
00980         << SgWriteLabel("OnEdge") << bd.OnEdge(p) << '\n'
00981         << SgWriteLabel("EmptyNb") << bd.NumEmptyNeighbors(p) << '\n'
00982         << SgWriteLabel("EmptyNb8") << bd.Num8EmptyNeighbors(p) << '\n'
00983         << SgWriteLabel("Pos") << bd.Pos(p) << '\n';
00984     if (bd.Occupied(p))
00985     {
00986         SgVector<SgPoint> adjBlocks;
00987         GoBoardUtil::AdjacentBlocks(bd, p, SG_MAXPOINT, &adjBlocks);
00988         cmd << "Block:\n"
00989             << SgWritePointList(adjBlocks, "AdjBlocks", true)
00990             << SgWriteLabel("Anchor") << SgWritePoint(bd.Anchor(p)) << '\n'
00991             << SgWriteLabel("InAtari") << bd.InAtari(p) << '\n'
00992             << SgWriteLabel("IsSingleStone") << bd.IsSingleStone(p) << '\n'
00993             << SgWriteLabel("Liberties") << bd.NumLiberties(p) << '\n'
00994             << SgWriteLabel("Stones") << bd.NumStones(p) << '\n';
00995     }
00996     else
00997         cmd << "EmptyPoint:\n"
00998             << SgWriteLabel("IsFirst") << bd.IsFirst(p) << '\n'
00999             << SgWriteLabel("IsLegal/B") << bd.IsLegal(p, SG_BLACK) << '\n'
01000             << SgWriteLabel("IsLegal/W") << bd.IsLegal(p, SG_WHITE) << '\n'
01001             << SgWriteLabel("IsSuicide") << bd.IsSuicide(p) << '\n'
01002             << SgWriteLabel("MakesNakadeShape/B") 
01003             << GoEyeUtil::MakesNakadeShape(bd, p, SG_BLACK) << '\n'
01004             << SgWriteLabel("MakesNakadeShape/W") 
01005             << GoEyeUtil::MakesNakadeShape(bd, p, SG_WHITE) << '\n'
01006             << SgWriteLabel("IsSimpleEye/B") 
01007             << GoEyeUtil::IsSimpleEye(bd, p, SG_BLACK) << '\n'
01008             << SgWriteLabel("IsSimpleEye/W") 
01009             << GoEyeUtil::IsSimpleEye(bd, p, SG_WHITE) << '\n'
01010             << SgWriteLabel("IsSinglePointEye/B") 
01011             << GoEyeUtil::IsSinglePointEye(bd, p, SG_BLACK) << '\n'
01012             << SgWriteLabel("IsSinglePointEye/W") 
01013             << GoEyeUtil::IsSinglePointEye(bd, p, SG_WHITE) << '\n'
01014             << SgWriteLabel("IsPossibleEye/B") 
01015             << GoEyeUtil::IsPossibleEye(bd, SG_BLACK, p) << '\n'
01016             << SgWriteLabel("IsPossibleEye/W") 
01017             << GoEyeUtil::IsPossibleEye(bd, SG_WHITE, p) << '\n'
01018             ;
01019 }
01020 
01021 /** Print some information about player board.
01022     See WriteBoardInfo for optional arguments.
01023 */
01024 void GoGtpEngine::CmdPlayerBoard(GtpCommand& cmd)
01025 {
01026     WriteBoardInfo(cmd, Player().Board());
01027 }
01028 
01029 /** Show point numbers used in GoBoard. */
01030 void GoGtpEngine::CmdPointNumbers(GtpCommand& cmd)
01031 {
01032     cmd.CheckArgNone();
01033     SgPointArray<int> array(0);
01034     for (GoBoard::Iterator p(Board()); p; ++p)
01035         array[*p] = *p;
01036     cmd << SgWritePointArray<int>(array, Board().Size());
01037 }
01038 
01039 void GoGtpEngine::CmdQuit(GtpCommand& cmd)
01040 {
01041     cmd.CheckArgNone();
01042     if (Board().MoveNumber() > 0)
01043         GameFinished();
01044     GtpEngine::CmdQuit(cmd);
01045 }
01046 
01047 /** Generate a move, but do not play it.
01048     Like in GNU Go, if there was a random seed set, it is initialized before
01049     each reg_genmove to avoid a dependency of the random numbers on previous
01050     move generations.
01051 */
01052 void GoGtpEngine::CmdRegGenMove(GtpCommand& cmd)
01053 {
01054     cmd.CheckNuArg(1);
01055     SgRandom::SetSeed(SgRandom::Seed());
01056     SgPoint move = GenMove(BlackWhiteArg(cmd, 0), true);
01057     if (move == SG_RESIGN)
01058         cmd << "resign";
01059     else
01060         cmd << SgWritePoint(move);
01061 }
01062 
01063 /** Version of CmdRegGenMove() without color argument.
01064     This is a non-standard version of reg_genmove without color argument.
01065     It generates a move for the color to play.
01066 
01067 */
01068 void GoGtpEngine::CmdRegGenMoveToPlay(GtpCommand& cmd)
01069 {
01070     cmd.CheckArgNone();
01071     SgRandom::SetSeed(SgRandom::Seed());
01072     SgPoint move = GenMove(Board().ToPlay(), true);
01073     cmd << SgWritePoint(move);
01074 }
01075 
01076 /** Set named rules.
01077     @see GoGtpEngine::SetNamedRules()
01078 */
01079 void GoGtpEngine::CmdRules(GtpCommand& cmd)
01080 {
01081     cmd.CheckNuArg(1);
01082     string arg = cmd.Arg(0);
01083     try
01084     {
01085         SetNamedRules(arg);
01086     }
01087     catch (const SgException&)
01088     {
01089         throw GtpFailure() << "unknown rules: " << arg;
01090     }
01091 }
01092 
01093 /** Save current game to file.
01094     Saves the complete game tree, including any trees from searches
01095     if storing searches is enabled with global flags.<br>
01096     Argument: filename
01097 */
01098 void GoGtpEngine::CmdSaveSgf(GtpCommand& cmd)
01099 {
01100     cmd.CheckNuArg(1);
01101     SaveGame(cmd.Arg(0));
01102 }
01103 
01104 /** Define a file that makes future clear_board commands fail.
01105     Defining a sentinel file can be used, for example, to abort playing on
01106     KGS, because <a href="http://www.gokgs.com/download.xhtml">kgsGtp.jar</a>
01107     quits, if a clear_board command fails. This command will remove the
01108     sentinel file, if it currently exists. Future invocations of clear_board
01109     will fail, if the sentinel file exists at that time. <br>
01110     Argument: filename
01111 */
01112 void GoGtpEngine::CmdSentinelFile(GtpCommand& cmd)
01113 {
01114     cmd.CheckNuArg(1);
01115     path sentinelFile = path(cmd.Arg(0));
01116     if (! sentinelFile.empty())
01117         try
01118         {
01119             remove(sentinelFile);
01120         }
01121         catch (const exception& e)
01122         {
01123             throw GtpFailure() << "could not remove sentinel file: "
01124                                << e.what();
01125         }
01126     m_sentinelFile = sentinelFile;
01127 }
01128 
01129 /** Standard GTP command for explicit placement of handicap stones.
01130     Arguments: list of points
01131  */
01132 void GoGtpEngine::CmdSetFreeHandicap(GtpCommand& cmd)
01133 {
01134     SgVector<SgPoint> stones = PointListArg(cmd);
01135     if (stones.RemoveDuplicates())
01136         throw GtpFailure("duplicate handicap stones not allowed");
01137     PlaceHandicap(stones);
01138 }
01139 
01140 /** Set game info property in root node of internal SGF tree.
01141     Arguments: info value (value is remaining line after gameinfo) <br>
01142     Supported infos:
01143     - game_name
01144     - player_black
01145     - player_white
01146     - result
01147 */
01148 void GoGtpEngine::CmdSetInfo(GtpCommand& cmd)
01149 {
01150     string key = cmd.Arg(0);
01151     string value = cmd.RemainingLine(0);
01152     SgNode& root = GetGame().Root();
01153     if (key == "game_name")
01154         root.SetStringProp(SG_PROP_GAME_NAME, value);
01155     else if (key == "player_black")
01156         AddPlayerProp(SG_BLACK, value, true);
01157     else if (key == "player_white")
01158         AddPlayerProp(SG_WHITE, value, true);
01159     else if (key == "result")
01160         root.SetStringProp(SG_PROP_RESULT, value);
01161     AutoSave();
01162 }
01163 
01164 /** Place setup stones.
01165     Command will be used by future versions of GoGui. <br>
01166     Argument: color point [color point ...] <br>
01167     With color: b, black, w, white
01168 */
01169 void GoGtpEngine::CmdSetup(GtpCommand& cmd)
01170 {
01171     const GoBoard& bd = Board();
01172     if (bd.MoveNumber() > 0)
01173         throw GtpFailure("setup only allowed on empty board");
01174     if (cmd.NuArg() % 2 != 0)
01175         throw GtpFailure("need even number of arguments");
01176     SgBWArray<SgPointSet> points;
01177     for (size_t i = 0; i < cmd.NuArg(); i += 2)
01178     {
01179         SgBlackWhite c = BlackWhiteArg(cmd, i);
01180         SgPoint p = PointArg(cmd, i + 1);
01181         for (SgBWIterator it; it; ++it)
01182             points[*it].Exclude(p);
01183         points[c].Include(p);
01184     }
01185     SgPropAddStone* addBlack = new SgPropAddStone(SG_PROP_ADD_BLACK);
01186     SgPropAddStone* addWhite = new SgPropAddStone(SG_PROP_ADD_WHITE);
01187     for (SgSetIterator it(points[SG_BLACK]); it; ++it)
01188         if (bd.GetColor(*it) != SG_BLACK)
01189             addBlack->PushBack(*it);
01190     for (SgSetIterator it(points[SG_WHITE]); it; ++it)
01191         if (bd.GetColor(*it) != SG_WHITE)
01192             addWhite->PushBack(*it);
01193     GoGame& game = GetGame();
01194     SgNode* node = game.CurrentNode()->NewRightMostSon();
01195     node->Add(addBlack);
01196     node->Add(addWhite);
01197     game.GoToNode(node);
01198     BoardChanged();
01199 }
01200 
01201 /** Set color to play.
01202     Command will be used by future versions of GoGui. <br>
01203     Argument: color <br>
01204 */
01205 void GoGtpEngine::CmdSetupPlayer(GtpCommand& cmd)
01206 {
01207     cmd.CheckNuArg(1);
01208     SgBlackWhite toPlay = BlackWhiteArg(cmd, 0);
01209     SgNode* node = GetGame().CurrentNode();
01210     node->Props().RemoveProp(SG_PROP_PLAYER);
01211     node->Add(new SgPropPlayer(SG_PROP_PLAYER, toPlay));
01212     Board().SetToPlay(toPlay);
01213     if (m_player != 0)
01214         m_player->UpdateSubscriber();
01215     BoardChanged();
01216 }
01217 
01218 /** Show current position. */
01219 void GoGtpEngine::CmdShowBoard(GtpCommand& cmd)
01220 {
01221     cmd.CheckArgNone();
01222     cmd << '\n' << Board();
01223 }
01224 
01225 /** Time of last ganmove command. */
01226 void GoGtpEngine::CmdTimeLastMove(GtpCommand& cmd)
01227 {
01228     cmd.CheckArgNone();
01229     cmd << setprecision(2) << m_timeLastMove;
01230 }
01231 
01232 /** Standard GTP command. */
01233 void GoGtpEngine::CmdTimeLeft(GtpCommand& cmd)
01234 {
01235     cmd.CheckNuArg(3);
01236     SgBlackWhite color = BlackWhiteArg(cmd, 0);
01237     // GTP draft 2 standard does not say if time left can be negative,
01238     // CGOS server sends negative time, but we replace a negative time by
01239     // zero (not sure, if SgTimeRecord::SetTimeLeft can handle negative times)
01240     int timeLeft = max(0, cmd.IntArg(1));
01241     int movesLeft = cmd.IntArg(2, 0);
01242     SgTimeRecord& time = GetGame().Time();
01243     time.SetTimeLeft(color, timeLeft);
01244     time.SetMovesLeft(color, movesLeft);
01245 }
01246 
01247 /** Standard GTP command. */
01248 void GoGtpEngine::CmdTimeSettings(GtpCommand& cmd)
01249 {
01250     cmd.CheckNuArg(3);
01251     int mainTime = cmd.IntArg(0, 0);
01252     int byoYomiTime = cmd.IntArg(1, 0);
01253     int byoYomiStones = cmd.IntArg(2, 0);
01254     GoGtpTimeSettings timeSettings(mainTime, byoYomiTime, byoYomiStones);
01255     if (m_timeSettings == timeSettings)
01256         return;
01257     if (Board().MoveNumber() > 0)
01258         throw GtpFailure("cannot change time settings during game");
01259     m_timeSettings = timeSettings;
01260     ApplyTimeSettings();
01261 }
01262 
01263 /** Undo a move. */
01264 void GoGtpEngine::CmdUndo(GtpCommand& cmd)
01265 {
01266     cmd.CheckArgNone();
01267     Undo(1);
01268     BoardChanged();
01269 }
01270 
01271 void GoGtpEngine::CheckMoveStackOverflow() const
01272 {
01273     const int RESERVE = 50;
01274     if (Board().MoveNumber() >= GO_MAX_NUM_MOVES - RESERVE)
01275         throw GtpFailure("too many moves");
01276     if (Board().StackOverflowLikely())
01277         throw GtpFailure("move stack overflow");
01278 }
01279 
01280 std::vector<std::string> GoGtpEngine::CreateStatisticsSlots()
01281 {
01282     return vector<string>();
01283 }
01284 
01285 SgBlackWhite GoGtpEngine::BlackWhiteArg(const GtpCommand& cmd,
01286                                         std::size_t number) const
01287 {
01288     return GoGtpCommandUtil::BlackWhiteArg(cmd, number);
01289 }
01290 
01291 void GoGtpEngine::CreateAutoSaveFileName()
01292 {
01293     time_t timeValue = time(0);
01294     struct tm* timeStruct = localtime(&timeValue);
01295     char timeBuffer[128];
01296     strftime(timeBuffer, sizeof(timeBuffer), "%Y%m%d%H%M%S", timeStruct);
01297     ostringstream fileName;
01298     fileName << m_autoSavePrefix << timeBuffer << ".sgf";
01299     m_autoSaveFileName = fileName.str();
01300 }
01301 
01302 void GoGtpEngine::DumpState(ostream& out) const
01303 {
01304     out << "GoGtpEngine board:\n";
01305     GoBoardUtil::DumpBoard(Board(), out);
01306     if (m_player != 0)
01307     {
01308         out << "GoPlayer board:\n";
01309         GoBoardUtil::DumpBoard(m_player->Board(), out);
01310     }
01311 }
01312 
01313 SgEmptyBlackWhite GoGtpEngine::EmptyBlackWhiteArg(const GtpCommand& cmd,
01314                                                   std::size_t number) const
01315 {
01316     return GoGtpCommandUtil::EmptyBlackWhiteArg(cmd, number);
01317 }
01318 
01319 SgPoint GoGtpEngine::EmptyPointArg(const GtpCommand& cmd,
01320                                    std::size_t number) const
01321 {
01322     return GoGtpCommandUtil::EmptyPointArg(cmd, number, Board());
01323 }
01324 
01325 /** Do what is necessary when a game is finished.
01326     Note that since GTP allows arbitrary state changes, it is not always
01327     clearly defined, if a game is played and when it is finished, but
01328     this function should at least be ensured to be called at the end of a
01329     game in the use case of playing a game or a series of games.
01330 */
01331 void GoGtpEngine::GameFinished()
01332 {
01333     if (m_player != 0)
01334         m_player->OnGameFinished();
01335 }
01336 
01337 SgPoint GoGtpEngine::GenMove(SgBlackWhite color, bool ignoreClock)
01338 {
01339     SG_ASSERT_BW(color);
01340     CheckMoveStackOverflow();
01341     StartStatistics();
01342     GoPlayer& player = Player();
01343     GoBoard& bd = Board();
01344     bd.SetToPlay(color);
01345     double startTime = SgTime::Get();
01346     SgTimeRecord time;
01347     if (ignoreClock || m_timeSettings.NoTimeLimits())
01348         time = SgTimeRecord(true, m_timeLimit);
01349     else
01350         time = GetGame().Time();
01351     AddStatistics("GAME", m_autoSaveFileName);
01352     AddStatistics("MOVE", GetGame().CurrentMoveNumber() + 1);
01353     SgPoint move = SG_NULLMOVE;
01354     if (m_autoBook.get() != 0)
01355     {
01356         SgDebug() << "GoGtpEngine: Checking AutoBook instead of book\n";
01357         move = m_autoBook->LookupMove(bd);
01358     }
01359     else 
01360         move = m_book.LookupMove(bd);
01361     m_mpiSynchronizer->SynchronizeMove(move);
01362     if (move != SG_NULLMOVE)
01363     {
01364         SgDebug() << "GoGtpEngine: Using move from opening book\n";
01365         AddStatistics("BOOK", 1);
01366     }
01367     else
01368         AddStatistics("BOOK", 0);
01369     if (move == SG_NULLMOVE)
01370         move = player.GenMove(time, color);
01371     m_mpiSynchronizer->SynchronizeMove(move);
01372     m_timeLastMove = SgTime::Get() - startTime;
01373     AddStatistics("TIME", m_timeLastMove);
01374     if (move == SG_NULLMOVE)
01375         throw GtpFailure() << player.Name() << " generated NULLMOVE";
01376     if (move != SG_RESIGN)
01377         CheckLegal(player.Name() + " generated illegal move: ", color, move,
01378                    false);
01379     AddPlayStatistics();
01380     SaveStatistics();
01381     return move;
01382 }
01383 
01384 void GoGtpEngine::Init(int size)
01385 {
01386     m_game.Init(size, m_defaultRules);
01387     time_t timeValue = time(0);
01388     struct tm* timeStruct = localtime(&timeValue);
01389     char dateBuffer[128];
01390     strftime(dateBuffer, sizeof(dateBuffer), "%Y-%m-%d", timeStruct);
01391     m_game.Root().SetStringProp(SG_PROP_DATE, dateBuffer);
01392     ApplyTimeSettings();
01393     CreateAutoSaveFileName();
01394 }
01395 
01396 void GoGtpEngine::InitStatistics()
01397 {
01398     m_statisticsSlots.clear();
01399     m_statisticsSlots.push_back("GAME");
01400     m_statisticsSlots.push_back("MOVE");
01401     m_statisticsSlots.push_back("TIME");
01402     m_statisticsSlots.push_back("BOOK");
01403     vector<string> slots = CreateStatisticsSlots();
01404     for (vector<string>::const_iterator i = slots.begin(); i != slots.end();
01405          ++i)
01406     {
01407         if (i->find('\t') != string::npos)
01408             throw SgException("GoGtpEngine::InitStatistics: statistics slot"
01409                               " contains tab: '" + (*i) + "'");
01410         if (find(m_statisticsSlots.begin(), m_statisticsSlots.end(), *i)
01411             != m_statisticsSlots.end())
01412             throw SgException("GoGtpEngine::InitStatistics: duplicate"
01413                               " statistics slot '" + (*i) + "'");
01414         m_statisticsSlots.push_back(*i);
01415     }
01416     if (MpiSynchronizer()->IsRootProcess())
01417     {
01418     ofstream out(m_statisticsFile.c_str(), ios::app);
01419     // TODO: What to do with an existing file? We want a single file, if
01420     // twogtp or Go server experiments are interrupted and restarted, but if
01421     // the file is from different player, the format is not compatible.
01422     // For now, we simple append to the file.
01423     out << '#'; // Start header line with a comment character
01424     for (size_t i = 0; i < m_statisticsSlots.size(); ++i)
01425     {
01426         out << m_statisticsSlots[i];
01427         if (i < m_statisticsSlots.size() - 1)
01428         out << '\t';
01429         else
01430         out << '\n';
01431     }
01432     }
01433 }
01434 
01435 SgPoint GoGtpEngine::MoveArg(const GtpCommand& cmd, std::size_t number) const
01436 {
01437     return GoGtpCommandUtil::MoveArg(cmd, number, Board());
01438 }
01439 
01440 void GoGtpEngine::PlaceHandicap(const SgVector<SgPoint>& stones)
01441 {
01442     CheckBoardEmpty();
01443     GoBoard& bd = Board();
01444     GoGame& game = GetGame();
01445     SgNode* node = game.CurrentNode();
01446     if (node->HasSon())
01447         node = game.CurrentNode()->NewRightMostSon();
01448     SgPropAddStone* addBlack = new SgPropAddStone(SG_PROP_ADD_BLACK);
01449     for (SgVectorIterator<SgPoint> it(stones); it; ++it)
01450         addBlack->PushBack(*it);
01451     node->Add(addBlack);
01452     SgPropInt* handicap = new SgPropInt(SG_PROP_HANDICAP, stones.Length());
01453     node->Add(handicap);
01454     bd.Rules().SetHandicap(stones.Length());
01455     RulesChanged();
01456     game.GoToNode(node);
01457     BoardChanged();
01458 }
01459 
01460 void GoGtpEngine::Play(SgBlackWhite color, SgPoint move)
01461 {
01462     CheckMoveStackOverflow();
01463     // Play "resign" is not allowed by the GTP standard (draft version 2),
01464     // but there could be controllers that send it (e.g. CGOS did in a
01465     // previous version ), so we just ignore it.
01466     if (move == SG_RESIGN)
01467         return;
01468     CheckLegal("illegal move: ", color, move, m_acceptIllegal);
01469     SgNode* node = GetGame().AddMove(move, color);
01470     GetGame().GoToNode(node);
01471 }
01472 
01473 GoPlayer& GoGtpEngine::Player() const
01474 {
01475     if (m_player == 0)
01476         throw GtpFailure("no player set");
01477     return *m_player;
01478 }
01479 
01480 SgPoint GoGtpEngine::PointArg(const GtpCommand& cmd) const
01481 {
01482     return GoGtpCommandUtil::PointArg(cmd, Board());
01483 }
01484 
01485 SgPoint GoGtpEngine::PointArg(const GtpCommand& cmd, std::size_t number) const
01486 {
01487     return GoGtpCommandUtil::PointArg(cmd, number, Board());
01488 }
01489 
01490 SgVector<SgPoint> GoGtpEngine::PointListArg(const GtpCommand& cmd,
01491                                           std::size_t number) const
01492 {
01493     return GoGtpCommandUtil::PointListArg(cmd, number, Board());
01494 }
01495 
01496 SgVector<SgPoint> GoGtpEngine::PointListArg(const GtpCommand& cmd) const
01497 {
01498     return GoGtpCommandUtil::PointListArg(cmd, Board());
01499 }
01500 
01501 void GoGtpEngine::RespondNumberArray(GtpCommand& cmd,
01502                                      const SgPointArray<int>& array,
01503                                      int scale)
01504 {
01505     GoGtpCommandUtil::RespondNumberArray(cmd, array, scale, Board());
01506 }
01507 
01508 void GoGtpEngine::RulesChanged()
01509 {
01510     if (m_player != 0)
01511         m_player->UpdateSubscriber();
01512 }
01513 
01514 void GoGtpEngine::SaveGame(const std::string& fileName) const
01515 {
01516     if (MpiSynchronizer()->IsRootProcess())
01517     {
01518     try
01519     {
01520         ofstream out(fileName.c_str());
01521         SgGameWriter writer(out);
01522         writer.WriteGame(GetGame().Root(), true, 0, "", 1, 19);
01523     }
01524     catch (const SgException& e)
01525     {
01526         throw GtpFailure(e.what());
01527     }
01528     }
01529 }
01530 
01531 void GoGtpEngine::SaveStatistics()
01532 {
01533     if (MpiSynchronizer()->IsRootProcess())
01534     {
01535     if (m_statisticsFile == "")
01536         return;
01537     SG_ASSERT(m_statisticsValues.size() == m_statisticsSlots.size());
01538     ofstream out(m_statisticsFile.c_str(), ios::app);
01539     for (size_t i = 0; i < m_statisticsSlots.size(); ++i)
01540     {
01541         out << m_statisticsValues[i];
01542         if (i < m_statisticsSlots.size() - 1)
01543         out << '\t';
01544         else
01545         out << '\n';
01546     }
01547     }
01548 }
01549 
01550 void GoGtpEngine::SetAutoSave(const std::string& prefix)
01551 {
01552     m_autoSave = true;
01553     m_autoSavePrefix = prefix;
01554     CreateAutoSaveFileName();
01555 }
01556 
01557 void GoGtpEngine::SetAutoShowBoard(bool showBoard)
01558 {
01559     m_autoShowBoard = showBoard;
01560     if (m_autoShowBoard)
01561         SgDebug() << Board();
01562 }
01563 
01564 inline void GoGtpEngine::SetStatisticsFile(const std::string& fileName)
01565 {
01566     m_statisticsFile = fileName;
01567     InitStatistics();
01568 }
01569 
01570 void GoGtpEngine::SetPlayer(GoPlayer* player)
01571 {
01572     m_player = player;
01573     GetGame().SetPlayer(SG_BLACK, player);
01574     GetGame().SetPlayer(SG_WHITE, player);
01575     if (m_player != 0)
01576         m_player->OnNewGame();
01577     InitStatistics();
01578 }
01579 
01580 void GoGtpEngine::SetNamedRules(const string& namedRules)
01581 {
01582     Board().Rules().SetNamedRules(namedRules);
01583     m_defaultRules.SetNamedRules(namedRules);
01584     RulesChanged();
01585 }
01586 
01587 void GoGtpEngine::StartStatistics()
01588 {
01589     m_statisticsValues.clear();
01590     m_statisticsValues.resize(m_statisticsSlots.size(), "-");
01591 }
01592 
01593 SgPoint GoGtpEngine::StoneArg(const GtpCommand& cmd, std::size_t number) const
01594 {
01595     return GoGtpCommandUtil::StoneArg(cmd, number, Board());
01596 }
01597 
01598 void GoGtpEngine::Undo(int n)
01599 {
01600     SG_ASSERT(n >= 0);
01601     GoGame& game = GetGame();
01602     SgNode* node = game.CurrentNode();
01603     for (int i = 0; i < n; ++i)
01604     {
01605         if (! node->HasNodeMove() || ! node->HasFather())
01606             throw GtpFailure() << "cannot undo " << n << " move(s)";
01607         node = node->Father();
01608     }
01609     game.GoToNode(node);
01610 }
01611 
01612 /** Write board info.
01613     Optional arguments:
01614     - countplay
01615 */
01616 void GoGtpEngine::WriteBoardInfo(GtpCommand& cmd, const GoBoard& bd)
01617 {
01618     cmd.CheckNuArgLessEqual(1);
01619     if (cmd.NuArg() == 1)
01620     {
01621         string arg = cmd.Arg(0);
01622         if (arg == "countplay")
01623             cmd << bd.CountPlay();
01624         else
01625             throw GtpFailure() << "unknown argument " << arg;
01626         return;
01627     }
01628     cmd << "Board:\n"
01629         << SgWriteLabel("Hash") << bd.GetHashCode() << '\n'
01630         << SgWriteLabel("HashToPlay") << bd.GetHashCodeInclToPlay() << '\n'
01631         << SgWriteLabel("KoColor") << SgEBW(bd.KoColor()) << '\n'
01632         << SgWriteLabel("MoveNumber") << bd.MoveNumber() << '\n'
01633         << SgWriteLabel("NumStones[B]") << bd.TotalNumStones(SG_BLACK) << '\n'
01634         << SgWriteLabel("NumStones[W]") << bd.TotalNumStones(SG_WHITE) << '\n'
01635         << SgWriteLabel("NumEmpty") << bd.TotalNumEmpty() << '\n'
01636         << SgWriteLabel("ToPlay") << SgBW(bd.ToPlay()) << '\n'
01637         << SgWriteLabel("CountPlay") << bd.CountPlay() << '\n'
01638         << "Sets:\n"
01639         << SgWritePointSet(bd.AllPoints(), "AllPoints")
01640         << SgWritePointSet(bd.All(SG_BLACK), "AllBlack")
01641         << SgWritePointSet(bd.All(SG_WHITE), "AllWhite")
01642         << SgWritePointSet(bd.AllEmpty(), "AllEmpty")
01643         << SgWritePointSet(bd.Corners(), "Corners")
01644         << SgWritePointSet(bd.Edges(), "Edges")
01645         << SgWritePointSet(bd.Centers(), "Centers")
01646         << SgWritePointSet(bd.SideExtensions(), "SideExtensions")
01647         << SgWritePointSet(bd.Occupied(), "Occupied");
01648 }
01649 
01650 #if GTPENGINE_PONDER
01651 
01652 void GoGtpEngine::Ponder()
01653 {
01654     if (m_player == 0)
01655         return;
01656     // Call GoPlayer::Ponder() after 0.2 seconds delay to avoid calls in very
01657     // short intervals between received commands
01658     boost::xtime time;
01659     boost::xtime_get(&time, boost::TIME_UTC);
01660     bool aborted = false;
01661     for (int i = 0; i < 200; ++i)
01662     {
01663         if (SgUserAbort())
01664         {
01665             aborted = true;
01666                 break;
01667         }
01668         time.nsec += 1000000; // 1 msec
01669         boost::thread::sleep(time);
01670     }
01671     m_mpiSynchronizer->SynchronizeUserAbort(aborted);
01672     if (! aborted)
01673     {
01674         m_mpiSynchronizer->OnStartPonder();
01675         m_player->Ponder();
01676         m_mpiSynchronizer->OnEndPonder();
01677     }
01678 }
01679 
01680 void GoGtpEngine::StopPonder()
01681 {
01682     SgSetUserAbort(true);
01683 }
01684 
01685 void GoGtpEngine::InitPonder()
01686 {
01687     SgSetUserAbort(false);
01688 }
01689 
01690 #endif // GTPENGINE_PONDER
01691 
01692 #if GTPENGINE_INTERRUPT
01693 
01694 void GoGtpEngine::Interrupt()
01695 {
01696     SgSetUserAbort(true);
01697 }
01698 
01699 #endif // GTPENGINE_INTERRUPT
01700 
01701 void GoGtpEngine::SetMpiSynchronizer(const SgMpiSynchronizerHandle &handle)
01702 {
01703     m_mpiSynchronizer = SgMpiSynchronizerHandle(handle);
01704 }
01705 
01706 SgMpiSynchronizerHandle GoGtpEngine::MpiSynchronizer()
01707 {
01708     return SgMpiSynchronizerHandle(m_mpiSynchronizer);
01709 }
01710 
01711 const SgMpiSynchronizerHandle GoGtpEngine::MpiSynchronizer() const
01712 {
01713     return SgMpiSynchronizerHandle(m_mpiSynchronizer);
01714 }
01715 
01716 
01717 //----------------------------------------------------------------------------
01718 
01719 GoGtpAssertionHandler::GoGtpAssertionHandler(const GoGtpEngine& engine)
01720     : m_engine(engine)
01721 {
01722 }
01723 
01724 void GoGtpAssertionHandler::Run()
01725 {
01726     m_engine.DumpState(SgDebug());
01727     SgDebug() << flush;
01728 }
01729 
01730 //----------------------------------------------------------------------------


17 Jun 2010 Doxygen 1.4.7