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