00001 //---------------------------------------------------------------------------- 00002 /** @file GoBook.cpp 00003 */ 00004 //---------------------------------------------------------------------------- 00005 00006 #include "SgSystem.h" 00007 #include "GoBook.h" 00008 00009 #include <algorithm> 00010 #include <fstream> 00011 #include <sstream> 00012 #include "GoBoard.h" 00013 #include "GoBoardUtil.h" 00014 #include "GoGtpCommandUtil.h" 00015 #include "GoGtpEngine.h" 00016 #include "GoModBoard.h" 00017 #include "SgDebug.h" 00018 #include "SgException.h" 00019 #include "SgWrite.h" 00020 00021 using namespace std; 00022 00023 //---------------------------------------------------------------------------- 00024 00025 namespace { 00026 00027 template<typename T> 00028 bool Contains(const vector<T>& v, const T& elem) 00029 { 00030 typename vector<T>::const_iterator end = v.end(); 00031 return (find(v.begin(), end, elem) != end); 00032 } 00033 00034 template<typename T> 00035 bool Erase(vector<T>& v, const T& elem) 00036 { 00037 typename vector<T>::iterator end = v.end(); 00038 typename vector<T>::iterator pos = find(v.begin(), end, elem); 00039 if (pos != end) 00040 { 00041 v.erase(pos); 00042 return true; 00043 } 00044 return false; 00045 } 00046 00047 vector<SgPoint> GetSequence(const GoBoard& bd) 00048 { 00049 vector<SgPoint> result; 00050 SgBlackWhite toPlay = SG_BLACK; 00051 for (int i = 0; i < bd.MoveNumber(); ++i) 00052 { 00053 GoPlayerMove move = bd.Move(i); 00054 if (move.Color() != toPlay) 00055 throw SgException("cannot add position " 00056 "(non-alternating move sequence)"); 00057 result.push_back(move.Point()); 00058 toPlay = SgOppBW(toPlay); 00059 } 00060 return result; 00061 } 00062 00063 } // namepsace 00064 00065 //---------------------------------------------------------------------------- 00066 00067 void GoBook::Entry::ApplyTo(GoBoard& bd) const 00068 { 00069 if (bd.Size() != m_size) 00070 bd.Init(m_size); 00071 GoBoardUtil::UndoAll(bd); 00072 for (vector<SgPoint>::const_iterator it = m_sequence.begin(); 00073 it != m_sequence.end(); ++it) 00074 { 00075 SG_ASSERT(bd.IsLegal(*it)); 00076 bd.Play(*it); 00077 } 00078 } 00079 00080 //---------------------------------------------------------------------------- 00081 00082 void GoBook::Add(const GoBoard& bd, SgPoint move) 00083 { 00084 if (move != SG_PASS && bd.Occupied(move)) 00085 throw SgException("point is not empty"); 00086 if (! bd.IsLegal(move)) 00087 throw SgException("move is not legal"); 00088 const GoBook::MapEntry* mapEntry = LookupEntry(bd); 00089 if (mapEntry == 0) 00090 { 00091 vector<SgPoint> moves; 00092 moves.push_back(move); 00093 GoBoard tempBoard; 00094 InsertEntry(GetSequence(bd), moves, bd.Size(), tempBoard, 0); 00095 } 00096 else 00097 { 00098 size_t id = mapEntry->m_id; 00099 SG_ASSERT(id < m_entries.size()); 00100 Entry& entry = m_entries[id]; 00101 int invRotation = SgPointUtil::InvRotation(mapEntry->m_rotation); 00102 SgPoint rotMove = SgPointUtil::Rotate(invRotation, move, bd.Size()); 00103 if (! Contains(entry.m_moves, rotMove)) 00104 entry.m_moves.push_back(rotMove); 00105 } 00106 } 00107 00108 void GoBook::Clear() 00109 { 00110 m_entries.clear(); 00111 m_map.clear(); 00112 } 00113 00114 void GoBook::Delete(const GoBoard& bd, SgPoint move) 00115 { 00116 const GoBook::MapEntry* mapEntry = LookupEntry(bd); 00117 if (mapEntry == 0) 00118 return; 00119 size_t id = mapEntry->m_id; 00120 SG_ASSERT(id < m_entries.size()); 00121 Entry& entry = m_entries[id]; 00122 int invRotation = SgPointUtil::InvRotation(mapEntry->m_rotation); 00123 SgPoint rotMove = SgPointUtil::Rotate(invRotation, move, bd.Size()); 00124 if (! Erase(entry.m_moves, rotMove)) 00125 throw SgException("GoBook::Delete: move not found"); 00126 } 00127 00128 /** Insert a new position entry and all its transformations 00129 @param sequence A move sequence that leads to the position 00130 @param moves The moves to play in this position 00131 @param size 00132 @param tempBoard A local temporary work board, reused for efficiency 00133 (does not have to be initialized) 00134 @param line Line number of entry from the file (0 if unknown or entry is 00135 not from a file) 00136 */ 00137 void GoBook::InsertEntry(const vector<SgPoint>& sequence, 00138 const vector<SgPoint>& moves, int size, 00139 GoBoard& tempBoard, int line) 00140 { 00141 if (moves.size() == 0) 00142 ThrowError("Line contains no moves"); 00143 if (tempBoard.Size() != size) 00144 tempBoard.Init(size); 00145 Entry entry; 00146 entry.m_size = size; 00147 entry.m_line = line; 00148 entry.m_sequence = sequence; 00149 entry.m_moves = moves; 00150 m_entries.push_back(entry); 00151 size_t id = m_entries.size() - 1; 00152 vector<Map::iterator> newEntries; 00153 for (int rot = 0; rot < 8; ++rot) 00154 { 00155 GoBoardUtil::UndoAll(tempBoard); 00156 for (vector<SgPoint>::const_iterator it = sequence.begin(); 00157 it != sequence.end(); ++it) 00158 { 00159 SgPoint p = SgPointUtil::Rotate(rot, *it, size); 00160 if (! tempBoard.IsLegal(p)) 00161 ThrowError("Illegal move in variation"); 00162 tempBoard.Play(p); 00163 } 00164 // It is enough to check the moves for legality for one rotation 00165 if (rot == 0) 00166 for (vector<SgPoint>::const_iterator it = moves.begin(); 00167 it != moves.end(); ++it) 00168 if (! tempBoard.IsLegal(SgPointUtil::Rotate(rot, *it, size))) 00169 ThrowError("Illegal move in move list"); 00170 MapEntry mapEntry; 00171 mapEntry.m_size = size; 00172 mapEntry.m_id = id; 00173 mapEntry.m_rotation = rot; 00174 SgHashCode hashCode = tempBoard.GetHashCodeInclToPlay(); 00175 pair<Map::iterator,Map::iterator> e = m_map.equal_range(hashCode); 00176 bool isInNewEntries = false; 00177 for (Map::iterator it = e.first; it != e.second; ++it) 00178 if (it->second.m_size == size) 00179 { 00180 if (! Contains(newEntries, it)) 00181 { 00182 ostringstream o; 00183 o << "Entry duplicates line " 00184 << m_entries[it->second.m_id].m_line; 00185 ThrowError(o.str()); 00186 } 00187 isInNewEntries = true; 00188 break; 00189 } 00190 if (! isInNewEntries) 00191 { 00192 Map::iterator newIt = 00193 m_map.insert(Map::value_type(hashCode, mapEntry)); 00194 newEntries.push_back(newIt); 00195 } 00196 } 00197 } 00198 00199 int GoBook::Line(const GoBoard& bd) const 00200 { 00201 const GoBook::MapEntry* mapEntry = LookupEntry(bd); 00202 if (mapEntry == 0) 00203 return 0; 00204 size_t id = mapEntry->m_id; 00205 SG_ASSERT(id < m_entries.size()); 00206 return m_entries[id].m_line; 00207 } 00208 00209 const GoBook::MapEntry* GoBook::LookupEntry(const GoBoard& bd) const 00210 { 00211 SgHashCode hashCode = bd.GetHashCodeInclToPlay(); 00212 typedef Map::const_iterator Iterator; 00213 pair<Iterator,Iterator> e = m_map.equal_range(hashCode); 00214 int size = bd.Size(); 00215 for (Iterator it = e.first; it != e.second; ++it) 00216 if (it->second.m_size == size) 00217 return &it->second; 00218 return 0; 00219 } 00220 00221 SgPoint GoBook::LookupMove(const GoBoard& bd) const 00222 { 00223 vector<SgPoint> moves = LookupAllMoves(bd); 00224 int nuMoves = moves.size(); 00225 if (nuMoves == 0) 00226 return SG_NULLMOVE; 00227 SgPoint p = moves[rand() % nuMoves]; 00228 return p; 00229 } 00230 00231 vector<SgPoint> GoBook::LookupAllMoves(const GoBoard& bd) const 00232 { 00233 vector<SgPoint> result; 00234 const GoBook::MapEntry* mapEntry = LookupEntry(bd); 00235 if (mapEntry == 0) 00236 return result; 00237 size_t id = mapEntry->m_id; 00238 SG_ASSERT(id < m_entries.size()); 00239 const vector<SgPoint>& moves = m_entries[id].m_moves; 00240 const int rotation = mapEntry->m_rotation; 00241 const int size = mapEntry->m_size; 00242 for (vector<SgPoint>::const_iterator it = moves.begin(); 00243 it != moves.end(); ++it) 00244 { 00245 SgPoint p = SgPointUtil::Rotate(rotation, *it, size); 00246 if (! bd.IsLegal(p)) 00247 { 00248 // Should not happen with 64-bit hashes, but not impossible 00249 SgWarning() << "illegal book move (hash code collision?)\n"; 00250 result.clear(); 00251 break; 00252 } 00253 result.push_back(p); 00254 } 00255 return result; 00256 } 00257 00258 void GoBook::ParseLine(const string& line, GoBoard& tempBoard) 00259 { 00260 istringstream in(line); 00261 int size; 00262 in >> size; 00263 if (size < 1) 00264 ThrowError("Invalid size"); 00265 if (size > SG_MAX_SIZE && ! m_warningMaxSizeShown) 00266 { 00267 SgDebug() << "GoBook::ParseLine: Ignoring size=" << size << '\n'; 00268 m_warningMaxSizeShown = true; 00269 return; 00270 } 00271 vector<SgPoint> variation = ReadPoints(in); 00272 vector<SgPoint> moves = ReadPoints(in); 00273 InsertEntry(variation, moves, size, tempBoard, m_lineCount); 00274 } 00275 00276 void GoBook::Read(istream& in, const string& streamName) 00277 { 00278 Clear(); 00279 m_warningMaxSizeShown = false; 00280 m_streamName = streamName; 00281 m_lineCount = 0; 00282 GoBoard tempBoard; 00283 while (in) 00284 { 00285 string line; 00286 getline(in, line); 00287 ++m_lineCount; 00288 if (line == "") 00289 continue; 00290 ParseLine(line, tempBoard); 00291 } 00292 } 00293 00294 void GoBook::Read(const string& filename) 00295 { 00296 ifstream in(filename.c_str()); 00297 if (! in) 00298 throw SgException("Cannot find file " + filename); 00299 Read(in, filename); 00300 } 00301 00302 vector<SgPoint> GoBook::ReadPoints(istream& in) const 00303 { 00304 vector<SgPoint> result; 00305 while (true) 00306 { 00307 string s; 00308 in >> s; 00309 if (! in || s == "|") 00310 break; 00311 istringstream in2(s); 00312 SgPoint p; 00313 in2 >> SgReadPoint(p); 00314 if (! in2) 00315 ThrowError("Invalid point"); 00316 result.push_back(p); 00317 } 00318 return result; 00319 } 00320 00321 void GoBook::ThrowError(const string& message) const 00322 { 00323 ostringstream out; 00324 if (m_streamName != "") 00325 out << m_streamName << ':'; 00326 out << m_lineCount << ": " << message; 00327 throw SgException(out.str()); 00328 } 00329 00330 void GoBook::Write(ostream& out) const 00331 { 00332 for (vector<Entry>::const_iterator it = m_entries.begin(); 00333 it != m_entries.end(); ++it) 00334 { 00335 if (it->m_moves.empty()) 00336 { 00337 SgDebug() << "pruning empty entry line=" << it->m_line << '\n'; 00338 continue; 00339 } 00340 out << it->m_size << ' '; 00341 for (vector<SgPoint>::const_iterator it2 = it->m_sequence.begin(); 00342 it2 != it->m_sequence.end(); ++it2) 00343 out << SgWritePoint(*it2) << ' '; 00344 out << '|'; 00345 for (vector<SgPoint>::const_iterator it2 = it->m_moves.begin(); 00346 it2 != it->m_moves.end(); ++it2) 00347 out << ' ' << SgWritePoint(*it2); 00348 out << '\n'; 00349 } 00350 } 00351 00352 void GoBook::WriteInfo(ostream& out) const 00353 { 00354 out << SgWriteLabel("NuBasic") << m_entries.size() << '\n' 00355 << SgWriteLabel("NuTransformed") << m_map.size() << '\n'; 00356 } 00357 00358 //---------------------------------------------------------------------------- 00359 00360 GoBookCommands::GoBookCommands(GoGtpEngine &engine, const GoBoard& bd, GoBook& book) 00361 : m_engine(engine), 00362 m_bd(bd), 00363 m_book(book) 00364 { 00365 } 00366 00367 void GoBookCommands::AddGoGuiAnalyzeCommands(GtpCommand& cmd) 00368 { 00369 cmd << 00370 "gfx/Book Add/book_add %p\n" 00371 "none/Book Clear/book_clear\n" 00372 "gfx/Book Delete/book_delete %p\n" 00373 "hstring/Book Info/book_info\n" 00374 "none/Book Load/book_load %r\n" 00375 "plist/Book Moves/book_moves\n" 00376 "gfx/Book Position/book_position\n" 00377 "none/Book Save/book_save\n" 00378 "none/Book Save As/book_save_as %w\n"; 00379 } 00380 00381 /** Add a move for the current position to the book. 00382 Arguments: point <br> 00383 Returns: Position information after the move deletion as in CmdPosition() 00384 */ 00385 void GoBookCommands::CmdAdd(GtpCommand& cmd) 00386 { 00387 cmd.CheckNuArg(1); 00388 SgPoint p = GoGtpCommandUtil::PointArg(cmd, m_bd); 00389 try 00390 { 00391 m_book.Add(m_bd, p); 00392 } 00393 catch (const SgException& e) 00394 { 00395 throw GtpFailure(e.what()); 00396 } 00397 PositionInfo(cmd); 00398 } 00399 00400 void GoBookCommands::CmdClear(GtpCommand& cmd) 00401 { 00402 cmd.CheckArgNone(); 00403 m_book.Clear(); 00404 } 00405 00406 /** Delete a move for the current position to the book. 00407 Arguments: point <br> 00408 Returns: Position information after the move deletion as in CmdPosition() 00409 */ 00410 void GoBookCommands::CmdDelete(GtpCommand& cmd) 00411 { 00412 cmd.CheckNuArg(1); 00413 vector<SgPoint> moves = m_book.LookupAllMoves(m_bd); 00414 if (moves.empty()) 00415 throw GtpFailure("book contains no moves for current position"); 00416 SgPoint p = GoGtpCommandUtil::PointArg(cmd, m_bd); 00417 try 00418 { 00419 m_book.Delete(m_bd, p); 00420 } 00421 catch (const SgException& e) 00422 { 00423 throw GtpFailure(e.what()) << SgWritePoint(p) 00424 << " is not a book move"; 00425 } 00426 PositionInfo(cmd); 00427 } 00428 00429 /** Show information about current book. */ 00430 void GoBookCommands::CmdInfo(GtpCommand& cmd) 00431 { 00432 cmd.CheckArgNone(); 00433 cmd << SgWriteLabel("FileName") << m_fileName << '\n'; 00434 m_book.WriteInfo(cmd); 00435 } 00436 00437 void GoBookCommands::CmdLoad(GtpCommand& cmd) 00438 { 00439 cmd.CheckNuArg(1); 00440 m_fileName = cmd.Arg(0); 00441 try 00442 { 00443 m_book.Read(m_fileName); 00444 } 00445 catch (const SgException& e) 00446 { 00447 m_fileName = ""; 00448 throw GtpFailure() << "loading opening book failed: " << e.what(); 00449 } 00450 } 00451 00452 void GoBookCommands::CmdMoves(GtpCommand& cmd) 00453 { 00454 cmd.CheckArgNone(); 00455 vector<SgPoint> active = m_book.LookupAllMoves(m_bd); 00456 for (vector<SgPoint>::const_iterator it = active.begin(); 00457 it != active.end(); ++it) 00458 cmd << SgWritePoint(*it) << ' '; 00459 } 00460 00461 /** Show book information for current positions. 00462 This command is compatible with the GoGui analyze command type "gfx". 00463 Moves in the book for the current position are marked with a green 00464 color (active moves), moves that lead to a known position in the book 00465 with yellow (other moves). The status line shows the line number of the 00466 position in the file (0, if unknown) and the number of active and other 00467 moves. 00468 */ 00469 void GoBookCommands::CmdPosition(GtpCommand& cmd) 00470 { 00471 cmd.CheckArgNone(); 00472 PositionInfo(cmd); 00473 } 00474 00475 void GoBookCommands::CmdSave(GtpCommand& cmd) 00476 { 00477 if (m_engine.MpiSynchronizer()->IsRootProcess()) 00478 { 00479 cmd.CheckArgNone(); 00480 if (m_fileName == "") 00481 throw GtpFailure("no filename associated with current book"); 00482 ofstream out(m_fileName.c_str()); 00483 m_book.Write(out); 00484 if (! out) 00485 { 00486 throw GtpFailure() << "error writing to file '" << m_fileName << "'"; 00487 } 00488 } 00489 } 00490 00491 void GoBookCommands::CmdSaveAs(GtpCommand& cmd) 00492 { 00493 if (m_engine.MpiSynchronizer()->IsRootProcess()) 00494 { 00495 cmd.CheckNuArg(1); 00496 m_fileName = cmd.Arg(0); 00497 ofstream out(m_fileName.c_str()); 00498 m_book.Write(out); 00499 if (! out) 00500 { 00501 m_fileName = ""; 00502 throw GtpFailure("write error"); 00503 } 00504 } 00505 } 00506 00507 void GoBookCommands::PositionInfo(GtpCommand& cmd) 00508 { 00509 vector<SgPoint> active = m_book.LookupAllMoves(m_bd); 00510 vector<SgPoint> other; 00511 { 00512 GoModBoard modBoard(m_bd); 00513 GoBoard& bd = modBoard.Board(); 00514 for (GoBoard::Iterator it(bd); it; ++it) 00515 if (bd.IsLegal(*it) && ! Contains(active, *it)) 00516 { 00517 bd.Play(*it); 00518 if (! m_book.LookupAllMoves(bd).empty()) 00519 other.push_back(*it); 00520 bd.Undo(); 00521 } 00522 } 00523 cmd << "COLOR green"; 00524 for (vector<SgPoint>::const_iterator it = active.begin(); 00525 it != active.end(); ++it) 00526 cmd << ' ' << SgWritePoint(*it); 00527 cmd << "\nCOLOR yellow"; 00528 for (vector<SgPoint>::const_iterator it = other.begin(); 00529 it != other.end(); ++it) 00530 cmd << ' ' << SgWritePoint(*it); 00531 int line = m_book.Line(m_bd); 00532 cmd << "\nTEXT Line=" << line << " Active=" << active.size() << " Other=" 00533 << other.size() << '\n'; 00534 } 00535 00536 void GoBookCommands::Register(GtpEngine& e) 00537 { 00538 e.Register("book_add", &GoBookCommands::CmdAdd, this); 00539 e.Register("book_clear", &GoBookCommands::CmdClear, this); 00540 e.Register("book_delete", &GoBookCommands::CmdDelete, this); 00541 e.Register("book_info", &GoBookCommands::CmdInfo, this); 00542 e.Register("book_load", &GoBookCommands::CmdLoad, this); 00543 e.Register("book_moves", &GoBookCommands::CmdMoves, this); 00544 e.Register("book_position", &GoBookCommands::CmdPosition, this); 00545 e.Register("book_save", &GoBookCommands::CmdSave, this); 00546 e.Register("book_save_as", &GoBookCommands::CmdSaveAs, this); 00547 } 00548 00549 //----------------------------------------------------------------------------