//------------------------------------------------------------------------------------- // This file contains the source code (written in C) to load Winboard/Xboard compliant // chess engines as player in the ChessRoom provided by the ChessRoomManager. // See ChessRoomManager.cpp // // Author - Michael Keating //------------------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include #include #include #define TRUE 1 #define FALSE 0 // the port and address of our server #define PORT 5555 //#define SERVERHOST "209.55.69.98" #define SERVERHOST "localhost" // server-to_client communication strings const char cszServerSendPlayersListKey[] = "SENDING_PLAYERS_LIST"; const char cszServerRoomFullKey[] = "ROOM_FULL"; const char cszServerWelcomeKey[] = "WELCOME"; const char cszServerNewPlayerKey[] = "SENDING_NEW_PLAYER"; const char cszServerRemovePlayerKey[] = "REMOVING_PLAYER"; const char cszServerChallengeRecievedKey[] = "CHALLENGE_RECIEVED"; const char cszServerChallengeRemovedKey[] = "CHALLENGE_REMOVE"; const char cszServerStartGameKey[] = "START_GAME"; const char cszServerNewGameKey[] = "NEW_GAME"; const char cszServerSendingMoveKey[] = "SENDING_MOVE"; const char cszServerIllegalMoveKey[] = "ILLEGAL_MOVE"; const char cszServerStalemateKey[] = "STALEMATE"; const char cszServerWhiteIsMatedKey[] = "WHITE_IS_MATED"; const char cszServerBlackIsMatedKey[] = "BLACK_IS_MATED"; const char cszServerWhiteLostOnTimeKey[] = "WHITE_LOST_ON_TIME"; const char cszServerBlackLostOnTimeKey[] = "BLACK_LOST_ON_TIME"; const char cszServerWhiteResignedKey[] = "WHITE_RESIGNED"; const char cszServerBlackResignedKey[] = "BLACK_RESIGNED"; const char cszServerDrawByRepetitionKey[] = "DRAW_BY_REPETITION"; const char cszServerDrawOfferedKey[] = "DRAW_OFFERED"; const char cszServerUpdatePlayerKey[] = "UPDATE_PLAYER"; const char cszServerUpdateRatingsKey[] = "UPDATE_RATINGS"; const char cszServerDrawAgreedKey[] = "DRAW_AGREED"; const char cszServerViewerInfoKey[] = "VIEWER_INFO"; // client_to_server communication strings const char cszClientSendingPlayerIDKey[] = "/SENDING_PLAYER_ID"; const char cszClientChallengeKey[] = "/CHALLENGE"; const char cszClientAcceptChallengeKey[] = "/ACCEPT_CHALLENGE"; const char cszClientIAmAComputerKey[] = "/I_AM_A_COMPUTER"; const char cszClientIAmAChessGameKey[] = "/I_AM_A_CHESS_GAME"; const char cszClientIAmAViewerKey[] = "/I_AM_A_VIEWER"; const char cszClientSendingMoveKey[] = "/SENDING_MOVE"; const char cszClientMyTimeExpiredKey[] = "/MY_TIME_EXPIRED"; const char cszClientClaimFlagKey[] = "/CLAIM_FLAG"; const char cszClientIResignKey[] = "/I_RESIGN"; const char cszClientOfferDrawKey[] = "/OFFER_DRAW"; const char cszClientDrawAgreedKey[] = "/DRAW_AGREED"; const char cszClientPlayAgainKey[] = "/PLAY_AGAIN"; // the general string sufix const char cszSuffix[] = "^@!"; // Our online handle char szMyHandle[22]; // pipes to communicate with the engine int ToEnginePipe[2], FromEnginePipe[2]; // sockets int intGameSocket = -1, intChatRoomSocket = -1; // flags int bIsPlaying = FALSE; int bReplay = FALSE; int bPlayingWhite = FALSE; int bFirstMessage = TRUE; int bIsOurMove = FALSE; int bOppMoved = FALSE; // time variables int intMinutes = 0; int intSeconds = 0; int intMillisLeft = 0; int intOppMillisLeft = 0; unsigned int intStartMillis = 0; unsigned int intStartOppMillis = 0; // the last move by the engine char szLastEngineMove[10] = {0}; // the active socket set fd_set activeSockets; // ProcessEngine will redirect it's stdin to ToEnginePipe[0], and stdout to FromEnginePipe[1]. // It will then launch the engine. void ProcessEngine(char *szEnginePath) { dup2(ToEnginePipe[0] , STDIN_FILENO); dup2(FromEnginePipe[1], STDOUT_FILENO); if (execl(szEnginePath, "exe", "-e100", NULL) == -1) { fprintf(stderr, "Failed to exectute %s\n, szEnginePath"); } } void sendString(int intFd, char *szMessage) { if (intFd == ToEnginePipe[1]) { if (write(intFd, szMessage, strlen(szMessage)) == -1) { perror("Error writing to intChatRoomSocket - write:"); } printf("To engine: %s\n", szMessage); } else if (write(intFd, szMessage, strlen(szMessage) + 1) == -1) { perror("Error writing to intChatRoomSocket - write:"); } } // intSockAddr is used to setup our socket connection with the server void initSockaddr(struct sockaddr_in *stName, const char *szHostname, unsigned short int intPort) { struct hostent *stHostinfo; stName->sin_family = AF_INET; stName->sin_port = htons(intPort); stHostinfo = gethostbyname(szHostname); if (stHostinfo == NULL) { fprintf (stderr, "Unknown host %s.\n", szHostname); sendString(ToEnginePipe[1], "quit\n"); exit (EXIT_FAILURE); } stName->sin_addr = *(struct in_addr *)stHostinfo->h_addr; } int CreateSocketConnection() { // connect to the server (mychess.com) int intSocket; struct sockaddr_in stServerName; // create the socket intSocket = socket(PF_INET, SOCK_STREAM, 0); if (intSocket < 0) { perror("socket (client)"); sendString(ToEnginePipe[1], "quit\n"); exit(EXIT_FAILURE); } // connect to the server initSockaddr(&stServerName, SERVERHOST, PORT); if (connect(intSocket, (struct sockaddr *)&stServerName, sizeof (stServerName)) < 0) { perror("connect (client)"); sendString(ToEnginePipe[1], "quit\n"); exit(EXIT_FAILURE); } return intSocket; } void processChatRoomMessage(char *szMessage) { printf("chat room: %s\n", szMessage); // ignore chat messages if where in a game if (bIsPlaying) { return; } char *szKey = strtok(szMessage, ","); if (szKey == NULL) { // assign a dummy value so strcmp ahead doesn't crash szKey = (char *)&cszSuffix[0]; } // Welcome key? get our handle if (!strcmp(szKey, cszServerWelcomeKey)) { // now get our handle char *szHandle; if (!(szHandle = strtok(NULL, ","))) { printf("Error with Welcome key - no handle\n"); return; } // remove the string suffix char *szSuffix = strstr(szHandle, cszSuffix); if (szSuffix) { szSuffix[0] = '\0'; } strcpy(szMyHandle, szHandle); printf("My Handle is: %s\n", szMyHandle); } // challenge recieved? if (!strcmp(szKey, cszServerChallengeRecievedKey)) { // accept all challenges // get the handle of our challenger - call strtok twice to skip over the "to/From" variable char *szHandle; if (!(szHandle = strtok(NULL, ",")) || !(szHandle = strtok(NULL, ","))) { printf("Error in Challenge Recieved - no handle\n"); return; } // now get the time control char *szMinutes; char *szSeconds; if (!(szMinutes = strtok(NULL, ",")) || !(szSeconds = strtok(NULL, ","))) { printf("Error in Challenge Recieved - no minutes/seconds\n"); return; } intMinutes = atoi(szMinutes); intSeconds = atoi(szSeconds); char szString[128]; sprintf(szString, "%s,%s", cszClientAcceptChallengeKey, szHandle); sendString(intChatRoomSocket, szString); printf("Accepting challenge from %s\n", szHandle); } // start a new game? else if (!strcmp(szKey, cszServerStartGameKey)) { // if our handle is the next token - we are playing white char *szHandle; if (!(szHandle = strtok(NULL, ","))) { printf("Error with Start Game key - no handle\n"); return; } // set bPlayingWhite bPlayingWhite = !strcmp(szHandle, szMyHandle); printf("bPlayingWhite is %d - %s - %s \n", bPlayingWhite, szHandle, szMyHandle); // create a new socket for the game (this is what the server expects) intGameSocket = CreateSocketConnection(); // add the new socket to our active sockets FD_SET(intGameSocket, &activeSockets); // send the IAmAChessGame key to identify this socket to the server char szString[128]; sprintf(szString, "%s,%s", cszClientIAmAChessGameKey, szMyHandle); sendString(intGameSocket, szString); printf("Start Game! - sent %s\n", szString); // set the time control for our computer if (intMinutes) { char szString[128]; sprintf(szString, "level 0 %d %d\n", intMinutes, intSeconds); sendString(ToEnginePipe[1], szString); sendString(ToEnginePipe[1], szString); } else { // default to game in 30 minutes sendString(ToEnginePipe[1], "level 0 30 0\n"); } } } void processGameMessage(char *szMessage) { printf("Game: %s\n", szMessage); // see if there is more that one message char szSubMessage[128]; szSubMessage[0] = '\0'; if (strlen(strstr(szMessage, cszSuffix)) > strlen(cszSuffix)) { strcpy(szSubMessage, strstr(szMessage, cszSuffix) + strlen(cszSuffix)); printf("szSubMessage is %s\n", szSubMessage); } // ignore chat messages if where in a game char *szKey = strtok(szMessage, ","); if (szKey == NULL) { // assign a dummy value so strcmp ahead doesn't crash szKey = (char *)&cszSuffix[0]; } // new Game/replay? if (!strcmp(szKey, cszServerNewGameKey)) { sendString(ToEnginePipe[1], "new\n"); sendString(ToEnginePipe[1], "new\n"); if (bPlayingWhite) { sendString(ToEnginePipe[1], "go\n"); // start counting the computer's time struct timeval stTimeVal = {0}; gettimeofday(&stTimeVal, NULL); intStartMillis = (stTimeVal.tv_sec * 1000) + (stTimeVal.tv_usec / 1000); bIsOurMove = TRUE; } else { // start counting the opponents's time struct timeval stTimeVal = {0}; gettimeofday(&stTimeVal, NULL); intStartOppMillis = (stTimeVal.tv_sec * 1000) + (stTimeVal.tv_usec / 1000); printf ("intStartOppMillis was set to %u\n", intStartOppMillis); bIsOurMove = FALSE; } if (intMinutes) { char szString[128]; sprintf(szString, "level 0 %d %d\n", intMinutes, intSeconds); sendString(ToEnginePipe[1], szString); sendString(ToEnginePipe[1], szString); intOppMillisLeft = intMinutes * 60 * 1000; } else { // default to game in 30 minutes sendString(ToEnginePipe[1], "level 0 30 0\n"); intOppMillisLeft = 30 * 60 * 1000; } // flip the bPlayingWhite flag to change colors next game bPlayingWhite = !bPlayingWhite; intMillisLeft = intMinutes * 60 * 1000; // consider ourselves playing now bIsPlaying = TRUE; bOppMoved = FALSE; } // did we just recieve a move? else if (!strcmp(szKey, cszServerSendingMoveKey)) { char *szMove; if (!(szMove = strtok(NULL, ","))) { printf("Error with Sending Move key - no move\n"); return; } char *szOpponentsMillisLeft; if (!(szOpponentsMillisLeft = strtok(NULL, ","))) { printf("Error with Sending Move key - no milles\n"); return; } // check to see if this was the engine's last move being sent back to us // (the server sends moves to all game clients) if (strcmp(szMove, szLastEngineMove)) { char szMove2[10]; sprintf(szMove2, "%s\n", szMove); // uppercase the promotion char if necessary szMove2[4] = isupper(szMove2[4]) ? tolower(szMove2[4]) : szMove2[4]; // send it to the engine sendString(ToEnginePipe[1], szMove2); // get the opponent's remaining time if (intMinutes) { intOppMillisLeft = atoi(szOpponentsMillisLeft); } else { // opponent remaining time is always 30 minutes if there is no clock intOppMillisLeft = 30 * 60 * 1000; } // start counting the computer's time struct timeval stTimeVal = {0}; gettimeofday(&stTimeVal, NULL); intStartMillis = (stTimeVal.tv_sec * 1000) + (stTimeVal.tv_usec / 1000); bIsOurMove = TRUE; bOppMoved = TRUE; } } // did black win? else if (!strcmp(szKey, cszServerWhiteIsMatedKey) || !strcmp(szKey, cszServerWhiteLostOnTimeKey) || !strcmp(szKey, cszServerWhiteResignedKey)) { sendString(ToEnginePipe[1], "result 0-1\n"); sendString(ToEnginePipe[1], "force\n"); sendString(intGameSocket, (char *)cszClientPlayAgainKey); bIsPlaying = FALSE; printf("Black Won\n"); } // did white win? else if (!strcmp(szKey, cszServerBlackIsMatedKey) || !strcmp(szKey, cszServerBlackLostOnTimeKey) || !strcmp(szKey, cszServerBlackResignedKey)) { sendString(ToEnginePipe[1], "result 1-0\n"); sendString(ToEnginePipe[1], "force\n"); sendString(intGameSocket, (char *)cszClientPlayAgainKey); bIsPlaying = FALSE; printf("White Won\n"); } // is it a draw? else if (!strcmp(szKey, cszServerStalemateKey) || !strcmp(szKey, cszServerDrawAgreedKey) || !strcmp(szKey, cszServerDrawByRepetitionKey)) { sendString(ToEnginePipe[1], "result 1/2-1/2\n"); sendString(ToEnginePipe[1], "force\n"); sendString(intGameSocket, (char *)cszClientPlayAgainKey); bIsPlaying = FALSE; printf("Drawn Game\n"); } // process the sub message if we have one if (szSubMessage[0]) { processGameMessage(szSubMessage); } } void processEngineMessage(char *szMessage, int bSubCall) { static int bIsMove = FALSE; if (!bSubCall) { printf("Engine: %s\n", szMessage); } // send the "xboard" message if the engine just started if (bFirstMessage) { sendString(ToEnginePipe[1], "xboard\n"); sendString(ToEnginePipe[1], "nopost\n"); sendString(ToEnginePipe[1], "bk\n"); bFirstMessage = FALSE; } // see if there is more that one message char szSubMessage[256]; szSubMessage[0] = '\0'; char *szString = strchr(szMessage, '\n'); if (szString && strlen(szString) > 2 && (szString != szMessage)) { strncpy(szSubMessage, szString + 1, 255); } char *szKey = strtok(szMessage, " "); if (szKey == NULL) { // assign a dummy value so strcmp ahead doesn't crash szKey = (char *)&cszSuffix[0]; } // look for engines that use the apparently undocumented '1. ... e2e4' style if (isdigit(szKey[0]) || isdigit(szKey[1])) { char *szDotDotDot; szDotDotDot = strtok(NULL, " "); printf(" IsDigit is true and szDotDotDot is %s", szDotDotDot); if (szDotDotDot && !strcmp(szDotDotDot, "...")) { printf("IsMove is TRUE"); bIsMove = TRUE; } } // move from engine? if (!strcmp(szKey, "move") || bIsMove) { // calculate and substract the computer's elasped thinking time if (intMinutes) { struct timeval stTimeVal = {0}; gettimeofday(&stTimeVal, NULL); unsigned int intStopMillis = (stTimeVal.tv_sec * 1000) + (stTimeVal.tv_usec / 1000); int intElaspedMillis = intStopMillis - intStartMillis; intMillisLeft -= intElaspedMillis; intMillisLeft += intSeconds * 1000; } else { intMillisLeft = 0; } char *szMove; if (!(szMove = strtok(NULL, " "))) { // if "..." was just send, then the key may be our move if ((strlen(szKey) >= 4 && islower(szKey[0]) && isdigit(szKey[1]) && islower(szKey[2]) && isdigit(szKey[3])) || !strcmp(szKey, "o-o") || !strcmp(szKey, "o-o-o")) { szMove = szKey; } else { printf("Error with Sending Move key - no move from engine"); return; } } // convert O-O and O-O-O if (!strcmp(szMove, "o-o")) { szMove = bPlayingWhite ? "e1g1" : "e8g8"; } else if (!strcmp(szMove, "o-o-o")) { szMove = bPlayingWhite ? "e1c1" : "e8c8"; } char szString[64]; if (intMinutes && intMillisLeft < 1) { sprintf(szString, "%s,%s", cszClientMyTimeExpiredKey, szMyHandle); sendString(intGameSocket, szString); sendString(ToEnginePipe[1], "force\n"); bIsPlaying = FALSE; } else { // send the move to the server sprintf(szString, "%s,%s,%d", cszClientSendingMoveKey, szMove, intMillisLeft); sendString(intGameSocket, szString); // start counting the opponets's time struct timeval stTimeVal = {0}; gettimeofday(&stTimeVal, NULL); intStartOppMillis = (stTimeVal.tv_sec * 1000) + (stTimeVal.tv_usec / 1000); strcpy(szLastEngineMove, szMove); bIsOurMove = FALSE; } bIsMove = FALSE; } // is the engine resigning? else if (!strcmp(szKey, "resign")) { sendString(intGameSocket, (char *)cszClientIResignKey); sendString(ToEnginePipe[1], "force\n"); bIsPlaying = FALSE; } else if (!strcmp(szKey, "1/2-1/2")) { char *szReason; szReason = strtok(NULL, " "); char szString[128]; if (szReason) { sprintf(szString, "%s,%s", cszClientDrawAgreedKey, szReason); } else { sprintf(szString, "%s", cszClientDrawAgreedKey); } sendString(intGameSocket, szString); sendString(ToEnginePipe[1], "force\n"); bIsPlaying = FALSE; } else if (!strcmp(szKey, "offer draw")) { sendString(intGameSocket, (char *)cszClientOfferDrawKey); } if(szSubMessage[0]) { processEngineMessage(szSubMessage, TRUE); } } // main int main(int argc, char *argv[]) { if (argc != 3) { printf("\n Usage:\n\n"); printf(" LoadEngine engineProgram intID\n"); return -1; } // create our pipes to communicate with the engine if (pipe(ToEnginePipe) || pipe(FromEnginePipe)) { fprintf (stderr, "Pipe creation failed.\n"); return -1; } // fork and lauch the engine in the child process switch (int pid = fork()) { case -1: // error perror("fork"); return -1; case 0: // child ProcessEngine(argv[1]); return 0; default: // parent break; } intChatRoomSocket = CreateSocketConnection(); // send our player ID // tell the server we are a computer sendString(intChatRoomSocket, (char *)cszClientIAmAComputerKey); printf("Sent %s\n", cszClientIAmAComputerKey); char szString[128]; sprintf(szString, "%s,%d", cszClientSendingPlayerIDKey, atoi(argv[2])); sendString(intChatRoomSocket, szString); // monitor the engine, keyboard, and server socket fd_set readSockets; FD_ZERO(&activeSockets); FD_SET(FromEnginePipe[0], &activeSockets); FD_SET(STDIN_FILENO, &activeSockets); FD_SET(intChatRoomSocket, &activeSockets); struct timeval stTimeOut = {0}; char receiveBuffer[1024]; int intBytesRead; for (;;) { memset(&receiveBuffer, 0, sizeof(receiveBuffer)); // block for 30 seconds - then check opponents time (in case he bails or something); stTimeOut.tv_sec = 30; stTimeOut.tv_usec = 0; readSockets = activeSockets; if (select(FD_SETSIZE, &readSockets, NULL, NULL, &stTimeOut) == -1) { perror("In main()- select"); sendString(ToEnginePipe[1], "quit\n"); return -1; } // message from the engine? if (FD_ISSET(FromEnginePipe[0], &readSockets)) { intBytesRead = read(FromEnginePipe[0], receiveBuffer, sizeof(receiveBuffer)); receiveBuffer[intBytesRead -1] = '\0'; processEngineMessage(receiveBuffer, FALSE); } // message from stdin? if (FD_ISSET(STDIN_FILENO, &readSockets)) { intBytesRead = read(STDIN_FILENO, receiveBuffer, sizeof(receiveBuffer)); receiveBuffer[intBytesRead] = '\0'; sendString(ToEnginePipe[1], receiveBuffer); //if (write(ToEnginePipe[1], receiveBuffer, strlen(receiveBuffer))== -1) //{ // perror("Error writing to ToEnginePipe[1] - write:"); //} //sendString(ToEnginePipe[1], receiveBuffer); //if (write(intChatRoomSocket, receiveBuffer, strlen(receiveBuffer) + 1) == -1) //{ // perror("Error writing to intChatRoomSocket - write:"); //} if (!strcmp(receiveBuffer, "quit\n")) { printf("main() Exiting\n"); sendString(ToEnginePipe[1], "quit\n"); close(intChatRoomSocket); close(intGameSocket); return 0; } } // message from the chat room? if (FD_ISSET(intChatRoomSocket, &readSockets)) { // read from the socket intBytesRead = read(intChatRoomSocket, receiveBuffer, sizeof(receiveBuffer)); if (intBytesRead <= 0) { // socket closed or read error sendString(ToEnginePipe[1], "quit\n"); close(intChatRoomSocket); return -1; } // put a null character at the end of the data to make it a string receiveBuffer[intBytesRead] = '\0'; processChatRoomMessage(receiveBuffer); } // message from a game? if (intGameSocket != -1 && FD_ISSET(intGameSocket, &readSockets)) { // read from the socket intBytesRead = read(intGameSocket, receiveBuffer, sizeof(receiveBuffer)); if (intBytesRead <= 0) { // socket closed or read error printf("Ending Game - clearing gamesocket\n"); FD_CLR(intGameSocket, &activeSockets); close(intGameSocket); intGameSocket = -1; bIsPlaying = FALSE; } else { // put a null character at the end of the data to make it a string receiveBuffer[intBytesRead] = '\0'; processGameMessage(receiveBuffer); } } // check to see if we need to call our opponents flag if (bIsPlaying && !bIsOurMove && intOppMillisLeft) { // get the current time struct timeval stTimeVal = {0}; gettimeofday(&stTimeVal, NULL); unsigned int intCurrentMillis = (stTimeVal.tv_sec * 1000) + (stTimeVal.tv_usec / 1000); int intElaspedMillis = intCurrentMillis - intStartOppMillis; int intMillisLeft2 = intOppMillisLeft - intElaspedMillis; // call our opponent's flag if his he is down for more than 15 seconds if (intMillisLeft2 < -15000) { if (bOppMoved) { sendString(intGameSocket, (char *)cszClientClaimFlagKey); printf("Called Client's flag .. ending Game\n"); } else { sendString(intGameSocket, "Timed out - aborting game"); } FD_CLR(intGameSocket, &activeSockets); close(intGameSocket); intGameSocket = -1; bIsPlaying = FALSE; } } } }