Inform MSCP 1.2 --------------- This is a relatively simple conversion of Marcel van Kervinck's MSCP chess playing engine to Inform. For details on the original MSCP, please visit http://combinational.com/mscp/. For your convenience (and mine), the original MSCP distribution tar file is included here, as mscp-1.2.tgz, but you don't need it to run Inform MSCP. MSCP is distributed under the GNU General Public License, and so is Inform MSCP. There's a COPYING file that describes licensing terms. If you want to include Inform MSCP in an IF game, this means that you may need to make your game source available under the GPL too; check the licensing carefully. Because Inform MSCP relies on 32-bit arithmetic which would be too hairy to write for the Z-machine, it requires Glulx Inform. All of the code for the Inform MSCP engine is in the file mscp.inf. Mscpe.inf is a small example runnable main program to exercise the engine. Quick start ----------- To run a simple MSCP program, compile mscpe.inf. This includes the main engine from mscp.inf, and creates a single file mscpe.ulx. MSCP requires more static storage that the Glulx Inform compiler 6.21 provides by default. On Linux, compilation would be something like: inform '$$MAX_STATIC_DATA=200000' mscpe.inf There's a Makefile to do this for you. Then, run mscpe.ulx with your standard Glulx interpreter. Interfaces ---------- There are two main interfaces to Inform MSCP: a programming interface, and an "xboard" interface (named this way because it generates output that will work with the xboard, and winboard, programs -- see later). Both interfaces have a single callable function, and you should be able to do everything you need to through these. Xboard interface ---------------- This is a simple line-based interface. You make an initial call to Inform MSCP to start the Xboard interface, then pass in strings (in the Inform sense, where element zero indicates the string length), and Inform MSCP prints out the response. The simplest Inform chess-playing code is a loop like this: mscp_xboard (); do { line = readline (window); retcode = mscp_xboard (line); } until (retcode == -1); Here, readline() is a function that returns a string (in Glulx, typically a call to request line input, then calls to select until a line event). The line is passed to mscp_xboard(), which interprets it and prints a response on the current output stream. The function returns a status value, true or false, indicating if the call was recognized and handleable, or -1 when asked to quit from the game (that is, line is "quit"). When called with a zero (or missing) argument, or with a zero-length string, mscp_xboard() initializes MSCP, printing introductory messages and an initial input prompt. The mscp_xboard() function can also accept two additional, and optional, arguments; a busy callback function, and an opaque argument for the callback. For example: retcode = mscp_xboard (line, tick_callback, my_value); See the section below on the MSCP programming interface for details about how busy callback functions operate. The source file mscpe.inf is an example program that creates a simple chess playing program. The resulting mscpe.ulx can be played on its own, or, under Xboard on Linux, with the help of a small shell script (Windows and Mac users will probably need some equivalent wrapper program): #!/bin/bash trap "" INT cheapglulxe mscpe.ulx This script is mscpe.sh. Assuming cheapglulxe is on the $PATH, the command xboard -fcp mscpe.sh should run Xboard using the Inform MSCP engine. In practice, apart from testing, there's no huge benefit in this, since it is the same as the original 'C' MSCP, just slower. Programming interface --------------------- The programming interface is aimed much more at a program or game that wants to integrate Inform MSCP into itself more closely than the xboard interface allows. In particular, xboard prints responses to the current output stream, and this might not be useful. The full function call for the programming interface is mscp_execute (command_code, arg1, arg2, arg3, arg4, arg5, arg6) but of this, only the command_code is required. In general, if a command takes a numeric or string argument, it's placed in arg1; otherwise, arg1 is omitted. The remaining arguments are, in order o The address to buffer output data into. o The size of the output buffer, in characters. o A buffer callback function, called at the end of each line of buffered output. o A busy callback function, called periodically during protracted calculations. o An opaque argument, passed on calls to the busy callback function. This is a fairly unlovely interface, but is in practice less complex than it appears because some arguments can almost always be omitted. For example, if the output of a command is not of interest, and it doesn't take a long time to execute, all except command_code can be left out of the call. Or where the output from a command is short, and it takes only a little time to execute, only command_code and the buffer address and size need to be passed in the call. The mscp_execute() function returns true if it executed the command it was given successfully, false otherwise. The following command codes are legal values for command_code: MSCP_EXEC_INITIALIZE: This initializes Inform MSCP. If you don't call it explicitly, it's called automatically on the first real MSCP function, so it's not something you have to call. Example usage: mscp_execute (MSCP_EXEC_INITIALIZE); MSCP_EXEC_STATUS: This is a one-stop shop to get details about the internal state of the chess engine. Ordinarily, you can ignore output from all of the other MSCP functions, but it doesn't make much sense to ignore this one. Typical usage is: Array buffer -> 256; ... mscp_execute (MSCP_EXEC_STATUS, buffer, 256, buf_callback); This allocates 256 bytes for the status output, and requests that MSCP call buf_callback(), the buffer callback, for each line of output. MSCP passes the buffer callback two arguments -- the buffer address, and the length of data available in it. Typically, buf_callback() would print out the data from the buffer. The buffer callback can indicate it has handled the line by returning true, in which case MSCP will delete the buffered data and reuse the buffer. If it returns false, MSCP will continue adding to the buffer until it's full, at which point MSCP stops buffering data. A suitable buffer callback function might look like this: [ buf_callback buf length i; for (i = 0: i < length: i++) { if (buf->i == '^') print "^"; else print (char) buf->i; } ]; Because the last character in the buffer is always a '^' newline, this can be shortened to [ buf_callback buf length i; for (i = 0: i < length - 1: i++) print (char) buf->i; print "^"; ]; For these definitions of buf_callback(), a buffer of 256 bytes is enough. Because each line is flushed as MSCP produces it, the actual buffer need be only as long as the longest line of output from the command. As an alternative, buf_callback() might just add up the count of characters that MSCP has buffered, and the rest of the program can print them all out when the call completes. For example: Array buffer -> 1024; Global total = 0; ... [ buf_callback buf length; total = total + length; return false; ]; ... total = 0; mscp_execute (MSCP_EXEC_STATUS, buffer, 1024, buf_callback); ... for (i = 0: i < total: i++) { if (buf->i == '^') print "^"; else print (char) buf->i; } For some MSCP commands, a buffer of 256 characters may not be enough to hold all of the output of the command, so this example uses a larger buffer. Inform MSCP status return strings look like this: 3 2 w -b 30 e2e4 e4 c7c5 c5 rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2 There are exactly two lines. The first line has nine fields, separated by spaces. Some are variable length, so the status string requires a little simple parsing. The fields are: o The current maximum search depth, 0-8 (see later). o The current move number, 1 or more. o The current colour to play, 'w' or 'b'. o Whether MSCP is playing white, black, neither, or both, shown as "-w", "-b", "--", or "wb". o The number of moves available to the colour to play next. A value "0" indicates the game is over. o The number of halfmoves since last pawn or capture move. A value "50" or higher indicates the game should end in a draw. o The full algebraic code of the move last played by white, or '-' if none recorded (start of game). o The standard algebraic notation for the same move, or '-'. o The full algebraic code of the move last played by black, or '-' if none recorded (start of game, or no black move yet). o The standard algebraic notation for the same move, or '-'. The second line is the Forsythe Edwards notation (FEN) for the current board position. This has six space separated fields, and is a standard chess notation. Fields are: o The current position, reading sequentially from board top left to right, and top to bottom. Black pieces are represented by the letters kqrbnp, white by KQRBNP, consecutive empty squares by a number 1-8, and the end of a board rank by /. o The current colour to play, 'w' or 'b'. o Castling availability, or '-' if none remaining. o En passant target square, or '-' if none. o Count of halfmoves since last pawn or capture move. o The current move number, 1 or more. A couple of these repeat data from the first line, but it's handy to have them all together to form a valid complete FEN string. Using this call, it should be pretty much possible to ignore buffer output from all other MSCP commands. MSCP_EXEC_DISPLAY_BOARD: This returns an xboard-like representation of the board. Because the board state is available from MSCP_EXEC_STATUS, this call isn't likely to be useful. But the following should work: Array buffer -> 256; ... mscp_execute (MSCP_EXEC_DISPLAY_BOARD, buffer, 256, buf_callback); MSCP_EXEC_LIST_MOVES: This returns a list of valid moves for the current colour, something like: moves are: e3 e4 ... 22 moves A suitable call might be: Array buffer -> 256; ... mscp_execute (MSCP_EXEC_LIST_MOVES, buffer, 256, buf_callback); MSCP_EXEC_NEW_GAME: This resets the board to starting positions, and clears any engine buffers. Typical call: mscp_execute (MSCP_NEW_GAME); MSCP_EXEC_GO: This asks Inform MSCP to make a move for the current colour. Because this may take some time, it may be necessary to give this call a callback function that MSCP can call back to fairly frequently. Something like: [ tick_callback my_value; glk_tick (); ...do something to keep the display looking active... return true; ]; ... mscp_execute (MSCP_EXEC_GO, 0, 0, 0, tick_callback, my_value); This calls tick_callback() several times each second while Inform MSCP calculates the best move to make. When Inform MSCP calls tick_callback(), it will pass the fifth call argument's value to tick_callback(). This way, you can pass data through MSCP, and have it supplied to each call of the tick callback. If you don't have any need for this, use 0 and forget about it, or just omit it from the call altogether. A busy callback would generally return true, but can return false to request MSCP to return from mscp_execute() as soon as possible, just making the current best move, ending the move search, or stopping listing game history. This can be used to to cancel a long-running search, or other lengthy MSCP operation. The buffer, buffer size, and buffer callback in the above example are set to 0, indicating that we don't need to know what move MSCP actually made. We can find this later with MSCP_EXEC_STATUS. Finally a word about calling mscp_execute() from within a busy callback: Don't. Requesting MSCP_EXEC_STATUS will return intermediate or invalid engine states since it is in mid calculation. Calling MSCP_EXEC_GO will certainly end in tears. Other MSCP operations will fail in varying, and unpleasant, ways. MSCP_EXEC_SEARCH: Requests a search of best moves at depths up to the maximum set, for the current colour, without making any actual move. Useful for getting hints at a good move for a player. Likely usage: Array buffer -> 80; ... mscp_execute (MSCP_EXEC_SEARCH, buffer, 80, buf_callback, tick_callback, my_value); This example call uses both a buffer with callback, to get moves as MSCP finds them, and a tick callback, because it is compute-intensive. MSCP_EXEC_SET_DEPTH: Sets the search depth for use when looking for the best move. Valid values are 1 to 8, with higher levels potentially taking several minutes to make a move. Example usage, to set depth 4, say: mscp_execute (MSCP_EXEC_SET_DEPTH, 4); MSCP_EXEC_BOTH: Tells MSCP to play both black and white. If used, MSCP will play the current game to conclusion, which might take a long time, and is probably not what you want to happen anyway, since it's not very interactive. Use with: mscp_execute (MSCP_EXEC_BOTH); MSCP_EXEC_WHITE: MSCP_EXEC_BLACK: These tell MSCP which colour to play. The definition of these functions is somewhat confused by the requirement to match xboard expectations, and changing them will cause strange behaviours. Their usage is: mscp_execute (MSCP_EXEC_WHITE); ... mscp_execute (MSCP_EXEC_BLACK); In practice, the xboard interface uses them, but they're probably best avoided for the programming interface; MSCP_EXEC_GO switches MSCP's play colour to the current colour to move, so use that instead. MSCP_EXEC_FORCE: Tells MSCP to play neither side. Handy, since otherwise MSCP may try to make a reply move when handed a player move with MSCP_EXEC_MOVE (see below). Use this call for absolute control over MSCP. Typical usage: mscp_execute (MSCP_EXEC_FORCE); MSCP_EXEC_BOOK: MSCP has a built in book of common game openings. For the current position, this will look up which moves, if any, from the book fit the situation. It's not wildly useful except for curiousity, and for the xboard interface. Typical usage: Array buffer -> 256; ... mscp_execute (MSCP_EXEC_BOOK, buffer, 256, buf_callback); MSCP_EXEC_UNDO: Undo the last move. MSCP's undo stack goes right back to the game start. Use with: mscp_execute (MSCP_EXEC_UNDO); MSCP_EXEC_RESET: Reset MSCP's internal tables and history, and reset search depth to its initial value. Usage is: mscp_execute (MSCP_EXEC_RESET); MSCP_EXEC_SETUP_FEN: Set up a board position using Forsythe Edwards notation. The argument is a string holding the FEN data: Array fen -> "4k3/4p3/8/8/8/8/4P3/4K3 w - - 0 1" ... mscp_execute (MSCP_EXEC_SETUP_FEN, fen); This sets up a board for a king-and-pawn exercise. It returns true if the FEN string is a valid one, false otherwise. MSCP_EXEC_HISTORY: Prints out a history of the complete set of game moves so far. The list is printed in PGN-like output, with several moves on each output line. Typical usage is: Array buffer -> 80; ... mscp_execute (MSCP_EXEC_HISTORY, buffer, 80, buf_callback); MSCP_EXEC_MOVE: This enters a player move. The move is a string, in either full algebraic or standard algebraic notation, for example: Array move string "Nc3"; ... if (~~mscp_execute (MSCP_EXEC_MOVE, move)) print "Invalid move!^"; else ... moves a knight to square c3. If the move is a valid one, the call returns true, otherwise false. MSCP_EXEC_ABOUT: Returns, in its buffer, credits and licensing for MSCP. Use with: Array buffer -> 256; ... mscp_execute (MSCP_EXEC_ABOUT, buffer, 256, buf_callback); Build notes ----------- The main mscp.inf engine doesn't need infglk.h, but mscpe.inf does. There are no Glk function calls in mscp.inf, so it's not Glk-specific source in that sense. It does however rely on 32-bit word length, so it does in fact needs Glulx, and this makes it not possible to build for the Z-machine. There are a couple of features in Inform MSCP that are extra to the original 'C' version, and that you can turn off with selected constant definitions: MSCP_NO_AGGRESSIVE - In normal use, 'C' MSCP will clear its tables before considering a move. However, the tables may contain a record of a prior search for the same position, so retaining the data makes searches faster, at the slightly increased risk of accidentally using an incorrect prior search result. By default, Inform MSCP retains the data. Define this constant to disable aggressive search optimization. MSCP_NO_TICK_CHECK - Define this constant to disable callbacks while calculating moves. Because checking for callbacks, even if none was passed in, takes time, this will speed up searching a little if you know that you'll never use them. MSCP_NO_UNWIND - Turns off the feature that causes MSCP to return the current best move as soon as possible if a busy callback function return false. Again, speeds up processing a little by removing runtime tests, if you'll never use them. Book move tables ---------------- 'C' MSCP holds its opening move book in a separate file, and loads it in on startup. To do the same in Inform MSCP is difficult and slow. To save effort in building book tables from a list of moves, this patch modifies mscp.c to print out its zobrist[] and book move tables in the form of Inform data structures. Then, providing that the hash function in Inform MSCP functions identically to the one in mscp.c, the data dumped by the C program is directly usable in Inform MSCP. The output from running 'C' MSCP modified by this diff is embedded in the file mscp.inf. 1968c1968 < int i; --- > int i, j, k; 1979,1981c1979,1995 < for (i=0; i /* for Inform, we need signed hash/random tables */ > rnd (); rnd (); rnd (); > for (i=0; i<12; i++) { > for (j=0; j<64; j++) { > zobrist[i][j] = rnd(); > } > } > > /* dump zobrist */ > printf ("Array\t\tmscp_zobrist\t-->"); > for (i = 0, k = 0; i < 12; i++) { > for (j = 0; j < 64; j++) { > printf ("%s", k++ % 7 == 0 ? "\n\t" : " "); > printf ("$%08lX", zobrist[i][j]); > } > } > printf (";\n\n"); 1994a2009,2023 > > /* dump book, with count and move packed into one word */ > printf ("Constant\tMSCP_C_BOOKSIZE\t\t%ld;\n\n", booksize); > printf ("Array\t\tmscp_book_data_hash\t-->"); > for (i = 0; i < booksize; i++) { > printf ("%s", i % 7 == 0 ? "\n\t" : " "); > printf ("$%08lX", book[i].hash); > } > printf (";\n\n"); > printf ("Array\t\tmscp_book_data_pack\t-->"); > for (i = 0; i < booksize; i++) { > printf ("%s", i % 7 == 0 ? "\n\t" : " "); > printf ("$%08lX", book[i].count << 16 | book[i].move); > } > printf (";\n\n"); -- Simon Baldwin 10th Sept 2003