/*
Protector -- a UCI chess engine
Copyright (C) 2009-2010 Raimund Heid (Raimund_Heid@yahoo.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "book.h"
#include "io.h"
#include "pgn.h"
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <time.h>
static const UINT32 ILLEGAL_OFFSET = 0xFFFFFFFF;
Book globalBook;
int openBook(Book * book, const char *name)
{
char indexfileName[256], movefileName[256];
strcat(indexfileName
, ".bki");
book->readonly = FALSE;
book
->indexFile
= fopen(indexfileName
, "r+");
book
->moveFile
= fopen(movefileName
, "r+");
if (book->indexFile == NULL)
{
book
->indexFile
= fopen(indexfileName
, "w+");
}
if (book->moveFile == NULL)
{
book
->moveFile
= fopen(movefileName
, "w+");
}
if (book->indexFile == NULL)
{
book
->indexFile
= fopen(indexfileName
, "r");
book->readonly = TRUE;
}
if (book->moveFile == NULL)
{
book
->moveFile
= fopen(movefileName
, "r");
book->readonly = TRUE;
}
if (book->indexFile != NULL)
{
fseek(book
->indexFile
, 0, SEEK_END
);
book->numberOfPositions =
(ftell(book
->indexFile
) + 1) / sizeof(BookPosition
);
}
if (book->moveFile != NULL)
{
fseek(book
->moveFile
, 0, SEEK_END
);
book
->numberOfMoves
= (ftell(book
->moveFile
) + 1) / sizeof(BookMove
);
}
if (book->indexFile != NULL && book->moveFile != NULL)
{
return 0;
}
else
{
if (book->indexFile != NULL)
{
book->indexFile = NULL;
}
if (book->moveFile != NULL)
{
book->moveFile = NULL;
}
return -1;
}
}
void closeBook(Book * book)
{
if (book->indexFile != NULL)
{
}
if (book->moveFile != NULL)
{
}
}
static BookPosition loadBookposition(const Book * book, const UINT32 offset)
{
BookPosition position;
fseek(book
->indexFile
, offset
, SEEK_SET
);
fread(&position
, sizeof(BookPosition
), 1, book
->indexFile
);
return position;
}
static void storeBookposition(Book * book, const BookPosition * position,
const UINT32 offset)
{
fseek(book
->indexFile
, offset
, SEEK_SET
);
fwrite(position
, sizeof(BookPosition
), 1, book
->indexFile
);
}
static BookMove loadBookmove(const Book * book, const UINT32 offset)
{
BookMove move;
fseek(book
->moveFile
, offset
, SEEK_SET
);
fread(&move
, sizeof(BookMove
), 1, book
->moveFile
);
return move;
}
static void storeBookmove(Book * book, const BookMove * move,
const UINT32 offset)
{
fseek(book
->moveFile
, offset
, SEEK_SET
);
fwrite(move
, sizeof(BookMove
), 1, book
->moveFile
);
}
void createEmptyBook(Book * book)
{
BookPosition position;
int i;
position.hashKey = 0;
position.nextPosition = ILLEGAL_OFFSET;
position.firstMove = ILLEGAL_OFFSET;
for (i = 0; i < BOOKINDEX_SIZE; i++)
{
storeBookposition(book, &position, i * sizeof(BookPosition));
}
book->numberOfPositions = BOOKINDEX_SIZE;
book->numberOfMoves = 0;
}
static UINT32 getBookpositionOffset(const Book * book, const UINT64 hashKey)
{
UINT32 offset = (UINT32) getBookpositionIndexOffset(hashKey);
BookPosition position = loadBookposition(book, offset);
while (position.hashKey != hashKey &&
position.nextPosition != ILLEGAL_OFFSET)
{
offset = position.nextPosition;
position = loadBookposition(book, offset);
}
return (position.hashKey == hashKey &&
position.firstMove != ILLEGAL_OFFSET ? offset : ILLEGAL_OFFSET);
}
static void addBookposition(Book * book, const BookPosition * position)
{
UINT32 offset = (UINT32) getBookpositionIndexOffset(position->hashKey);
BookPosition currentPosition = loadBookposition(book, offset);
if (currentPosition.firstMove == ILLEGAL_OFFSET)
{
storeBookposition(book, position, offset);
}
else
{
UINT32 newOffset = book->numberOfPositions++ * sizeof(BookPosition);
storeBookposition(book, position, newOffset);
while (currentPosition.nextPosition != ILLEGAL_OFFSET)
{
offset = currentPosition.nextPosition;
currentPosition = loadBookposition(book, offset);
}
currentPosition.nextPosition = newOffset;
storeBookposition(book, ¤tPosition, offset);
}
}
static UINT32 getBookmoveOffset(const Book * book, const UINT64 hashKey,
const UINT16 move)
{
UINT32 offset = getBookpositionOffset(book, hashKey);
BookMove currentMove;
if (offset == ILLEGAL_OFFSET)
{
return ILLEGAL_OFFSET;
}
else
{
BookPosition position = loadBookposition(book, offset);
if (position.firstMove == ILLEGAL_OFFSET)
{
return ILLEGAL_OFFSET;
}
else
{
offset = position.firstMove;
currentMove = loadBookmove(book, offset);
}
}
while (currentMove.move != move &&
currentMove.nextAlternative != ILLEGAL_OFFSET)
{
offset = currentMove.nextAlternative;
currentMove = loadBookmove(book, offset);
}
return (currentMove.move == move ? offset : ILLEGAL_OFFSET);
}
static void appendBookmove(Book * book, const UINT64 hashKey,
const BookMove * move)
{
UINT32 moveOffset = book->numberOfMoves++ * sizeof(BookMove);
UINT32 positionOffset = getBookpositionOffset(book, hashKey);
BookPosition position;
storeBookmove(book, move, moveOffset);
if (positionOffset == ILLEGAL_OFFSET)
{
position.hashKey = hashKey;
position.firstMove = moveOffset;
position.nextPosition = ILLEGAL_OFFSET;
addBookposition(book, &position);
}
else
{
position = loadBookposition(book, positionOffset);
if (position.firstMove == ILLEGAL_OFFSET)
{
position.firstMove = moveOffset;
storeBookposition(book, &position, positionOffset);
}
else
{
UINT32 currentMoveOffset = position.firstMove;
BookMove currentMove = loadBookmove(book, currentMoveOffset);
while (currentMove.nextAlternative != ILLEGAL_OFFSET)
{
currentMoveOffset = currentMove.nextAlternative;
currentMove = loadBookmove(book, currentMoveOffset);
}
currentMove.nextAlternative = moveOffset;
storeBookmove(book, ¤tMove, currentMoveOffset);
}
}
}
static void updateBookmove(BookMove * move, const Color activeColor,
const GameResult result, const bool personalResult)
{
if (personalResult != FALSE)
{
move->numberOfPersonalGames++;
if (result == RESULT_WHITE_WINS)
{
move->personalScore += (activeColor == WHITE ? 1 : -1);
}
else if (result == RESULT_BLACK_WINS)
{
move->personalScore += (activeColor == BLACK ? 1 : -1);
}
}
else
{
move->numberOfGames++;
if (result == RESULT_WHITE_WINS)
{
move->score += (activeColor == WHITE ? 1 : -1);
}
else if (result == RESULT_BLACK_WINS)
{
move->score += (activeColor == BLACK ? 1 : -1);
}
}
}
void addBookmove(Book * book, const Position * position,
const Move move, const GameResult result,
const bool personalResult)
{
BookMove bookMove;
UINT32 bookmoveOffset;
bookMove.move = packedMove(move);
bookmoveOffset = getBookmoveOffset(book, position->hashKey, bookMove.move);
if (bookmoveOffset == ILLEGAL_OFFSET)
{
bookMove.numberOfGames = 0;
bookMove.score = 0;
bookMove.numberOfPersonalGames = 0;
bookMove.personalScore = 0;
bookMove.nextAlternative = ILLEGAL_OFFSET;
updateBookmove(&bookMove, position->activeColor, result,
personalResult);
appendBookmove(book, position->hashKey, &bookMove);
}
else
{
bookMove = loadBookmove(book, bookmoveOffset);
updateBookmove(&bookMove, position->activeColor, result,
personalResult);
storeBookmove(book, &bookMove, bookmoveOffset);
}
}
static void appendBookGame(Book * book, const PGNGame * game,
const int maximumNumberOfPlies)
{
Gamemove *currentMove = game->firstMove;
int plycount = 0;
GameResult result = RESULT_UNKNOWN;
if (strcmp(game
->result
, GAMERESULT_WHITE_WINS
) == 0)
{
result = RESULT_WHITE_WINS;
}
if (strcmp(game
->result
, GAMERESULT_DRAW
) == 0)
{
result = RESULT_DRAW;
}
if (strcmp(game
->result
, GAMERESULT_BLACK_WINS
) == 0)
{
result = RESULT_BLACK_WINS;
}
if (result
== RESULT_UNKNOWN
|| strcmp(game
->setup
, "1") == 0)
{
logDebug("Skipping book game %s-%s.\n", game->white, game->black);
return;
}
while (plycount++ < maximumNumberOfPlies && currentMove != 0)
{
addBookmove(book, ¤tMove->position,
gameMove2Move(currentMove), result, FALSE);
currentMove = currentMove->nextMove;
}
}
void appendBookDatabase(Book * book, const char *filename,
int maximumNumberOfPlies)
{
PGNFile pgnfile;
PGNGame *game;
long i;
if (openPGNFile(&pgnfile, filename) != 0)
{
return;
}
logReport("\nProcessing book file '%s' [%ld game(s)]\n", filename,
pgnfile.numGames);
for (i = 1; i <= pgnfile.numGames; i++)
{
game = getGame(&pgnfile, i);
logReport("Processing book game #%ld.\n", i);
appendBookGame(book, game, maximumNumberOfPlies);
freePgnGame(game);
}
closePGNFile(&pgnfile);
}
static int getSuccessProbability(UINT16 numGames, INT16 score)
{
int result;
if (numGames == 0)
{
return 0;
}
result = (50 * (long) (numGames + score)) / (long) numGames;
assert(result
>= 0 && result
<= 100);
return result;
}
static int getBookmoveValue(const BookMove * move, const UINT32 numberOfGames)
{
const int weightGames = 10, weightPersonalGames = 1;
int successProbability, personalSuccessProbability, moveProbability;
successProbability =
getSuccessProbability(move->numberOfGames, move->score);
personalSuccessProbability =
getSuccessProbability(move->numberOfPersonalGames, move->personalScore);
moveProbability = (move->numberOfGames * 100) / numberOfGames;
successProbability = (successProbability * weightGames +
personalSuccessProbability * weightPersonalGames) /
(weightGames + weightPersonalGames);
return successProbability * moveProbability;
}
void getBookmoves(const Book * book, const UINT64 hashKey,
const Movelist * legalMoves, Movelist * bookMoves)
{
UINT32 positionOffset = getBookpositionOffset(book, hashKey), moveOffset;
BookPosition position;
BookMove bookMove, bookMoveStore[MAX_MOVES_PER_POSITION];
UINT32 numberOfGames = 0;
int i;
bookMoves->numberOfMoves = 0;
if (positionOffset == ILLEGAL_OFFSET)
{
return;
}
position = loadBookposition(book, positionOffset);
moveOffset = position.firstMove;
while (moveOffset != ILLEGAL_OFFSET)
{
Move move;
bookMove = loadBookmove(book, moveOffset);
move = (Move) bookMove.move;
if (listContainsMove(legalMoves, move))
{
numberOfGames += bookMove.numberOfGames;
bookMoveStore[bookMoves->numberOfMoves] = bookMove;
bookMoves->moves[bookMoves->numberOfMoves++] = move;
}
moveOffset = bookMove.nextAlternative;
}
for (i = 0; i < bookMoves->numberOfMoves; i++)
{
const int value =
max(1, getBookmoveValue(&bookMoveStore[i], numberOfGames));
setMoveValue(&bookMoves->moves[i], value);
}
}
static Move chooseBookmove(const Movelist * bookMoves)
{
int i;
UINT32 randomNumber, sum = 0;
for (i = 0; i < bookMoves->numberOfMoves; i++)
{
sum += getMoveValue(bookMoves->moves[i]);
}
srand((unsigned int) (getTimestamp
() + time(0)));
randomNumber
= ((UINT32
) rand() * (UINT32
) rand() + (UINT32
) rand()) % sum
;
logDebug("dumping bookmove list...\n");
dumpMovelist(bookMoves);
logDebug("sum: %lu random: %lu\n", sum, randomNumber);
sum = 0;
for (i = 0; i < bookMoves->numberOfMoves; i++)
{
sum += getMoveValue(bookMoves->moves[i]);
if (sum > randomNumber)
{
return bookMoves->moves[i];
}
}
return bookMoves->moves[bookMoves->numberOfMoves - 1];
}
Move getBookmove(const Book * book, const UINT64 hashKey,
const Movelist * legalMoves)
{
Movelist bookMoves;
Move move = NO_MOVE;
if (book->indexFile != 0 && book->moveFile != 0 &&
book->numberOfPositions > 0 && book->numberOfMoves > 0)
{
initMovelist(&bookMoves, 0);
getBookmoves(book, hashKey, legalMoves, &bookMoves);
if (bookMoves.numberOfMoves > 0)
{
logDebug("%d bookmoves available. choosing bookmove...\n",
bookMoves.numberOfMoves);
move = chooseBookmove(&bookMoves);
logDebug("chose bookmove %d-%d\n", getFromSquare(move),
getToSquare(move));
}
}
return move;
}
int initializeModuleBook()
{
if (openBook(&globalBook, "book") < 0)
{
logDebug("No opening book available.\n");
}
else
{
logDebug("Opening book found. %ld positions, %ld moves\n",
globalBook.numberOfPositions, globalBook.numberOfMoves);
}
return 0;
}
static int testPositionOperations()
{
Book book;
UINT64 hashKey = BOOKINDEX_SIZE + 100;
BookPosition position;
position.hashKey = hashKey;
position.nextPosition = ILLEGAL_OFFSET;
position.firstMove = 0;
openBook(&book, "moduletest");
createEmptyBook(&book);
assert(getBookpositionOffset
(&book
, hashKey
) == ILLEGAL_OFFSET
);
addBookposition(&book, &position);
assert(getBookpositionOffset
(&book
, hashKey
) ==
getBookpositionIndexOffset(hashKey));
position.hashKey += BOOKINDEX_SIZE;
addBookposition(&book, &position);
assert(getBookpositionOffset
(&book
, position.
hashKey) ==
BOOKINDEX_SIZE * sizeof(BookPosition));
closeBook(&book);
return 0;
}
static int testMoveOperations()
{
Book book;
UINT64 hashKey = 4711;
BookMove move;
openBook(&book, "moduletest");
createEmptyBook(&book);
move.move = 7;
assert(getBookmoveOffset
(&book
, hashKey
, move.
move) == ILLEGAL_OFFSET
);
move.move = 17;
move.nextAlternative = ILLEGAL_OFFSET;
appendBookmove(&book, hashKey, &move);
assert(getBookmoveOffset
(&book
, hashKey
, move.
move) ==
0 * sizeof(BookMove));
move.move = 19;
appendBookmove(&book, hashKey, &move);
assert(getBookmoveOffset
(&book
, hashKey
, move.
move) ==
1 * sizeof(BookMove));
hashKey += 160939;
assert(getBookmoveOffset
(&book
, hashKey
, move.
move) == ILLEGAL_OFFSET
);
appendBookmove(&book, hashKey, &move);
assert(getBookmoveOffset
(&book
, hashKey
, move.
move) ==
2 * sizeof(BookMove));
closeBook(&book);
return 0;
}
int testModuleBook()
{
int result;
if ((result = testPositionOperations()) != 0)
{
return result;
}
if ((result = testMoveOperations()) != 0)
{
return result;
}
return 0;
}