00001 //---------------------------------------------------------------------------- 00002 /** @file HexHtpEngine.cpp 00003 */ 00004 //---------------------------------------------------------------------------- 00005 00006 #include "SgSystem.h" 00007 00008 #include <cmath> 00009 #include <iomanip> 00010 #include <sstream> 00011 #include <limits> 00012 #include <time.h> 00013 #include <signal.h> 00014 #include <iostream> 00015 00016 #include "SgGameReader.h" 00017 #include "SgNode.h" 00018 #include "SgTimer.h" 00019 00020 #include "BitsetIterator.hpp" 00021 #include "BoardUtils.hpp" 00022 #include "Groups.hpp" 00023 #include "HexSgUtil.hpp" 00024 #include "HexProgram.hpp" 00025 #include "HexHtpEngine.hpp" 00026 #include "Time.hpp" 00027 00028 using namespace benzene; 00029 00030 //---------------------------------------------------------------------------- 00031 00032 HexHtpEngine::HexHtpEngine(int boardsize) 00033 : GtpEngine(), 00034 m_board(boardsize, boardsize), 00035 m_game(m_board) 00036 { 00037 RegisterCmd("all_legal_moves", &HexHtpEngine::CmdAllLegalMoves); 00038 RegisterCmd("board_id", &HexHtpEngine::CmdBoardID); 00039 RegisterCmd("boardsize", &HexHtpEngine::CmdNewGame); 00040 RegisterCmd("clear_board", &HexHtpEngine::CmdClearBoard); 00041 RegisterCmd("exec", &HexHtpEngine::CmdExec); 00042 RegisterCmd("final_score", &HexHtpEngine::CmdFinalScore); 00043 RegisterCmd("genmove", &HexHtpEngine::CmdGenMove); 00044 RegisterCmd("reg_genmove", &HexHtpEngine::CmdRegGenMove); 00045 #if GTPENGINE_INTERRUPT 00046 RegisterCmd("gogui-interrupt", &HexHtpEngine::CmdInterrupt); 00047 #endif 00048 RegisterCmd("loadsgf", &HexHtpEngine::CmdLoadSgf); 00049 RegisterCmd("name", &HexHtpEngine::CmdName); 00050 RegisterCmd("param_game", &HexHtpEngine::CmdParamGame); 00051 RegisterCmd("play", &HexHtpEngine::CmdPlay); 00052 RegisterCmd("showboard", &HexHtpEngine::CmdShowboard); 00053 RegisterCmd("time_left", &HexHtpEngine::CmdTimeLeft); 00054 RegisterCmd("undo", &HexHtpEngine::CmdUndo); 00055 RegisterCmd("version", &HexHtpEngine::CmdVersion); 00056 00057 NewGame(m_board.Width(), m_board.Height()); 00058 } 00059 00060 HexHtpEngine::~HexHtpEngine() 00061 { 00062 } 00063 00064 //---------------------------------------------------------------------------- 00065 00066 void HexHtpEngine::RegisterCmd(const std::string& name, 00067 GtpCallback<HexHtpEngine>::Method method) 00068 { 00069 Register(name, new GtpCallback<HexHtpEngine>(this, method)); 00070 } 00071 00072 void HexHtpEngine::Play(HexColor color, HexPoint move) 00073 { 00074 bool illegal = false; 00075 std::string reason = ""; 00076 00077 // do nothing if a resign move 00078 if (move == RESIGN) 00079 return; 00080 00081 Game::ReturnType result = m_game.PlayMove(color, move); 00082 if (result == Game::INVALID_MOVE) { 00083 illegal = true; 00084 reason = " (invalid)"; 00085 } else if (result == Game::OCCUPIED_CELL) { 00086 illegal = true; 00087 reason = " (occupied)"; 00088 } 00089 00090 if (illegal) { 00091 throw HtpFailure() << "illegal move: " << ' ' 00092 << color << ' ' << move << reason; 00093 } 00094 } 00095 00096 void HexHtpEngine::NewGame(int width, int height) 00097 { 00098 if (width != m_game.Board().Width() || 00099 height != m_game.Board().Height()) 00100 { 00101 m_board = StoneBoard(width, height); 00102 m_game.SetBoard(m_board); 00103 } 00104 m_game.NewGame(); 00105 } 00106 00107 void HexHtpEngine::BeforeHandleCommand() 00108 { 00109 SgSetUserAbort(false); 00110 } 00111 00112 void HexHtpEngine::BeforeWritingResponse() 00113 { 00114 } 00115 00116 //---------------------------------------------------------------------------- 00117 00118 /** Returns program's name. */ 00119 void HexHtpEngine::CmdName(HtpCommand& cmd) 00120 { 00121 cmd << HexProgram::Get().getName(); 00122 } 00123 00124 /** Returns program's version. */ 00125 void HexHtpEngine::CmdVersion(HtpCommand& cmd) 00126 { 00127 cmd << HexProgram::Get().getVersion(); 00128 } 00129 00130 /** Executes HTP commands contained in given file. */ 00131 void HexHtpEngine::CmdExec(HtpCommand& cmd) 00132 { 00133 cmd.CheckNuArg(1); 00134 std::string filename = cmd.Arg(0); 00135 00136 try { 00137 ExecuteFile(filename, std::cerr); 00138 } 00139 catch (std::exception& e) { 00140 LogInfo() << "Errors occured." << '\n'; 00141 } 00142 } 00143 00144 #if GTPENGINE_INTERRUPT 00145 00146 /** Does nothing, but lets gogui know we can be interrupted with the 00147 "# interrupt" gtp command. */ 00148 void HexHtpEngine::CmdInterrupt(HtpCommand& cmd) 00149 { 00150 cmd.CheckArgNone(); 00151 } 00152 00153 #endif 00154 00155 /** Starts new game with the given board size. */ 00156 void HexHtpEngine::CmdNewGame(HtpCommand& cmd) 00157 { 00158 cmd.CheckNuArgLessEqual(2); 00159 if (cmd.NuArg() == 0) 00160 throw HtpFailure() << "Must specify board dimensions!"; 00161 int width = cmd.IntArg(0, 1, MAX_WIDTH); 00162 int height = width; 00163 if (cmd.NuArg() == 2) 00164 height = cmd.IntArg(1, 1, MAX_HEIGHT); 00165 NewGame(width, height); 00166 } 00167 00168 /** Starts a new game with the same board size. */ 00169 void HexHtpEngine::CmdClearBoard(HtpCommand& cmd) 00170 { 00171 cmd.CheckArgNone(); 00172 NewGame(m_board.Width(), m_board.Height()); 00173 } 00174 00175 /** Plays a move. */ 00176 void HexHtpEngine::CmdPlay(HtpCommand& cmd) 00177 { 00178 cmd.CheckNuArg(2); 00179 Play(HtpUtil::ColorArg(cmd, 0), HtpUtil::MoveArg(cmd, 1)); 00180 } 00181 00182 /** Generates a move and handles time remaining. */ 00183 void HexHtpEngine::CmdGenMove(HtpCommand& cmd) 00184 { 00185 cmd.CheckNuArg(1); 00186 if (GameUtil::IsGameOver(m_game)) 00187 cmd << RESIGN; 00188 else 00189 { 00190 HexColor color = HtpUtil::ColorArg(cmd, 0); 00191 SgTime::SetDefaultMode(SG_TIME_REAL); 00192 SgTimer timer; 00193 timer.Start(); 00194 double oldTimeRemaining = m_game.TimeRemaining(color); 00195 HexPoint move = GenMove(color, true); 00196 timer.Stop(); 00197 00198 m_game.SetTimeRemaining(color, oldTimeRemaining - timer.GetTime()); 00199 if (m_game.TimeRemaining(color) < 0) 00200 LogWarning() << "**** FLAG DROPPED ****\n"; 00201 00202 Play(color, move); 00203 cmd << move; 00204 } 00205 } 00206 00207 /** Generates a move, but does not play it. Sets random seed. */ 00208 void HexHtpEngine::CmdRegGenMove(HtpCommand& cmd) 00209 { 00210 cmd.CheckNuArg(1); 00211 SgRandom::SetSeed(SgRandom::Seed()); 00212 if (GameUtil::IsGameOver(m_game)) 00213 cmd << RESIGN; 00214 else 00215 { 00216 HexPoint move = GenMove(HtpUtil::ColorArg(cmd, 0), false); 00217 cmd << move; 00218 } 00219 } 00220 00221 /** Undo the last move. */ 00222 void HexHtpEngine::CmdUndo(HtpCommand& cmd) 00223 { 00224 cmd.CheckNuArg(0); 00225 m_game.UndoMove(); 00226 } 00227 00228 /** Displays the board. */ 00229 void HexHtpEngine::CmdShowboard(HtpCommand& cmd) 00230 { 00231 cmd << "\n"; 00232 cmd << m_game.Board(); 00233 } 00234 00235 /** Outputs BoardID of current position. */ 00236 void HexHtpEngine::CmdBoardID(HtpCommand& cmd) 00237 { 00238 cmd.CheckNuArg(0); 00239 cmd << m_game.Board().GetBoardIDString(); 00240 } 00241 00242 /** Displays time left for both players or given player. */ 00243 void HexHtpEngine::CmdTimeLeft(HtpCommand& cmd) 00244 { 00245 cmd.CheckNuArgLessEqual(2); 00246 if (cmd.NuArg() == 0) 00247 { 00248 cmd << "Black: " << Time::Formatted(m_game.TimeRemaining(BLACK)) 00249 << ", " 00250 << "White: " << Time::Formatted(m_game.TimeRemaining(WHITE)); 00251 } 00252 else if (cmd.NuArg() == 1) 00253 { 00254 HexColor color = HtpUtil::ColorArg(cmd, 0); 00255 cmd << Time::Formatted(m_game.TimeRemaining(color)); 00256 } 00257 else 00258 { 00259 HexColor color = HtpUtil::ColorArg(cmd, 0); 00260 m_game.SetTimeRemaining(color, cmd.IntArg(1)); 00261 } 00262 } 00263 00264 /** Returns a string with what we think the outcome of the game is. 00265 Value will be "B+" for a black win, and "W+" for a white win. 00266 */ 00267 void HexHtpEngine::CmdFinalScore(HtpCommand& cmd) 00268 { 00269 Groups groups; 00270 GroupBuilder::Build(m_game.Board(), groups); 00271 HexColor winner = groups.GetWinner(); 00272 std::string ret = "cannot score"; 00273 if (winner == BLACK) 00274 ret = "B+"; 00275 else if (winner == WHITE) 00276 ret = "W+"; 00277 cmd << ret; 00278 } 00279 00280 /** Returns a list of all legal moves on current board position. */ 00281 void HexHtpEngine::CmdAllLegalMoves(HtpCommand& cmd) 00282 { 00283 int c = 0; 00284 bitset_t legal = m_game.Board().GetLegal(); 00285 for (BitsetIterator i(legal); i; ++i) 00286 { 00287 cmd << " " << *i; 00288 if ((++c % 10) == 0) cmd << "\n"; 00289 } 00290 } 00291 00292 /** @bug This won't work if we're overwriting previosly played stones! */ 00293 void HexHtpEngine::SetPosition(const SgNode* node) 00294 { 00295 std::vector<HexPoint> black, white, empty; 00296 HexSgUtil::GetSetupPosition(node, m_game.Board().Height(), 00297 black, white, empty); 00298 for (unsigned i=0; ; ++i) 00299 { 00300 bool bdone = (i >= black.size()); 00301 bool wdone = (i >= white.size()); 00302 if (!bdone) Play(BLACK, black[i]); 00303 if (!wdone) Play(WHITE, white[i]); 00304 if (bdone && wdone) break; 00305 } 00306 } 00307 00308 /** Loads game or position from given sgf. Sets position to given move 00309 number or the last move of the game if none is given. 00310 */ 00311 void HexHtpEngine::CmdLoadSgf(HtpCommand& cmd) 00312 { 00313 cmd.CheckNuArgLessEqual(2); 00314 std::string filename = cmd.Arg(0); 00315 int movenumber = 1024; 00316 if (cmd.NuArg() == 2) 00317 movenumber = cmd.IntArg(1, 0); 00318 00319 std::ifstream file(filename.c_str()); 00320 if (!file) { 00321 throw HtpFailure() << "cannot load file"; 00322 return; 00323 } 00324 00325 SgGameReader sgreader(file, 11); 00326 SgNode* root = sgreader.ReadGame(); 00327 if (root == 0) { 00328 throw HtpFailure() << "cannot load file"; 00329 return; 00330 } 00331 sgreader.PrintWarnings(std::cerr); 00332 00333 int size = root->GetIntProp(SG_PROP_SIZE); 00334 00335 NewGame(size, size); 00336 00337 const StoneBoard& brd = m_game.Board(); 00338 00339 if (HexSgUtil::NodeHasSetupInfo(root)) { 00340 LogWarning() << "Root has setup info!" << '\n'; 00341 SetPosition(root); 00342 } 00343 00344 // play movenumber moves; stop if we hit the end of the game 00345 SgNode* cur = root; 00346 for (int mn=0; mn<movenumber;) { 00347 cur = cur->NodeInDirection(SgNode::NEXT); 00348 if (!cur) break; 00349 00350 if (HexSgUtil::NodeHasSetupInfo(cur)) { 00351 SetPosition(cur); 00352 continue; 00353 } else if (!cur->HasNodeMove()) { 00354 continue; 00355 } 00356 00357 HexColor color = HexSgUtil::SgColorToHexColor(cur->NodePlayer()); 00358 HexPoint point = HexSgUtil::SgPointToHexPoint(cur->NodeMove(), 00359 brd.Height()); 00360 Play(color, point); 00361 mn++; 00362 } 00363 } 00364 00365 /** Displays/changes parameters relating to the current game. 00366 00367 Parameters: 00368 @arg @c allow_swap See Game::AllowSwap 00369 @arg @c game_time See Game::GameTime 00370 */ 00371 void HexHtpEngine::CmdParamGame(HtpCommand& cmd) 00372 { 00373 if (cmd.NuArg() == 0) 00374 { 00375 cmd << "\n" 00376 << "[bool] allow_swap " 00377 << m_game.AllowSwap() << '\n' 00378 << "[string] game_time " 00379 << m_game.GameTime() << '\n'; 00380 } 00381 else if (cmd.NuArg() == 2) 00382 { 00383 std::string name = cmd.Arg(0); 00384 if (name == "allow_swap") 00385 m_game.SetAllowSwap(cmd.BoolArg(1)); 00386 else if (name == "game_time") 00387 { 00388 if (!m_game.History().empty()) 00389 throw HtpFailure("Cannot set game time if game started!"); 00390 m_game.SetGameTime(cmd.FloatArg(1)); 00391 } 00392 } 00393 else 00394 throw HtpFailure("Expected 0 or 2 arguments"); 00395 } 00396 00397 #if GTPENGINE_INTERRUPT 00398 00399 void HexHtpEngine::Interrupt() 00400 { 00401 SgSetUserAbort(true); 00402 } 00403 00404 #endif // GTPENGINE_INTERRUPT 00405 00406 //---------------------------------------------------------------------------- 00407 00408 HexColor HtpUtil::ColorArg(const HtpCommand& cmd, std::size_t number) 00409 { 00410 std::string value = cmd.ArgToLower(number); 00411 if (value == "e" || value == "empty") 00412 return EMPTY; 00413 if (value == "b" || value == "black") 00414 return BLACK; 00415 if (value == "w" || value == "white") 00416 return WHITE; 00417 throw HtpFailure() << "argument " << (number + 1) << " must be color"; 00418 } 00419 00420 HexPoint HtpUtil::MoveArg(const HtpCommand& cmd, std::size_t number) 00421 { 00422 return HexPointUtil::FromString(cmd.ArgToLower(number)); 00423 } 00424 00425 //----------------------------------------------------------------------------