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


Sun Mar 13 2011 Doxygen 1.7.1