I've been making a small program that runs engines through the STS test suite, and produces a log with the score added up for successful moves, including the the 2nd, 3rd, 4th etc best scoring moves.
It works ok, but I'm cheating a bit with the communication program <-> engine.
Code: Select all
void cSTStester::run_engine()
{
logfile = "log.txt";
const char *executable = this->EngineName.c_str();
log.open(logfile.c_str(), ios::trunc);
log<<"\n\n********** NEW LOG "<<__DATE__<<" "<<__TIME__<<" ***********"<<endl<<endl;
SECURITY_ATTRIBUTES saAttr;
printf("\n->Start of parent execution.\n");
setbuf(stdout, NULL);
setbuf(stdout, NULL);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
engineoutput.clear();
if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, ULONG_MAX) )
ErrorExit(TEXT("StdoutRd CreatePipe"));
// Create a pipe for the child process's STDIN.
if (! CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, ULONG_MAX))
ErrorExit(TEXT("Stdin CreatePipe"));
// Create the child process.
readyok = false;
CreateChildProcess(executable);
string command;
cout<<" starting engine... "<<executable<<"\n";
WriteToPipe("uci\n");
Sleep(1000);
WriteToPipe("isready\n");
Sleep(1000);
engineoutput.clear();
run_positions();
WriteToPipe("quit\n");
cout<<" stop quit sent.. \n";
cout<<" closing engine\n";
if (!CloseHandle(g_hChildStd_OUT_Rd))
ErrorExit(TEXT("StdOutWr CloseHandle"));
if (!CloseHandle(g_hChildStd_IN_Rd))
ErrorExit(TEXT("StdOutWr CloseHandle"));
log.close();
}
void CreateChildProcess(const char *szCmdline)
{
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
cout<<" creating "<<szCmdline<<endl;
bSuccess = CreateProcess(NULL,
(LPSTR) szCmdline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
if ( ! bSuccess )
ErrorExit(TEXT("CreateProcess"));
else
{
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
}
}
void WriteToPipe(string input)
{
log<<"toengine -> "<<input;
Sleep(50);
DWORD dwWritten;
string chBuf = input;
BOOL bSuccess = FALSE;
bSuccess = WriteFile(g_hChildStd_IN_Wr, chBuf.c_str(), chBuf.length(), &dwWritten, NULL);
}
void ReadFromPipe(bool start)
{
DWORD dwRead, dwWritten;
BOOL bSuccess = FALSE;
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
char *tempstore = NULL;
trimmed.clear();
outputlines.clear();
engineoutput.clear();
DWORD bytes;
if(PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, 0, NULL, &bytes, NULL));
if(bytes-1 >= ULONG_MAX)
{
cout<<"\n PIPE BUFFER EXCEEDED ";
exit(1);
}
tempstore = new char[bytes];
if(bytes)
{
bSuccess = ReadFile( g_hChildStd_OUT_Rd, tempstore, bytes, &dwRead, NULL);
}
//copy the tempstore to our global output string
for(uint i = 0; i < bytes; ++i) engineoutput += tempstore[i];
//split output into lines, and store
stringsplitter(engineoutput,'\n',outputlines);
for(uint i = 0; i < outputlines.size(); ++i)
{
log<<"fromengine -> "<<outputlines[i];
}
log<<"\n bytes was "<<bytes;
findbest(outputlines,start);
delete tempstore;
tempstore = NULL;
}
void cSTStester::run_positions()
{
string line;
string command;
for(uint i=0; i < TotalResult.files.size(); ++i)
{
this->detailfile<<"\nFile "<<TotalResult.files[i].filename<<endl;
TotalResult.files[i].positionstested = 0;
for(uint j = 0; j < TotalResult.files[i].positions.size(); ++j)
{
line.clear();
command.clear();
line = TotalResult.files[i].positions[j].fen;
command = "position fen ";
command += line;
command +="\n";
this->detailfile<<"\nposition "<<j+1<<" < "<<line<<" > ";
this->board.setboard(line);
//this->board.printboard();
WriteToPipe(command);
WriteToPipe("go infinite\n");
Sleep(this->timelimit);
WriteToPipe("stop\n");
Sleep(200);
ReadFromPipe(false);
log<<"\nEngine bestmove found "<<trimmed;
log.flush();
this->score_position(TotalResult.files[i].positions[j],trimmed);
TotalResult.files[i].positionstested++;
Sleep(100);
if(j>=PositionsToAnalyse) break;
}
}
}
If you look in run_positions(), you see that the fen is sent, and then I wait for the specified search time for the position.
After this is up, the pipe from the engine is read, stop is sent and then a 200ms second pause "hopefully" enough time to assure a "bestmove xxxx" has been written by the engine.
When I look at the output from (admittedly, much more proficiently programmed efforts) Winboard, Arena etc, they seem to catch each line from the pipe as it comes.
How do they do this?
I tried a loop reading reading bytes until each newline, but then the full pipe is read, irrespective of a newline or not, so I end up with broken lines.
Sorry if this is not clear.... very much an amateur

Richard
PS No doubt Linux has a MUCH easier way...