Third article of the FICS bot writing tutorial. In this chapter, I am to discuss the proper way of issuing FICS commands and analyzing the replies obtained from FICS. I will also introduce my preferred framework.
Obtaining replies from FICS
In the previous chapter I wrote some not so beautiful code while trying to grab the FICS reply to the finger
command issued by a bot. There is a better way, so called block mode. Once working in this mode, we label every command issued with some numerical identifier, and FICS wraps every reply with special markers, allowing us to identify the reply as such, match it with the command we issued, and grab the whole reply text (and nothing more).
Let’s see how does it work. Connect to FICS using some telnet client (as discussed in the first part), and issue the following command:
iset block 1
You should see the information block set
followed by some unreadable character (your telnet client can show some ugly mark, or do not show it at all).
Now try something like:
27 date
(some 1-2 digit number, space, and the command, note that normally written commands won’t work anymore)
Expect the reply like:
¥27¤32¤Local time - Wed Aug 13, 14:29 PDT 2008
Server time - Wed Aug 13, 14:29 PDT 2008
GMT - Wed Aug 13, 21:29 GMT 2008
§
I am using ¥, ¤, and § symbols to designate those unreadable characters in this article. This is not what your telnet interface will show you, I just picked them as some ugly chars which visualize in the browser properly.
What is going on here? FICS sent us the following:
-
ugly character meaning here reply to some command starts (character of code 21, easy to recognize in the bot or interface code),
-
the number – the command identifier (yes, this is exactly the same number which you wrote before
date
, and this is how you can match which reply is related to which command), -
next ugly character, so called separator (character of code 22),
-
next number - the command code (this is the numeric code of the command, 32 means
date
, 37 meansfinger
, 51 meanshistory
, 132 meanstell
and so on, I will quote them all at some later article, but personally I feel they are more useful for interfaces than for bots), -
separator (once more the same character of code 22),
-
normal reply to the command,
-
third ugly character – the command reply end (character of code 23)
Try a few more commands, for example:
44 history
7 games
1 news
1 messages
Note, that you are not forced to use different identifier for every command, FICS does not care, it just sends you back what you provided, you can prefix every command with 1 if you like. It is up to your bot code not to reuse the same number when you need so.
Some sensible idea is to use 1 whenever you do not need the result of the command and unique numbers when you care. I am not sure what is the maximum (it seemed to me that using 1-99 range was recommended some time ago, but even very big numbers seem to work currently).
Note also, that this format is used for replies only. Everything else (tells to the bot, shouts, announcements, notifications, …) is provided as previously, without any marks.
How do we use it? In my bots I apply the following procedure (my bots use the block mode):
-
whenever I am to issue some command, I allocate a spare identifier,
-
I send the command prefixed with this identifier to FICS, and
-
I save the callback function intended to handle the reply to this command (bound to this identifier)
Then, in my main read and parse the FICS output routine, before looking for anything else, I check for the presence of the command reply start mark. If spotted, I switch the routine to the reply gathering mode, and accumulate the text until I obtain the command reply end. Then I extract the identifier and the reply body, locate (by identifier) the appropriate callback function, and run it (giving it the reply text as a parameter).
This way, I am always sure that – whichever command I called – I handle the reply with the proper routine, and I properly grab the whole reply.
While
iset block
is specific for FICS, the idea is not. Internet Chess Club provides very similar bracketing when you doset level1 1
.
Recommended framework
FICS bot writing is (especially in case of more complicated bots) about asynchronous callbacks. You can handle many simultaneous flows, for example:
-
await the reply to the just issued
finger
command to proceed with user registration once his rating info is available (or maybe even lead a few such interactions simultaneously), -
be tracking conversations with a few users (including cases when the user is given a more option),
-
await the result of some database query (to provide extracted info to yet another user),
-
be sending email (or a few),
-
await the first move of some game or games (which bot started to observe),
-
be saving some data to the database (and maybe waiting for a lock),
-
…
Therefore I strongly recommend using some framework with good, natural and easy to use support for asynchronous, event- and callback- based execution. For me, in case of Python programming, this means Twisted.
WatchBot is written using Twisted, and I am really, really glad I picked this framework.
I won’t describe the installation (easy_install Twisted
works
nowadays). I also won’t rewrite the Twisted Documentation here,
but give a few most important pointers:
-
Asynchronous Programming with Twisted is a must read, explaining the whole programming model (this is a short, readable article),
-
Writing Clients shows an example very similar to the code we are to write,
-
inlineCallbacks are a programming trick which make the Twisted code far more readable (and easier to write) – this article also shows some simple example of Twisted code.
If you prefer reading on paper, there is a book Twisted Network Programming Essentials available. I don't own it myself, but saw it recommended a few times.
Note: unless you need them for other purposes, don’t waste time
learning twistd
, .tac
files, Perspective Broker, and the whole
application/service/component model. For our purposes the low level
Twisted (just reactor, protocols, and deferreds) is sufficient and
natural.
In the next section I will show some code written in this manner.