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 //----------------------------------------------------------------------------