Several weeks ago I wrote about Prophet, my C/C++ chess program. I described in that post the approach I was using in the ParseXBoard( ) method to map user input to a “handler function” using a table of function pointers.
As it turns out, I actually have TWO chess programs under active development. The other is a Java/Groovy based chess program named chess4j. I will likely have more to say about chess4j in future posts, but for now I wanted to mention how Groovy could be used to map user input to handler functions in even fewer lines of code.
First, how it used to look. Similar to how the old Prophet used to look, except in Java:
public boolean parseXBoardCommand(String command) throws ParseException {
if (command.startsWith("accepted")) {
// ...
return true;
}
if (command.equalsIgnoreCase("analyze")) {
// ...
return true;
}
if (command.equalsIgnoreCase("black")) {
// ...
return true;
}
if (command.equalsIgnoreCase("bk")) {
// ...
return true;
}
// more commands
}
That works just fine, but it’s not very elegant. I wanted to find an approach similar to what I did with Prophet. I wanted to create a map of Strings to functions, maybe using a Java Action. But then I remembered something I read in the excellent ‘Groovy In Action’ book – invokeMethod(String methodName,List args)! invokeMethod dynamically invokes the method ‘methodName’ on the current Object, or throws a MissingMethodException if a matching method doesn’t exist. With invokeMethod(), the Groovy solution is even more succinct than the C solution. There is no need to create a data structure to map user input to functions. We just catch the user input and call invokeMethod( ).
The first step is to create the input handlers, like so:
def moveNow(def arg) {}
def name(List args) {
String opponent = args.get(0)
println "opponent is: " + opponent
}
def newGame(def arg) {
joinSearchThread();
_forceMode = false;
App.getBoard().resetBoard();
}
Once all the input handlers are in place, it’s time to write the parse routine:
public void parseCommand(String command) throws IllegalMoveException,
MissingMethodException,ParseException {
def input = command.tokenize()
def method = input.remove(0)
if ("new".equals(method)) { // because 'new' is a reserved word
invokeMethod("newGame",input)
} else if ("?".equals(method)) { // can't have a method named '?'
invokeMethod("moveNow",input)
} else {
invokeMethod(method,input)
}
}
As you can see, it’s pretty simple. There are two exception cases to deal with. The XBoard protocol specifies a ‘new’ command, which is a reserved word in most programming languages. Therefore, if ‘new’ is caught the ‘newGame’ method is invoked. Similarly, the XBoard protocol says ‘?’ means ‘move now’, and we can’t have a method called ‘?’. Other than that, input is dynamically mapped to a method of the same name.
By the way, chess4j is hosted on SourceForge.net at http://chess4j.sourceforge.net/. It’s still very early in an ambitious project but feel free to download the source code and take a peek.