Zillions Development Confidential
How
To Write An Engine Plug-In For Zillions of Games
By
Jeff Mallett 2/2/99
Copyright 1999 Zillions Development Corporation
The
following technical documentation is intended for third-party developers
interested in programming game-specific engines to replace Zillions
of Games' default engine. It assumes fluency in programming
and is not intended as end-user documentation.
Zillions
of Games allows the use of plug-in engines, add-on game AIs hard-coded
to come up with moves for specific games. These exist in the
form of DLLs that Zillions calls according to a predefined interface.
ADLLs they can be written in any language and compiled in a variety
of development environments.
The
Zillions of Games CD-ROM comes with two engine plug-ins, which handle
certain variants from the Go-Moku and Reversi ZRFs. You'll
find them in: Zillions/Engines/
A ZRF
indicates that it wants to use a plug-in by including an engine
command. For example,
(engine "Engines\Jello.dll")
indicates
the use of the plug-ing called "Jello.dll"
in the Engines directory.
Engine
Routines
There
are only four routines that the plug-in engine needs to support.
These are defined in the following C header files. The routines
allow Zillions to tell the engine to start a new game, make a move
in the real game, search from the current position, and clean up.
The engine returns a DLL_* constant back to the
Zillions, which should be DLL_OK under normal circumstances.
If the engine returns a negative error code, Zillions of Games will
report this to the user and then unload the engine plug-in.
If an engine plug-in is unloaded, either for this reason or because
it returned a move that Zillions did not recognize as valid, Zillions
will revert to using its built-in, universal engine.
// EngineDLL.h
//
// Copyright 1998-9 Zillions Development
//
// Shared DLL plug-in for DLL engine and Zillions
#include "windows.h"
typedef enum {
kKEEPSEARCHING = 0,
kSTOPSOON = 1,
kSTOPNOW = 2
} Search_Status;
typedef enum {
DLL_OK = 0,
DLL_OK_DONT_SEND_SETUP = 1, // only supported in 1.0.2 and higher!
DLL_GENERIC_ERROR = -1,
DLL_OUT_OF_MEMORY_ERROR = -2,
DLL_UNKNOWN_VARIANT_ERROR = -3,
DLL_UNKNOWN_PLAYER_ERROR = -4,
DLL_UNKNOWN_PIECE_ERROR = -5,
DLL_WRONG_SIDE_TO_MOVE_ERROR = -6,
DLL_INVALID_POSITION_ERROR = -7,
DLL_NO_MOVES = -8
} DLL_Result;
enum {
UNKNOWN_SCORE = -2140000000L,
WIN_SCORE = 2130000000L
};
// ***** REQUIRED ROUTINES
// DLL_Search
//
// The DLL should search from the current position. If it returns DLL_OK it should
// also return the best move found in str; however, it should not make the move
// internally. A separate call to MakeAMove() will follow to make the move the
// engine returns.
//
// -> lSearchTime: Target search time in milliseconds
// -> lDepthLimit: Maximum moves deep the engine should search
// -> lVariety: Variety setting for engine. 0 = no variety, 10 = most variety
// -> pSearchStatus: Pointer to variable where Zillions will report search status
// -> bestMove: Pointer to a string where engine can report the best move found so far
// -> currentMove: Pointer to a string where engine can report the move being searched
// -> plNodes: Pointer to a long where engine can report # of positions searched so far
// -> plScore: Pointer to a long where engine can report current best score in search
// -> plDepth: Pointer to a long where engine can report current search depth
//
// Returns DLL_OK or a negative error code
typedef DLL_Result (FAR PASCAL *SEARCH)(long lSearchTime, long lDepthLimit, long lVariety,
const Search_Status *pSearchStatus, LPSTR bestMove, LPSTR currentMove,
long *plNodes, long *plScore, long *plDepth);
// DLL_MakeAMove
//
// The DLL should try to make the given move internally.
//
// -> move: notation for the move that the engine should make
//
// Returns DLL_OK or a negative error code
typedef DLL_Result (FAR PASCAL *MAKEAMOVE)(LPCSTR move);
// DLL_StartNewGame
//
// The DLL should reset the board for a new game.
//
// -> variant: The variant to be played as it appears in the variant menu
//
// Returns DLL_OK, DLL_OK_DONT_SEND_SETUP, DLL_OUT_OF_MEMORY_ERROR, or
// DLL_GENERIC_ERROR
typedef DLL_Result (FAR PASCAL *STARTNEWGAME)(LPCSTR variant);
// DLL_CleanUp
//
// The DLL should free memory and prepare to be unloaded.
//
// Returns DLL_OK, DLL_OUT_OF_MEMORY_ERROR, or DLL_GENERIC_ERROR
typedef DLL_Result (FAR PASCAL *CLEANUP)(void);
// Engine.h
//
// Copyright 1998-9 Zillions Development
//
// Header file for plug-in DLL engine to Zillions
#include "EngineDLL.h"
DLL_Result FAR PASCAL DLL_Search(long lSearchTime, long lDepthLimit, long lVariety,
Search_Status *pSearchStatus, LPSTR bestMove, LPSTR currentMove,
long *plNodes, long *plScore, long *plDepth);
DLL_Result FAR PASCAL DLL_MakeAMove(LPCSTR move);
DLL_Result FAR PASCAL DLL_StartNewGame(LPCSTR variant);
DLL_Result FAR PASCAL DLL_CleanUp();
The
engine does not call Zillions of Games. However, during a
search it can find out from Zillions whether it should continue
searching. When DLL_Search is called, the
engine should store away the argument pSearchStatus
and then refer to it during the search. If the user requests
that the program move now or the time has expired, the value will
change to kSTOPSOON. In this case the engine
should return a result as soon as possible. If Zillions of
Games needs to abort the search prematurely, e.g. the user has chosen
to exit the program, the value will change to kSTOPNOW.
In this case the engine should return as soon as possible, whether
or not a good result is available. The engine should not change
the value of *pSearchStatus itself.
During
a search the engine should continually report its own search status
by updating the values of *plNodes,
*plScore, and *plDepth.
Zillions uses these values to display feedback on progress to the
user. *plScore is assumed to
be the following units:
-2,140,000,000: Score isn't known
-2,130,000,000: Immediate loss
-2,130,000,000 + x: Loss in x (partial) moves
(-2,130,000,000+150)..-1001: Bad for side on the move
-1000...1000: Close game
1001..(2,130,000,000-150): Good for side on the move
2,130,000,000 - x: Win in x (partial) moves
As
a DLL, the plug-in will also need to have these two routines defined.
The LibMain function will be called when the DLL
is loaded, so that gives you a chance to do once-only initialization.
(Another method is to simply check a static variable inside DLL_StartNewGame.)
The following code gives an example implementation of these
two routines that does nothing except initialize the randomizer.
#include <time.h>
int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize)
{
srand( (unsigned)time( NULL ) );
return 1;
}
int FAR PASCAL WEP(void)
{
return 1;
}
With
these six functions, the EXPORTS section of your
project's .DEF file might look like this if you are using Microsoft's
Visual Studio:
EXPORTS
LibMain
WEP
DLL_Search
DLL_MakeAMove
DLL_StartNewGame
DLL_CleanUp
Passing
Moves and Edits
Moves
are passed back and forth as move strings. For most games
these strings are identical to the move strings displayed in the
move list (the part following the move number). This makes
it easy for you to see what is being passed to your engine.
The one exception to this rule is when the move involves setting
piece attributes, for example, when a Rook in Chess moves,
the ZRF sets an attribute saying that the Rook may no longer be
used in castling. The setting of piece attributes is not displayed
to the user on the movelist, but it is sent to the engine in case
the engine needs that information in differentiating moves.
It is also written out to a saved game. Thus, to see the exact
format of moves involving piece attributes when they are sent to
your engine, simply look at what is saved in the .ZSG.
Note
that the sending moves to the engine serves two purposes, both as
a method to pass actual game moves and to pass edits made to the
board. Thus, your engine needs to handle any edits the user
can make to the board. The Zillions of Games GUI and engine
is flexible enough to deal with any position resulting from edits.
If your engine can't cope with the existing board position, for
example, it is a Chess program that is hard-coded to assume that
there is exactly one White King and one Black King, then the engine
can indicate this by returning the code DLL_INVALID_POSITION_ERROR.
Edits
to the board are passed just as they are displayed in the move list,
as a capture or drop surrounded by parentheses.
After
calling DLL_StartNewGame Zillions of Games will
always pass down a series of board edits to place all the initial
pieces on the board. This was done for two reasons:
- It
allows the engine to be simpler. The engine can simply assume
at the start of the game that the board is empty and let Zillions
handle the logic for setting up the board correctly.
- It
allows an engine to be used in a wide variety of games without
re-coding. Many games, such as Blobs or Turn Off, can be
played with the same rules and board, but with different starting
configurations. For example, someone could write a ZRF variant
that uses an existing engine in new setups. Also, at some
point Zillions of Games may support setup randomization, in which
case, an existing Chess engine could be used to play Shuffle or
Fischer Random chess.
Of
course, your engine may prefer a certain setup, for instance, if
it has an opening book. If getting the series of initial board
edits is a problem, your DLL_StartNewGame should
return DLL_OK_DONT_SEND_SETUP to indicate you're
assuming a fixed setup and want to bypass this stage.
Didn't
find the answer to your question? Email
your question to Zillions Development.
|