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