/*
Protector -- a UCI chess engine
Copyright (C) 2008 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 "xboard.h"
#include "coordination.h"
#include "tools.h"
#include "io.h"
#include "fen.h"
#include "pgn.h"
#include "tablebase.h"
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <ctype.h>
static SearchTask task;
static Variation variation;
static PGNGame game;
static XboardStatus status;
bool resetGlobalHashtable = FALSE;
static char pv[256];
static int numUciMoves = 1000;
/* #define DEBUG_GUI_PROTOCOL */
/* #define DEBUG_GUI_CONVERSATION */
static Move readUciMove(const char *buffer)
{
const Square from = getSquare(buffer[0] - 'a', buffer[1] - '1');
const Square to = getSquare(buffer[2] - 'a', buffer[3] - '1');
Piece newPiece = NO_PIECE;
switch (buffer[4])
{
case 'q':
case 'Q':
newPiece = (Piece) (QUEEN);
break;
case 'r':
case 'R':
newPiece = (Piece) (ROOK);
break;
case 'b':
case 'B':
newPiece = (Piece) (BISHOP);
break;
case 'n':
case 'N':
newPiece = (Piece) (KNIGHT);
break;
default:
newPiece = (Piece) (NO_PIECE);
}
return getPackedMove(from, to, newPiece);
}
static const char *getUciToken(const char *uciString, const char *tokenName)
{
size_t tokenNameLength = strlen(tokenName);
const char *tokenHit = strstr(uciString, tokenName);
#ifdef DEBUG_GUI_PROTOCOL
logDebug("find >%s< in >%s<\n", tokenName, uciString);
#endif
if (tokenHit == 0)
{
return 0;
}
else
{
const char nextChar = *(tokenHit + tokenNameLength);
if ((tokenHit == uciString || isspace((int) *(tokenHit - 1))) &&
(nextChar == '\0' || isspace((int) nextChar)))
{
return tokenHit;
}
else
{
const char *nextPosstibleTokenOccurence =
tokenHit + tokenNameLength + 1;
if (nextPosstibleTokenOccurence + tokenNameLength <=
uciString + strlen(uciString))
{
return getUciToken(nextPosstibleTokenOccurence, tokenName);
}
else
{
return 0;
}
}
}
}
static void getNextUciToken(const char *uciString, char *buffer)
{
const char *start = uciString, *end;
unsigned int tokenLength;
while (*start != '\0' && isspace(*start))
{
start++;
}
if (*start == '\0')
{
strcpy(buffer, "");
return;
}
assert(*start != '\0' && isspace(*start) == FALSE);
end = start + 1;
while (*end != '\0' && isspace(*end) == FALSE)
{
end++;
}
assert(*end == '\0' || isspace(*end));
tokenLength = (unsigned int) (end - start);
strncpy(buffer, start, tokenLength);
buffer[tokenLength] = '\0';
}
static long getLongUciValue(const char *uciString, const char *name,
int defaultValue)
{
long value;
char valueBuffer[256];
const char *nameStart = getUciToken(uciString, name);
if (nameStart == 0)
{
value = defaultValue;
}
else
{
getNextUciToken(nameStart + strlen(name), valueBuffer);
value = atol(valueBuffer);
}
#ifdef DEBUG_GUI_PROTOCOL
logDebug("get uci long value; %s = %ld\n", name, value);
#endif
return value;
}
static void getUciNamedValue(const char *uciString, char *name, char *value)
{
const char *nameTagStart = getUciToken(uciString, "name");
const char *valueTagStart = getUciToken(uciString, "value");
name[0] = value[0] = '\0';
if (nameTagStart != 0 && valueTagStart != 0 &&
nameTagStart < valueTagStart)
{
const int nameLength = (int) (valueTagStart - 1 - (nameTagStart + 5));
strncpy(name, nameTagStart + 5, nameLength);
name[nameLength] = '\0';
getNextUciToken(valueTagStart + 6, value);
/* logDebug("name=<%s> value=<%s>\n", name, value); */
}
}
/******************************************************************************
*
* Get the specified move in xboard format.
*
******************************************************************************/
static void getGuiMoveString(const Move move, char *buffer)
{
char from[3], to[3];
getSquareName(getFromSquare(move), from);
getSquareName(getToSquare(move), to);
if (getNewPiece(move) == NO_PIECE)
{
sprintf(buffer, "%s%s", from, to);
}
else
{
sprintf(buffer, "%s%s%c", from, to, pieceSymbol[getNewPiece(move)]);
}
}
/******************************************************************************
*
* Get the specified param from the specified xboard command string.
*
******************************************************************************/
static void getTokenByNumber(const char *command, int paramNumber,
char *buffer)
{
int paramCount = 0;
char currentChar;
bool escapeMode = FALSE;
char *pbuffer = buffer;
while ((currentChar = *command++) != '\0' && paramCount <= paramNumber)
{
if (currentChar == '{')
{
escapeMode = TRUE;
}
else if (currentChar == '}')
{
escapeMode = FALSE;
}
if (isspace(currentChar) && escapeMode == FALSE)
{
paramCount++;
while (isspace(*command))
{
command++;
}
}
if (paramCount == paramNumber)
{
*pbuffer++ = currentChar;
}
}
*pbuffer = '\0';
trim(buffer);
}
/******************************************************************************
*
* Send the specified command via stdout to xboard.
*
******************************************************************************/
static void sendToXboard(const char *fmt, ...)
{
va_list args;
char buffer[4096];
va_start(args, fmt);
vsprintf(buffer, fmt, args);
va_end(args);
fprintf(stdout, "%s\n", buffer);
fflush(stdout);
#ifdef DEBUG_GUI_CONVERSATION
logDebug("### sent to xboard: %s\n", buffer);
#endif
}
/******************************************************************************
*
* Send the specified command via stdout to xboard.
*
******************************************************************************/
static void sendToXboardNonDebug(const char *fmt, ...)
{
va_list args;
char buffer[4096];
va_start(args, fmt);
vsprintf(buffer, fmt, args);
va_end(args);
fprintf(stdout, "%s\n", buffer);
fflush(stdout);
}
/******************************************************************************
*
* Determine the calculation time for the next task in centiseconds.
*
******************************************************************************/
static int getCalculationTime(TimecontrolData * data)
{
if (data->restTime < 0)
{
return 2 * data->incrementTime;
}
if (data->numberOfMoves > 0)
{
int restMoves = data->numberOfMoves -
(data->numberOfMovesPlayed % data->numberOfMoves);
return data->restTime / max(2, restMoves);
}
else
{
return (data->incrementTime > 0 ?
data->incrementTime + data->restTime / 25 :
data->restTime / 40);
}
}
/******************************************************************************
*
* Determine the calculation time for the next task in milliseconds.
*
******************************************************************************/
static int getMaximumCalculationTime(TimecontrolData * data)
{
if (data->restTime < 0)
{
return data->incrementTime;
}
if (data->numberOfMoves > 0)
{
const int restMoves = data->numberOfMoves -
(data->numberOfMovesPlayed % data->numberOfMoves);
const int factor = 5;
return (factor * data->restTime) / max(2 * factor, restMoves);
}
else
{
return (data->incrementTime > 0 ?
data->restTime / 2 : data->restTime / 4);
}
}
/******************************************************************************
*
* Determine the calculation time for the next task in milliseconds.
*
******************************************************************************/
static int determineCalculationTime(bool targetTime)
{
if (status.operationMode == XBOARD_OPERATIONMODE_ANALYSIS)
{
return 0;
}
else
{
TimecontrolData *tcd = &status.timecontrolData[status.engineColor];
initializeVariationFromGame(&variation, &game);
tcd->numberOfMovesPlayed = variation.singlePosition.moveNumber - 1;
if (targetTime)
{
return max(1, 95 * getCalculationTime(tcd) / 100);
}
else
{
return max(1, 95 * getMaximumCalculationTime(tcd) / 100);
}
}
}
/******************************************************************************
*
* Start the calculation of the current position.
*
******************************************************************************/
static void startCalculation()
{
variation.timeTarget = determineCalculationTime(TRUE);
variation.timeLimit = determineCalculationTime(FALSE);
setDrawScore(&variation, 0, status.engineColor);
status.bestMoveWasSent = FALSE;
#ifdef DEBUG_GUI_CONVERSATION
logDebug("Scheduling task. Timelimits: %d/%d\n", variation.timeTarget,
variation.timeLimit);
#endif
scheduleTask(&task);
}
/******************************************************************************
*
* Delete the current ponder result.
*
******************************************************************************/
static void deletePonderResult()
{
status.ponderResultMove = NO_MOVE;
}
/******************************************************************************
*
* Get a pv according to the uci protocol.
*
******************************************************************************/
char *getUciPv(const Variation * variation)
{
char *buffer = malloc(256);
int i;
strcpy(buffer, "");
for (i = 0; i < min(8, variation->pv.length); i++)
{
const Move move = (Move) variation->pv.move[i];
if (move != NO_MOVE)
{
char moveBuffer[8];
if (i > 0)
{
strcat(buffer, " ");
}
getGuiMoveString(move, moveBuffer);
strcat(buffer, moveBuffer);
}
else
{
break;
}
}
return buffer;
}
/******************************************************************************
*
* Post a principal variation line.
*
******************************************************************************/
static void postPv(Variation * var, bool sendAnyway)
{
double time = var->timestamp - var->startTime;
char *pvmoves;
char scoreBuffer[16];
if (time >= 250 || sendAnyway)
{
pvmoves = getUciPv(var);
formatUciValue(var->pv.score, scoreBuffer);
sprintf(pv,
"info depth %d seldepth %d time %.0f nodes %lld pv %s score %s tbhits %lu",
var->nominalDepth, var->selDepth, time, var->nodes, pvmoves,
scoreBuffer, var->tbHits);
sendToXboardNonDebug("%s", pv);
free(pvmoves);
var->numPvUpdates++;
}
}
/******************************************************************************
*
* Post a statistics information about the current search.
*
******************************************************************************/
static void reportBaseMoveUpdate(const Variation * var)
{
const double time = var->timestamp - var->startTime;
char movetext[10];
if (time >= 500)
{
getGuiMoveString(var->currentBaseMove, movetext);
sendToXboardNonDebug
("info depth %d seldepth %d currmove %s currmovenumber %d",
var->nominalDepth, var->selDepth, movetext,
var->numberOfCurrentBaseMove);
}
}
/******************************************************************************
*
* Post a statistics information about the current search.
*
******************************************************************************/
static void reportStatisticsUpdate(Variation * var)
{
UINT64 nodeCount = getNodeCount();
const double time = var->timestamp - var->startTime;
const double nps = (nodeCount / max((double) 0.001, (time / 1000.0)));
const double hashUsage =
((double) var->hashtable->entriesUsed * 1000.0) /
((double) var->hashtable->tableSize);
sendToXboardNonDebug
("info time %0.f nodes %lld nps %.0f hashfull %.0f tbhits %lu",
time, nodeCount, nps, hashUsage, var->tbHits);
reportBaseMoveUpdate(var);
if (var->numPvUpdates == 0)
{
postPv(var, FALSE);
}
}
/******************************************************************************
*
* Send a bestmove info to the gui.
*
******************************************************************************/
static void sendBestmoveInfo(Variation * var)
{
char moveBuffer[8];
postPv(var, TRUE);
if (moveIsLegal(&var->startPosition, var->bestBaseMove))
{
Variation tmp = *var;
variation.expectedScore = getMoveValue(var->bestBaseMove);
getGuiMoveString(var->bestBaseMove, moveBuffer);
status.ponderingMove = (Move) var->pv.move[1];
setBasePosition(&tmp, &var->startPosition);
makeMove(&tmp, var->bestBaseMove);
if (status.ponderingMove != NO_MOVE &&
moveIsLegal(&tmp.singlePosition, status.ponderingMove))
{
char ponderMoveBuffer[8];
getGuiMoveString(status.ponderingMove, ponderMoveBuffer);
sendToXboard("bestmove %s ponder %s", moveBuffer, ponderMoveBuffer);
}
else
{
status.ponderingMove = NO_MOVE;
sendToXboard("bestmove %s", moveBuffer);
}
unmakeLastMove(&tmp);
}
else
{
getGuiMoveString(var->bestBaseMove, moveBuffer);
#ifdef DEBUG_GUI_CONVERSATION
logDebug("### Illegal best move %s ###\n", moveBuffer);
#endif
}
status.bestMoveWasSent = TRUE;
}
/******************************************************************************
*
* Stop the calculation of the current position.
*
******************************************************************************/
static void stopCalculation()
{
Variation tmp;
prepareSearchAbort(&tmp);
status.engineIsPondering = FALSE;
}
/******************************************************************************
*
* Handle events generated by the search engine.
*
******************************************************************************/
static void handleSearchEvent(int eventId, void *var)
{
Variation *variation = (Variation *) var;
getGuiSearchMutex();
switch (eventId)
{
case SEARCHEVENT_SEARCH_FINISHED:
if (status.engineIsPondering == FALSE)
{
sendBestmoveInfo(var);
}
else
{
#ifdef DEBUG_GUI_CONVERSATION
logDebug("Search finished. Engine is pondering.\n");
logDebug("No best move info sent.\n");
#endif
}
status.engineIsPondering = status.engineIsActive = FALSE;
break;
case SEARCHEVENT_PLY_FINISHED:
postPv(variation, FALSE);
break;
case SEARCHEVENT_NEW_BASEMOVE:
reportBaseMoveUpdate(variation);
break;
case SEARCHEVENT_STATISTICS_UPDATE:
reportStatisticsUpdate(variation);
break;
case SEARCHEVENT_NEW_PV:
postPv(variation, TRUE);
break;
default:
break;
}
releaseGuiSearchMutex();
}
/******************************************************************************
*
* Process the specified UCI command.
*
******************************************************************************/
static int processUciCommand(const char *command)
{
char buffer[8192];
getTokenByNumber(command, 0, buffer);
if (strcmp(buffer, "uci") == 0)
{
char nameString[256];
strcpy(nameString, "id name Protector ");
strcat(nameString, programVersion);
sendToXboard(nameString);
sendToXboard("id author Raimund Heid");
sendToXboard("option name Hash type spin default 16 min 8 max 256");
sendToXboard("option name NalimovPath type string default <empty>");
sendToXboard
("option name NalimovCache type spin default 4 min 1 max 64");
sendToXboard("option name Ponder type check default true");
sendToXboard
("option name Number Of Threads type combo default 1 var 1 var 2 var 3 var 4");
sendToXboard("uciok");
return TRUE;
}
if (strcmp(buffer, "isready") == 0)
{
sendToXboard("readyok");
return TRUE;
}
if (strcmp(buffer, "ucinewgame") == 0)
{
resetGlobalHashtable = TRUE;
variation.expectedScore = 0;
return TRUE;
}
if (strcmp(buffer, "setoption") == 0)
{
char name[256], value[256];
getUciNamedValue(command, name, value);
if (strcmp(name, "NalimovPath") == 0)
{
initializeTablebase(value);
return TRUE;
}
if (strcmp(name, "NalimovCache") == 0)
{
const int cacheSize = atoi(value);
setTablebaseCacheSize(cacheSize);
return TRUE;
}
if (strcmp(name, "Hash") == 0)
{
const unsigned int hashsize = (unsigned int) max(8, atoi(value));
setHashtableSize(hashsize);
return TRUE;
}
if (strcmp(name, "Number Of Threads") == 0)
{
const unsigned int numThreads = (unsigned int) max(1, atoi(value));
setNumberOfThreads(numThreads);
return TRUE;
}
}
if (strcmp(buffer, "position") == 0)
{
resetPGNGame(&game);
if (getUciToken(command, "fen") != 0)
{
const char *fenStart = getUciToken(command, "fen") + 3;
const char *fenEnd = getUciToken(command, "moves");
if (fenEnd == 0)
{
strcpy(game.fen, fenStart);
}
else
{
const int length = (int) (fenEnd - fenStart - 1);
strncpy(game.fen, fenStart, length);
game.fen[length] = '\0';
}
trim(game.fen);
strcpy(game.setup, "1");
#ifdef DEBUG_GUI_PROTOCOL
logDebug("fen set: >%s<\n", game.fen);
#endif
}
if (getUciToken(command, "moves") != 0)
{
char moveBuffer[8];
const char *currentMove = getUciToken(command, "moves") + 5;
bool finished = FALSE;
bool ponderingMoveWasPlayed = FALSE;
int moveCount = 0;
do
{
getNextUciToken(currentMove, moveBuffer);
if (strlen(moveBuffer) > 0)
{
Move move = readUciMove(moveBuffer);
#ifdef DEBUG_GUI_PROTOCOL
logDebug("move found: >%s<\n", moveBuffer);
#endif
if (appendMove(&game, move) == 0)
{
currentMove += strlen(moveBuffer) + 1;
moveCount++;
ponderingMoveWasPlayed =
movesAreEqual(move, status.ponderingMove);
}
else
{
finished = TRUE;
}
}
else
{
finished = TRUE;
}
}
while (finished == FALSE);
if (moveCount < numUciMoves - 1)
{
resetGlobalHashtable = TRUE;
variation.expectedScore = 0;
}
numUciMoves = moveCount;
}
return TRUE;
}
if (strcmp(buffer, "stop") == 0)
{
if (status.engineIsActive)
{
#ifdef DEBUG_GUI_CONVERSATION
logDebug("stopping search...\n");
#endif
status.engineIsPondering = FALSE;
stopCalculation();
}
else
{
if (status.bestMoveWasSent == FALSE)
{
#ifdef DEBUG_GUI_CONVERSATION
logDebug("sending best move info...\n");
#endif
sendBestmoveInfo(getCurrentVariation());
}
else
{
#ifdef DEBUG_GUI_CONVERSATION
logDebug("sending 'readyok'...\n");
#endif
sendToXboard("readyok");
}
}
return TRUE;
}
if (strcmp(buffer, "ponderhit") == 0)
{
if (status.engineIsPondering)
{
status.engineIsPondering = FALSE;
if (getCurrentVariation()->terminatePondering &&
getCurrentVariation()->failingHighOrLow == FALSE)
{
Variation tmp;
#ifdef DEBUG_GUI_CONVERSATION
logDebug("immediate termination of pondering.\n");
#endif
prepareSearchAbort(&tmp);
}
else
{
#ifdef DEBUG_GUI_CONVERSATION
logDebug("unsetting pondering mode.\n");
#endif
unsetPonderMode();
}
}
else
{
#ifdef DEBUG_GUI_CONVERSATION
logDebug("Pondering finished prematurely. Sending best move info\n");
#endif
sendBestmoveInfo(getCurrentVariation());
}
return TRUE;
}
if (strcmp(buffer, "go") == 0)
{
if (status.engineIsActive)
{
stopCalculation();
releaseGuiSearchMutex();
#ifdef DEBUG_GUI_CONVERSATION
logDebug("search still running; waiting for termination...\n");
#endif
waitForSearchTermination();
getGuiSearchMutex();
}
status.engineIsActive = TRUE;
task.type = TASKTYPE_BEST_MOVE;
initializeVariationFromGame(&variation, &game);
status.engineColor = variation.singlePosition.activeColor;
if (getUciToken(command, "depth") != 0)
{
status.operationMode = XBOARD_OPERATIONMODE_ANALYSIS;
}
else if (getUciToken(command, "nodes") != 0)
{
status.operationMode = XBOARD_OPERATIONMODE_ANALYSIS;
}
else if (getUciToken(command, "mate") != 0)
{
task.type = TASKTYPE_MATE_IN_N;
task.numberOfMoves = getLongUciValue(command, "mate", 1);
status.operationMode = XBOARD_OPERATIONMODE_ANALYSIS;
#ifdef DEBUG_GUI_PROTOCOL
logDebug("Searching mate in %d", task.numberOfMoves);
#endif
}
else if (getUciToken(command, "movetime") != 0)
{
status.operationMode = XBOARD_OPERATIONMODE_USERGAME;
status.timecontrolData[WHITE].restTime =
status.timecontrolData[BLACK].restTime = -1;
status.timecontrolData[WHITE].incrementTime =
status.timecontrolData[BLACK].incrementTime =
getLongUciValue(command, "movetime", 5000);
}
else if (getUciToken(command, "infinite") != 0)
{
status.operationMode = XBOARD_OPERATIONMODE_ANALYSIS;
}
else
{
const int numMovesPlayed = variation.singlePosition.moveNumber - 1;
const int movesToGo = (int) getLongUciValue(command, "movestogo", 0);
status.operationMode = XBOARD_OPERATIONMODE_USERGAME;
status.timecontrolData[WHITE].restTime =
getLongUciValue(command, "wtime", 1000);
status.timecontrolData[WHITE].incrementTime =
getLongUciValue(command, "winc", 0);
status.timecontrolData[BLACK].restTime =
getLongUciValue(command, "btime", 1000);
status.timecontrolData[BLACK].incrementTime =
getLongUciValue(command, "binc", 0);
status.timecontrolData[WHITE].numberOfMovesPlayed =
status.timecontrolData[BLACK].numberOfMovesPlayed =
numMovesPlayed;
if (movesToGo > 0)
{
status.timecontrolData[WHITE].numberOfMoves =
status.timecontrolData[BLACK].numberOfMoves =
numMovesPlayed + movesToGo;
}
else
{
status.timecontrolData[WHITE].numberOfMoves =
status.timecontrolData[BLACK].numberOfMoves = 0;
}
}
if (getUciToken(command, "ponder") == 0)
{
status.engineIsPondering = variation.ponderMode = FALSE;
}
else
{
status.engineIsPondering = variation.ponderMode = TRUE;
}
startCalculation();
return TRUE;
}
if (strcmp(buffer, "quit") == 0)
{
stopCalculation();
return FALSE;
}
return TRUE;
}
/******************************************************************************
*
* Read xboard's commands from stdin and handle them.
*
******************************************************************************/
void acceptGuiCommands()
{
bool finished = FALSE;
char command[8192];
signal(SIGINT, SIG_IGN);
while (finished == FALSE)
{
if (fgets(command, sizeof(command), stdin) == NULL)
{
finished = TRUE;
}
else
{
trim(command);
#ifdef DEBUG_GUI_CONVERSATION
logDebug("\n### gui command: >%s<\n", command);
#endif
getGuiSearchMutex();
finished = (bool) (processUciCommand(command) == FALSE);
releaseGuiSearchMutex();
#ifdef DEBUG_GUI_CONVERSATION
logDebug(">%s< processed.\n\n", command);
#endif
}
}
#ifdef DEBUG_GUI_PROTOCOL
logDebug("GUI command processing terminated.\n");
#endif
}
/******************************************************************************
*
* (See the header file comment for this function.)
*
******************************************************************************/
int initializeModuleXboard()
{
status.operationMode = XBOARD_OPERATIONMODE_USERGAME;
status.engineColor = WHITE;
status.pondering = TRUE;
status.ponderingMove = NO_MOVE;
status.engineIsPondering = FALSE;
status.engineIsActive = FALSE;
status.bestMoveWasSent = TRUE;
status.maxPlies = 0;
status.timecontrolData[WHITE].numberOfMoves = 0;
status.timecontrolData[WHITE].baseTime = 300 * 1000;
status.timecontrolData[WHITE].incrementTime = 0;
status.timecontrolData[WHITE].numberOfMovesPlayed = 0;
status.timecontrolData[WHITE].restTime = 300 * 1000;
status.timecontrolData[BLACK].numberOfMoves = 0;
status.timecontrolData[BLACK].baseTime =
status.timecontrolData[WHITE].baseTime;
status.timecontrolData[BLACK].incrementTime =
status.timecontrolData[WHITE].incrementTime;
status.timecontrolData[BLACK].numberOfMovesPlayed = 0;
status.timecontrolData[BLACK].restTime =
status.timecontrolData[WHITE].restTime;
deletePonderResult();
initializePGNGame(&game);
variation.timeLimit = 5000;
variation.ponderMode = FALSE;
variation.handleSearchEvent = &handleSearchEvent;
variation.expectedScore = 0;
task.variation = &variation;
task.type = TASKTYPE_BEST_MOVE;
return 0;
}
#ifndef NDEBUG
/******************************************************************************
*
* Test parameter parsing.
*
******************************************************************************/
static int testParameterParsing()
{
char buffer[1024];
getTokenByNumber("protover 2", 1, buffer);
assert(strcmp("2", buffer) == 0);
getTokenByNumber("result 1-0 {White mates}", 2, buffer);
assert(strcmp("{White mates}", buffer) == 0);
return 0;
}
/******************************************************************************
*
* Test time calculation.
*
******************************************************************************/
static int testTimeCalculation()
{
TimecontrolData data;
data.numberOfMoves = 40;
data.baseTime = 300000;
data.incrementTime = 0;
data.numberOfMovesPlayed = 60;
data.restTime = data.baseTime / 2;
assert(getCalculationTime(&data) == 150000 / 20);
data.numberOfMoves = 0;
data.baseTime = 300000;
data.incrementTime = 3000;
data.numberOfMovesPlayed = 50;
data.restTime = 60000;
assert(getCalculationTime(&data) == 3000 + 60000 / 30);
return 0;
}
/******************************************************************************
*
* Test the uci tokenizer.
*
******************************************************************************/
static int testUciTokenizer()
{
char buffer[64], name[64], value[64];
const char *uciString =
"setoption name\tNalimovPath value \t C:\\chess\\tablebases time 641273423";
const char *trickyUciString =
"setoption name\tNalimovPathvalue value \t C:\\chess\\tablebases time 641273423 tablebases";
const char *token1 = getUciToken(uciString, "NalimovPath");
const char *token2 = getUciToken(uciString, "tablebases");
const char *token3 = getUciToken(uciString, "name");
const char *token4 = getUciToken(uciString, "value");
const char *token5 = getUciToken(trickyUciString, "tablebases");
assert(strstr(token1, "NalimovPath") == token1);
assert(token2 == 0);
assert(strstr(token3, "name") == token3);
getNextUciToken(token3 + 4, buffer);
assert(strcmp(buffer, "NalimovPath") == 0);
getNextUciToken(token4 + 5, buffer);
assert(strcmp(buffer, "C:\\chess\\tablebases") == 0);
assert(getLongUciValue(uciString, "time", 0) == 641273423);
assert(strstr(trickyUciString, "423 tablebases") == token5 - 4);
getUciNamedValue(uciString, name, value);
assert(strcmp(name, "NalimovPath") == 0);
assert(strcmp(value, "C:\\chess\\tablebases") == 0);
getUciNamedValue(trickyUciString, name, value);
assert(strcmp(name, "NalimovPathvalue") == 0);
assert(strcmp(value, "C:\\chess\\tablebases") == 0);
return 0;
}
#endif
/******************************************************************************
*
* (See the header file comment for this function.)
*
******************************************************************************/
int testModuleXboard()
{
#ifndef NDEBUG
int result;
if ((result = testParameterParsing()) != 0)
{
return result;
}
if ((result = testTimeCalculation()) != 0)
{
return result;
}
if ((result = testUciTokenizer()) != 0)
{
return result;
}
#endif
return 0;
}