Talking To Chess AI Using C++ (UCI Protocol)

There are many free chess AI engines out there, and most utilize the UCI protocol. It’s fairly tricky to configure a chess program you’ve made to communicate with it–it took me several days to figure out. And since there are no available tutorials, I’ll show you how to do it on Windows with C++. It’s going to be quite in-depth–anyone who wants to do this should be proficient in programming.

Deep Blue, the chess-playing computer.

I’ll be using Windows named pipes for this implementation. You basically open a hidden communication tunnel to the AI process from your chess program. My chess program opens the AI engine process from within itself and creates the pipes. It’s very important that you understand the UCI protocol documentation, at least the basics.

Here’s the header file for the AI class:

#include
#include

class AI{
public:
	AI();
	~AI();

	bool init(void);
	void reset(void);

	void moveAgainst(void); // sends user's move

	// setter functions
	void stop(void);
	void setPos(const char* pos);

These function concepts should be pretty self explanatory for now. Now onto the private members.

private:
	void cleanup(void);
	static unsigned long WINAPI InitThread(void* lpThread);
	DWORD WINAPI _AI(LPVOID lpBuffer);
	void parseAIMove(const char* str);
	void moveAIPiece(void);

	char m_pos[4096];			// holds all the moves for engine to interpret
	char m_lastAIMove[32];
	char m_lastUserMove[32];

	// states
	bool m_active;
	bool m_sendMove;

	unsigned long m_exit;
	unsigned long m_bread;
	unsigned long m_avail;

	// engine process variables
	STARTUPINFO m_si;
	PROCESS_INFORMATION m_pi;
	SECURITY_ATTRIBUTES m_sa;
	HANDLE m_child_stdin, m_child_stdout, m_hRead, m_hWrite;
	HANDLE m_hJob;
	JOBOBJECT_EXTENDED_LIMIT_INFORMATION m_jeli;
};

inline void AI::stop(void)
{
	m_active = false;
}

inline void AI::setPos(const char* pos)
{
	strcpy(m_pos, pos);
}

Now that I’ve laid out the header file, I’ll go through the actual function code and explain it in more detail. First we have the constructor and desctructor.

#include "ai.h"

AI::AI()
{
	/* setup the position command */
	strcpy(m_pos,"position startpos moves ");
	m_searchDepth = 10; // how many moves the engine will search
}

AI::~AI()
{
	cleanup();
}

Again, make sure you understand the UCI protocol well enough, otherwise the strcpy line won’t make any sense. This is just one way of communicating with the engine and it’s the way I chose. Everything after “position startpos moves ” will consist of the game moves. And our C string m_pos will be storing those.

And here’s the init function:

/* determine if we are running 64-bit Windows */
static int isOS64Bit(void)
{
	typedef BOOL (WINAPI* LPFN_ISWOW64PROCESS)(HANDLE, PBOOL);

	LPFN_ISWOW64PROCESS _IsWow64Process = NULL;
	BOOL				b64bit			= FALSE;

	_IsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(LoadLibrary("Kernel32.dll"), "IsWow64Process");
	if(_IsWow64Process == NULL)
		return false;

	if(_IsWow64Process(GetCurrentProcess(), &b64bit))
		return b64bit;

	return false;
}

bool AI::inst(void)
{
	char engine_path[MAX_PATH] = {0};
	bool engine_started = false;
	bool b64bit = isOS64Bit();
	bool jobSuccess = false;

	// set up the security attributes for AI process
	m_sa.bInheritHandle = true;
	m_sa.lpSecurityDescriptor = NULL;
	m_sa.nLength = sizeof(SECURITY_ATTRIBUTES);

	// setup job to kill the child process when calling process terminates
	// this prevents the AI process from running when the main program closes
	m_hJob = CreateJobObject(NULL, NULL);
	if(m_hJob != NULL){
		m_jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
		jobSuccess = SetInformationJobObject(m_hJob, JobObjectExtendedLimitInformation, &m_jeli, sizeof(m_jeli));
	}

Okay, so far so good. Now we will setup the pipe to the AI process.

    // create the child input pipe
	if(!CreatePipe(&m_child_stdin, &m_hWrite, &m_sa, 0)){
		return false;
	}

	// create the child output pipe
	if(!CreatePipe(&m_hRead, &m_child_stdout, &m_sa, 0)){
		return false;
	}

	// prepare to launch the process
	GetStartupInfo(&m_si);
	m_si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
	m_si.wShowWindow = SW_HIDE;
	m_si.hStdOutput = m_child_stdout;
	m_si.hStdError = m_child_stdout;
	m_si.hStdInput = m_child_stdin;

Not so bad, right? Keep in mind that you will need to know some Win32 API to understand how this works. The startup info structure is essential for spawning any new process with CreateProcess().

Now we’ll actually start the AI engine and apply some process settings.

    // set the path to the engine process
	strcpy(engine_path, "C:/engine.exe");

	// start the process
	engine_started = CreateProcess(engine_path,
								   NULL,
								   NULL,
								   NULL,
								   TRUE,
								   0,
								   NULL,
								   &m_si,
								   &m_pi);

	if(!engine_started){
		// further error handling...
		return false;
	}

	// assign the auto-terminate job we created earlier to the process
	if(jobSuccess){
		jobSuccess = AssignProcessToJobObject(m_hJob, m_pi.hProcess);
	}

	// Optional: set the process priority to below normal to prevent CPU over-usage
	SetPriorityClass(m_pi.hProcess, BELOW_NORMAL_PRIORITY_CLASS);

Pretty straightforward. We’ve created the process and assigned the job to it, which will force the engine to terminate once the chess program closes. The more automation within the class, the better.

Now all that’s left to do is start the AI thread and loop for input and output.

    if(engine_started){
		m_active = true;

		(void)CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&InitThread, this, 0, 0);
	}

	return true;
}

Now for the fun part. This actually took a while to perfect. The AI thread will listen to the pipe and check for output, while simultaneously listening for input from the chess program and sending it to the engine when needed. Here’s the code, along with the function to start the thread:

unsigned long WINAPI AI::InitThread(void* lpThread)
{
	if(lpThread){
		return ((AI*)lpThread)->_AI(NULL);
	}

	return 0;
}

/* AI looping thread */
DWORD WINAPI AI::_AI(LPVOID lpBuffer)
{
	char buf[BUFSIZE];
	memset(buf, 0, sizeof(buf));

	// wait for process to initialize
	Sleep(500);

	for(int i=0; m_active; ++i){
		// test if the engine is still running
		GetExitCodeProcess(m_pi.hProcess, &m_exit);
		if(m_exit != STILL_ACTIVE){
			m_active = false;
		}

		// check for output from the engine
		PeekNamedPipe(m_hRead, buf, BUFSIZE, &m_bread, &m_avail, NULL);
		if(m_bread > 0){
			for(;;){
				memset(buf, 0, sizeof(buf));

				// read the output buffer
				ReadFile(m_hRead, buf, BUFSIZE, &m_bread, NULL);

				/// if it's not the player's turn
					parseAIMove(buf);

				Sleep(100);

				PeekNamedPipe(m_hRead, buf, BUFSIZE, &m_bread, &m_avail, NULL);
				if(m_bread > 0)
					continue;
				else
					break;
			}
		}

I am not including any of the functions from my chess interface because I want to keep this AI class easily adaptable. Before I call the parseAIMove() function, I use three slashes for the comment–this means you should handle that if statement within your own chess interface.

Moving on to the input:

        // loop through the startup sequence for the engine
		sprintf(buf, (i == 0) ? "uci " : (i == 1) ? "isready " : (i == 2) ? "setoption name Hash value 512 " : "");
		if(m_sendMove == true){
			// append the new move from the user
			strcat(m_pos, m_lastUserMove);
			strcat(m_pos, " "); // a space is essential for the pipes to work here

			// send the input
			WriteFile(m_hWrite, m_pos, sizeof(m_pos), &m_bread, NULL);
			WriteFile(m_hWrite, "\n", 1, &m_bread, NULL); // send newline separately

			// now tell the engine to think about the next move
			sprintf(buf, "go wtime 60000 btime 60000 depth %d ", m_searchDepth); // insert your own variable for time here, in ms
			WriteFile(m_hWrite, buf, sizeof(buf), &m_bread, NULL);
			WriteFile(m_hWrite, "\n", 1, &m_bread, NULL);

			m_sendMove = false;
			/// set the game turn to black
		}
		else{
			WriteFile(m_hWrite, buf, sizeof(buf), &m_bread, NULL);
			WriteFile(m_hWrite, "\n", 1, &m_bread, NULL);

			if(i > 100000)
				i = 5; // reset the incrementer when necessary
		}
	}

	cleanup();

	ExitThread(0);
}

That wraps up the main loop. Of course you will have to modify some portions to make it work with your program. The if else statement at the end is specially crafted to start the engine with the conventional method and set some options.

Now that we’ve got that done, we can move on to the other class functions. Most of the work involves translating the user’s moves into the algebraic representation and vice versa.

void AI::parseAIMove(const char* str)
{
	const int BESTMOVE_OFFSET		= 9;
	const int BESTMOVE_END_OFFSET	= 4;
	char buf[BUFSIZE];
    char* token;
    char newmove[32];

    strcpy(buf, str);
    token = strtok(buf, "\n");
    while(token != NULL){
        if(strstr(token, "bestmove")){
			char* p = token + BESTMOVE_OFFSET;

            *(p + BESTMOVE_END_OFFSET) = 0;

            strcat(m_pos, p);
            strcat(m_pos, " ");

			strcpy(m_lastAIMove, p);
			moveAIPiece();
        }

        token = strtok(NULL, "\n");
    }
}

I figured out how to do this by just watching the output of the engine while I entered a hypothetical game. This function looks line by line at the engine’s output for the string “bestmove” and finds the move afterward. This is how the engine displays its chosen move.

Now for the function that actually translates the engine’s move into an easier form for the chess program to read.

void AI::moveAIPiece(void)
{
	int fromX, fromY;
	int toX, toY;

	/* get the 'from' move first */
	// get x component
	fromX = m_lastAIMove[1] - '0';

	// get y component
	switch(m_lastAIMove[0]){
		case 'a': fromY = 1; break;
		case 'b': fromY = 2; break;
		case 'c': fromY = 3; break;
		case 'd': fromY = 4; break;
		case 'e': fromY = 5; break;
		case 'f': fromY = 6; break;
		case 'g': fromY = 7; break;
		case 'h': fromY = 8; break;
		default:  fromY = 0; break;
	}

	/* now get the 'to' move */
	// get x component
	toX = m_lastAIMove[3] - '0';

	// get y component
	switch(m_lastAIMove[2]){
		case 'a': toY = 1; break;
		case 'b': toY = 2; break;
		case 'c': toY = 3; break;
		case 'd': toY = 4; break;
		case 'e': toY = 5; break;
		case 'f': toY = 6; break;
		case 'g': toY = 7; break;
		case 'h': toY = 8; break;
		default:  toY = 0; break;
	}

	/// tell chess program to move piece
	/// ...
}

Basic stuff, now the function to translate the user’s move into a way the AI will understand.

void AI::moveAgainst(void)
{
	/* Note: the global variables referenced here are assumed to hold the user's last move */

	/* get the 'from' move */
	// x component
	switch(g_fromX - 1){
		case 0: m_lastUserMove[0] = 'a'; break;
		case 1: m_lastUserMove[0] = 'b'; break;
		case 2: m_lastUserMove[0] = 'c'; break;
		case 3: m_lastUserMove[0] = 'd'; break;
		case 4: m_lastUserMove[0] = 'e'; break;
		case 5: m_lastUserMove[0] = 'f'; break;
		case 6: m_lastUserMove[0] = 'g'; break;
		case 7: m_lastUserMove[0] = 'h'; break;
	}

	// y component
	m_lastUserMove[1] = (char)(((int)'0') + g_fromY);

	/* get the 'to' move */
	// x component
	switch(g_toX - 1){
		case 0: m_lastUserMove[2] = 'a'; break;
		case 1: m_lastUserMove[2] = 'b'; break;
		case 2: m_lastUserMove[2] = 'c'; break;
		case 3: m_lastUserMove[2] = 'd'; break;
		case 4: m_lastUserMove[2] = 'e'; break;
		case 5: m_lastUserMove[2] = 'f'; break;
		case 6: m_lastUserMove[2] = 'g'; break;
		case 7: m_lastUserMove[2] = 'h'; break;
	}

	// y component
	m_lastUserMove[3] = (char)(((int)'0') + g_toY);

	// set the sendMove boolean to true, telling the main loop to process the move
	m_sendMove = true;
}

So to make a move against the AI engine, it would go something like this:

// retrieve user input and set global x and y values
g_ai.moveAgainst();

And with a few of your own customizations that’s all there is to it. Besides the cleanup function:

void AI::cleanup(void)
{
	char buf[1024];

	// tell the AI engine to terminate
	sprintf(buf, "stop ");
	WriteFile(m_hWrite, buf, sizeof(buf), &m_bread, NULL);
	WriteFile(m_hWrite, "\n", 1, &m_bread, NULL);

	sprintf(buf, "quit ");
	WriteFile(m_hWrite, buf, sizeof(buf), &m_bread, NULL);
	WriteFile(m_hWrite, "\n", 1, &m_bread, NULL);

	if(m_pi.hThread) CloseHandle(m_pi.hThread);
	if(m_pi.hProcess) CloseHandle(m_pi.hProcess);
	if(m_child_stdin)	CloseHandle(m_child_stdin);
	if(m_child_stdout)	CloseHandle(m_child_stdout);
	if(m_hRead)		CloseHandle(m_hRead);
	if(m_hWrite)	CloseHandle(m_hWrite);
}

And also, here is the reset function:

void AI::reset(void)
{
	char buf[1024];

	sprintf(buf, "stop ");
	WriteFile(m_hWrite, buf, sizeof(buf), &m_bread, NULL);
	WriteFile(m_hWrite, "\n", 1, &m_bread, NULL);

	sprintf(buf, "ucinewgame ");
	WriteFile(m_hWrite, buf, sizeof(buf), &m_bread, NULL);
	WriteFile(m_hWrite, "\n", 1, &m_bread, NULL);

	strcpy(m_pos,"position startpos moves ");
}

Just don’t forget to call the init function before using the class. If you have any questions just let me know.

Saving and Loading Games With C/C++

I realize I haven’t written anything about programming yet, so I thought I’d break it in with a simple post about saving a game state and loading it with C/C++. I’ll cover the basic functions needed, and you can build on that.

It doesn’t matter if you are using C or C++, they’ll both work just the same. For this example, I’ll write in C++ style. So you should have a basic understanding of game programming methodology and C++.

Here’s an example game class I’ll be using:

class Game{
	public:
        enum{  // save/load function return values
            SUCCESS = 0,
            OPEN_FAILED,
            INVALID_MAGIC
        };

		Game();
		~Game();

		int load(const char* file);
		int save(const char* file);

	protected:
		int m_lives;
        int m_importantThings[10][10];
		int m_level;
		int m_strength;
};

Then you’ll want to set up your file save structure:


#define MAGIC_SIZE 12
#define MAGIC_STR  "(=_MAGIC_=)"

typedef struct{
	char magic[MAGIC_SIZE]; // magic header

	int lives; // game data
    int importantThings[10][10];
	int level;
	int strength;
} GAMESAVE, *PGAMESAVE;

This structure is the binary data you’ll be writing to and loading from the game file. The “magic” string represents the file header, and provides a method to check if the game file is valid, by comparing the strings when loading the file, which I’ll cover shortly.

Now you’ll want to create a function to save the game to a file (you might need to add the proper headers, look up the function names on Google to find the include file):

int Game::save(const char* file)
{
	FILE* fp = 0;
	GAMESAVE save;

	memset(&save, 0, sizeof(save)); 	// fill struct with zeroes

	strcpy(save.magic, MAGIC_STR); 		// set the magic string for later
	memcpy(save.importantThings, m_importantThings, sizeof(m_importantThings)); // copy the important things
	save.lives = m_lives;				// copy the rest of the data
	save.level = m_level;
	save.strength = m_strength;

	if((fp = fopen(file, "wb")) != NULL){ // open save file in write-binary mode
		fwrite(&save, sizeof(save), 1, fp); // write the game save data
		fclose(fp);
		return SUCCESS;
	}

	return OPEN_FAILED;
}

And now for the slightly more complex loading function:

int Game::load(const char* file)
{
	FILE* fp = 0;
	GAMESAVE save;

	memset(&save, 0, sizeof(save));

	if((fp = fopen(file, "rb")) == NULL){ // open file in read-binary mode
		return OPEN_FAILED;
	}

	fread(&save, sizeof(save), 1, fp); // load the game save data
	fclose(fp);

	/* now test if the file's header is correct */
	if(strncmp(save.magic, MAGIC_STR, MAGIC_SIZE) != 0){
		return INVALID_MAGIC;
	}

	/* we're good, load the game data */
	memcpy(m_importantThings, save.importantThings, sizeof(save.importantThings));
	m_lives = save.lives;
	m_level = save.level;
	m_strength = save.strength;

	return SUCCESS;
}

And here’s an example of using these new functions (game is assumed to be global):

int someFunction(void){
	char file[256] = {0};
	int x = -1;

	/* save the file first */

	// get file save location from user...
        // etc...

	if(game.save(file) == Game::SUCCESS){
		printf("File saved!\n");
	}
	else{
		printf("File not saved :(\n");
	}

	/* now load the file */
	switch((x = game.load(file))){
		case Game::SUCCESS:
			printf("There was great success.\n");
			break;

		case Game::OPEN_FAILED:
			printf("Failed to open the file :(\n");
			break;

		case Game::INVALID_MAGIC:
			printf("Your magic is invalid.\n");
			break;

		default:
			break;
	}

	return 0;
}

You’ll want to create a special extension for your file, so it won’t be confused with other file types. If you want to automate the save function, so that it writes to a new save file each time, simply search the directory for files that have your custom extension, find the highest number, and increment the number.

If done correctly, when you try to view the saved file in notepad, it should look something like this:

I’ll write a tutorial later on directory searching in Windows for those that want to learn.