summaryrefslogtreecommitdiff
path: root/src/book.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2016-06-01 18:58:58 -0700
committerScott Gasch <[email protected]>2016-06-01 18:58:58 -0700
commit3fd43cd5fcb22bb65bf2a92a25d95d801b11c9e0 (patch)
tree9b6443235d16ba17f094a1a1c7ae53d2bcb9267b /src/book.c
Initial checkin for typhoon chess engine.
Diffstat (limited to 'src/book.c')
-rwxr-xr-xsrc/book.c3198
1 files changed, 3198 insertions, 0 deletions
diff --git a/src/book.c b/src/book.c
new file mode 100755
index 0000000..217b4ad
--- /dev/null
+++ b/src/book.c
@@ -0,0 +1,3198 @@
+/**
+
+Copyright (c) Scott Gasch
+
+Module Name:
+
+ book.c
+
+Abstract:
+
+ Opening book routines: everything from probing the opening book to
+ building a new one to editing the current book. The structure of
+ a book entry is in book.h. The book is kept on disk and sorted by
+ position signature. There may be more than one entry per
+ signature -- there will be one entry per book move from that
+ positions so for example the initial position will have quite a
+ few entries.
+
+ Probing the book (BookMove, called from Think in brain.c) entails
+ binary searching the on disk book file for a matching signature,
+ reading all entries that match, computing a "weight" for each one,
+ and then randomly choosing one.
+
+ Building a new book is done with a temporary "membook" buffer in
+ memory. It requires quite a bit of memory so before the operation
+ takes place the main hash table and pawn hash table structures are
+ freed (see movelist.c). A PGN file is read one game at a time and
+ the moves are made on a POSITION struct. Once the game is over
+ the signature-move pairs are used to create new entries in the
+ membook. Finally the membook is sorted and written to disk. The
+ process can take a while, especially for a large PGN file or on a
+ machine with limited memory resources.
+
+Author:
+
+ Scott Gasch ([email protected]) 27 Jun 2004
+
+Revision History:
+
+ $Id: book.c 355 2008-07-01 15:46:43Z scott $
+
+**/
+
+#include "chess.h"
+
+static int g_fdBook = -1; // file descriptor for open book
+static ULONG g_uMemBookCount = 0; // count of entries in membook
+static BOOK_ENTRY *g_pMemBook = NULL; // allocated membook struct
+static BOOK_ENTRY *g_pMemBookHead = NULL; // ptr to 1st entry in membook
+
+//
+// Globals exported
+//
+FLAG g_fTournamentMode = FALSE; // are we in tournament mode
+ULONG g_uBookProbeFailures = 0; // how many times have we missed
+ULONG g_uMemBookSize = 0x1000000;
+OPENING_NAME_MAPPING *g_pOpeningNames = NULL; // opening names file
+
+static FLAG
+_VerifyBook(CHAR *szName)
+/**
+
+Routine description:
+
+Parameters:
+
+ CHAR *szName
+
+Return value:
+
+ static FLAG
+
+**/
+{
+ int fd = -1;
+ int iRet;
+ ULONG uPos;
+ ULONG uNum;
+ ULONG u;
+ struct stat s;
+ UINT64 u64LastSig;
+ ULONG uLastMove;
+ BOOK_ENTRY be;
+ FLAG fMyRetVal = FALSE;
+
+ fd = open(szName, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ {
+ Trace("VerifyBook: Can't open %s for read/write.\n", szName);
+ goto end;
+ }
+
+ if (0 != stat(szName, &s))
+ {
+ Trace("VerifyBook: Can't stat %s.\n", szName);
+ goto end;
+ }
+
+ if ((s.st_size % sizeof(BOOK_ENTRY)) != 0)
+ {
+ Trace("VerifyBook: Size of book is not a multiple of entry size.\n");
+ goto end;
+ }
+
+ //
+ // Foreach book entry...
+ //
+ uNum = (s.st_size / sizeof(BOOK_ENTRY));
+ u64LastSig = 0;
+ uLastMove = 0;
+ for (u = 0; u < uNum; u++)
+ {
+ //
+ // Seek to and read the entry.
+ //
+ uPos = u * sizeof(BOOK_ENTRY);
+ if (uPos != lseek(fd, uPos, SEEK_SET))
+ {
+ Trace("VerifyBook: Can't seek to entry %u (offset %u) in book.\n",
+ u, uPos);
+ goto end;
+ }
+ iRet = read(fd, &be, sizeof(BOOK_ENTRY));
+ if (sizeof(BOOK_ENTRY) != iRet)
+ {
+ Trace("VerifyBook: Can't read entry number %u (offset %u) in the "
+ "book. Read returned %u but I expected %u.\n",
+ u, uPos, iRet, sizeof(BOOK_ENTRY));
+ goto end;
+ }
+
+ //
+ // Make sure the book is sorted by signature
+ //
+ if (be.u64Sig < u64LastSig)
+ {
+ Trace("VerifyBook: Book signatures out-of-order at index %u "
+ "(offset %u).\n", u, uPos);
+ goto end;
+ }
+
+ //
+ // If our sig is equal to the last entry's then the move here must
+ // be greater than or equal to the last entry's move.
+ //
+ else if (be.u64Sig == u64LastSig)
+ {
+ if (be.mvNext.uMove < uLastMove)
+ {
+ Trace("VerifyBook: Book moves out-of-order at index %u "
+ "(offset %u).\n", u, uPos);
+ goto end;
+ }
+ }
+
+ u64LastSig = be.u64Sig;
+ uLastMove = be.mvNext.uMove;
+ }
+ fMyRetVal = TRUE;
+
+ end:
+ if (-1 != fd)
+ {
+ close(fd);
+ }
+ return(fMyRetVal);
+}
+
+
+static CHAR *
+_BookLineToString(UINT64 u64Line)
+/**
+
+Routine description:
+
+ Converts a position signature into an opening name, if known.
+
+Parameters:
+
+ UINT64 u64Line
+
+Return value:
+
+ static CHAR
+
+**/
+{
+ ULONG u = 0;
+
+ if (NULL == g_pOpeningNames) return(NULL);
+ while(g_pOpeningNames[u].szString != NULL)
+ {
+ if (g_pOpeningNames[u].u64Sig == u64Line)
+ {
+ return(g_pOpeningNames[u].szString);
+ }
+ u++;
+ }
+ return(NULL);
+}
+
+
+static void
+_DumpUserOpeningList(void)
+/**
+
+Routine description:
+
+ Called in response to a user "book dump openings" command, this
+ routine simply shows every named opening in the opening names
+ file. This file is not the opening book, it's an auxillary disk
+ file containing a list of moves and a name for the opening. It's
+ only used to announce the opening line played in a game and
+ annotate the PGN for a game stored in monsoon's logfile.
+
+Parameters:
+
+ void
+
+Return value:
+
+ static void
+
+**/
+{
+ int fd = open(OPENING_LEARNING_FILENAME,
+ O_RDWR | O_CREAT | O_BINARY | O_RANDOM,
+ _S_IREAD | _S_IWRITE);
+ ULONG uNumRecords;
+ ULONG u;
+ OPENING_LEARNING_ENTRY ole;
+ struct stat s;
+ CHAR *p;
+
+ //
+ // If we failed to open it, abort.
+ //
+ if (fd < 0) goto end;
+
+ //
+ // Stat it to see how many entries there are.
+ //
+ if (0 != stat(OPENING_LEARNING_FILENAME, &s))
+ {
+ ASSERT(FALSE);
+ Trace("UpdateUserOpeningList: Corrupt %s file.\n",
+ OPENING_LEARNING_FILENAME);
+ goto end;
+ }
+ if ((s.st_size % sizeof(OPENING_LEARNING_ENTRY)) != 0)
+ {
+ ASSERT(FALSE);
+ Trace("UpdateUserOpeningList: Corrupt %s file.\n",
+ OPENING_LEARNING_FILENAME);
+ goto end;
+ }
+ uNumRecords = s.st_size / sizeof(OPENING_LEARNING_ENTRY);
+ Trace("Learned data on %u openings:\n\n", uNumRecords);
+
+ //
+ // Read each entry looking for a match.
+ //
+ for (u = 0; u < uNumRecords; u++)
+ {
+ if (sizeof(OPENING_LEARNING_ENTRY) !=
+ read(fd, &ole, sizeof(OPENING_LEARNING_ENTRY)))
+ {
+ Trace("UpdateUserOpeningList: Read error.\n");
+ goto end;
+ }
+
+ p = _BookLineToString(ole.u64Sig);
+ if (NULL != p)
+ {
+ Trace("%-45s ww=%u, dr=%u, bw=%u\n",
+ p, ole.uWhiteWins, ole.uDraws, ole.uBlackWins);
+ }
+ }
+
+ end:
+ if (fd >= 0)
+ {
+ close(fd);
+ }
+}
+
+FLAG
+InitializeOpeningBook(void)
+/**
+
+Routine description:
+
+Parameters:
+
+ void
+
+Return value:
+
+ FLAG
+
+**/
+{
+ return(TRUE);
+}
+
+void
+ResetOpeningBook(void)
+/**
+
+Routine description:
+
+Parameters:
+
+ void
+
+Return value:
+
+ void
+
+**/
+{
+ g_uBookProbeFailures = 0;
+}
+
+void
+CleanupOpeningBook(void)
+/**
+
+Routine description:
+
+Parameters:
+
+ void
+
+Return value:
+
+ void
+
+**/
+{
+ ULONG x = 0;
+
+ //
+ // If we have read an "opening names" list and created a dynamic memory
+ // structure for it, free that now.
+ //
+ if (NULL != g_pOpeningNames)
+ {
+ while (g_pOpeningNames[x].szString != NULL)
+ {
+ SystemFreeMemory(g_pOpeningNames[x].szString);
+ g_pOpeningNames[x].szString = NULL;
+ g_pOpeningNames[x].u64Sig = 0;
+ x++;
+ }
+ SystemFreeMemory(g_pOpeningNames);
+ g_pOpeningNames = NULL;
+ }
+
+ //
+ // Cleanup membook if they quit in the middle of book building somehow
+ //
+ if (NULL != g_pMemBook)
+ {
+ ASSERT(FALSE);
+ SystemFreeMemory(g_pMemBook);
+ }
+}
+
+
+static FLAG
+_BookOpen(void)
+/**
+
+Routine description:
+
+Parameters:
+
+ void
+
+Return value:
+
+ static FLAG
+
+**/
+{
+ //
+ // Need to have set the book name before calling this.
+ //
+ if (!strlen(g_Options.szBookName))
+ {
+ return(FALSE);
+ }
+
+ //
+ // Open it.
+ //
+ ASSERT(g_fdBook == -1);
+ g_fdBook = open(g_Options.szBookName,
+ O_RDWR | O_CREAT |
+ O_RANDOM | O_BINARY | _S_IREAD | _S_IWRITE);
+ if (g_fdBook < 0)
+ {
+ Bug("_BookOpen: Failed to open book \"%s\".\n", g_Options.szBookName);
+ return(FALSE);
+ }
+
+ return((FLAG)(g_fdBook >= 0));
+}
+
+
+static void
+_BookClose(void)
+/**
+
+Routine description:
+
+Parameters:
+
+ void
+
+Return value:
+
+ static void
+
+**/
+{
+ if (-1 != g_fdBook)
+ {
+ (void)close(g_fdBook);
+ }
+ g_fdBook = -1;
+}
+
+
+static FLAG
+_BookSeek(ULONG n)
+/**
+
+Routine description:
+
+Parameters:
+
+ ULONG n
+
+Return value:
+
+ static FLAG
+
+**/
+{
+ ULONG uPos;
+
+ //
+ // During normal book operations we have an open file handle and we
+ // simply calculate and set its file pointer.
+ //
+ // However when in the process of building the opening book from a
+ // file full of PGN games all book commands access a large buffer in
+ // memory... so instead of setting a file pointer to a byte offset in
+ // the book file on disk, we set a normal pointer to an offset in
+ // this large book building buffer.
+ //
+ if (g_pMemBook == NULL)
+ {
+ uPos = n * sizeof(BOOK_ENTRY);
+ ASSERT(g_fdBook != -1);
+
+ if (uPos != lseek(g_fdBook, uPos, SEEK_SET))
+ {
+ Trace("_BookSeek: Cannot seek to byte offset %u\n", uPos);
+ return(FALSE);
+ }
+ }
+ else
+ {
+ ASSERT(g_uMemBookSize >= 0);
+ ASSERT(n < g_uMemBookSize);
+ g_pMemBookHead = g_pMemBook + n;
+ }
+ return(TRUE);
+}
+
+#if 0
+static FLAG
+_BookWrite(BOOK_ENTRY *x)
+/**
+
+Routine description:
+
+Parameters:
+
+ BOOK_ENTRY *x
+
+Return value:
+
+ static FLAG
+
+**/
+{
+ int iNum;
+
+ //
+ // If we are building the book, operate on memory
+ // buffer... otherwise operate on the book file.
+ //
+ if (g_pMemBook != NULL)
+ {
+ memcpy(g_pMemBookHead, x, sizeof(BOOK_ENTRY));
+ }
+ else
+ {
+ ASSERT(g_fdBook != -1);
+
+ iNum = write(g_fdBook, x, sizeof(BOOK_ENTRY));
+ if (sizeof(BOOK_ENTRY) != iNum)
+ {
+ Trace("_BookWrite: I/O error on fd=%d, got %d and expected %d\n",
+ g_fdBook, iNum, sizeof(BOOK_ENTRY));
+ ASSERT(FALSE);
+ return(FALSE);
+ }
+ }
+ return(TRUE);
+}
+#endif
+
+static FLAG
+_BookRead(BOOK_ENTRY *x)
+/**
+
+Routine description:
+
+Parameters:
+
+ BOOK_ENTRY *x
+
+Return value:
+
+ static FLAG
+
+**/
+{
+ int iNum;
+
+ if (g_pMemBook != NULL)
+ {
+ memcpy(x, g_pMemBookHead, sizeof(BOOK_ENTRY));
+ }
+ else
+ {
+ ASSERT(g_fdBook != -1);
+
+ iNum = read(g_fdBook, x, sizeof(BOOK_ENTRY));
+ if (sizeof(BOOK_ENTRY) != iNum)
+ {
+ Trace("_BookRead: I/O error on fd=%d, got %d and expected %d\n",
+ g_fdBook, iNum, sizeof(BOOK_ENTRY));
+ perror("_BookRead");
+ ASSERT(FALSE);
+ return(FALSE);
+ }
+ }
+ return(TRUE);
+}
+
+
+static ULONG
+_BookCount(void)
+/**
+
+Routine description:
+
+Parameters:
+
+ void
+
+Return value:
+
+ static ULONG
+
+**/
+{
+ struct stat s;
+
+ if (NULL != g_pMemBook)
+ {
+ return(g_uMemBookCount);
+ }
+ else
+ {
+ if (!strlen(g_Options.szBookName))
+ {
+ return(0);
+ }
+
+ if (0 != stat(g_Options.szBookName, &s))
+ {
+ Trace("_BookCount: Stat failed.\n");
+ ASSERT(FALSE);
+ return(0);
+ }
+ else
+ {
+ if ((s.st_size % sizeof(BOOK_ENTRY)) != 0)
+ {
+ Trace("_BookCount: Size is %u... not a mult of %u!\n",
+ s.st_size, sizeof(BOOK_ENTRY));
+ return(0);
+ }
+ return(s.st_size / sizeof(BOOK_ENTRY));
+ }
+ }
+}
+
+#if 0
+static FLAG
+_BookAppend(BOOK_ENTRY *x)
+/**
+
+Routine description:
+
+Parameters:
+
+ BOOK_ENTRY *x
+
+Return value:
+
+ static FLAG
+
+**/
+{
+ if (g_pMemBook != NULL)
+ {
+ g_pMemBookHead = g_pMemBook + g_uMemBookCount;
+ memcpy(g_pMemBookHead, x, sizeof(BOOK_ENTRY));
+ g_uMemBookCount++;
+ return(TRUE);
+ }
+ else
+ {
+ ASSERT(g_fdBook != -1);
+
+ if (-1 == lseek(g_fdBook, 0, SEEK_END))
+ {
+ ASSERT(FALSE);
+ return(FALSE);
+ }
+ return((FLAG)(sizeof(BOOK_ENTRY) ==
+ write(g_fdBook, x, sizeof(BOOK_ENTRY))));
+ }
+}
+#endif
+
+static ULONG
+_BookFindSig(UINT64 u64Sig, ULONG *puLow, ULONG *puHigh)
+/**
+
+Routine description:
+
+Parameters:
+
+ UINT64 u64Sig,
+ ULONG *puLow,
+ ULONG *puHigh
+
+Return value:
+
+ static ULONG
+
+**/
+{
+ ULONG uLow = 0;
+ ULONG uHigh = 0;
+ ULONG uCurrent;
+ ULONG uIndex;
+ ULONG uTotalEntries = _BookCount();
+ BOOK_ENTRY entry;
+ ULONG uRetVal = 0;
+ FLAG fFound = FALSE;
+
+ //
+ // Open the book.
+ //
+ if (0 == uTotalEntries)
+ {
+ Trace("BookFindSig: Opening book \"%s\" is empty.\n",
+ g_Options.szBookName);
+ goto end;
+ }
+ if (g_fdBook < 0)
+ {
+ Trace("BookFindSig: No book is opened!\n");
+ goto end;
+ }
+
+ //
+ // Perform a a binary search looking for an entry with a signature
+ // that matches this position's.
+ //
+ uLow = 0;
+ uHigh = uTotalEntries - 1;
+
+ while ((uLow <= uHigh) &&
+ (FALSE == fFound))
+ {
+ uCurrent = (uLow + uHigh) / 2;
+ _BookSeek(uCurrent);
+ _BookRead(&entry);
+
+ if (u64Sig == entry.u64Sig)
+ {
+ fFound = TRUE;
+ break;
+ }
+ else if (u64Sig < entry.u64Sig)
+ {
+ uHigh = uCurrent - 1;
+ }
+ else
+ {
+ ASSERT(u64Sig > entry.u64Sig);
+ uLow = uCurrent + 1;
+ }
+ }
+ if (FALSE == fFound)
+ {
+ goto end; // not found
+ }
+ ASSERT(uCurrent >= 0);
+ ASSERT(uCurrent < uTotalEntries);
+ uRetVal = uCurrent;
+
+ //
+ // Ok, we matched a signature at current. Since there can be many
+ // entries with the same checksum (many moves from the same board
+ // position), seek backwards until the first one matching the
+ // checksum.
+ //
+ uIndex = uCurrent - 1;
+ while (uIndex != (ULONG)-1)
+ {
+ _BookSeek(uIndex);
+ _BookRead(&entry);
+
+ if (entry.u64Sig != u64Sig)
+ {
+ uIndex++;
+#ifdef DEBUG
+ ASSERT(uIndex <= uCurrent);
+ _BookSeek(uIndex);
+ _BookRead(&entry);
+ ASSERT(entry.u64Sig == u64Sig);
+#endif
+ break;
+ }
+ uIndex--;
+ }
+ uLow = uIndex;
+
+ //
+ // Walk forward until the last book entry with a matching signature.
+ //
+ uIndex = uCurrent + 1;
+ while (uIndex < uTotalEntries)
+ {
+ _BookSeek(uIndex);
+ _BookRead(&entry);
+ if (entry.u64Sig != u64Sig)
+ {
+ uIndex--;
+#ifdef DEBUG
+ ASSERT(uIndex >= uCurrent);
+ _BookSeek(uIndex);
+ _BookRead(&entry);
+ ASSERT(entry.u64Sig == u64Sig);
+#endif
+ break;
+ }
+ uIndex++;
+ }
+ uHigh = uIndex;
+
+ end:
+ *puLow = uLow;
+ *puHigh = uHigh;
+ return(uRetVal);
+}
+
+
+static ULONG _BookComputeWeight(BOOK_ENTRY *e)
+/**
+
+Routine description:
+
+
+ Given a book entry, compute its weight. The higher the more
+ likely it will be chosen and played. Used in BookMove and
+ BookEdit. The formula is:
+
+ weight(mv) = winningness_coefficient * popularity_factor
+
+ wins
+ winningness_coefficient = ---------------
+ (wins + losses)
+
+ popularity_factor = wins + losses + draws
+
+Parameters:
+
+ BOOK_ENTRY *e
+
+Return value:
+
+ static ULONG
+
+**/
+{
+ ULONG uRet; // computed return value
+ double dWinning = 0.0; // winningness coefficient
+ double dPopular; // popularity factor
+
+ //
+ // dWinning is 0.0 now. The only way it gets a value is if we've
+ // seen this line win at least once.
+ //
+ if (e->uWins > 0)
+ {
+ dWinning = e->uWins;
+ dWinning /= (double)(e->uWins + e->uLosses);
+
+ //
+ // dWinning is now the ratio of wins to deterministic games
+ // (games that were not draws). If this is less than 20% then
+ // ignore the line (give the move a zero weight).
+ //
+ if (dWinning < 0.20) return(0);
+ }
+
+ //
+ // If we get here dWinning, the winningness coefficient, has been
+ // computed and is non-zero. Compute the rest of the weight term
+ // which is based on the popularity of the move (how many times it
+ // has been played).
+ //
+ dPopular = (double)(e->uWins + e->uLosses + e->uDraws);
+ uRet = (ULONG)(dPopular * dWinning);
+ return(uRet);
+}
+
+
+static void
+_QuickSortBook(ULONG uLeft, ULONG uRight)
+/**
+
+Routine description:
+
+Parameters:
+
+ ULONG uLeft,
+ ULONG uRight
+
+Return value:
+
+ static void
+
+**/
+{
+ UINT64 u64Pivot;
+ ULONG uPivotSec;
+ ULONG uL, uR, uMid;
+ BOOK_ENTRY temp;
+
+ ASSERT(uLeft >= 0);
+ ASSERT(uLeft < g_uMemBookSize);
+ ASSERT(uRight >= 0);
+ ASSERT(uRight < g_uMemBookSize);
+ if (uLeft < uRight)
+ {
+ //
+ // Select a pivot point.
+ //
+ u64Pivot = g_pMemBook[uLeft].u64Sig;
+ uPivotSec = g_pMemBook[uLeft].mvNext.uMove;
+
+ //
+ // Partition the space based on the pivot. Everything left of
+ // it is less, everything right is greater.
+ //
+ uL = uLeft;
+ uR = uRight;
+ while (uL < uR)
+ {
+ while (((g_pMemBook[uL].u64Sig < u64Pivot) ||
+ ((g_pMemBook[uL].u64Sig == u64Pivot) &&
+ (g_pMemBook[uL].mvNext.uMove <= uPivotSec))) &&
+ (uL <= uRight))
+ {
+ uL++;
+ }
+
+ while (((g_pMemBook[uR].u64Sig > u64Pivot) ||
+ ((g_pMemBook[uR].u64Sig == u64Pivot) &&
+ (g_pMemBook[uR].mvNext.uMove > uPivotSec))) &&
+ (uR >= uLeft))
+ {
+ uR--;
+ }
+
+ if (uL < uR)
+ {
+ temp = g_pMemBook[uL];
+ g_pMemBook[uL] = g_pMemBook[uR];
+ g_pMemBook[uR] = temp;
+ }
+ }
+ uMid = uR;
+
+ temp = g_pMemBook[uLeft];
+ g_pMemBook[uLeft] = g_pMemBook[uMid];
+ g_pMemBook[uMid] = temp;
+
+ //
+ // Recurse on the two halves
+ //
+ if (uLeft != uMid)
+ {
+ _QuickSortBook(uLeft, uMid - 1);
+ }
+ if (uMid != uRight)
+ {
+ _QuickSortBook(uMid + 1, uRight);
+ }
+ }
+}
+
+
+static void
+_CompactMemBook(void)
+/**
+
+Routine description:
+
+Parameters:
+
+ void
+
+Return value:
+
+ static void
+
+**/
+{
+ ULONG uCount = 0;
+ ULONG i;
+ ULONG uOccurances;
+
+ Trace("Compacting the opening book... one moment.\n");
+
+ //
+ // Lose the blank signatures to reduce the working set.
+ //
+ for (i = 0;
+ i < g_uMemBookSize;
+ i++)
+ {
+ if (0 != g_pMemBook[i].u64Sig)
+ {
+ uOccurances = (g_pMemBook[i].uWins +
+ g_pMemBook[i].uDraws +
+ g_pMemBook[i].uLosses);
+
+ if (0 != uOccurances)
+ {
+ memcpy(&(g_pMemBook[uCount]),
+ &(g_pMemBook[i]),
+ sizeof(BOOK_ENTRY));
+ uCount++;
+ }
+ }
+ }
+
+ g_uMemBookCount = 0;
+ if (uCount > 0)
+ {
+ g_uMemBookCount = uCount - 1;
+ }
+}
+
+
+static void
+_StrainMemBook(ULONG uLimit)
+/**
+
+Routine description:
+
+Parameters:
+
+ ULONG uLimit
+
+Return value:
+
+ static void
+
+**/
+{
+ ULONG i;
+ ULONG uOccurances;
+
+ Trace("Straining out unpopular positions to compact buffer...\n");
+
+ for (i = 0;
+ i < g_uMemBookSize;
+ i++)
+ {
+ if (0 != g_pMemBook[i].u64Sig)
+ {
+ uOccurances = (g_pMemBook[i].uWins +
+ g_pMemBook[i].uDraws +
+ g_pMemBook[i].uLosses);
+
+ if (uOccurances <= uLimit)
+ {
+ memset(&(g_pMemBook[i]),
+ 0,
+ sizeof(BOOK_ENTRY));
+ g_uMemBookCount--;
+ }
+ }
+ }
+}
+
+
+//+----------------------------------------------------------------------------
+//
+// Function: _BookHashFind
+//
+// Arguments: BOOK_ENTRY *p - pointer to a book entry
+//
+// Returns: BOOK_ENTRY * - pointer to a membook location to store in
+//
+//+----------------------------------------------------------------------------
+
+
+
+
+
+BOOK_ENTRY *_BookHashFind(BOOK_ENTRY *p)
+/**
+
+Routine description:
+
+ This routine returns a pointer to the membook entry that should be
+ used to store data about a position-move. It's called while
+ building a new opening book (BookBuild, see below). It uses a
+ simple hash-lookup algorthm with linear probing in the event of a
+ collision.
+
+Parameters:
+
+ BOOK_ENTRY *p
+
+Return value:
+
+ BOOK_ENTRY
+
+**/
+{
+ ULONG uKey = (ULONG)((p->u64Sig >> 32) & (g_uMemBookSize - 1));
+ BOOK_ENTRY *pBe = g_pMemBook + uKey;
+#ifdef DEBUG
+ ULONG uInitialKey = uKey;
+
+ ASSERT(g_pMemBook != NULL);
+ ASSERT(p->u64Sig);
+#endif
+
+ while(1)
+ {
+ //
+ // If this is an empty book entry, use it.
+ //
+ if (pBe->u64Sig == 0) break;
+
+ //
+ // If this book entry is the same position and move as the one
+ // we are adding, return it.
+ //
+ if ((pBe->u64Sig == p->u64Sig) &&
+ (pBe->mvNext.uMove == p->mvNext.uMove))
+ {
+ break;
+ }
+
+ //
+ // Otherwise check the next book entry... if we go off the end
+ // then loop around.
+ //
+ uKey++;
+ if (uKey >= g_uMemBookSize) uKey = 0;
+ pBe = g_pMemBook + uKey;
+
+ //
+ // This should never happen -- it means a totally full book.
+ // Before we let the membook fill up we should have already
+ // called the strain routine to drop unpopular position-moves.
+ //
+ ASSERT(uKey != uInitialKey);
+ }
+ return(pBe);
+}
+
+
+
+MOVE
+BookMove(POSITION *pos, BITV bvFlags)
+/**
+
+Routine description:
+
+ This is a bit confusing.
+
+ The primary purpose of this function is, given a position pointer,
+ to find and return a book move to play.
+
+ This behavior can be achieved by calling with a good pos pointer
+ and bvFlags containing BOOKMOVE_SELECT_MOVE.
+
+ If bvFlags doesn't contain this flag then no move will be
+ returned.
+
+ If bvFlags contains BOOKMOVE_DUMP this function will trace a bunch
+ of info about move weights. Hence, something like BookDump can be
+ achieved via:
+
+ (void)BookMove(pos, BOOKMOVE_DUMP);
+
+ This is done so that the code to find all moves from a position
+ and show them is not duplicated more than once.
+
+Parameters:
+
+ POSITION *pos,
+ BITV bvFlags
+
+Return value:
+
+ MOVE
+
+*/
+{
+ LIGHTWEIGHT_SEARCHER_CONTEXT *ctx = NULL;
+ UINT64 u64Sig;
+ ULONG uTotalWeight = 0;
+ static ULONG uMoveWeights[32];
+ static CHAR *szNames[32];
+ ULONG uMoveNum;
+ static BOOK_ENTRY entry;
+ ULONG uBestWeight = 0;
+ ULONG uBestIndex = (ULONG)-1;
+ ULONG uLow, uHigh;
+ ULONG uIndex;
+ MOVE mvBook = {0};
+ ULONG uRandom;
+ ULONG uCounter;
+ ULONG x;
+ double d;
+ CHAR *szName = NULL;
+ FLAG fOldValue = g_Options.fShouldAnnounceOpening;
+
+ g_Options.fShouldAnnounceOpening = FALSE;
+#ifdef DEBUG
+ memset(uMoveWeights, 0, sizeof(uMoveWeights));
+#endif
+ ctx = SystemAllocateMemory(sizeof(LIGHTWEIGHT_SEARCHER_CONTEXT));
+ if (NULL == ctx)
+ {
+ Trace("Out of memory.\n");
+ goto end;
+ }
+
+ //
+ // We cannot probe the opening book if membook is non-NULL (which
+ // indicates the opening book is currently in-memory and still
+ // being built.
+ //
+ if (g_pMemBook != NULL)
+ {
+ Trace("Cannot probe the book while its still being built!\n");
+ ASSERT(FALSE);
+ goto end;
+ }
+ if (FALSE == _BookOpen())
+ {
+ Trace("BookMove: Could not open the book file.\n");
+ goto end;
+ }
+
+ //
+ // Binary search for sig in the book, this sets iLow and iHigh.
+ //
+ u64Sig = pos->u64PawnSig ^ pos->u64NonPawnSig;
+ if (0 == _BookFindSig(u64Sig, &uLow, &uHigh))
+ {
+ Trace("BookMove: Signature not in book.\n", u64Sig);
+ goto end;
+ }
+ ASSERT((uHigh - uLow) < 32);
+ ASSERT(uLow <= uHigh);
+
+ //
+ // Consider each entry from iLow to iHigh and determine the chances
+ // of playing it.
+ //
+ for (uIndex = uLow, uMoveNum = 0;
+ uIndex <= uHigh;
+ uIndex++, uMoveNum++)
+ {
+ _BookSeek(uIndex);
+ _BookRead(&entry);
+#ifdef DEBUG
+ u64Sig = pos->u64PawnSig ^ pos->u64NonPawnSig;
+ ASSERT(entry.u64Sig == u64Sig);
+#endif
+ uMoveWeights[uMoveNum] = 0;
+ szNames[uMoveNum] = NULL;
+
+ //
+ // Don't consider the move if its flagged deleted or disabled.
+ //
+ if ((entry.bvFlags & FLAG_DISABLED) ||
+ (entry.bvFlags & FLAG_DELETED))
+ {
+ continue;
+ }
+
+ //
+ // Make sure it's legal and doesn't draw... also get the name
+ // of this opening.
+ //
+ InitializeLightweightSearcherContext(pos, ctx);
+ if (FALSE == MakeMove((SEARCHER_THREAD_CONTEXT *)ctx, entry.mvNext))
+ {
+ ASSERT(FALSE);
+ continue;
+ }
+ else
+ {
+ if (TRUE == IsDraw((SEARCHER_THREAD_CONTEXT *)ctx))
+ {
+ UnmakeMove((SEARCHER_THREAD_CONTEXT *)ctx, entry.mvNext);
+ continue;
+ }
+
+ u64Sig = (ctx->sPosition.u64PawnSig ^
+ ctx->sPosition.u64NonPawnSig);
+ szNames[uMoveNum] = _BookLineToString(u64Sig);
+ UnmakeMove((SEARCHER_THREAD_CONTEXT *)ctx, entry.mvNext);
+ }
+
+ //
+ // If one is marked always, just return it.
+ //
+ if ((entry.bvFlags & FLAG_ALWAYSPLAY) &&
+ (bvFlags & BOOKMOVE_SELECT_MOVE))
+ {
+ mvBook = entry.mvNext;
+ szName = szNames[uMoveNum];
+ goto end;
+ }
+
+ //
+ // Set this move's weight.
+ //
+ uMoveWeights[uMoveNum] = _BookComputeWeight(&entry);
+ uTotalWeight += uMoveWeights[uMoveNum];
+
+ //
+ // If we are in tournament mode we will play the top weighted
+ // move; look for it now.
+ //
+ if (uMoveWeights[uMoveNum] > uBestWeight)
+ {
+ uBestWeight = uMoveWeights[uMoveNum];
+ uBestIndex = uIndex;
+ }
+
+ } // next book move
+
+ //
+ // If we threw every move out (there is no total weight) then we
+ // don't have a book move here.
+ //
+ if (0 == uTotalWeight)
+ {
+ ASSERT(mvBook.uMove == 0);
+ goto end;
+ }
+
+ //
+ // We are done assigning weights to moves. If we are in tournament
+ // mode we should have picked a best one. If so, return it.
+ //
+ if ((TRUE == g_fTournamentMode) && (bvFlags & BOOKMOVE_SELECT_MOVE))
+ {
+ if (uBestIndex != (ULONG)-1)
+ {
+ ASSERT(uBestWeight);
+ _BookSeek(uBestIndex);
+ _BookRead(&entry);
+ mvBook = entry.mvNext;
+ ASSERT(uBestIndex >= uLow);
+ szName = szNames[uBestIndex - uLow];
+ goto end;
+ }
+ }
+
+ //
+ // We didn't throw everything out... so pick a move randomly based
+ // on the weights assigned.
+ //
+ uRandom = (randomMT() % uTotalWeight) + 1;
+ if (bvFlags & BOOKMOVE_DUMP)
+ {
+ u64Sig = pos->u64PawnSig ^ pos->u64NonPawnSig;
+ Trace(" BookMove: List of moves in position 0x%"
+ COMPILER_LONGLONG_HEX_FORMAT ":\n",
+ u64Sig);
+ for (x = 0; x < uMoveNum; x++)
+ {
+ _BookSeek(uLow + x);
+ _BookRead(&entry);
+ ASSERT(entry.u64Sig == u64Sig);
+
+ d = 0.0;
+ if (uMoveWeights[x] != 0)
+ {
+ d = (double)uMoveWeights[x] / (double)uTotalWeight;
+ d *= 100.0;
+ }
+ Trace("%02u. %-4s [+%u =%u -%u] \"%s\" @ %5.3f\n",
+ x,
+ MoveToSan(entry.mvNext, pos),
+ entry.uWins, entry.uDraws, entry.uLosses,
+ szNames[x],
+ d);
+ }
+ Trace(" Total weight was %u.\n\n", uTotalWeight);
+ if (!(bvFlags & BOOKMOVE_SELECT_MOVE))
+ {
+ mvBook.uMove = 0;
+ goto end;
+ }
+ }
+
+ //
+ // Based on the weighted list and the random number we
+ // selected pick a move.
+ //
+ for (uIndex = 0, uCounter = 0;
+ uIndex < uMoveNum;
+ uIndex++)
+ {
+ uCounter += uMoveWeights[uIndex];
+
+ if (uCounter >= uRandom)
+ {
+ _BookSeek(uLow + uIndex);
+ _BookRead(&entry);
+ mvBook = entry.mvNext;
+ szName = szNames[uIndex];
+ goto end;
+ }
+ }
+#ifdef DEBUG
+ UtilPanic(SHOULD_NOT_GET_HERE,
+ NULL, NULL, NULL, NULL,
+ __FILE__, __LINE__);
+#endif
+
+ end:
+ g_Options.fShouldAnnounceOpening = fOldValue;
+ _BookClose();
+
+ if (mvBook.uMove)
+ {
+ ASSERT(bvFlags & BOOKMOVE_SELECT_MOVE);
+
+ //
+ // We could be probing the book after a predicted ponder move (in
+ // which case we will have forced fAnnounceOpening to FALSE because
+ // we don't want to whisper two book moves in a row).
+ //
+ if (FALSE != g_Options.fShouldAnnounceOpening) {
+ Trace("tellothers book move %s [+%u =%u -%u]\n",
+ MoveToSan(entry.mvNext, pos),
+ entry.uWins, entry.uDraws, entry.uLosses);
+ }
+ }
+
+ if (NULL != ctx)
+ {
+ SystemFreeMemory(ctx);
+ }
+ return(mvBook);
+}
+
+
+#if 0
+//+---------------------------------------------------------------------------
+//
+// Function: BookEdit
+//
+// Synopsis: Giant hack to edit the book.
+//
+// Arguments: POSITION *pos
+//
+// Returns: void
+//
+//+---------------------------------------------------------------------------
+void
+BookEdit(POSITION *pos)
+{
+ int iTotalEntries = _BookCount();
+ int iNumMoves;
+ int iLow, iHigh;
+ FILE *pfLearn = NULL;
+ FLAG fFound = FALSE;
+ static BOOK_ENTRY entry;
+ int iIndex = 0;
+ int iCurrent = 0;
+ int x;
+ char *pCh, *pArg;
+ int arg;
+ int iSum;
+ int iWeights[64];
+ float fl;
+ FLAG fOldValue = g_Options.fAnnounceOpening;
+
+ g_Options.fAnnounceOpening = FALSE;
+
+ //
+ // We cannot probe the opening book if membook is non-NULL (which
+ // indicates the opening book is currently in-memory and still being
+ // built.
+ //
+ if (g_pMemBook != NULL)
+ {
+ ASSERT(FALSE);
+ goto end;
+ }
+
+ //
+ // Open the book.
+ //
+ iTotalEntries = _BookCount();
+ if (0 == iTotalEntries)
+ {
+ Trace("BookDump: Opening book \"%s\" is empty.\n",
+ g_Options.szBookName);
+ goto end;
+ }
+
+ ASSERT(g_fdBook == -1);
+ if (!_BookOpen())
+ {
+ Trace("BookDump: Could not open the book.\n");
+ goto end;
+ }
+
+ //
+ // Open the learning recorder
+ //
+ pfLearn = fopen(BOOK_EDITING_RECORD, "a+b");
+ if (NULL == pfLearn)
+ {
+ Trace("BookEdit: Warning: can't open %s to save learning.\n",
+ BOOK_EDITING_RECORD);
+ }
+
+ if (!_BookFindSig(pos->u64Sig, &iLow, &iHigh))
+ {
+ Trace("BookEdit: Position %I64x is not in the book.\n");
+ goto end;
+ }
+
+ iNumMoves = iHigh - iLow + 1;
+ iCurrent = 0;
+ do
+ {
+ printf("\n");
+ Trace("--EDITING OPENING BOOK--\n\n");
+ Trace("There are %u positions in the opening book \"%s\".\n",
+ iTotalEntries, g_Options.szBookName);
+ Trace("There are %u book moves in this position, %u to %u:\n",
+ iNumMoves, iLow, iHigh);
+
+ for (x = 0, iSum = 0;
+ x < iNumMoves;
+ x++)
+ {
+ _BookSeek(iLow + x);
+ _BookRead(&entry);
+
+ //
+ // If the disabled flag is asserted, do not allow this
+ // move to be played. If the disabled flag is not
+ // asserted, calculate a weight for this move.
+ //
+ if ((entry.bvFlags & FLAG_DISABLED) ||
+ (entry.bvFlags & FLAG_DELETED))
+ {
+ iWeights[x] = 0;
+ }
+ else
+ {
+ iWeights[x] = _BookComputeWeight(&entry);
+ }
+ iSum += iWeights[x];
+ }
+
+ for (x = 0;
+ x < iNumMoves;
+ x++)
+ {
+ _BookSeek(iLow + x);
+ _BookRead(&entry);
+
+ fl = (float)iWeights[x];
+ if (iSum)
+ {
+ fl /= (float)iSum;
+ fl *= 100.0;
+ }
+
+ if (x == iCurrent)
+ {
+ Trace(">> ");
+ }
+ else
+ {
+ Trace(" ");
+ }
+
+ Trace("%02u. %6u..%6u = %4s [+%6u =%6u -%6u] @ ~%6.3f%% | %s%s",
+ x + 1,
+ 0,
+ iWeights[x],
+ MoveToSAN(entry.mvNext, pos),
+ entry.iWins,
+ entry.iDraws,
+ entry.iLosses,
+ fl,
+ (entry.bvFlags & FLAG_DISABLED) ? "NEV" : "",
+ (entry.bvFlags & FLAG_ALWAYSPLAY) ? "ALW" : "");
+
+ if (x == iCurrent)
+ {
+ Trace("<<\n");
+ }
+ else
+ {
+ Trace("\n");
+ }
+ }
+
+ _BookSeek(iLow + iCurrent);
+ _BookRead(&entry);
+ Trace("Command (q, p, n, +, =, -, h, l, e, a, d, ?): ");
+ pCh = BlockingRead();
+
+ switch(tolower(*pCh))
+ {
+ case 'q':
+ Trace("--DONE EDITING OPENING BOOK--\n\n");
+ goto end;
+ case 'p':
+ iCurrent--;
+ if (iCurrent < 0) iCurrent = iNumMoves - 1;
+ break;
+ case 'n':
+ iCurrent++;
+ if (iCurrent >= iNumMoves) iCurrent = 0;
+ break;
+ case '+':
+ Trace("Enter new wincount: ");
+ pArg = BlockingRead();
+ arg = atoi(pArg);
+ if (arg)
+ {
+ entry.iWins = arg;
+ _BookSeek(iLow + iCurrent);
+ _BookWrite(&entry);
+ if (NULL != pfLearn)
+ {
+ fprintf(pfLearn, "%I64x (%x) + %u // %s\n",
+ entry.u64Sig,
+ entry.mvNext.move,
+ arg,
+ BoardToFEN(pos));
+ }
+ }
+ SystemFreeMemory(pArg);
+ break;
+ case '=':
+ Trace("Enter new drawcount: ");
+ pArg = BlockingRead();
+ arg = atoi(pArg);
+ if (arg)
+ {
+ entry.iDraws = arg;
+ _BookSeek(iLow + iCurrent);
+ _BookWrite(&entry);
+ if (NULL != pfLearn)
+ {
+ fprintf(pfLearn, "%I64x (%x) = %u // %s\n",
+ entry.u64Sig,
+ entry.mvNext.move,
+ arg,
+ BoardToFEN(pos));
+ }
+ }
+ SystemFreeMemory(pArg);
+ break;
+ case '-':
+ Trace("Enter new losscount: ");
+ pArg = BlockingRead();
+ arg = atoi(pArg);
+ if (arg)
+ {
+ entry.iLosses = arg;
+ _BookSeek(iLow + iCurrent);
+ _BookWrite(&entry);
+ if (NULL != pfLearn)
+ {
+ fprintf(pfLearn, "%I64x (%x) - %u // %s\n",
+ entry.u64Sig,
+ entry.mvNext.move,
+ arg,
+ BoardToFEN(pos));
+ }
+ }
+ SystemFreeMemory(pArg);
+ break;
+ case 'e':
+ if (entry.bvFlags & FLAG_DISABLED)
+ {
+ entry.bvFlags &= ~FLAG_DISABLED;
+ }
+ else
+ {
+ entry.bvFlags |= FLAG_DISABLED;
+ entry.bvFlags &= ~FLAG_ALWAYSPLAY;
+ }
+ _BookSeek(iLow + iCurrent);
+ _BookWrite(&entry);
+ if (NULL != pfLearn)
+ {
+ fprintf(pfLearn, "%I64x (%x) FLAGS %x // %s\n",
+ entry.u64Sig,
+ entry.mvNext.move,
+ entry.bvFlags,
+ BoardToFEN(pos));
+ }
+ break;
+ case 'a':
+ if (entry.bvFlags & FLAG_ALWAYSPLAY)
+ {
+ entry.bvFlags &= ~FLAG_ALWAYSPLAY;
+ }
+ else
+ {
+ entry.bvFlags |= FLAG_ALWAYSPLAY;
+ entry.bvFlags &= ~FLAG_DISABLED;
+ }
+ _BookSeek(iLow + iCurrent);
+ _BookWrite(&entry);
+ if (NULL != pfLearn)
+ {
+ fprintf(pfLearn, "%I64x (%x) FLAGS %x // %s\n",
+ entry.u64Sig,
+ entry.mvNext.move,
+ entry.bvFlags,
+ BoardToFEN(pos));
+ }
+ break;
+ case 'h':
+ PreferPosition(pos, pfLearn, pos->u64Sig, -1);
+ Trace("I hate this position.\n");
+ break;
+ case 'i':
+ PreferPosition(pos, pfLearn, pos->u64Sig, 0);
+ Trace("I'm indifferent towards this position.\n");
+ break;
+ case 'l':
+ PreferPosition(pos, pfLearn, pos->u64Sig, 1);
+ Trace("I love this position!\n");
+ break;
+ case '?':
+ Trace(
+"Commands: \n"
+" +) adjust wins =) adjust draws -) adjust losses\n"
+" h) hate this pos i) indifferent l) love this pos\n\n"
+
+" e) enable/disable\n"
+" a) always/sometimes play\n\n"
+
+" p) previous move n) next move q) quit editing\n\n");
+ break;
+ default:
+ break;
+ }
+ SystemFreeMemory(pCh);
+ }
+ while(1);
+
+ end:
+ g_Options.fAnnounceOpening = fOldValue;
+
+ ASSERT(g_fdBook != -1);
+ _BookClose();
+
+ if (NULL != pfLearn)
+ {
+ fclose(pfLearn);
+ }
+}
+#endif
+
+
+
+
+
+static FLAG
+_BookBuild(void)
+/**
+
+Routine description:
+
+ Builds a new opening book. This routine is called by
+ LearnPGNOpenings in movelist.c after it has played through one PGN
+ game. g_MoveList is loaded with position signatures and move
+ structs representing this game. Our goal is to parse these and
+ create/update the membook entries to reflect the game played and
+ its result.
+
+Parameters:
+
+ void
+
+Return value:
+
+ static FLAG
+
+**/
+{
+ BOOK_ENTRY entry;
+ BOOK_ENTRY *pEntry;
+ ULONG n;
+ GAME_MOVE *q;
+ ULONG u;
+ double dFull;
+ GAME_RESULT result = GetGameResult();
+
+ if (result.eResult != RESULT_WHITE_WON &&
+ result.eResult != RESULT_BLACK_WON &&
+ result.eResult != RESULT_DRAW) {
+ return(FALSE);
+ }
+
+ //
+ // If the membook has not yet been allocated, do it now.
+ //
+ if (g_pMemBook == NULL)
+ {
+ ASSERT((g_uMemBookSize & (g_uMemBookSize - 1)) == 0);
+ g_uMemBookCount = 0;
+ g_pMemBookHead = NULL;
+ g_pMemBook = (BOOK_ENTRY *)
+ SystemAllocateMemory(g_uMemBookSize * sizeof(BOOK_ENTRY));
+ g_pMemBookHead = g_pMemBook;
+ ASSERT(g_pMemBook != NULL);
+ }
+
+ //
+ // Consider each position in the game we have played up until now.
+ //
+ u = 0;
+ q = GetNthOfficialMoveRecord(u);
+ while((q != NULL) && (q->mv.uMove != 0))
+ {
+ entry.u64Sig = q->u64PositionSigBeforeMove;
+ entry.mvNext = q->mv;
+ entry.u64NextSig = q->u64PositionSigAfterMove;
+
+ //
+ // Have we ever seen this position before?
+ //
+ pEntry = _BookHashFind(&entry);
+ if (0 != pEntry->u64Sig)
+ {
+ //
+ // Yes we have seen this signature-move before. Update the
+ // win/draw/loss counter at this entry in the membook.
+ //
+ ASSERT(pEntry->u64Sig == entry.u64Sig);
+ ASSERT(pEntry->mvNext.uMove == entry.mvNext.uMove);
+ if (((result.eResult == RESULT_BLACK_WON) &&
+ (GET_COLOR(entry.mvNext.pMoved) == BLACK)) ||
+ ((result.eResult == RESULT_WHITE_WON) &&
+ (GET_COLOR(entry.mvNext.pMoved) == WHITE)))
+ {
+ pEntry->uWins++;
+ }
+ else
+ {
+ if (RESULT_DRAW == result.eResult)
+ {
+ pEntry->uDraws++;
+ }
+ else
+ {
+ pEntry->uLosses++;
+ }
+ }
+ }
+
+ //
+ // Nope never seen this signature-move entry before, create a new
+ // entry for it and add it to the membook.
+ //
+ else
+ {
+ memset(pEntry, 0, sizeof(pEntry));
+ pEntry->u64Sig = entry.u64Sig;
+ pEntry->mvNext = entry.mvNext;
+ pEntry->u64NextSig = entry.u64NextSig;
+
+ if (((result.eResult == RESULT_BLACK_WON) &&
+ (GET_COLOR(entry.mvNext.pMoved) == BLACK)) ||
+ ((result.eResult == RESULT_WHITE_WON) &&
+ (GET_COLOR(entry.mvNext.pMoved) == WHITE)) )
+ {
+ pEntry->uWins = 1;
+ }
+ else
+ {
+ if (RESULT_DRAW == result.eResult)
+ {
+ pEntry->uDraws = 1;
+ }
+ else
+ {
+ pEntry->uLosses = 1;
+ }
+ }
+ g_uMemBookCount++;
+
+ //
+ // If the in memory buffer is getting too full, throw out
+ // all positions that occur only once in it in order to
+ // compact it. We should try to limit this because it can
+ // weaken the book.
+ //
+ n = 1;
+ dFull = (double)g_uMemBookCount;
+ dFull /= (double)g_uMemBookSize;
+ dFull *= 100.0;
+ while (dFull > 97.0)
+ {
+ _StrainMemBook(n);
+ n++;
+
+ dFull = (double)g_uMemBookCount;
+ dFull /= (double)g_uMemBookSize;
+ dFull *= 100.0;
+ }
+ }
+ u++;
+ q = GetNthOfficialMoveRecord(u);
+ }
+ return(TRUE);
+}
+
+
+static FLAG
+_BookToDisk(ULONG uStrainLimit)
+/**
+
+Routine description:
+
+Parameters:
+
+ ULONG uStrainLimit
+
+Return value:
+
+ static FLAG
+
+**/
+{
+ ULONG i;
+ int ifd;
+ CHAR *szTempBook = "tempbook.bin";
+ CHAR szCmd[SMALL_STRING_LEN_CHAR];
+ BOOK_ENTRY sBe;
+ ULONG uOldBook = 0;
+ ULONG uNewBook = 0;
+ ULONG uOld_BookCount;
+ ULONG uNew_BookCount;
+ BOOK_ENTRY *pOldBook = NULL;
+ BOOK_ENTRY *pNewBook = NULL;
+ FLAG fMyRetVal = FALSE;
+ struct stat s;
+
+ if (!strlen(g_Options.szBookName))
+ {
+ return(FALSE);
+ }
+ i = g_uMemBookCount;
+
+ //
+ // Drops all book entries with less than limit occurrances
+ //
+ _StrainMemBook(uStrainLimit);
+
+ //
+ // Drops all unused entries
+ //
+ Trace("Stage 2: Compacting the book.\n");
+ _CompactMemBook();
+
+ //
+ // Sorts the by signature.
+ //
+ Trace("Stage 3: Sorting the book.\n");
+ _QuickSortBook(0, g_uMemBookCount);
+ uNew_BookCount = g_uMemBookCount;
+
+ //
+ // Create the temporary book on the disk.
+ //
+ (void)unlink(szTempBook);
+ if (SystemDoesFileExist(szTempBook))
+ {
+ Trace("_BookToDisk: Can't create a temporary book!\n");
+ return(FALSE);
+ }
+
+ ifd = open(szTempBook,
+ O_RDWR | O_CREAT | O_BINARY,
+ _S_IREAD | _S_IWRITE);
+ if (ifd < 0)
+ {
+ Trace("_BookToDisk: Can't create a temporary book!\n");
+ return(FALSE);
+ }
+#ifndef _MSC_VER
+ fchmod(ifd, 0644);
+#endif
+
+ //
+ // Count the entries in the old book on disk.
+ //
+ uOld_BookCount = 0;
+ if ((TRUE == _VerifyBook(g_Options.szBookName)) && (TRUE == _BookOpen()))
+ {
+ if (0 == stat(g_Options.szBookName, &s))
+ {
+ uOld_BookCount = (s.st_size / sizeof(BOOK_ENTRY));
+ }
+ }
+
+ //
+ // Merge the old book (on disk) with the new (in memory) in the
+ // temp book.
+ //
+ Trace("Merging book lines and writing to disk...\n");
+ do
+ {
+ pOldBook = pNewBook = NULL;
+
+ //
+ // Read the next old book entry.
+ //
+ retry:
+ if (uOldBook < uOld_BookCount)
+ {
+ if ((int)(uOldBook * sizeof(BOOK_ENTRY)) !=
+ lseek(g_fdBook, (uOldBook * sizeof(BOOK_ENTRY)), SEEK_SET))
+ {
+ Trace("_BookToDisk: Cannot seek to byte offset %u in old "
+ "book.\n", uOldBook * sizeof(BOOK_ENTRY));
+ ASSERT(FALSE);
+ goto end;
+ }
+
+ if (sizeof(BOOK_ENTRY) != read(g_fdBook,
+ &sBe,
+ sizeof(BOOK_ENTRY)))
+ {
+ Trace("_BookToDisk: Can't read error in old book, trying "
+ "to recover...\n");
+ uOldBook++;
+ goto retry;
+ }
+ pOldBook = &sBe;
+ }
+
+ //
+ // Get the next new book entry
+ //
+ if (uNewBook < uNew_BookCount)
+ {
+ ASSERT(0 != g_pMemBook[uNewBook].u64Sig);
+ pNewBook = &(g_pMemBook[uNewBook]);
+ }
+
+ //
+ // Decide which to write into the merged (temporary) book we
+ // are constructing next...
+ //
+ if ((NULL == pNewBook) && (NULL == pOldBook))
+ {
+ break;
+ }
+ else if ((NULL == pNewBook) && (NULL != pOldBook))
+ {
+ goto writeoldbook;
+ }
+ else if ((NULL != pNewBook) && (NULL == pOldBook))
+ {
+ goto writenewbook;
+ }
+ else
+ {
+ if (pOldBook->u64Sig < pNewBook->u64Sig)
+ {
+ goto writeoldbook;
+ }
+ else if (pOldBook->u64Sig > pNewBook->u64Sig)
+ {
+ goto writenewbook;
+ }
+ else
+ {
+ ASSERT(pOldBook->u64Sig == pNewBook->u64Sig);
+ if (pOldBook->mvNext.uMove < pNewBook->mvNext.uMove)
+ {
+ goto writeoldbook;
+ }
+ else if (pOldBook->mvNext.uMove > pNewBook->mvNext.uMove)
+ {
+ goto writenewbook;
+ }
+ else
+ {
+ //
+ // Same position and move...
+ //
+ pNewBook->uWins += pOldBook->uWins;
+ pNewBook->uDraws += pOldBook->uDraws;
+ pNewBook->uLosses += pOldBook->uLosses;
+ pNewBook->bvFlags |= pOldBook->bvFlags;
+ uOldBook++;
+ goto writenewbook;
+ }
+ }
+ }
+#ifdef DEBUG
+ UtilPanic(SHOULD_NOT_GET_HERE,
+ NULL, NULL, NULL, NULL,
+ __FILE__, __LINE__);
+#endif
+
+ writeoldbook:
+ if (-1 == lseek(ifd, 0, SEEK_END))
+ {
+ ASSERT(FALSE);
+ }
+ if (sizeof(BOOK_ENTRY) != write(ifd, pOldBook, sizeof(BOOK_ENTRY)))
+ {
+ Trace("_BookToDisk: Error writing new book, aborting procedure.\n");
+ goto end;
+ }
+ uOldBook++;
+ continue;
+
+ writenewbook:
+ if (-1 == lseek(ifd, 0, SEEK_END))
+ {
+ ASSERT(FALSE);
+ }
+ if (sizeof(BOOK_ENTRY) != write(ifd, pNewBook, sizeof(BOOK_ENTRY)))
+ {
+ Trace("_BookToDisk: Error writing new book, aborting procedure."
+ "\n");
+ goto end;
+ }
+ uNewBook++;
+ continue;
+ }
+ while(1);
+ fMyRetVal = TRUE;
+
+ end:
+ close(ifd);
+ SystemFreeMemory(g_pMemBook);
+ g_pMemBook = NULL;
+ g_pMemBookHead = NULL;
+ g_uMemBookCount = 0;
+ _BookClose();
+
+ //
+ // Verify the consistency of the new book before klobbering the old one
+ //
+ if (FALSE == _VerifyBook(szTempBook))
+ {
+ Trace("_BookToDisk: The book I just wrote is corrupt!!!\n");
+ fMyRetVal = FALSE;
+ ASSERT(FALSE);
+ }
+ else
+ {
+ //
+ // HACKHACK: put this in system, use CopyFile in win32
+ //
+#ifdef _MSC_VER
+ _snprintf(szCmd, 255, "move %s %s%c", szTempBook,
+ g_Options.szBookName, 0);
+#else
+ snprintf(szCmd, 255, "/bin/mv %s %s%c", szTempBook,
+ g_Options.szBookName, 0);
+#endif
+ system(szCmd);
+ }
+
+ if (TRUE == fMyRetVal)
+ {
+ Trace("Book successfully built.\n");
+ }
+ return(fMyRetVal);
+}
+
+
+static FLAG
+_LearnPgnOpenings(CHAR *szFilename, ULONG uStrainLimit)
+/**
+
+Routine description:
+
+Parameters:
+
+ CHAR *szFilename,
+ ULONG uStrainLimit
+
+Return value:
+
+ static FLAG
+
+**/
+{
+ CHAR *p;
+ FILE *pf = NULL;
+ struct stat s;
+ double d;
+
+ if (FALSE == SystemDoesFileExist(szFilename))
+ {
+ Trace("LearnPgnOpenings: file \"%s\" doesn't exist.\n",
+ szFilename);
+ return(FALSE);
+ }
+
+ if (0 != stat(szFilename, &s))
+ {
+ Trace("LearnPgnOpenings: Can't read file \"%s\".\n",
+ szFilename);
+ return(FALSE);
+ }
+
+ pf = fopen(szFilename, "rb");
+ if (NULL == pf)
+ {
+ Trace("LearnPgnOpenings: error reading file %s.\n",
+ szFilename);
+ return(FALSE);
+ }
+
+ CleanupHashSystem();
+
+ Trace("Stage 1: reading and parsing PGN\n");
+ while((p = ReadNextGameFromPgnFile(pf)) != NULL)
+ {
+ if (TRUE == LoadPgn(p))
+ {
+ _BookBuild();
+ d = (double)ftell(pf);
+ d /= (double)s.st_size;
+ d *= 100.0;
+ printf("%u/%u (%5.2f%% done) ",
+ (unsigned)ftell(pf), (unsigned)s.st_size, d);
+ d = (double)g_uMemBookCount;
+ d /= (double)g_uMemBookSize;
+ d *= 100.0;
+ printf("[membook %5.2f%% full]\r", d);
+ }
+ SystemFreeMemory(p);
+ }
+ fclose(pf);
+
+ _BookToDisk(uStrainLimit);
+
+ InitializeHashSystem();
+
+ return(TRUE);
+}
+
+
+#if 0
+static void
+_UpdateUserOpeningList(INT iResult)
+{
+ int fd = open(OPENING_LEARNING_FILENAME,
+ O_RDWR | O_CREAT | O_BINARY | O_RANDOM,
+ _S_IREAD | _S_IWRITE);
+ ULONG uNumRecords;
+ ULONG u;
+ OPENING_LEARNING_ENTRY ole;
+ ULONG uPos;
+ struct stat s;
+
+ //
+ // If we failed to open it, abort.
+ //
+ if (fd < 0) goto end;
+
+ //
+ // Stat it to see how many entries there are.
+ //
+ if (0 != stat(OPENING_LEARNING_FILENAME, &s))
+ {
+ ASSERT(FALSE);
+ Trace("UpdateUserOpeningList: Corrupt %s file.\n",
+ OPENING_LEARNING_FILENAME);
+ goto end;
+ }
+ if ((s.st_size % sizeof(OPENING_LEARNING_ENTRY)) != 0)
+ {
+ ASSERT(FALSE);
+ Trace("UpdateUserOpeningList: Corrupt %s file.\n",
+ OPENING_LEARNING_FILENAME);
+ goto end;
+ }
+ uNumRecords = s.st_size / sizeof(OPENING_LEARNING_ENTRY);
+
+ //
+ // Read each entry looking for a match.
+ //
+ for (u = 0; u < uNumRecords; u++)
+ {
+ if (sizeof(OPENING_LEARNING_ENTRY) !=
+ read(fd, &ole, sizeof(OPENING_LEARNING_ENTRY)))
+ {
+ Trace("UpdateUserOpeningList: Read error.\n");
+ goto end;
+ }
+
+ if (ole.u64Sig == g_Options.u64OpeningSig)
+ {
+ //
+ // Found it, so update and write back in place.
+ //
+ switch (iResult)
+ {
+ case -1:
+ ole.uBlackWins++;
+ break;
+ case 0:
+ ole.uDraws++;
+ break;
+ case 1:
+ ole.uWhiteWins++;
+ break;
+#ifdef DEBUG
+ default:
+ ASSERT(FALSE);
+ break;
+#endif
+ }
+
+ uPos = u * sizeof(OPENING_LEARNING_ENTRY);
+ if (uPos != lseek(fd, uPos, SEEK_SET))
+ {
+ Trace("UpdateUserOpeningList: Seek error.\n");
+ goto end;
+ }
+ if (sizeof(OPENING_LEARNING_ENTRY) !=
+ write(fd, &ole, sizeof(OPENING_LEARNING_ENTRY)))
+ {
+ Trace("UpdateUserOpeningList: Write error.\n");
+ }
+ goto end;
+ }
+ }
+
+ //
+ // We never found it, so add one at the end.
+ //
+ memset(&ole, 0, sizeof(ole));
+ ole.u64Sig = g_Options.u64OpeningSig;
+ switch(iResult)
+ {
+ case -1:
+ ole.uBlackWins++;
+ break;
+ case 0:
+ ole.uDraws++;
+ break;
+ case 1:
+ ole.uWhiteWins++;
+ break;
+#ifdef DEBUG
+ default:
+ ASSERT(FALSE);
+ break;
+#endif
+ }
+
+ if (-1 == lseek(fd, 0, SEEK_END))
+ {
+ Trace("UpdateUserOpeningList: Seek error.\n");
+ goto end;
+ }
+
+ if (sizeof(OPENING_LEARNING_ENTRY) !=
+ write(fd, &ole, sizeof(OPENING_LEARNING_ENTRY)))
+ {
+ Trace("UpdateUserOpeningList: Append error.\n");
+ }
+
+ end:
+ if (fd >= 0)
+ {
+ close(fd);
+ }
+}
+#endif
+
+
+#if 0
+//+----------------------------------------------------------------------------
+//
+// Function: BookLearn
+//
+// Synopsis: Handles book learning at the end of a game based on the
+// result of the game and the calibre of the opponent.
+//
+// Arguments: int iResult
+//
+// Returns: void
+//
+//+----------------------------------------------------------------------------
+void
+BookLearn(INT iResult)
+{
+ ULONG iIndex, x;
+ unsigned int iChanged = 0;
+ int iTotalEntries = _BookCount();
+ int iLow, iHigh, iCurrent = 0;
+ BOOK_ENTRY entry;
+ FLAG fFound = FALSE;
+
+#ifdef DEBUG
+ if (g_Options.ePlayMode == I_PLAY_BLACK)
+ {
+ Trace("BookLearn: I was playing black\n");
+ }
+ else if (g_Options.ePlayMode == I_PLAY_WHITE)
+ {
+ Trace("BookLearn: I was playing white\n");
+ }
+
+ if (iResult == -1)
+ {
+ Trace("BookLearn: Black won.\n");
+ }
+ else if (iResult == 1)
+ {
+ Trace("BookLearn: White won.\n");
+ }
+ else
+ {
+ Trace("BookLearn: The game was a draw.\n");
+ }
+#endif
+
+ //
+ // Should we learn anything?
+ //
+
+ //
+ // Not if the game was bullet
+ //
+ if ((g_Options.eGameType == GAME_BULLET) ||
+ (g_Options.eGameType == GAME_UNKNOWN))
+ {
+ Trace("BookLearn: Game was too fast or unknown speed, don't learn.\n");
+ goto end;
+ }
+
+ //
+ // Not if the game wasn't rated.
+ //
+ if ((g_Options.fGameIsRated == FALSE))
+ {
+ Trace("BookLearn: Game was unrated, don't learn.\n");
+ goto end;
+ }
+
+ //
+ // Not if someone lost on time or because of forfeit.
+ //
+ if ((NULL != strstr(g_Options.szDescription, "forfeit")) ||
+ (NULL != strstr(g_Options.szDescription, "time")) ||
+ (NULL != strstr(g_Options.szDescription, "abort")))
+ {
+ Trace("BookLearn: Loss by forfeit/abort, don't learn.\n");
+ goto end;
+ }
+
+ //
+ // Not if we won but the opponent was weak.
+ //
+ if (((g_Options.ePlayMode == I_PLAY_WHITE) && (iResult == 1)) ||
+ ((g_Options.ePlayMode == I_PLAY_BLACK) && (iResult == -1)))
+ {
+#ifdef DEBUG
+ Trace("BookLearn: I won, my opponent's rating was %u, "
+ "my rating is %u.\n",
+ g_Options.iOpponentsRating,
+ g_Options.iMyRating);
+#endif
+
+ if (g_Options.iOpponentsRating < g_Options.iMyRating - 300)
+ {
+ Trace("BookLearn: Opponent sucks, not learning.\n");
+ goto end;
+ }
+ }
+
+ //
+ // Not if we lost but the opponent was strong.
+ //
+ if (((g_Options.ePlayMode == I_PLAY_BLACK) && (iResult == 1)) ||
+ ((g_Options.ePlayMode == I_PLAY_WHITE) && (iResult == -1)))
+ {
+ Trace("BookLearn: I lost, my opponent's rating was %u, "
+ "my rating is %u.\n",
+ g_Options.iOpponentsRating,
+ g_Options.iMyRating);
+
+ if (g_Options.iOpponentsRating > g_Options.iMyRating + 300)
+ {
+ Trace("BookLearn: Opponent kicks ass, not learning.\n");
+ goto end;
+ }
+ ToXboard("tellics say Nice game!\n");
+ }
+
+ //
+ // Yes, we have decided to learn.
+ //
+ Trace("BookLearn: Revising opening book line...\n");
+
+ //
+ // 1. Save the learning on a user-friendly list...
+ //
+ UpdateUserOpeningList(iResult);
+
+ //
+ // 2. Save the learning in the real opening book...
+ //
+ if ((0 == iTotalEntries) ||
+ (FALSE == _BookOpen()))
+ {
+ Trace("BookLearn: Could not open the book.\n");
+ goto end;
+ }
+
+ //
+ // Look at the moves in the move list.
+ //
+ for (iIndex = 0;
+ iIndex < g_iMoveNum - 1;
+ iIndex++)
+ {
+ //
+ // Only learn on the first 30 moves.
+ //
+ if (iIndex > 60) break;
+
+ Trace("BookLearn: Seeking %I64x with move %s (%x) in the book.\n",
+ g_MoveList[iIndex].u64Sig,
+ MoveToICS(g_MoveList[iIndex + 1].mv),
+ g_MoveList[iIndex + 1].mv.move);
+
+ //
+ // Binary search for sig in the book, this sets iLow and iHigh.
+ //
+ if (0 != _BookFindSig(g_MoveList[iIndex].u64Sig, &iLow, &iHigh))
+ {
+ Trace("BookLearn: Found a (P) : %I64x at %u.\n",
+ g_MoveList[iIndex].u64Sig, iCurrent);
+
+ //
+ // Current now points to the first book entry matching
+ // the sig... look for the entry matching the move too.
+ //
+ for (x = iLow; x <= iHigh; x++)
+ {
+ _BookSeek(x);
+ _BookRead(&entry);
+
+ ASSERT(entry.u64Sig ==
+ g_MoveList[iIndex].u64Sig);
+
+ //
+ // If we match the move and signature, update the entry
+ // based on this game's results.
+ //
+ if (entry.mvNext.move == g_MoveList[iIndex + 1].mv.move)
+ {
+ Trace("BookLearn: Matched mv : %x at %u.\n",
+ g_MoveList[iIndex + 1].mv.move, x);
+ iChanged++;
+
+ //
+ // Did the side making the move win in the end?
+ //
+ if (((iResult == -1) &&
+ (IS_BLACK(g_MoveList[iIndex + 1].mv.data.pMoved))) ||
+ ((iResult == 1) &&
+ (IS_WHITE(g_MoveList[iIndex + 1].mv.data.pMoved))))
+ {
+ Trace("BookLearn: Side moving won, enforce this "
+ "book entry.\n");
+ entry.iWins++;
+ }
+ else
+ {
+ //
+ // No so it drew or lost.
+ //
+ if (0 == iResult)
+ {
+ Trace("BookLearn: Game was a draw, increase "
+ "drawishness of this book entry.\n");
+ entry.iDraws++;
+ }
+ else
+ {
+ Trace("BookLearn: Side moving lost, discourage "
+ "this book entry.\n");
+ entry.iLosses++;
+ }
+ }
+
+ //
+ // Write it back into the book.
+ //
+ _BookSeek(x);
+ _BookWrite(&entry);
+
+ //
+ // Done looking for this move.
+ //
+ break;
+
+ } // entry.move matches
+
+ } // loop to look at all moves on entries w/ matching sig
+
+ } // was sig found in book?
+
+ } // main game move loop
+
+ end:
+ Trace("BookLearn: Changed %u book positions.\n", iChanged);
+ _BookClose();
+}
+
+
+//+----------------------------------------------------------------------------
+//
+// Function: PreferPosition
+//
+// Synopsis: Given a position, love, hate or be neutral towards it.
+//
+// If the position is to be loved, all other book entries
+// that lead to it will be flagged "always play".
+//
+// If the position is to be hated, all other book entries
+// that lead to it will be flagged "never play".
+//
+// A neutral setting will cause all other book entries that
+// lead to have their flags cleared.
+//
+// Arguments: POSITION pos - the pos, needed to make a FEN
+// FILE *fd - file to record permanently in
+// INT64 i64Targ - the signature to love/hate/neuter
+// int i : -1 means hate
+// : 0 means neuter
+// : 1 means love
+//
+// Returns: void
+//
+//+----------------------------------------------------------------------------
+void
+PreferPosition(POSITION *pos, FILE *fd, INT64 i64Targ, int i)
+{
+ int iTotalEntries = _BookCount();
+ int x;
+ int iChanged = 0;
+ static BOOK_ENTRY entry;
+
+ ASSERT(pos->u64Sig == i64Targ);
+
+ if (g_pMemBook != NULL)
+ {
+ ASSERT(FALSE);
+ return;
+ }
+
+ iTotalEntries = _BookCount();
+
+ for (x = 0;
+ x < iTotalEntries - 1;
+ x++)
+ {
+ if ((x % 10000) == 0)
+ {
+ printf("%u / %u\r", x, iTotalEntries);
+ }
+ _BookSeek(x);
+ _BookRead(&entry);
+
+ if (entry.i64NextSig == i64Targ)
+ {
+ iChanged++;
+
+ switch(i)
+ {
+ case -1:
+ _BookSeek(x);
+ entry.bvFlags = FLAG_DISABLED;
+ _BookWrite(&entry);
+ if (NULL != fd)
+ {
+ fprintf(fd, "%I64x (%x) FLAGS %x // %s\n",
+ entry.u64Sig,
+ entry.mvNext.move,
+ entry.bvFlags,
+ BoardToFEN(pos));
+ }
+ break;
+ case 0:
+ _BookSeek(x);
+ entry.bvFlags = 0;
+ _BookWrite(&entry);
+ if (NULL != fd)
+ {
+ fprintf(fd, "%I64x (%x) FLAGS %x // %s\n",
+ entry.u64Sig,
+ entry.mvNext.move,
+ entry.bvFlags,
+ BoardToFEN(pos));
+ }
+ break;
+ case 1:
+ _BookSeek(x);
+ entry.bvFlags = FLAG_ALWAYSPLAY;
+ _BookWrite(&entry);
+ if (NULL != fd)
+ {
+ fprintf(fd, "%I64x (%x) FLAGS %x // %s\n",
+ entry.u64Sig,
+ entry.mvNext.move,
+ entry.bvFlags,
+ BoardToFEN(pos));
+ }
+ break;
+ }
+ }
+ }
+
+ printf("\n\n");
+ Trace("PreferPosition: Changed %u lines\n", iChanged);
+}
+
+//+----------------------------------------------------------------------------
+//
+// Function: ImportEditing
+//
+// Synopsis: Affect the opening book file based on an old editing record
+// (.EDT file). Apply editing / learning record to the new book
+// positions.
+//
+// Arguments: char *szFile
+//
+// Returns: FLAG
+//
+//+----------------------------------------------------------------------------
+FLAG
+ImportEditing(char *szFile)
+{
+ FILE *p = fopen(szFile, "r");
+ FLAG fMyRetVal = FALSE;
+ static char szLine[512];
+ char szOpcode[512];
+ int iArg;
+ int iTotalEntries;
+ INT64 u64Sig;
+ MOVE mv;
+ int iLow, iHigh, iCurrent = 0;
+ int iIndex;
+ BOOK_ENTRY entry;
+ BOOK_ENTRY klobber;
+
+ //
+ // We cannot probe the opening book if membook is non-NULL (which
+ // indicates the opening book is currently in-memory and still being
+ // built.
+ //
+ if (g_pMemBook != NULL)
+ {
+ goto end;
+ }
+
+ //
+ // Open the book.
+ //
+ iTotalEntries = _BookCount();
+ if (0 == iTotalEntries)
+ {
+ Trace("BookMove: Opening book \"%s\" is empty.\n",
+ g_Options.szBookName);
+ goto end;
+ }
+
+ if (FALSE == _BookOpen())
+ {
+ Trace("BookMove: Could not open the book.\n");
+ goto end;
+ }
+
+ if (NULL != p)
+ {
+ while(fgets(szLine, 511, p))
+ {
+ if (_snscanf(szLine, 511, "%I64x (%x) %s",
+ &u64Sig,
+ &mv.move,
+ &szOpcode) != 3)
+ {
+ continue;
+ }
+
+#ifdef DEBUG
+ Trace("Searching for %I64x move %x\n",
+ u64Sig, mv.move);
+#endif
+
+ if (_BookFindSig(u64Sig, &iLow, &iHigh))
+ {
+ //
+ // Walk forward looking for the right move.
+ //
+ memset(&entry, 0, sizeof(entry));
+ for (iIndex = iLow;
+ iIndex <= iHigh;
+ iIndex++)
+ {
+ _BookSeek(iIndex);
+ _BookRead(&entry);
+ ASSERT(entry.u64Sig == u64Sig);
+
+ if (entry.mvNext.move == mv.move) break;
+ }
+
+ //
+ // Did we find the move?
+ //
+ if (entry.mvNext.move != mv.move)
+ {
+ Trace("Move %x not present in book at position %I64x\n",
+ mv.move, u64Sig);
+ continue;
+ }
+
+ //
+ // Yes. What should we do with it?
+ //
+ if (!STRNCMP(szOpcode, "FLAGS", 5))
+ {
+ if (_snscanf(szLine, 511, "%I64x (%x) %s %x",
+ &u64Sig,
+ &mv.move,
+ &szOpcode,
+ &iArg) == 4)
+ {
+ if (iArg >= 0) entry.bvFlags = iArg;
+ }
+ else
+ {
+ Trace("Error\n");
+ }
+ }
+ else if (!STRNCMP(szOpcode, "+", 1))
+ {
+ if (_snscanf(szLine, 511, "%I64x (%x) %s %u",
+ &u64Sig,
+ &mv.move,
+ &szOpcode,
+ &iArg) == 4)
+ {
+ if (iArg >= 0) entry.iWins = iArg;
+ }
+ else
+ {
+ Trace("Error\n");
+ }
+ }
+ else if (!STRNCMP(szOpcode, "=", 1))
+ {
+ if (_snscanf(szLine, 511, "%I64x (%x) %s %u",
+ &u64Sig,
+ &mv.move,
+ &szOpcode,
+ &iArg) == 4)
+ {
+ if (iArg >= 0) entry.iDraws = iArg;
+ }
+ else
+ {
+ Trace("Error\n");
+ }
+ }
+ else if (!STRNCMP(szOpcode, "-", 1))
+ {
+ if (_snscanf(szLine, 511, "%I64x (%x) %s %u",
+ &u64Sig,
+ &mv.move,
+ &szOpcode,
+ &iArg) == 4)
+ {
+ if (iArg >= 0) entry.iLosses = iArg;
+ }
+ else
+ {
+ Trace("Error\n");
+ }
+ }
+ else
+ {
+ Trace("Error\n");
+ }
+
+ //
+ // Write it back to the book
+ //
+ _BookSeek(iIndex);
+ _BookRead(&klobber);
+
+ if ((entry.u64Sig ==
+ klobber.u64Sig) &&
+ (entry.mvNext.move ==
+ klobber.mvNext.move))
+ {
+ _BookWrite(&entry);
+ }
+ }
+ }
+ }
+
+ fMyRetVal = TRUE;
+ end:
+ _BookClose();
+ return(fMyRetVal);
+}
+#endif
+
+
+static FLAG
+_FindTerminalPositions(CHAR *szFilename,
+ ULONG uPlyBeforeMate)
+/**
+
+Routine description:
+
+Parameters:
+
+ CHAR *szFilename,
+ ULONG uMovesBeforeMate
+
+Return value:
+
+ static FLAG
+
+**/
+{
+ CHAR *p;
+ FILE *pf = NULL;
+ struct stat s;
+ POSITION *pos;
+ SEARCHER_THREAD_CONTEXT *ctx;
+ FLAG fInCheck;
+ MOVE mv;
+ ULONG u;
+ FLAG fRet = FALSE;
+
+ ctx = SystemAllocateMemory(sizeof(SEARCHER_THREAD_CONTEXT));
+ if (NULL == ctx)
+ {
+ Trace("Out of memory.\n");
+ goto end;
+ }
+
+ if (FALSE == SystemDoesFileExist(szFilename))
+ {
+ Trace("FindTerminalPositions: file \"%s\" doesn't exist.\n",
+ szFilename);
+ goto end;
+ }
+
+ if (0 != stat(szFilename, &s))
+ {
+ Trace("FindTerminalPositions: Can't read file \"%s\".\n",
+ szFilename);
+ goto end;
+ }
+
+ pf = fopen(szFilename, "rb");
+ if (NULL == pf)
+ {
+ Trace("FindTerminalPositions: error reading file %s.\n",
+ szFilename);
+ goto end;
+ }
+
+ mv.uMove = 0;
+ while((p = ReadNextGameFromPgnFile(pf)) != NULL)
+ {
+ if (TRUE == LoadPgn(p))
+ {
+ pos = GetRootPosition();
+
+ fInCheck = InCheck(pos, pos->uToMove);
+ if (TRUE == fInCheck)
+ {
+ InitializeSearcherContext(pos, ctx);
+ GenerateMoves(ctx, mv, GENERATE_ESCAPES);
+ if (0 == MOVE_COUNT(ctx, 0))
+ {
+ u = uPlyBeforeMate;
+ while(u)
+ {
+ if (FALSE == OfficiallyTakebackMove())
+ {
+ break;
+ }
+ u--;
+ }
+ pos = GetRootPosition();
+ InitializeSearcherContext(pos, ctx);
+ DumpPosition(pos);
+ (void)Eval(ctx, -INFINITY, +INFINITY);
+ Trace("King safeties: W=%d, B=%d\n",
+ ctx->sPlyInfo[ctx->uPly].iKingScore[WHITE],
+ ctx->sPlyInfo[ctx->uPly].iKingScore[BLACK]);
+ }
+ }
+ }
+ SystemFreeMemory(p);
+ }
+ fclose(pf);
+ fRet = TRUE;
+
+ end:
+ if (NULL != ctx)
+ {
+ SystemFreeMemory(ctx);
+ }
+ return(fRet);
+}
+
+
+
+COMMAND(BookCommand)
+{
+ CHAR *szOpcode;
+ CHAR *pDot;
+
+ ASSERT(!STRNCMPI(argv[0], "BOOK", 4));
+ if (argc < 2)
+ {
+ Trace("Available opening book commands:\n\n"
+ " book name : set opening book file\n"
+ " book import : import PGN or EDT data to book file\n"
+ " book dump learning : show book learning\n"
+ " book dump moves : show book moves in current position\n"
+ " book edit : edit book moves in current position\n"
+ " book tourn : view / toggle tournament mode\n"
+ " book openings : read opening line names PGN file\n"
+ " book hackhack : read PGN files looking for mate\n"
+ "\n");
+ return;
+ }
+
+ szOpcode = argv[1];
+ if (!STRNCMPI(szOpcode, "NAME", 4))
+ {
+ //
+ // book name <filename> : set opening book to use
+ //
+ if (argc == 2)
+ {
+ Trace("Usage: book name <required-filename>\n");
+ return;
+ }
+ strncpy(g_Options.szBookName, argv[2], SMALL_STRING_LEN_CHAR);
+ Trace("Opening book name set to \"%s\"\n", argv[2]);
+ }
+ else if (!STRNCMPI(argv[1], "DUMP", 4))
+ {
+ //
+ // book dump : show book dump help
+ //
+ if (argc == 2)
+ {
+ Trace("Available book dump commands:\n"
+ " book dump learning : show book learning\n"
+ " book dump moves : show book moves in current"
+ "position\n"
+ "\n");
+ return;
+ }
+ ASSERT(argc > 2);
+
+ if (!STRNCMPI(argv[2], "LEARNING", 5))
+ {
+ //
+ // book dump learning : show book learning on stdout
+ //
+ _DumpUserOpeningList();
+ }
+ else
+ {
+ //
+ // book dump moves : show book moves in this position
+ //
+ BookMove(pos, BOOKMOVE_DUMP);
+ }
+ }
+ else if (!STRNCMPI(argv[1], "TOURNAMENTMODE", 5))
+ {
+ if (argc == 2)
+ {
+ //
+ // book tourn : show current tournament mode setting
+ //
+ if (g_fTournamentMode == TRUE)
+ {
+ Trace("Tournament mode is currently TRUE.\n");
+ }
+ else
+ {
+ Trace("Tournament mode is currently FALSE.\n");
+ }
+ }
+ else
+ {
+ //
+ // book tourn <t|f> : set tournament mode on or off
+ //
+ if (tolower(*argv[2]) == 't')
+ {
+ g_fTournamentMode = TRUE;
+ Trace("Tournament mode set to TRUE.\n");
+ }
+ else if (tolower(*argv[2]) == 'f')
+ {
+ g_fTournamentMode = FALSE;
+ Trace("Tournament mode set to FALSE.\n");
+ }
+ else
+ {
+ Trace("Usage: book tournamentmode [<true | false>]\n");
+ }
+ }
+ }
+
+#if 0
+ else if (!STRNCMPI(argv[1], "EDIT", 4))
+ {
+ //
+ // book edit : edit book moves in this position
+ //
+ BookEdit(pos);
+ return(TRUE);
+ }
+#endif
+
+ else if (!STRNCMPI(argv[1], "HACKHACK", 8))
+ {
+ if (argc == 2)
+ {
+ Trace("Usage: book hackhack <required-filename>\n");
+ return;
+ }
+ _FindTerminalPositions(argv[2], 4);
+ }
+
+ else if (!STRNCMPI(argv[1], "OPENINGS", 8))
+ {
+ //
+ // book openings : read book opening names file
+ //
+ if (argc == 2)
+ {
+ Trace("Usage: book openings <required-filename>\n");
+ return;
+ }
+
+ if (FALSE == SystemDoesFileExist(argv[2]))
+ {
+ Trace("Error (file doesn't exist): %s\n", argv[2]);
+ return;
+ }
+
+// Trace("%u opening lines named.\n", LearnOpeningNames(argv[2]));
+ }
+
+ else if (!STRNCMPI(argv[1], "IMPORT", 6))
+ {
+ //
+ // book import : create new book file from PGN -or-
+ // apply editing / learning to a book file
+ //
+ if (argc == 2)
+ {
+ Trace("Usage: book import <required-filename>\n");
+ return;
+ }
+
+ if (FALSE == SystemDoesFileExist(argv[2]))
+ {
+ Trace("Error (file doesn't exist): %s\n", argv[2]);
+ return;
+ }
+
+ pDot = strrchr(argv[2], '.');
+ if (NULL == pDot)
+ {
+ Trace("Error (unknown filetype): %s\n", argv[2]);
+ return;
+ }
+
+ if (!STRNCMPI(pDot, ".pgn", 4))
+ {
+ if (TRUE == SystemDoesFileExist(g_Options.szBookName))
+ {
+ Trace("The opening book %s already exists, "
+ "importing new lines\n", g_Options.szBookName);
+ _LearnPgnOpenings(argv[2], 0);
+ }
+ else
+ {
+ Trace("The opening book %s does not exists, "
+ "creating new book\n", g_Options.szBookName);
+ _LearnPgnOpenings(argv[2], 1);
+ }
+ }
+#if 0
+ else if (!STRNCMPI(pDot, ".edt", 4))
+ {
+ if (FALSE == ImportEditing(argv[2]))
+ {
+ Trace("An error occurred while importing .edt file.\n");
+ }
+ }
+#endif
+ else
+ {
+ Trace("Error (unknown filetype): %s\n", argv[2]);
+ }
+ }
+}