* Copyright (C) 1995-1997 William Shubert.
* You may use this code in any way you wish as long as you retain the
* above copyright notice.
* This is based on David Fotland's Go Modem Protocol Code and the
* "protocol.Z" info file from Bruce Wilcox. It has been pretty much
* completely rewritten now, though.
/* Modification by Daniel Bump for GNU Go: I made all GMP messages
contingent on gmp_debug. Otherwise this is identical to Bill Shubert's
version distributed with GoDummy.
/* Modified by Paul Pogonyshev on 10.07.2003.
* Added support for Simplified GTP.
/**********************************************************************
**********************************************************************/
#define GMP_TIMEOUTRETRIES 60
#define SGMP_TIMEOUTRETRIES 9
#define SGMP_RETRYSECS 20
#define GMP_MAXSENDSQUEUED 16
/**********************************************************************
**********************************************************************/
cmd_ack
, cmd_deny
, cmd_reset
, cmd_query
, cmd_respond
, cmd_move
,
query_game
, query_bufSize
, query_protocol
, query_stones
,
query_bTime
, query_wTime
, query_charSet
, query_rules
, query_handicap
,
query_boardSize
, query_timeLimit
, query_color
, query_who
,
typedef struct Gmp_struct
{
int boardSize
, sizeVerified
;
int handicap
, handicapVerified
;
int chineseRules
, rulesVerified
;
int iAmWhite
, colorVerified
;
int recvSoFar
, sendsQueued
;
int sendFailures
, noResponseSecs
;
int myLastSeq
, hisLastSeq
;
unsigned char recvData
[4];
unsigned char sendData
[4];
} sendsPending
[GMP_MAXSENDSQUEUED
];
int earlyMoveX
, earlyMoveY
;
/**********************************************************************
**********************************************************************/
static const char *commandNames
[] = {
"ACK", "DENY", "RESET", "QUERY", "RESPOND", "MOVE", "UNDO"};
static const char *queryNames
[] = {
"GAME", "BUFFER SIZE", "PROTOCOL", "STONES",
"BLACK TIME", "WHITE TIME", "CHAR SET", "RULES", "HANDICAP",
"BOARD SIZE", "TIME LIMIT", "COLOR", "WHO"};
/**********************************************************************
**********************************************************************/
/* Get the forward declaration of externally visible functions. */
static unsigned char checksum(unsigned char p
[4]);
static GmpResult
gotQueryResponse(Gmp
*ge
, int val
, const char **err
);
static void putCommand(Gmp
*ge
, Command cmd
, int val
);
static GmpResult
respond(Gmp
*ge
, Query query
);
static void askQuery(Gmp
*ge
);
static int heartbeat(Gmp
*ge
);
static GmpResult
getPacket(Gmp
*ge
, int *out1
, int *out2
,
static GmpResult
parsePacket(Gmp
*ge
, int *out1
, int *out2
,
static GmpResult
processCommand(Gmp
*ge
, Command command
, int val
,
int *out1
, int *out2
, const char **error
);
static void processQ(Gmp
*ge
);
/**********************************************************************
**********************************************************************/
#define gmp_verified(ge) \
((ge)->sizeVerified && (ge)->colorVerified && \
(ge)->handicapVerified && (ge)->rulesVerified)
Gmp
*gmp_create(int inFile
, int outFile
) {
ge
= malloc(sizeof(Gmp
));
ge
->handicapVerified
= 0;
ge
->earlyMovePresent
= 0;
void gmp_destroy(Gmp
*ge
) {
GmpResult
gmp_check(Gmp
*ge
, int gsleep
, int *out1
, int *out2
,
const char *charPtrDummy
;
if (gmp_verified(ge
) && ge
->earlyMovePresent
) {
ge
->earlyMovePresent
= 0;
fprintf(stderr
, "GMP: Returning early move.\n");
if (time(NULL
) != ge
->lastSendTime
) {
FD_SET(ge
->inFile
, &readReady
);
select(ge
->inFile
+ 1, &readReady
, NULL
, NULL
, &noTime
);
if (!gsleep
&& !FD_ISSET(ge
->inFile
, &readReady
))
result
= getPacket(ge
, out1
, out2
, error
);
} while (result
== gmp_nothing
);
static GmpResult
getPacket(Gmp
*ge
, int *out1
, int *out2
,
unsigned char charsIn
[4], c
;
count
= read(ge
->inFile
, charsIn
, 4 - ge
->recvSoFar
);
sprintf(errOut
, "System error.");
for (cNum
= 0; cNum
< count
; ++cNum
) {
/* idle, looking for start of packet */
if ((c
& 0xfc) == 0) { /* start of packet */
fprintf(stderr
, "GMP: Received invalid packet.\n");
/* We're in the packet. */
if ((c
& 0x80) == 0) { /* error */
fprintf(stderr
, "GMP: Received invalid packet.\n");
ge
->recvData
[ge
->recvSoFar
++] = c
;
/* A valid character for in a packet. */
ge
->recvData
[ge
->recvSoFar
++] = c
;
if (ge
->recvSoFar
== 4) { /* check for extra bytes */
assert(cNum
+ 1 == count
);
if (checksum(ge
->recvData
) == ge
->recvData
[1])
return(parsePacket(ge
, out1
, out2
, error
));
static unsigned char checksum(unsigned char p
[4]) {
sum
= p
[0] + p
[2] + p
[3];
sum
|= 0x80; /* set sign bit */
static GmpResult
parsePacket(Gmp
*ge
, int *out1
, int *out2
,
seq
= ge
->recvData
[0] & 1;
ack
= (ge
->recvData
[0] & 2) >> 1;
if (ge
->recvData
[2] & 0x08) { /* Not-understood command. */
fprintf(stderr
, "GMP: Unknown command byte 0x%x received.\n",
(unsigned int) ge
->recvData
[2]);
command
= (ge
->recvData
[2] >> 4) & 7;
val
= ((ge
->recvData
[2] & 7) << 7) | (ge
->recvData
[3] & 0x7f);
if (command
== cmd_query
) {
fprintf(stderr
, "GMP: Read in command: %s unkown value %d\n",
commandNames
[command
], val
);
fprintf(stderr
, "GMP: Read in command: %s %s\n",
commandNames
[command
], queryNames
[val
]);
fprintf(stderr
, "GMP: Read in command: %s\n",
if (!ge
->waitingHighAck
) {
if ((command
== cmd_ack
) || /* An ack. We don't need an ack now. */
(ack
!= ge
->myLastSeq
)) { /* He missed my last message. */
fprintf(stderr
, "GMP: Unexpected ACK.\n");
} else if (seq
== ge
->hisLastSeq
) { /* Seen this one before. */
fprintf(stderr
, "GMP: Received repeated message.\n");
putCommand(ge
, cmd_ack
, ~0);
return(processCommand(ge
, command
, val
, out1
, out2
, error
));
if (command
== cmd_ack
) {
if ((ack
!= ge
->myLastSeq
) || (seq
!= ge
->hisLastSeq
)) {
fprintf(stderr
, "Sequence error.\n");
} else if ((command
== cmd_reset
) && (ge
->iAmWhite
== -1)) {
fprintf(stderr
, "gmp/his last seq = %d\n", seq
);
return(processCommand(ge
, command
, val
, out1
, out2
, error
));
} else if (seq
== ge
->hisLastSeq
) {
/* His command is old. */
} else if (ack
== ge
->myLastSeq
) {
result
= processCommand(ge
, command
, val
, out1
, out2
, error
);
/* Conflict with opponent. */
fprintf(stderr
, "Sending conflict.\n");
ge
->myLastSeq
= 1 - ge
->myLastSeq
;
static GmpResult
processCommand(Gmp
*ge
, Command command
, int val
,
int *out1
, int *out2
, const char **error
) {
putCommand(ge
, cmd_ack
, ~0);
return(respond(ge
, val
));
case cmd_reset
: /* New game. */
fprintf(stderr
, "GMP: Resetted. New game.\n");
case cmd_undo
: /* Take back moves. */
putCommand(ge
, cmd_ack
, ~0);
y
= ge
->boardSize
- 1 - (s
/ ge
->boardSize
);
putCommand(ge
, cmd_ack
, ~0);
assert(ge
->earlyMovePresent
== 0);
ge
->earlyMovePresent
= 1;
return(gotQueryResponse(ge
, val
, error
));
default: /* Don't understand command. */
putCommand(ge
, cmd_deny
, 0);
static void putCommand(Gmp
*ge
, Command cmd
, int val
) {
if (ge
->waitingHighAck
&&
(cmd
!= cmd_ack
) && (cmd
!= cmd_respond
) && (cmd
!= cmd_deny
)) {
if (ge
->sendsQueued
< 1024) {
ge
->sendsPending
[ge
->sendsQueued
].cmd
= cmd
;
ge
->sendsPending
[ge
->sendsQueued
].val
= val
;
fprintf(stderr
, "GMP: Send buffer full. Catastrophic error.");
if ((cmd
== cmd_ack
) && (ge
->sendsQueued
)) {
ge
->sendData
[0] = ge
->myLastSeq
| (ge
->hisLastSeq
<< 1);
ge
->sendData
[2] = 0x80 | (cmd
<< 4) | ((val
>> 7) & 7);
ge
->sendData
[3] = 0x80 | val
;
ge
->sendData
[1] = checksum(ge
->sendData
);
ge
->lastSendTime
= time(NULL
);
fprintf(stderr
, "GMP: Sending command: %s %s\n",
commandNames
[cmd
], queryNames
[val
]);
fprintf(stderr
, "GMP: Sending command: %s\n", commandNames
[cmd
]);
write(ge
->outFile
, ge
->sendData
, 4);
ge
->waitingHighAck
= (cmd
!= cmd_ack
);
static GmpResult
respond(Gmp
*ge
, Query query
) {
wasVerified
= gmp_verified(ge
);
/* Do you support this extended query? */
if (ge
->chineseRules
== -1) {
if (ge
->chineseRules
== 1)
ge
->handicapVerified
= 1;
if (ge
->boardSize
== -1) {
response
= ge
->boardSize
;
if (ge
->iAmWhite
== -1) {
putCommand(ge
, cmd_respond
, response
);
if (!wasVerified
&& gmp_verified(ge
)) {
fprintf(stderr
, "GMP: New game ready.\n");
static void askQuery(Gmp
*ge
) {
if (!ge
->rulesVerified
) {
ge
->lastQuerySent
= query_rules
;
} else if (!ge
->sizeVerified
) {
ge
->lastQuerySent
= query_boardSize
;
} else if (!ge
->handicapVerified
) {
ge
->lastQuerySent
= query_handicap
;
/* } else if (!ge->komiVerified) {
ge->lastQuerySent = query_komi; query komi is not define in GMP !? */
assert(!ge
->colorVerified
);
ge
->lastQuerySent
= query_color
;
ge
->lastQuerySent
= query_color
;
else if (!ge
->handicapVerified
)
ge
->lastQuerySent
= query_handicap
;
putCommand(ge
, cmd_query
, ge
->lastQuerySent
);
static GmpResult
gotQueryResponse(Gmp
*ge
, int val
, const char **err
) {
static const char *ruleNames
[] = {"Japanese", "Chinese"};
static const char *colorNames
[] = {"Black", "White"};
switch(ge
->lastQuerySent
) {
if (ge
->handicap
== -1) {
sprintf(errOut
, "Neither player knows what the handicap should be.");
ge
->handicapVerified
= 1;
ge
->handicapVerified
= 1;
if ((val
!= -1) && (val
!= ge
->handicap
)) {
sprintf(errOut
, "Handicaps do not agree; I want %d, he wants %d.",
if (ge
->boardSize
== -1) {
sprintf(errOut
, "Neither player knows what the board size should be.");
if ((val
!= 0) && (val
!= ge
->boardSize
)) {
sprintf(errOut
, "Board sizes do not agree; I want %d, he wants %d.",
if (ge
->chineseRules
== -1) {
sprintf(errOut
, "Neither player knows what rule set to use.");
ge
->chineseRules
= val
- 1;
if (ge
->chineseRules
!= (val
== 2)) {
sprintf(errOut
, "Rule sets do not agree; I want %s, he wants %s.",
ruleNames
[ge
->chineseRules
], ruleNames
[val
== 2]);
if (ge
->iAmWhite
== -1) {
sprintf(errOut
, "Neither player knows who is which color.");
ge
->iAmWhite
= !(val
== 1);
if (ge
->iAmWhite
== (val
== 1)) {
sprintf(errOut
, "Colors do not agree; we both want to be %s.",
colorNames
[ge
->iAmWhite
]);
putCommand(ge
, cmd_ack
, ~0);
fprintf(stderr
, "GMP: New game ready.\n");
static int heartbeat(Gmp
*ge
) {
if (ge
->waitingHighAck
) {
> (ge
->simplified
? SGMP_RETRYSECS
: GMP_RETRYSECS
)) {
> (ge
->simplified
? SGMP_TIMEOUTRETRIES
: GMP_TIMEOUTRETRIES
)) {
cmd
= (ge
->sendData
[2] >> 4) & 7;
fprintf(stderr
, "GMP: Sending command: %s %s (retry)\n",
queryNames
[ge
->sendData
[3] & 0x7f]);
fprintf(stderr
, "GMP: Sending command: %s (retry)\n",
write(ge
->outFile
, ge
->sendData
, 4);
void gmp_startGame(Gmp
*ge
, int size
, int handicap
, float komi
,
int chineseRules
, int iAmWhite
, int simplified
) {
assert((size
== -1) || ((size
> 1) && (size
<= 22)));
assert((handicap
>= -1) && (handicap
<= 27));
assert((chineseRules
>= -1) && (chineseRules
<= 1));
assert((iAmWhite
>= -1) && (iAmWhite
<= 1));
ge
->sizeVerified
= simplified
;
ge
->handicapVerified
= 0;
ge
->chineseRules
= chineseRules
;
ge
->rulesVerified
= simplified
;
ge
->earlyMovePresent
= 0;
ge
->simplified
= simplified
;
putCommand(ge
, cmd_reset
, 0);
void gmp_sendPass(Gmp
*ge
) {
putCommand(ge
, cmd_move
, arg
);
void gmp_sendMove(Gmp
*ge
, int x
, int y
) {
val
= x
+ ge
->boardSize
* (ge
->boardSize
- 1 - y
) + 1;
putCommand(ge
, cmd_move
, val
);
void gmp_sendUndo(Gmp
*ge
, int numUndos
) {
putCommand(ge
, cmd_undo
, numUndos
);
const char *gmp_resultString(GmpResult result
) {
static const char *names
[] = {
"Nothing", "Move", "Pass", "Reset", "New game", "Undo", "Error"};
assert(result
<= gmp_err
);
int gmp_handicap(Gmp
*ge
) {
float gmp_komi(Gmp
*ge
) {
int gmp_chineseRules(Gmp
*ge
) {
return(ge
->chineseRules
);
int gmp_iAmWhite(Gmp
*ge
) {
static void processQ(Gmp
*ge
) {
if (!ge
->waitingHighAck
&& ge
->sendsQueued
) {
putCommand(ge
, ge
->sendsPending
[0].cmd
, ge
->sendsPending
[0].val
);
for (i
= 0; i
< ge
->sendsQueued
; ++i
) {
ge
->sendsPending
[i
] = ge
->sendsPending
[i
+ 1];