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.

The Ultimate Guide For Trolling

Everyday, hundreds of kids on the Internet ask me to teach them how to troll. I think that half of these kids are probably in their thirties.

If you are not familiar with trolling, then it’s advised that you leave immediately.

The purpose of trolling is to generate lulz. And it’s brilliant because other people do most of the work for you, once you’ve set the bait. Most humans view trolls as basement-dwelling idiots with no life and little to no experience in the sunlight. This couldn’t be further from the truth.

Trolls are among the highest members of society. The elite, the unknown, the mysterious, the intelligent, the wise. Most trolls live in America, because Americans are the smartest, sharpest, quickest, and least obese people in the world.

When you’re sitting at work on the computer and there is no demanding task at the moment, the need for trolling may arise. All of your big projects are back at home and you have 15 minutes to spare. So you can either waste time reading cracked.com articles or get something done by trolling and producing your own creative satire.

I don’t even like this picture. I don’t think anyone does.

One of the most successful groups of IRL(in real life) trolls is Westboro Baptist Church. They are so good, that they make money and get free publicity while trolling. The brilliance here is evident in their ability to troll two groups simultaneously–Christians and atheists (which is surprisingly easy, which also brings about another interesting point I’ll save for another time). This is due to Poe’s Law–it’s difficult, if not impossible, to distinguish between parodies of religious or other fundamentalism and its genuine proponents, since they both seem equally insane.

If you have ever been angry at Westboro Baptist Church, then I’m afraid to inform you that you’ve been a victim of trolling. Their website is titled “godhatesfags.com” after all, so any moron with half a brain should be able to realize they’re not being serious. If you really want to get rid of them, shut up and stop mentioning them or acknowledging them. Every time you do, it gives them more power over you. But people are not going to do this, because everyone is an idiot.

Another fine example of Poe’s Law is Landover Baptist Church. I’d recommend visiting the forum for the full package.

Anyway, those are just a couple of examples of dominant trolls. I will now go through some guidelines for trolling. Then I will provide a few simple examples after you have mastered the basics. And then I will provide a full example from my past experience.

The Environment

A troll’s environment is critical to the operation. Forums are a common place, as well as YouTube and Facebook. Trolling on Facebook can be difficult, but very rewarding, since many people are so concerned about their image. I great place to do some quick, basic trolling is in the YouTube comments. A bunch of no-lifers roam these comments, hungry for fierce debates. You can take full advantage of this opportunity.

Make sure your profile is convincing if you choose a forum. It really depends on the type of forum. The goal is to get people to think you’re being serious in all of your words, when you’re actually not.

Selecting A Victim

The most efficient form of victim is one who takes themselves too seriously. These victims are most likely to give a huge emotional response. And if lucky, the trolling of a single victim like this can go on for weeks, or even longer. Typically, these victims are the first to post something, and the troll attacks.

Sometimes, the victims will come to you, assuming you set the bait correctly. This way is much more efficient for drawing in large numbers of victims. You will see this demonstrated at the end of this article.

Strategies

A troll must not take himself or herself seriously. This is the key ingredient to being a successful troll. A good way to tell if you take yourself too seriously is to read encyclopedia dramatica (an article about something you like). If you are offended and hurt, you are in good shape and will never be trolled.

A common tactic is to repeatedly use one sentence to state your position. Then allow the victim to write a paragraph for each of your sentences. You could write an entire college essay using this method, constructed of the victim’s responses.

A great way to attract multiple victims is to trick everyone into thinking that you’re some kind of idiot. They will proceed with endless put-downs against you. And you just keep the ball rolling with more trickery.

This is a troll I found earlier in my backyard.

The following are a couple of basic trolling samples. I never actually carried these out–they are hypothetical.

Example 1

Premise: The troll is roaming a hunting forum.

Troll: I don’t understand you people. You go around killing all the animals for fun, for your own enjoyment. You separate the animals from their families and leave them stranded to starve and die. It’s very bad for the environment, too.

Victim: Dear sir, you are very confused. Hunting is actually good for the environment because the hunting community ensures that wildlife populations of game species are sustainable from one generation to the next. This requires that a diversity of natural habitats be kept intact, unpolluted, and undisturbed. Hunters support all these efforts.

Troll: I still don’t know why you people are so evil. You are going to burn in Hell for all the murdering you’ve done. You should consider becoming a vegan. Ever since I did, my life has been so much better and I am more pure than ever.

Victim: I’m sorry to hear that. It would be nice if we could enjoy our hobby and not be bothered by kids like you.

Other Victim: GET OUT QUEER BOY.

Commentary: If I was this troll, I’d have nothing in common with his or her point of view. Sometimes, this makes trolling easier, especially for beginners. This troll will probably have a PETA profile picture and have a link to an environmentalist website, for extra lulz.

Example 2

Premise: The victim here might not be saying this word for word, but it’s all implied. This could be IRL or online.

Victim: I’m kind of a big deal, I go to an expensive, public university and am majoring in [insert science/engineering degree]. I got an A in every class last semester, but this semester I might be getting a B in [insert junior+ level class]. I’m so screwed.

Troll: I learn twice as much as you from my room and local library, for $39,800 less. Looks like you have security issues, and need external validation from businesses and people who want to steal your money and value. Have fun wasting money, bitch.

Commentary: This is what I like to call honest trolling. The troll uses his or her own opinions to create an argument, and casts it at the victim in such a way to provoke a response. This is only recommended for experienced trolls, as beginner trolls may actually end up getting involved in a heated debate. In cases like this, when the victim is an arrogant imbecile, it will be easier for the troll.

A Real Example

Below is one of my past experiences when trolling forums of wannabe hackers. I think I took about 15 minutes to create the bait at work one day. I’m the first poster (Jingle-Berry), click the image and start at the top to have the full experience.

:)