3 Copyright (c) Scott Gasch
11 Opening book routines: everything from probing the opening book to
12 building a new one to editing the current book. The structure of
13 a book entry is in book.h. The book is kept on disk and sorted by
14 position signature. There may be more than one entry per
15 signature -- there will be one entry per book move from that
16 positions so for example the initial position will have quite a
19 Probing the book (BookMove, called from Think in brain.c) entails
20 binary searching the on disk book file for a matching signature,
21 reading all entries that match, computing a "weight" for each one,
22 and then randomly choosing one.
24 Building a new book is done with a temporary "membook" buffer in
25 memory. It requires quite a bit of memory so before the operation
26 takes place the main hash table and pawn hash table structures are
27 freed (see movelist.c). A PGN file is read one game at a time and
28 the moves are made on a POSITION struct. Once the game is over
29 the signature-move pairs are used to create new entries in the
30 membook. Finally the membook is sorted and written to disk. The
31 process can take a while, especially for a large PGN file or on a
32 machine with limited memory resources.
40 $Id: book.c 355 2008-07-01 15:46:43Z scott $
46 static int g_fdBook = -1; // file descriptor for open book
47 static ULONG g_uMemBookCount = 0; // count of entries in membook
48 static BOOK_ENTRY *g_pMemBook = NULL; // allocated membook struct
49 static BOOK_ENTRY *g_pMemBookHead = NULL; // ptr to 1st entry in membook
54 FLAG g_fTournamentMode = FALSE; // are we in tournament mode
55 ULONG g_uBookProbeFailures = 0; // how many times have we missed
56 ULONG g_uMemBookSize = 0x1000000;
57 OPENING_NAME_MAPPING *g_pOpeningNames = NULL; // opening names file
60 _VerifyBook(CHAR *szName)
84 FLAG fMyRetVal = FALSE;
86 fd = open(szName, O_RDONLY | O_BINARY);
89 Trace("VerifyBook: Can't open %s for read/write.\n", szName);
93 if (0 != stat(szName, &s))
95 Trace("VerifyBook: Can't stat %s.\n", szName);
99 if ((s.st_size % sizeof(BOOK_ENTRY)) != 0)
101 Trace("VerifyBook: Size of book is not a multiple of entry size.\n");
106 // Foreach book entry...
108 uNum = (s.st_size / sizeof(BOOK_ENTRY));
111 for (u = 0; u < uNum; u++)
114 // Seek to and read the entry.
116 uPos = u * sizeof(BOOK_ENTRY);
117 if (uPos != lseek(fd, uPos, SEEK_SET))
119 Trace("VerifyBook: Can't seek to entry %u (offset %u) in book.\n",
123 iRet = read(fd, &be, sizeof(BOOK_ENTRY));
124 if (sizeof(BOOK_ENTRY) != iRet)
126 Trace("VerifyBook: Can't read entry number %u (offset %u) in the "
127 "book. Read returned %u but I expected %u.\n",
128 u, uPos, iRet, sizeof(BOOK_ENTRY));
133 // Make sure the book is sorted by signature
135 if (be.u64Sig < u64LastSig)
137 Trace("VerifyBook: Book signatures out-of-order at index %u "
138 "(offset %u).\n", u, uPos);
143 // If our sig is equal to the last entry's then the move here must
144 // be greater than or equal to the last entry's move.
146 else if (be.u64Sig == u64LastSig)
148 if (be.mvNext.uMove < uLastMove)
150 Trace("VerifyBook: Book moves out-of-order at index %u "
151 "(offset %u).\n", u, uPos);
156 u64LastSig = be.u64Sig;
157 uLastMove = be.mvNext.uMove;
171 _BookLineToString(UINT64 u64Line)
176 Converts a position signature into an opening name, if known.
190 if (NULL == g_pOpeningNames) return(NULL);
191 while(g_pOpeningNames[u].szString != NULL)
193 if (g_pOpeningNames[u].u64Sig == u64Line)
195 return(g_pOpeningNames[u].szString);
204 _DumpUserOpeningList(void)
209 Called in response to a user "book dump openings" command, this
210 routine simply shows every named opening in the opening names
211 file. This file is not the opening book, it's an auxillary disk
212 file containing a list of moves and a name for the opening. It's
213 only used to announce the opening line played in a game and
214 annotate the PGN for a game stored in monsoon's logfile.
226 int fd = open(OPENING_LEARNING_FILENAME,
227 O_RDWR | O_CREAT | O_BINARY | O_RANDOM,
228 _S_IREAD | _S_IWRITE);
231 OPENING_LEARNING_ENTRY ole;
236 // If we failed to open it, abort.
238 if (fd < 0) goto end;
241 // Stat it to see how many entries there are.
243 if (0 != stat(OPENING_LEARNING_FILENAME, &s))
246 Trace("UpdateUserOpeningList: Corrupt %s file.\n",
247 OPENING_LEARNING_FILENAME);
250 if ((s.st_size % sizeof(OPENING_LEARNING_ENTRY)) != 0)
253 Trace("UpdateUserOpeningList: Corrupt %s file.\n",
254 OPENING_LEARNING_FILENAME);
257 uNumRecords = s.st_size / sizeof(OPENING_LEARNING_ENTRY);
258 Trace("Learned data on %u openings:\n\n", uNumRecords);
261 // Read each entry looking for a match.
263 for (u = 0; u < uNumRecords; u++)
265 if (sizeof(OPENING_LEARNING_ENTRY) !=
266 read(fd, &ole, sizeof(OPENING_LEARNING_ENTRY)))
268 Trace("UpdateUserOpeningList: Read error.\n");
272 p = _BookLineToString(ole.u64Sig);
275 Trace("%-45s ww=%u, dr=%u, bw=%u\n",
276 p, ole.uWhiteWins, ole.uDraws, ole.uBlackWins);
288 InitializeOpeningBook(void)
307 ResetOpeningBook(void)
322 g_uBookProbeFailures = 0;
326 CleanupOpeningBook(void)
344 // If we have read an "opening names" list and created a dynamic memory
345 // structure for it, free that now.
347 if (NULL != g_pOpeningNames)
349 while (g_pOpeningNames[x].szString != NULL)
351 SystemFreeMemory(g_pOpeningNames[x].szString);
352 g_pOpeningNames[x].szString = NULL;
353 g_pOpeningNames[x].u64Sig = 0;
356 SystemFreeMemory(g_pOpeningNames);
357 g_pOpeningNames = NULL;
361 // Cleanup membook if they quit in the middle of book building somehow
363 if (NULL != g_pMemBook)
366 SystemFreeMemory(g_pMemBook);
388 // Need to have set the book name before calling this.
390 if (!strlen(g_Options.szBookName))
398 ASSERT(g_fdBook == -1);
399 g_fdBook = open(g_Options.szBookName,
401 O_RANDOM | O_BINARY | _S_IREAD | _S_IWRITE);
404 Bug("_BookOpen: Failed to open book \"%s\".\n", g_Options.szBookName);
408 return((FLAG)(g_fdBook >= 0));
430 (void)close(g_fdBook);
455 // During normal book operations we have an open file handle and we
456 // simply calculate and set its file pointer.
458 // However when in the process of building the opening book from a
459 // file full of PGN games all book commands access a large buffer in
460 // memory... so instead of setting a file pointer to a byte offset in
461 // the book file on disk, we set a normal pointer to an offset in
462 // this large book building buffer.
464 if (g_pMemBook == NULL)
466 uPos = n * sizeof(BOOK_ENTRY);
467 ASSERT(g_fdBook != -1);
469 if (uPos != lseek(g_fdBook, uPos, SEEK_SET))
471 Trace("_BookSeek: Cannot seek to byte offset %u\n", uPos);
477 ASSERT(g_uMemBookSize >= 0);
478 ASSERT(n < g_uMemBookSize);
479 g_pMemBookHead = g_pMemBook + n;
486 _BookWrite(BOOK_ENTRY *x)
504 // If we are building the book, operate on memory
505 // buffer... otherwise operate on the book file.
507 if (g_pMemBook != NULL)
509 memcpy(g_pMemBookHead, x, sizeof(BOOK_ENTRY));
513 ASSERT(g_fdBook != -1);
515 iNum = write(g_fdBook, x, sizeof(BOOK_ENTRY));
516 if (sizeof(BOOK_ENTRY) != iNum)
518 Trace("_BookWrite: I/O error on fd=%d, got %d and expected %d\n",
519 g_fdBook, iNum, sizeof(BOOK_ENTRY));
529 _BookRead(BOOK_ENTRY *x)
546 if (g_pMemBook != NULL)
548 memcpy(x, g_pMemBookHead, sizeof(BOOK_ENTRY));
552 ASSERT(g_fdBook != -1);
554 iNum = read(g_fdBook, x, sizeof(BOOK_ENTRY));
555 if (sizeof(BOOK_ENTRY) != iNum)
557 Trace("_BookRead: I/O error on fd=%d, got %d and expected %d\n",
558 g_fdBook, iNum, sizeof(BOOK_ENTRY));
586 if (NULL != g_pMemBook)
588 return(g_uMemBookCount);
592 if (!strlen(g_Options.szBookName))
597 if (0 != stat(g_Options.szBookName, &s))
599 Trace("_BookCount: Stat failed.\n");
605 if ((s.st_size % sizeof(BOOK_ENTRY)) != 0)
607 Trace("_BookCount: Size is %u... not a mult of %u!\n",
608 s.st_size, sizeof(BOOK_ENTRY));
611 return(s.st_size / sizeof(BOOK_ENTRY));
618 _BookAppend(BOOK_ENTRY *x)
633 if (g_pMemBook != NULL)
635 g_pMemBookHead = g_pMemBook + g_uMemBookCount;
636 memcpy(g_pMemBookHead, x, sizeof(BOOK_ENTRY));
642 ASSERT(g_fdBook != -1);
644 if (-1 == lseek(g_fdBook, 0, SEEK_END))
649 return((FLAG)(sizeof(BOOK_ENTRY) ==
650 write(g_fdBook, x, sizeof(BOOK_ENTRY))));
656 _BookFindSig(UINT64 u64Sig, ULONG *puLow, ULONG *puHigh)
677 ULONG uTotalEntries = _BookCount();
685 if (0 == uTotalEntries)
687 Trace("BookFindSig: Opening book \"%s\" is empty.\n",
688 g_Options.szBookName);
693 Trace("BookFindSig: No book is opened!\n");
698 // Perform a a binary search looking for an entry with a signature
699 // that matches this position's.
702 uHigh = uTotalEntries - 1;
704 while ((uLow <= uHigh) &&
707 uCurrent = (uLow + uHigh) / 2;
711 if (u64Sig == entry.u64Sig)
716 else if (u64Sig < entry.u64Sig)
718 uHigh = uCurrent - 1;
722 ASSERT(u64Sig > entry.u64Sig);
728 goto end; // not found
730 ASSERT(uCurrent >= 0);
731 ASSERT(uCurrent < uTotalEntries);
735 // Ok, we matched a signature at current. Since there can be many
736 // entries with the same checksum (many moves from the same board
737 // position), seek backwards until the first one matching the
740 uIndex = uCurrent - 1;
741 while (uIndex != (ULONG)-1)
746 if (entry.u64Sig != u64Sig)
750 ASSERT(uIndex <= uCurrent);
753 ASSERT(entry.u64Sig == u64Sig);
762 // Walk forward until the last book entry with a matching signature.
764 uIndex = uCurrent + 1;
765 while (uIndex < uTotalEntries)
769 if (entry.u64Sig != u64Sig)
773 ASSERT(uIndex >= uCurrent);
776 ASSERT(entry.u64Sig == u64Sig);
791 static ULONG _BookComputeWeight(BOOK_ENTRY *e)
797 Given a book entry, compute its weight. The higher the more
798 likely it will be chosen and played. Used in BookMove and
799 BookEdit. The formula is:
801 weight(mv) = winningness_coefficient * popularity_factor
804 winningness_coefficient = ---------------
807 popularity_factor = wins + losses + draws
819 ULONG uRet; // computed return value
820 double dWinning = 0.0; // winningness coefficient
821 double dPopular; // popularity factor
824 // dWinning is 0.0 now. The only way it gets a value is if we've
825 // seen this line win at least once.
830 dWinning /= (double)(e->uWins + e->uLosses);
833 // dWinning is now the ratio of wins to deterministic games
834 // (games that were not draws). If this is less than 20% then
835 // ignore the line (give the move a zero weight).
837 if (dWinning < 0.20) return(0);
841 // If we get here dWinning, the winningness coefficient, has been
842 // computed and is non-zero. Compute the rest of the weight term
843 // which is based on the popularity of the move (how many times it
846 dPopular = (double)(e->uWins + e->uLosses + e->uDraws);
847 uRet = (ULONG)(dPopular * dWinning);
853 _QuickSortBook(ULONG uLeft, ULONG uRight)
875 ASSERT(uLeft < g_uMemBookSize);
877 ASSERT(uRight < g_uMemBookSize);
881 // Select a pivot point.
883 u64Pivot = g_pMemBook[uLeft].u64Sig;
884 uPivotSec = g_pMemBook[uLeft].mvNext.uMove;
887 // Partition the space based on the pivot. Everything left of
888 // it is less, everything right is greater.
894 while (((g_pMemBook[uL].u64Sig < u64Pivot) ||
895 ((g_pMemBook[uL].u64Sig == u64Pivot) &&
896 (g_pMemBook[uL].mvNext.uMove <= uPivotSec))) &&
902 while (((g_pMemBook[uR].u64Sig > u64Pivot) ||
903 ((g_pMemBook[uR].u64Sig == u64Pivot) &&
904 (g_pMemBook[uR].mvNext.uMove > uPivotSec))) &&
912 temp = g_pMemBook[uL];
913 g_pMemBook[uL] = g_pMemBook[uR];
914 g_pMemBook[uR] = temp;
919 temp = g_pMemBook[uLeft];
920 g_pMemBook[uLeft] = g_pMemBook[uMid];
921 g_pMemBook[uMid] = temp;
924 // Recurse on the two halves
928 _QuickSortBook(uLeft, uMid - 1);
932 _QuickSortBook(uMid + 1, uRight);
939 _CompactMemBook(void)
958 Trace("Compacting the opening book... one moment.\n");
961 // Lose the blank signatures to reduce the working set.
967 if (0 != g_pMemBook[i].u64Sig)
969 uOccurances = (g_pMemBook[i].uWins +
970 g_pMemBook[i].uDraws +
971 g_pMemBook[i].uLosses);
973 if (0 != uOccurances)
975 memcpy(&(g_pMemBook[uCount]),
986 g_uMemBookCount = uCount - 1;
992 _StrainMemBook(ULONG uLimit)
1010 Trace("Straining out unpopular positions to compact buffer...\n");
1016 if (0 != g_pMemBook[i].u64Sig)
1018 uOccurances = (g_pMemBook[i].uWins +
1019 g_pMemBook[i].uDraws +
1020 g_pMemBook[i].uLosses);
1022 if (uOccurances <= uLimit)
1024 memset(&(g_pMemBook[i]),
1026 sizeof(BOOK_ENTRY));
1034 //+----------------------------------------------------------------------------
1036 // Function: _BookHashFind
1038 // Arguments: BOOK_ENTRY *p - pointer to a book entry
1040 // Returns: BOOK_ENTRY * - pointer to a membook location to store in
1042 //+----------------------------------------------------------------------------
1048 BOOK_ENTRY *_BookHashFind(BOOK_ENTRY *p)
1051 Routine description:
1053 This routine returns a pointer to the membook entry that should be
1054 used to store data about a position-move. It's called while
1055 building a new opening book (BookBuild, see below). It uses a
1056 simple hash-lookup algorthm with linear probing in the event of a
1069 ULONG uKey = (ULONG)((p->u64Sig >> 32) & (g_uMemBookSize - 1));
1070 BOOK_ENTRY *pBe = g_pMemBook + uKey;
1072 ULONG uInitialKey = uKey;
1074 ASSERT(g_pMemBook != NULL);
1081 // If this is an empty book entry, use it.
1083 if (pBe->u64Sig == 0) break;
1086 // If this book entry is the same position and move as the one
1087 // we are adding, return it.
1089 if ((pBe->u64Sig == p->u64Sig) &&
1090 (pBe->mvNext.uMove == p->mvNext.uMove))
1096 // Otherwise check the next book entry... if we go off the end
1097 // then loop around.
1100 if (uKey >= g_uMemBookSize) uKey = 0;
1101 pBe = g_pMemBook + uKey;
1104 // This should never happen -- it means a totally full book.
1105 // Before we let the membook fill up we should have already
1106 // called the strain routine to drop unpopular position-moves.
1108 ASSERT(uKey != uInitialKey);
1116 BookMove(POSITION *pos, BITV bvFlags)
1119 Routine description:
1121 This is a bit confusing.
1123 The primary purpose of this function is, given a position pointer,
1124 to find and return a book move to play.
1126 This behavior can be achieved by calling with a good pos pointer
1127 and bvFlags containing BOOKMOVE_SELECT_MOVE.
1129 If bvFlags doesn't contain this flag then no move will be
1132 If bvFlags contains BOOKMOVE_DUMP this function will trace a bunch
1133 of info about move weights. Hence, something like BookDump can be
1136 (void)BookMove(pos, BOOKMOVE_DUMP);
1138 This is done so that the code to find all moves from a position
1139 and show them is not duplicated more than once.
1152 LIGHTWEIGHT_SEARCHER_CONTEXT *ctx = NULL;
1154 ULONG uTotalWeight = 0;
1155 static ULONG uMoveWeights[32];
1156 static CHAR *szNames[32];
1158 static BOOK_ENTRY entry;
1159 ULONG uBestWeight = 0;
1160 ULONG uBestIndex = (ULONG)-1;
1168 CHAR *szName = NULL;
1169 FLAG fOldValue = g_Options.fShouldAnnounceOpening;
1171 g_Options.fShouldAnnounceOpening = FALSE;
1173 memset(uMoveWeights, 0, sizeof(uMoveWeights));
1175 ctx = SystemAllocateMemory(sizeof(LIGHTWEIGHT_SEARCHER_CONTEXT));
1178 Trace("Out of memory.\n");
1183 // We cannot probe the opening book if membook is non-NULL (which
1184 // indicates the opening book is currently in-memory and still
1187 if (g_pMemBook != NULL)
1189 Trace("Cannot probe the book while its still being built!\n");
1193 if (FALSE == _BookOpen())
1195 Trace("BookMove: Could not open the book file.\n");
1200 // Binary search for sig in the book, this sets iLow and iHigh.
1202 u64Sig = pos->u64PawnSig ^ pos->u64NonPawnSig;
1203 if (0 == _BookFindSig(u64Sig, &uLow, &uHigh))
1205 Trace("BookMove: Signature not in book.\n", u64Sig);
1208 ASSERT((uHigh - uLow) < 32);
1209 ASSERT(uLow <= uHigh);
1212 // Consider each entry from iLow to iHigh and determine the chances
1215 for (uIndex = uLow, uMoveNum = 0;
1217 uIndex++, uMoveNum++)
1222 u64Sig = pos->u64PawnSig ^ pos->u64NonPawnSig;
1223 ASSERT(entry.u64Sig == u64Sig);
1225 uMoveWeights[uMoveNum] = 0;
1226 szNames[uMoveNum] = NULL;
1229 // Don't consider the move if its flagged deleted or disabled.
1231 if ((entry.bvFlags & FLAG_DISABLED) ||
1232 (entry.bvFlags & FLAG_DELETED))
1238 // Make sure it's legal and doesn't draw... also get the name
1241 InitializeLightweightSearcherContext(pos, ctx);
1242 if (FALSE == MakeMove((SEARCHER_THREAD_CONTEXT *)ctx, entry.mvNext))
1249 if (TRUE == IsDraw((SEARCHER_THREAD_CONTEXT *)ctx))
1251 UnmakeMove((SEARCHER_THREAD_CONTEXT *)ctx, entry.mvNext);
1255 u64Sig = (ctx->sPosition.u64PawnSig ^
1256 ctx->sPosition.u64NonPawnSig);
1257 szNames[uMoveNum] = _BookLineToString(u64Sig);
1258 UnmakeMove((SEARCHER_THREAD_CONTEXT *)ctx, entry.mvNext);
1262 // If one is marked always, just return it.
1264 if ((entry.bvFlags & FLAG_ALWAYSPLAY) &&
1265 (bvFlags & BOOKMOVE_SELECT_MOVE))
1267 mvBook = entry.mvNext;
1268 szName = szNames[uMoveNum];
1273 // Set this move's weight.
1275 uMoveWeights[uMoveNum] = _BookComputeWeight(&entry);
1276 uTotalWeight += uMoveWeights[uMoveNum];
1279 // If we are in tournament mode we will play the top weighted
1280 // move; look for it now.
1282 if (uMoveWeights[uMoveNum] > uBestWeight)
1284 uBestWeight = uMoveWeights[uMoveNum];
1285 uBestIndex = uIndex;
1291 // If we threw every move out (there is no total weight) then we
1292 // don't have a book move here.
1294 if (0 == uTotalWeight)
1296 ASSERT(mvBook.uMove == 0);
1301 // We are done assigning weights to moves. If we are in tournament
1302 // mode we should have picked a best one. If so, return it.
1304 if ((TRUE == g_fTournamentMode) && (bvFlags & BOOKMOVE_SELECT_MOVE))
1306 if (uBestIndex != (ULONG)-1)
1308 ASSERT(uBestWeight);
1309 _BookSeek(uBestIndex);
1311 mvBook = entry.mvNext;
1312 ASSERT(uBestIndex >= uLow);
1313 szName = szNames[uBestIndex - uLow];
1319 // We didn't throw everything out... so pick a move randomly based
1320 // on the weights assigned.
1322 uRandom = (randomMT() % uTotalWeight) + 1;
1323 if (bvFlags & BOOKMOVE_DUMP)
1325 u64Sig = pos->u64PawnSig ^ pos->u64NonPawnSig;
1326 Trace(" BookMove: List of moves in position 0x%"
1327 COMPILER_LONGLONG_HEX_FORMAT ":\n",
1329 for (x = 0; x < uMoveNum; x++)
1331 _BookSeek(uLow + x);
1333 ASSERT(entry.u64Sig == u64Sig);
1336 if (uMoveWeights[x] != 0)
1338 d = (double)uMoveWeights[x] / (double)uTotalWeight;
1341 Trace("%02u. %-4s [+%u =%u -%u] \"%s\" @ %5.3f\n",
1343 MoveToSan(entry.mvNext, pos),
1344 entry.uWins, entry.uDraws, entry.uLosses,
1348 Trace(" Total weight was %u.\n\n", uTotalWeight);
1349 if (!(bvFlags & BOOKMOVE_SELECT_MOVE))
1357 // Based on the weighted list and the random number we
1358 // selected pick a move.
1360 for (uIndex = 0, uCounter = 0;
1364 uCounter += uMoveWeights[uIndex];
1366 if (uCounter >= uRandom)
1368 _BookSeek(uLow + uIndex);
1370 mvBook = entry.mvNext;
1371 szName = szNames[uIndex];
1376 UtilPanic(SHOULD_NOT_GET_HERE,
1377 NULL, NULL, NULL, NULL,
1378 __FILE__, __LINE__);
1382 g_Options.fShouldAnnounceOpening = fOldValue;
1387 ASSERT(bvFlags & BOOKMOVE_SELECT_MOVE);
1390 // We could be probing the book after a predicted ponder move (in
1391 // which case we will have forced fAnnounceOpening to FALSE because
1392 // we don't want to whisper two book moves in a row).
1394 if (FALSE != g_Options.fShouldAnnounceOpening) {
1395 Trace("tellothers book move %s [+%u =%u -%u]\n",
1396 MoveToSan(entry.mvNext, pos),
1397 entry.uWins, entry.uDraws, entry.uLosses);
1403 SystemFreeMemory(ctx);
1410 //+---------------------------------------------------------------------------
1412 // Function: BookEdit
1414 // Synopsis: Giant hack to edit the book.
1416 // Arguments: POSITION *pos
1420 //+---------------------------------------------------------------------------
1422 BookEdit(POSITION *pos)
1424 int iTotalEntries = _BookCount();
1427 FILE *pfLearn = NULL;
1428 FLAG fFound = FALSE;
1429 static BOOK_ENTRY entry;
1438 FLAG fOldValue = g_Options.fAnnounceOpening;
1440 g_Options.fAnnounceOpening = FALSE;
1443 // We cannot probe the opening book if membook is non-NULL (which
1444 // indicates the opening book is currently in-memory and still being
1447 if (g_pMemBook != NULL)
1456 iTotalEntries = _BookCount();
1457 if (0 == iTotalEntries)
1459 Trace("BookDump: Opening book \"%s\" is empty.\n",
1460 g_Options.szBookName);
1464 ASSERT(g_fdBook == -1);
1467 Trace("BookDump: Could not open the book.\n");
1472 // Open the learning recorder
1474 pfLearn = fopen(BOOK_EDITING_RECORD, "a+b");
1475 if (NULL == pfLearn)
1477 Trace("BookEdit: Warning: can't open %s to save learning.\n",
1478 BOOK_EDITING_RECORD);
1481 if (!_BookFindSig(pos->u64Sig, &iLow, &iHigh))
1483 Trace("BookEdit: Position %I64x is not in the book.\n");
1487 iNumMoves = iHigh - iLow + 1;
1491 printf("
\e[H
\e[J\n");
1492 Trace("--EDITING OPENING BOOK--\n\n");
1493 Trace("There are %u positions in the opening book \"%s\".\n",
1494 iTotalEntries, g_Options.szBookName);
1495 Trace("There are %u book moves in this position, %u to %u:\n",
1496 iNumMoves, iLow, iHigh);
1498 for (x = 0, iSum = 0;
1502 _BookSeek(iLow + x);
1506 // If the disabled flag is asserted, do not allow this
1507 // move to be played. If the disabled flag is not
1508 // asserted, calculate a weight for this move.
1510 if ((entry.bvFlags & FLAG_DISABLED) ||
1511 (entry.bvFlags & FLAG_DELETED))
1517 iWeights[x] = _BookComputeWeight(&entry);
1519 iSum += iWeights[x];
1526 _BookSeek(iLow + x);
1529 fl = (float)iWeights[x];
1545 Trace("%02u. %6u..%6u = %4s [+%6u =%6u -%6u] @ ~%6.3f%% | %s%s",
1549 MoveToSAN(entry.mvNext, pos),
1554 (entry.bvFlags & FLAG_DISABLED) ? "NEV" : "",
1555 (entry.bvFlags & FLAG_ALWAYSPLAY) ? "ALW" : "");
1567 _BookSeek(iLow + iCurrent);
1569 Trace("Command (q, p, n, +, =, -, h, l, e, a, d, ?): ");
1570 pCh = BlockingRead();
1572 switch(tolower(*pCh))
1575 Trace("--DONE EDITING OPENING BOOK--\n\n");
1579 if (iCurrent < 0) iCurrent = iNumMoves - 1;
1583 if (iCurrent >= iNumMoves) iCurrent = 0;
1586 Trace("Enter new wincount: ");
1587 pArg = BlockingRead();
1592 _BookSeek(iLow + iCurrent);
1594 if (NULL != pfLearn)
1596 fprintf(pfLearn, "%I64x (%x) + %u // %s\n",
1603 SystemFreeMemory(pArg);
1606 Trace("Enter new drawcount: ");
1607 pArg = BlockingRead();
1612 _BookSeek(iLow + iCurrent);
1614 if (NULL != pfLearn)
1616 fprintf(pfLearn, "%I64x (%x) = %u // %s\n",
1623 SystemFreeMemory(pArg);
1626 Trace("Enter new losscount: ");
1627 pArg = BlockingRead();
1631 entry.iLosses = arg;
1632 _BookSeek(iLow + iCurrent);
1634 if (NULL != pfLearn)
1636 fprintf(pfLearn, "%I64x (%x) - %u // %s\n",
1643 SystemFreeMemory(pArg);
1646 if (entry.bvFlags & FLAG_DISABLED)
1648 entry.bvFlags &= ~FLAG_DISABLED;
1652 entry.bvFlags |= FLAG_DISABLED;
1653 entry.bvFlags &= ~FLAG_ALWAYSPLAY;
1655 _BookSeek(iLow + iCurrent);
1657 if (NULL != pfLearn)
1659 fprintf(pfLearn, "%I64x (%x) FLAGS %x // %s\n",
1667 if (entry.bvFlags & FLAG_ALWAYSPLAY)
1669 entry.bvFlags &= ~FLAG_ALWAYSPLAY;
1673 entry.bvFlags |= FLAG_ALWAYSPLAY;
1674 entry.bvFlags &= ~FLAG_DISABLED;
1676 _BookSeek(iLow + iCurrent);
1678 if (NULL != pfLearn)
1680 fprintf(pfLearn, "%I64x (%x) FLAGS %x // %s\n",
1688 PreferPosition(pos, pfLearn, pos->u64Sig, -1);
1689 Trace("I hate this position.\n");
1692 PreferPosition(pos, pfLearn, pos->u64Sig, 0);
1693 Trace("I'm indifferent towards this position.\n");
1696 PreferPosition(pos, pfLearn, pos->u64Sig, 1);
1697 Trace("I love this position!\n");
1702 " +) adjust wins =) adjust draws -) adjust losses\n"
1703 " h) hate this pos i) indifferent l) love this pos\n\n"
1705 " e) enable/disable\n"
1706 " a) always/sometimes play\n\n"
1708 " p) previous move n) next move q) quit editing\n\n");
1713 SystemFreeMemory(pCh);
1718 g_Options.fAnnounceOpening = fOldValue;
1720 ASSERT(g_fdBook != -1);
1723 if (NULL != pfLearn)
1738 Routine description:
1740 Builds a new opening book. This routine is called by
1741 LearnPGNOpenings in movelist.c after it has played through one PGN
1742 game. g_MoveList is loaded with position signatures and move
1743 structs representing this game. Our goal is to parse these and
1744 create/update the membook entries to reflect the game played and
1763 GAME_RESULT result = GetGameResult();
1765 if (result.eResult != RESULT_WHITE_WON &&
1766 result.eResult != RESULT_BLACK_WON &&
1767 result.eResult != RESULT_DRAW) {
1772 // If the membook has not yet been allocated, do it now.
1774 if (g_pMemBook == NULL)
1776 ASSERT((g_uMemBookSize & (g_uMemBookSize - 1)) == 0);
1777 g_uMemBookCount = 0;
1778 g_pMemBookHead = NULL;
1779 g_pMemBook = (BOOK_ENTRY *)
1780 SystemAllocateMemory(g_uMemBookSize * sizeof(BOOK_ENTRY));
1781 g_pMemBookHead = g_pMemBook;
1782 ASSERT(g_pMemBook != NULL);
1786 // Consider each position in the game we have played up until now.
1789 q = GetNthOfficialMoveRecord(u);
1790 while((q != NULL) && (q->mv.uMove != 0))
1792 entry.u64Sig = q->u64PositionSigBeforeMove;
1793 entry.mvNext = q->mv;
1794 entry.u64NextSig = q->u64PositionSigAfterMove;
1797 // Have we ever seen this position before?
1799 pEntry = _BookHashFind(&entry);
1800 if (0 != pEntry->u64Sig)
1803 // Yes we have seen this signature-move before. Update the
1804 // win/draw/loss counter at this entry in the membook.
1806 ASSERT(pEntry->u64Sig == entry.u64Sig);
1807 ASSERT(pEntry->mvNext.uMove == entry.mvNext.uMove);
1808 if (((result.eResult == RESULT_BLACK_WON) &&
1809 (GET_COLOR(entry.mvNext.pMoved) == BLACK)) ||
1810 ((result.eResult == RESULT_WHITE_WON) &&
1811 (GET_COLOR(entry.mvNext.pMoved) == WHITE)))
1817 if (RESULT_DRAW == result.eResult)
1829 // Nope never seen this signature-move entry before, create a new
1830 // entry for it and add it to the membook.
1834 memset(pEntry, 0, sizeof(pEntry));
1835 pEntry->u64Sig = entry.u64Sig;
1836 pEntry->mvNext = entry.mvNext;
1837 pEntry->u64NextSig = entry.u64NextSig;
1839 if (((result.eResult == RESULT_BLACK_WON) &&
1840 (GET_COLOR(entry.mvNext.pMoved) == BLACK)) ||
1841 ((result.eResult == RESULT_WHITE_WON) &&
1842 (GET_COLOR(entry.mvNext.pMoved) == WHITE)) )
1848 if (RESULT_DRAW == result.eResult)
1854 pEntry->uLosses = 1;
1860 // If the in memory buffer is getting too full, throw out
1861 // all positions that occur only once in it in order to
1862 // compact it. We should try to limit this because it can
1866 dFull = (double)g_uMemBookCount;
1867 dFull /= (double)g_uMemBookSize;
1869 while (dFull > 97.0)
1874 dFull = (double)g_uMemBookCount;
1875 dFull /= (double)g_uMemBookSize;
1880 q = GetNthOfficialMoveRecord(u);
1887 _BookToDisk(ULONG uStrainLimit)
1890 Routine description:
1904 CHAR *szTempBook = "tempbook.bin";
1905 CHAR szCmd[SMALL_STRING_LEN_CHAR];
1909 ULONG uOld_BookCount;
1910 ULONG uNew_BookCount;
1911 BOOK_ENTRY *pOldBook = NULL;
1912 BOOK_ENTRY *pNewBook = NULL;
1913 FLAG fMyRetVal = FALSE;
1916 if (!strlen(g_Options.szBookName))
1920 i = g_uMemBookCount;
1923 // Drops all book entries with less than limit occurrances
1925 _StrainMemBook(uStrainLimit);
1928 // Drops all unused entries
1930 Trace("Stage 2: Compacting the book.\n");
1934 // Sorts the by signature.
1936 Trace("Stage 3: Sorting the book.\n");
1937 _QuickSortBook(0, g_uMemBookCount);
1938 uNew_BookCount = g_uMemBookCount;
1941 // Create the temporary book on the disk.
1943 (void)unlink(szTempBook);
1944 if (SystemDoesFileExist(szTempBook))
1946 Trace("_BookToDisk: Can't create a temporary book!\n");
1950 ifd = open(szTempBook,
1951 O_RDWR | O_CREAT | O_BINARY,
1952 _S_IREAD | _S_IWRITE);
1955 Trace("_BookToDisk: Can't create a temporary book!\n");
1963 // Count the entries in the old book on disk.
1966 if ((TRUE == _VerifyBook(g_Options.szBookName)) && (TRUE == _BookOpen()))
1968 if (0 == stat(g_Options.szBookName, &s))
1970 uOld_BookCount = (s.st_size / sizeof(BOOK_ENTRY));
1975 // Merge the old book (on disk) with the new (in memory) in the
1978 Trace("Merging book lines and writing to disk...\n");
1981 pOldBook = pNewBook = NULL;
1984 // Read the next old book entry.
1987 if (uOldBook < uOld_BookCount)
1989 if ((int)(uOldBook * sizeof(BOOK_ENTRY)) !=
1990 lseek(g_fdBook, (uOldBook * sizeof(BOOK_ENTRY)), SEEK_SET))
1992 Trace("_BookToDisk: Cannot seek to byte offset %u in old "
1993 "book.\n", uOldBook * sizeof(BOOK_ENTRY));
1998 if (sizeof(BOOK_ENTRY) != read(g_fdBook,
2000 sizeof(BOOK_ENTRY)))
2002 Trace("_BookToDisk: Can't read error in old book, trying "
2011 // Get the next new book entry
2013 if (uNewBook < uNew_BookCount)
2015 ASSERT(0 != g_pMemBook[uNewBook].u64Sig);
2016 pNewBook = &(g_pMemBook[uNewBook]);
2020 // Decide which to write into the merged (temporary) book we
2021 // are constructing next...
2023 if ((NULL == pNewBook) && (NULL == pOldBook))
2027 else if ((NULL == pNewBook) && (NULL != pOldBook))
2031 else if ((NULL != pNewBook) && (NULL == pOldBook))
2037 if (pOldBook->u64Sig < pNewBook->u64Sig)
2041 else if (pOldBook->u64Sig > pNewBook->u64Sig)
2047 ASSERT(pOldBook->u64Sig == pNewBook->u64Sig);
2048 if (pOldBook->mvNext.uMove < pNewBook->mvNext.uMove)
2052 else if (pOldBook->mvNext.uMove > pNewBook->mvNext.uMove)
2059 // Same position and move...
2061 pNewBook->uWins += pOldBook->uWins;
2062 pNewBook->uDraws += pOldBook->uDraws;
2063 pNewBook->uLosses += pOldBook->uLosses;
2064 pNewBook->bvFlags |= pOldBook->bvFlags;
2071 UtilPanic(SHOULD_NOT_GET_HERE,
2072 NULL, NULL, NULL, NULL,
2073 __FILE__, __LINE__);
2077 if (-1 == lseek(ifd, 0, SEEK_END))
2081 if (sizeof(BOOK_ENTRY) != write(ifd, pOldBook, sizeof(BOOK_ENTRY)))
2083 Trace("_BookToDisk: Error writing new book, aborting procedure.\n");
2090 if (-1 == lseek(ifd, 0, SEEK_END))
2094 if (sizeof(BOOK_ENTRY) != write(ifd, pNewBook, sizeof(BOOK_ENTRY)))
2096 Trace("_BookToDisk: Error writing new book, aborting procedure."
2108 SystemFreeMemory(g_pMemBook);
2110 g_pMemBookHead = NULL;
2111 g_uMemBookCount = 0;
2115 // Verify the consistency of the new book before klobbering the old one
2117 if (FALSE == _VerifyBook(szTempBook))
2119 Trace("_BookToDisk: The book I just wrote is corrupt!!!\n");
2126 // HACKHACK: put this in system, use CopyFile in win32
2129 _snprintf(szCmd, 255, "move %s %s%c", szTempBook,
2130 g_Options.szBookName, 0);
2132 snprintf(szCmd, 255, "/bin/mv %s %s%c", szTempBook,
2133 g_Options.szBookName, 0);
2138 if (TRUE == fMyRetVal)
2140 Trace("Book successfully built.\n");
2147 _LearnPgnOpenings(CHAR *szFilename, ULONG uStrainLimit)
2150 Routine description:
2168 if (FALSE == SystemDoesFileExist(szFilename))
2170 Trace("LearnPgnOpenings: file \"%s\" doesn't exist.\n",
2175 if (0 != stat(szFilename, &s))
2177 Trace("LearnPgnOpenings: Can't read file \"%s\".\n",
2182 pf = fopen(szFilename, "rb");
2185 Trace("LearnPgnOpenings: error reading file %s.\n",
2190 CleanupHashSystem();
2192 Trace("Stage 1: reading and parsing PGN\n");
2193 while((p = ReadNextGameFromPgnFile(pf)) != NULL)
2195 if (TRUE == LoadPgn(p))
2198 d = (double)ftell(pf);
2199 d /= (double)s.st_size;
2201 printf("%u/%u (%5.2f%% done) ",
2202 (unsigned)ftell(pf), (unsigned)s.st_size, d);
2203 d = (double)g_uMemBookCount;
2204 d /= (double)g_uMemBookSize;
2206 printf("[membook %5.2f%% full]\r", d);
2208 SystemFreeMemory(p);
2212 _BookToDisk(uStrainLimit);
2214 InitializeHashSystem();
2222 _UpdateUserOpeningList(INT iResult)
2224 int fd = open(OPENING_LEARNING_FILENAME,
2225 O_RDWR | O_CREAT | O_BINARY | O_RANDOM,
2226 _S_IREAD | _S_IWRITE);
2229 OPENING_LEARNING_ENTRY ole;
2234 // If we failed to open it, abort.
2236 if (fd < 0) goto end;
2239 // Stat it to see how many entries there are.
2241 if (0 != stat(OPENING_LEARNING_FILENAME, &s))
2244 Trace("UpdateUserOpeningList: Corrupt %s file.\n",
2245 OPENING_LEARNING_FILENAME);
2248 if ((s.st_size % sizeof(OPENING_LEARNING_ENTRY)) != 0)
2251 Trace("UpdateUserOpeningList: Corrupt %s file.\n",
2252 OPENING_LEARNING_FILENAME);
2255 uNumRecords = s.st_size / sizeof(OPENING_LEARNING_ENTRY);
2258 // Read each entry looking for a match.
2260 for (u = 0; u < uNumRecords; u++)
2262 if (sizeof(OPENING_LEARNING_ENTRY) !=
2263 read(fd, &ole, sizeof(OPENING_LEARNING_ENTRY)))
2265 Trace("UpdateUserOpeningList: Read error.\n");
2269 if (ole.u64Sig == g_Options.u64OpeningSig)
2272 // Found it, so update and write back in place.
2292 uPos = u * sizeof(OPENING_LEARNING_ENTRY);
2293 if (uPos != lseek(fd, uPos, SEEK_SET))
2295 Trace("UpdateUserOpeningList: Seek error.\n");
2298 if (sizeof(OPENING_LEARNING_ENTRY) !=
2299 write(fd, &ole, sizeof(OPENING_LEARNING_ENTRY)))
2301 Trace("UpdateUserOpeningList: Write error.\n");
2308 // We never found it, so add one at the end.
2310 memset(&ole, 0, sizeof(ole));
2311 ole.u64Sig = g_Options.u64OpeningSig;
2330 if (-1 == lseek(fd, 0, SEEK_END))
2332 Trace("UpdateUserOpeningList: Seek error.\n");
2336 if (sizeof(OPENING_LEARNING_ENTRY) !=
2337 write(fd, &ole, sizeof(OPENING_LEARNING_ENTRY)))
2339 Trace("UpdateUserOpeningList: Append error.\n");
2352 //+----------------------------------------------------------------------------
2354 // Function: BookLearn
2356 // Synopsis: Handles book learning at the end of a game based on the
2357 // result of the game and the calibre of the opponent.
2359 // Arguments: int iResult
2363 //+----------------------------------------------------------------------------
2365 BookLearn(INT iResult)
2368 unsigned int iChanged = 0;
2369 int iTotalEntries = _BookCount();
2370 int iLow, iHigh, iCurrent = 0;
2372 FLAG fFound = FALSE;
2375 if (g_Options.ePlayMode == I_PLAY_BLACK)
2377 Trace("BookLearn: I was playing black\n");
2379 else if (g_Options.ePlayMode == I_PLAY_WHITE)
2381 Trace("BookLearn: I was playing white\n");
2386 Trace("BookLearn: Black won.\n");
2388 else if (iResult == 1)
2390 Trace("BookLearn: White won.\n");
2394 Trace("BookLearn: The game was a draw.\n");
2399 // Should we learn anything?
2403 // Not if the game was bullet
2405 if ((g_Options.eGameType == GAME_BULLET) ||
2406 (g_Options.eGameType == GAME_UNKNOWN))
2408 Trace("BookLearn: Game was too fast or unknown speed, don't learn.\n");
2413 // Not if the game wasn't rated.
2415 if ((g_Options.fGameIsRated == FALSE))
2417 Trace("BookLearn: Game was unrated, don't learn.\n");
2422 // Not if someone lost on time or because of forfeit.
2424 if ((NULL != strstr(g_Options.szDescription, "forfeit")) ||
2425 (NULL != strstr(g_Options.szDescription, "time")) ||
2426 (NULL != strstr(g_Options.szDescription, "abort")))
2428 Trace("BookLearn: Loss by forfeit/abort, don't learn.\n");
2433 // Not if we won but the opponent was weak.
2435 if (((g_Options.ePlayMode == I_PLAY_WHITE) && (iResult == 1)) ||
2436 ((g_Options.ePlayMode == I_PLAY_BLACK) && (iResult == -1)))
2439 Trace("BookLearn: I won, my opponent's rating was %u, "
2440 "my rating is %u.\n",
2441 g_Options.iOpponentsRating,
2442 g_Options.iMyRating);
2445 if (g_Options.iOpponentsRating < g_Options.iMyRating - 300)
2447 Trace("BookLearn: Opponent sucks, not learning.\n");
2453 // Not if we lost but the opponent was strong.
2455 if (((g_Options.ePlayMode == I_PLAY_BLACK) && (iResult == 1)) ||
2456 ((g_Options.ePlayMode == I_PLAY_WHITE) && (iResult == -1)))
2458 Trace("BookLearn: I lost, my opponent's rating was %u, "
2459 "my rating is %u.\n",
2460 g_Options.iOpponentsRating,
2461 g_Options.iMyRating);
2463 if (g_Options.iOpponentsRating > g_Options.iMyRating + 300)
2465 Trace("BookLearn: Opponent kicks ass, not learning.\n");
2468 ToXboard("tellics say Nice game!\n");
2472 // Yes, we have decided to learn.
2474 Trace("BookLearn: Revising opening book line...\n");
2477 // 1. Save the learning on a user-friendly list...
2479 UpdateUserOpeningList(iResult);
2482 // 2. Save the learning in the real opening book...
2484 if ((0 == iTotalEntries) ||
2485 (FALSE == _BookOpen()))
2487 Trace("BookLearn: Could not open the book.\n");
2492 // Look at the moves in the move list.
2495 iIndex < g_iMoveNum - 1;
2499 // Only learn on the first 30 moves.
2501 if (iIndex > 60) break;
2503 Trace("BookLearn: Seeking %I64x with move %s (%x) in the book.\n",
2504 g_MoveList[iIndex].u64Sig,
2505 MoveToICS(g_MoveList[iIndex + 1].mv),
2506 g_MoveList[iIndex + 1].mv.move);
2509 // Binary search for sig in the book, this sets iLow and iHigh.
2511 if (0 != _BookFindSig(g_MoveList[iIndex].u64Sig, &iLow, &iHigh))
2513 Trace("BookLearn: Found a (P) : %I64x at %u.\n",
2514 g_MoveList[iIndex].u64Sig, iCurrent);
2517 // Current now points to the first book entry matching
2518 // the sig... look for the entry matching the move too.
2520 for (x = iLow; x <= iHigh; x++)
2525 ASSERT(entry.u64Sig ==
2526 g_MoveList[iIndex].u64Sig);
2529 // If we match the move and signature, update the entry
2530 // based on this game's results.
2532 if (entry.mvNext.move == g_MoveList[iIndex + 1].mv.move)
2534 Trace("BookLearn: Matched mv : %x at %u.\n",
2535 g_MoveList[iIndex + 1].mv.move, x);
2539 // Did the side making the move win in the end?
2541 if (((iResult == -1) &&
2542 (IS_BLACK(g_MoveList[iIndex + 1].mv.data.pMoved))) ||
2544 (IS_WHITE(g_MoveList[iIndex + 1].mv.data.pMoved))))
2546 Trace("BookLearn: Side moving won, enforce this "
2553 // No so it drew or lost.
2557 Trace("BookLearn: Game was a draw, increase "
2558 "drawishness of this book entry.\n");
2563 Trace("BookLearn: Side moving lost, discourage "
2564 "this book entry.\n");
2570 // Write it back into the book.
2576 // Done looking for this move.
2580 } // entry.move matches
2582 } // loop to look at all moves on entries w/ matching sig
2584 } // was sig found in book?
2586 } // main game move loop
2589 Trace("BookLearn: Changed %u book positions.\n", iChanged);
2594 //+----------------------------------------------------------------------------
2596 // Function: PreferPosition
2598 // Synopsis: Given a position, love, hate or be neutral towards it.
2600 // If the position is to be loved, all other book entries
2601 // that lead to it will be flagged "always play".
2603 // If the position is to be hated, all other book entries
2604 // that lead to it will be flagged "never play".
2606 // A neutral setting will cause all other book entries that
2607 // lead to have their flags cleared.
2609 // Arguments: POSITION pos - the pos, needed to make a FEN
2610 // FILE *fd - file to record permanently in
2611 // INT64 i64Targ - the signature to love/hate/neuter
2612 // int i : -1 means hate
2618 //+----------------------------------------------------------------------------
2620 PreferPosition(POSITION *pos, FILE *fd, INT64 i64Targ, int i)
2622 int iTotalEntries = _BookCount();
2625 static BOOK_ENTRY entry;
2627 ASSERT(pos->u64Sig == i64Targ);
2629 if (g_pMemBook != NULL)
2635 iTotalEntries = _BookCount();
2638 x < iTotalEntries - 1;
2641 if ((x % 10000) == 0)
2643 printf("%u / %u\r", x, iTotalEntries);
2648 if (entry.i64NextSig == i64Targ)
2656 entry.bvFlags = FLAG_DISABLED;
2660 fprintf(fd, "%I64x (%x) FLAGS %x // %s\n",
2673 fprintf(fd, "%I64x (%x) FLAGS %x // %s\n",
2682 entry.bvFlags = FLAG_ALWAYSPLAY;
2686 fprintf(fd, "%I64x (%x) FLAGS %x // %s\n",
2698 Trace("PreferPosition: Changed %u lines\n", iChanged);
2701 //+----------------------------------------------------------------------------
2703 // Function: ImportEditing
2705 // Synopsis: Affect the opening book file based on an old editing record
2706 // (.EDT file). Apply editing / learning record to the new book
2709 // Arguments: char *szFile
2713 //+----------------------------------------------------------------------------
2715 ImportEditing(char *szFile)
2717 FILE *p = fopen(szFile, "r");
2718 FLAG fMyRetVal = FALSE;
2719 static char szLine[512];
2725 int iLow, iHigh, iCurrent = 0;
2731 // We cannot probe the opening book if membook is non-NULL (which
2732 // indicates the opening book is currently in-memory and still being
2735 if (g_pMemBook != NULL)
2743 iTotalEntries = _BookCount();
2744 if (0 == iTotalEntries)
2746 Trace("BookMove: Opening book \"%s\" is empty.\n",
2747 g_Options.szBookName);
2751 if (FALSE == _BookOpen())
2753 Trace("BookMove: Could not open the book.\n");
2759 while(fgets(szLine, 511, p))
2761 if (_snscanf(szLine, 511, "%I64x (%x) %s",
2770 Trace("Searching for %I64x move %x\n",
2774 if (_BookFindSig(u64Sig, &iLow, &iHigh))
2777 // Walk forward looking for the right move.
2779 memset(&entry, 0, sizeof(entry));
2786 ASSERT(entry.u64Sig == u64Sig);
2788 if (entry.mvNext.move == mv.move) break;
2792 // Did we find the move?
2794 if (entry.mvNext.move != mv.move)
2796 Trace("Move %x not present in book at position %I64x\n",
2802 // Yes. What should we do with it?
2804 if (!STRNCMP(szOpcode, "FLAGS", 5))
2806 if (_snscanf(szLine, 511, "%I64x (%x) %s %x",
2812 if (iArg >= 0) entry.bvFlags = iArg;
2819 else if (!STRNCMP(szOpcode, "+", 1))
2821 if (_snscanf(szLine, 511, "%I64x (%x) %s %u",
2827 if (iArg >= 0) entry.iWins = iArg;
2834 else if (!STRNCMP(szOpcode, "=", 1))
2836 if (_snscanf(szLine, 511, "%I64x (%x) %s %u",
2842 if (iArg >= 0) entry.iDraws = iArg;
2849 else if (!STRNCMP(szOpcode, "-", 1))
2851 if (_snscanf(szLine, 511, "%I64x (%x) %s %u",
2857 if (iArg >= 0) entry.iLosses = iArg;
2870 // Write it back to the book
2873 _BookRead(&klobber);
2875 if ((entry.u64Sig ==
2877 (entry.mvNext.move ==
2878 klobber.mvNext.move))
2895 _FindTerminalPositions(CHAR *szFilename,
2896 ULONG uPlyBeforeMate)
2899 Routine description:
2904 ULONG uMovesBeforeMate
2916 SEARCHER_THREAD_CONTEXT *ctx;
2922 ctx = SystemAllocateMemory(sizeof(SEARCHER_THREAD_CONTEXT));
2925 Trace("Out of memory.\n");
2929 if (FALSE == SystemDoesFileExist(szFilename))
2931 Trace("FindTerminalPositions: file \"%s\" doesn't exist.\n",
2936 if (0 != stat(szFilename, &s))
2938 Trace("FindTerminalPositions: Can't read file \"%s\".\n",
2943 pf = fopen(szFilename, "rb");
2946 Trace("FindTerminalPositions: error reading file %s.\n",
2952 while((p = ReadNextGameFromPgnFile(pf)) != NULL)
2954 if (TRUE == LoadPgn(p))
2956 pos = GetRootPosition();
2958 fInCheck = InCheck(pos, pos->uToMove);
2959 if (TRUE == fInCheck)
2961 InitializeSearcherContext(pos, ctx);
2962 GenerateMoves(ctx, mv, GENERATE_ESCAPES);
2963 if (0 == MOVE_COUNT(ctx, 0))
2968 if (FALSE == OfficiallyTakebackMove())
2974 pos = GetRootPosition();
2975 InitializeSearcherContext(pos, ctx);
2977 (void)Eval(ctx, -INFINITY, +INFINITY);
2978 Trace("King safeties: W=%d, B=%d\n",
2979 ctx->sPlyInfo[ctx->uPly].iKingScore[WHITE],
2980 ctx->sPlyInfo[ctx->uPly].iKingScore[BLACK]);
2984 SystemFreeMemory(p);
2992 SystemFreeMemory(ctx);
2999 COMMAND(BookCommand)
3004 ASSERT(!STRNCMPI(argv[0], "BOOK", 4));
3007 Trace("Available opening book commands:\n\n"
3008 " book name : set opening book file\n"
3009 " book import : import PGN or EDT data to book file\n"
3010 " book dump learning : show book learning\n"
3011 " book dump moves : show book moves in current position\n"
3012 " book edit : edit book moves in current position\n"
3013 " book tourn : view / toggle tournament mode\n"
3014 " book openings : read opening line names PGN file\n"
3015 " book hackhack : read PGN files looking for mate\n"
3021 if (!STRNCMPI(szOpcode, "NAME", 4))
3024 // book name <filename> : set opening book to use
3028 Trace("Usage: book name <required-filename>\n");
3031 strncpy(g_Options.szBookName, argv[2], SMALL_STRING_LEN_CHAR);
3032 Trace("Opening book name set to \"%s\"\n", argv[2]);
3034 else if (!STRNCMPI(argv[1], "DUMP", 4))
3037 // book dump : show book dump help
3041 Trace("Available book dump commands:\n"
3042 " book dump learning : show book learning\n"
3043 " book dump moves : show book moves in current"
3050 if (!STRNCMPI(argv[2], "LEARNING", 5))
3053 // book dump learning : show book learning on stdout
3055 _DumpUserOpeningList();
3060 // book dump moves : show book moves in this position
3062 BookMove(pos, BOOKMOVE_DUMP);
3065 else if (!STRNCMPI(argv[1], "TOURNAMENTMODE", 5))
3070 // book tourn : show current tournament mode setting
3072 if (g_fTournamentMode == TRUE)
3074 Trace("Tournament mode is currently TRUE.\n");
3078 Trace("Tournament mode is currently FALSE.\n");
3084 // book tourn <t|f> : set tournament mode on or off
3086 if (tolower(*argv[2]) == 't')
3088 g_fTournamentMode = TRUE;
3089 Trace("Tournament mode set to TRUE.\n");
3091 else if (tolower(*argv[2]) == 'f')
3093 g_fTournamentMode = FALSE;
3094 Trace("Tournament mode set to FALSE.\n");
3098 Trace("Usage: book tournamentmode [<true | false>]\n");
3104 else if (!STRNCMPI(argv[1], "EDIT", 4))
3107 // book edit : edit book moves in this position
3114 else if (!STRNCMPI(argv[1], "HACKHACK", 8))
3118 Trace("Usage: book hackhack <required-filename>\n");
3121 _FindTerminalPositions(argv[2], 4);
3124 else if (!STRNCMPI(argv[1], "OPENINGS", 8))
3127 // book openings : read book opening names file
3131 Trace("Usage: book openings <required-filename>\n");
3135 if (FALSE == SystemDoesFileExist(argv[2]))
3137 Trace("Error (file doesn't exist): %s\n", argv[2]);
3141 // Trace("%u opening lines named.\n", LearnOpeningNames(argv[2]));
3144 else if (!STRNCMPI(argv[1], "IMPORT", 6))
3147 // book import : create new book file from PGN -or-
3148 // apply editing / learning to a book file
3152 Trace("Usage: book import <required-filename>\n");
3156 if (FALSE == SystemDoesFileExist(argv[2]))
3158 Trace("Error (file doesn't exist): %s\n", argv[2]);
3162 pDot = strrchr(argv[2], '.');
3165 Trace("Error (unknown filetype): %s\n", argv[2]);
3169 if (!STRNCMPI(pDot, ".pgn", 4))
3171 if (TRUE == SystemDoesFileExist(g_Options.szBookName))
3173 Trace("The opening book %s already exists, "
3174 "importing new lines\n", g_Options.szBookName);
3175 _LearnPgnOpenings(argv[2], 0);
3179 Trace("The opening book %s does not exists, "
3180 "creating new book\n", g_Options.szBookName);
3181 _LearnPgnOpenings(argv[2], 1);
3185 else if (!STRNCMPI(pDot, ".edt", 4))
3187 if (FALSE == ImportEditing(argv[2]))
3189 Trace("An error occurred while importing .edt file.\n");
3195 Trace("Error (unknown filetype): %s\n", argv[2]);