//-------------------------------------------------------------------------------------- // ChessRoomManager.cpp // // This file contains the source a web based internet Chess server. It allows // players to meet, chat, and play chess online. It stores records of users, games // and ratings in a MySQL database. It was initially written for the Linux operating // system. // // Author - Michael Keating //-------------------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TRUE 1 #define FALSE 0 // bTerminateManager is set when SIGTERM or SIGINT is caught int bTerminateManager = FALSE; // 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 cszClientIAmReturningKey[] = "/I_AM_RETURNING"; 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"; // status defines #define STATUS_CHATTING 0 #define STATUS_PLAYING 1 #define STATUS_VIEWING 2 // the general string sufix const char cszSuffix[] = "^@!"; // the challenge "from" structer typedef struct { char szHandle[22]; char szMinutes[4]; char szSeconds[4]; } CHALLENGE_FROM_T; // the challenge "to" structer typedef struct { char szHandle[22]; } CHALLENGE_TO_T; // the PLAYER_T structer will define each player in the room. It will // be used to initialize an PLAYER_T stPlayers[intMaxPlayers] array. typedef struct { int intSocket; char szID[10]; char szHandle[22]; char szRating[6]; char szGamesWon[10]; char szGamesLost[10]; char szGamesDrawn[10]; char szCurrentOpponent[22]; char szOpponentRating[6]; char intStatus; int intBlacksInARow; int bPlayAgain; int bIsComputer; vector vChallengesTo; vector vChallengesFrom; } PLAYER_T; // the CHESS_GAME_T structer will define a chess game. typedef struct { PLAYER_T stWhitePlayer; PLAYER_T stBlackPlayer; char szWhiteMillisLeft[12]; char szBlackMillisLeft[12]; char szCurGameMinutes[10]; char szCurGameSeconds[10]; ChessScoreKeeper *pChessScoreKeeper; vector vViewers; } CHESS_GAME_T; // the TIME_CLOCK_T structer for TimeKeeper() typedef struct { time_t startTime; int intAddSeconds; int intWhitesSecondsLeft; int intBlacksSecondsLeft; char strWhiteHandle[22]; } TIMECLOCK_T; class ChessRoomManager { public: ChessRoomManager(unsigned short int intPort); ~ChessRoomManager(); private: void ConnectionManagementLoop(); int CreateServerSocket(unsigned short int intPort); int ReadFromClient(int intPlayerIndex); int ReadFromGame(int intGameIndex, int intSocket); int SendMessageToClient(int intClientSocket, const char *cszKey, const char *szMessage); void SendMessageToAllClients(const char *cszKey, char *szMessage); void SendPlayersList(int intSocket); void RemovePlayer(int intPlayerIndex); void SendNewPlayerToAll(int intPlayer); void UpdatePlayer(int intPlayerIndex, int intOpponentIndex); void RemoveChallenge(int intChallengedPlayer, int intChallengingPlayer); void RemovePlayersChallenges(int intPlayerIndex); int GetPlayerIndex(char *lpszHandle); void SendMessageToGameClients(int intGameIndex, const char *cszKey, const char *szMessage); void GameOver(int intGameIndex, int intResult); void CloseGame(int intGameIndex); void TimeKeeper(); // data MYSQL m_mysql; vector m_players; vector m_chessGames; fd_set m_activeSockets; int m_intServerSocket; int m_AppToTimeKeeperPipe[2]; int m_TimeKeeperToAppPipe[2]; }; //***************************************************************** // // ChessRoomManager::ChessRoomManger(unsigned short int intPort) // // Parameters: // // intPort - the port to setup our server socket on // // Description: // // This constructor intializes the server socket and starts a general // management loop that processes client reqests. This include chat messages, // pairing requests, exit room, etc. // //***************************************************************** ChessRoomManager::ChessRoomManager(unsigned short int intPort) { // create the TimeKeeper and the pipes to comminicate with them /* if (pipe(m_TimeKeeperToAppPipe) || pipe(m_AppToTimeKeeperPipe)) { fprintf (stderr, "Pipe creation failed.\n"); return; } switch (int pid = fork()) { case -1: // error perror("fork"); exit(EXIT_FAILURE); case 0: // child TimeKeeper(); exit(EXIT_SUCCESS); default: // parent write(m_AppToTimeKeeperPipe[1], "This is a test..", strlen("This is a test..")); break; }*/ // create our server socket if (CreateServerSocket(intPort) == -1) { fprintf(stderr, "CreateServerSocket failed\n"); return; } // connect to the database mysql_init(&m_mysql); if (!mysql_real_connect(&m_mysql, "localhost", "root", "speaker", "ChessServer", 0, NULL, 0)) { fprintf(stderr, "Failed to connect to database: Error: %s\n", mysql_error(&m_mysql)); return; } ConnectionManagementLoop(); } //***************************************************************** // // ChessRoomManager::~ChessRoomManger() // // Parameters: // // none // // Description: // // This destructor cleans up by closing any active socket connections // and clears the m_players vector // //***************************************************************** ChessRoomManager::~ChessRoomManager() { // close the database connection mysql_close(&m_mysql); // close our active sockets for (int i = 0; i < m_players.size(); ++i) { close(m_players[i].intSocket); } // close our chess game sockets and delete the ChessScoreKeepers for (int i = 0; i < m_chessGames.size(); ++i) { close(m_chessGames[i].stBlackPlayer.intSocket); close(m_chessGames[i].stWhitePlayer.intSocket); for (int j = 0; j < m_chessGames[i].vViewers.size(); ++j) { close(m_chessGames[i].vViewers[j].intSocket); } m_chessGames[i].vViewers.clear(); delete m_chessGames[i].pChessScoreKeeper; } // clear the vectors m_players.clear(); m_chessGames.clear(); // close the server socket shutdown(m_intServerSocket, 2); close(m_intServerSocket); // tell the Time Keeper to exit //write(m_AppToTimeKeeperPipe[1], "~quit", strlen("~quit")); } //***************************************************************** // // void ChessRoomManager::ConnectionManagementLoop() // // Parameters: // // none // // Description: // // This is the primary work loop for the program. It accepts // socket connections, processes client requests and manages // all IO for the chat room. // // Returns: // // 0 if the socket was created successfully, -1 otherwise // //***************************************************************** void ChessRoomManager::ConnectionManagementLoop() { // initialize the set of active sockets. fd_set readSockets; FD_ZERO(&m_activeSockets); FD_SET(m_intServerSocket, &m_activeSockets); while (!bTerminateManager) { // Block until input arrives on one or more active sockets. readSockets = m_activeSockets; if (select(FD_SETSIZE, &readSockets, NULL, NULL, NULL) == -1) { perror("select"); return; } // service all the sockets with input pending. The select function // overwrites readSockets with sockets that only have input pending // first check for input the server socket (which means // that a new connection is being requested) if (FD_ISSET(m_intServerSocket, &readSockets)) { // this is a new connection // accept it and add it to m_activeSockets if there's room int intNewSocket; struct sockaddr_in stClientAddress; size_t size = sizeof(stClientAddress); intNewSocket = accept(m_intServerSocket, (struct sockaddr *)&stClientAddress, &size); if (intNewSocket < 0) { perror("accept"); return; } // add our new socket to the m_players vector PLAYER_T player; memset(&player, 0, sizeof(player)); player.intSocket = intNewSocket; m_players.push_back(player); // add the new socket to our active socket set FD_SET(intNewSocket, &m_activeSockets); } // check for input on client (player) sockets int intSize = m_players.size(); for (int i = 0; i < intSize; ++i) { if (FD_ISSET(m_players[i].intSocket, &readSockets)) { if (ReadFromClient(i) == -1) { // error on socket - probably closed by client // remove the player and his socket RemovePlayer(i); continue; } // clear the socket from reading before the next call to select(). // this is because it will block in ReadFromGame()'s call to read() // if it thinks this is an active read socket. The IAmAGame key section // in ReadFromClient may have set this socket to one of the game sockets. FD_CLR(m_players[i].intSocket, &readSockets); } } // check for input on Current game sockets intSize = m_chessGames.size(); // we decrement i when we close down a game, so it is // possible that it is -1 for (int i = 0; i < intSize || i == -1; ++i) { int intReadStatus = 0; // input from the white player? if (FD_ISSET(m_chessGames[i].stWhitePlayer.intSocket, &readSockets)) { intReadStatus = ReadFromGame(i, m_chessGames[i].stWhitePlayer.intSocket); if (intReadStatus == -1) { // error on socket - probably closed by client // first close and clear the socket close(m_chessGames[i].stWhitePlayer.intSocket); FD_CLR(m_chessGames[i].stWhitePlayer.intSocket, &m_activeSockets); m_chessGames[i].stWhitePlayer.intSocket = 0; // close down the game if there is no other open socket or the // game is not in progress if (!m_chessGames[i].stBlackPlayer.intSocket || !m_chessGames[i].pChessScoreKeeper->isGameInProgress()) { // close the opponent's socket if necessary if (m_chessGames[i].stBlackPlayer.intSocket) { FD_CLR(m_chessGames[i].stBlackPlayer.intSocket, &readSockets); FD_CLR(m_chessGames[i].stBlackPlayer.intSocket, &m_activeSockets); close(m_chessGames[i].stBlackPlayer.intSocket); } // close any viewers for (int j = 0; j < m_chessGames[i].vViewers.size(); ++j) { FD_CLR(m_chessGames[i].vViewers[j].intSocket, &readSockets); FD_CLR(m_chessGames[i].vViewers[j].intSocket, &m_activeSockets); close(m_chessGames[i].vViewers[j].intSocket); } // free the game CloseGame(i); // decrement i and reset intSize since the the vector // just got smaller intSize = m_chessGames.size(); --i; } continue; } } // input from the black player? // if intReadStatus is not zero here, skip the read: a value of one // indicates that white and black just switched sides and this socket // is the whiteSocket we just read. Reading it here would block indefinitely // since there is no data (as it was just read) on the socket. This would // lock the server up until data arrived on this socket. if (FD_ISSET(m_chessGames[i].stBlackPlayer.intSocket, &readSockets) && intReadStatus == 0) { if (ReadFromGame(i, m_chessGames[i].stBlackPlayer.intSocket) == -1) { // error on socket - probably closed by client // TODO: some kind of status setting that a game // player has lost a connection. Set up some kind of // recover system close(m_chessGames[i].stBlackPlayer.intSocket); FD_CLR(m_chessGames[i].stBlackPlayer.intSocket, &m_activeSockets); m_chessGames[i].stBlackPlayer.intSocket = 0; // close down the game if there is no other open socket or the // game is not in progress if (!m_chessGames[i].stWhitePlayer.intSocket || !m_chessGames[i].pChessScoreKeeper->isGameInProgress()) { // close the opponent's socket if necessary if (m_chessGames[i].stWhitePlayer.intSocket) { FD_CLR(m_chessGames[i].stWhitePlayer.intSocket, &readSockets); FD_CLR(m_chessGames[i].stWhitePlayer.intSocket, &m_activeSockets); close(m_chessGames[i].stWhitePlayer.intSocket); } // close any viewers for (int j = 0; j < m_chessGames[i].vViewers.size(); ++j) { FD_CLR(m_chessGames[i].vViewers[j].intSocket, &readSockets); FD_CLR(m_chessGames[i].vViewers[j].intSocket, &m_activeSockets); close(m_chessGames[i].vViewers[j].intSocket); } // free the game CloseGame(i); // decrement i and reset intSize since the the vector // just got smaller intSize = m_chessGames.size(); --i; } continue; } } // input from a viewer (chat input) int intViewerSize = m_chessGames[i].vViewers.size(); for (int j = 0; j < intViewerSize; ++j) { if (FD_ISSET(m_chessGames[i].vViewers[j].intSocket, &readSockets)) { if (ReadFromGame(i, m_chessGames[i].vViewers[j].intSocket) == -1) { // error on socket - probably closed by client close(m_chessGames[i].vViewers[j].intSocket); FD_CLR(m_chessGames[i].vViewers[j].intSocket, &m_activeSockets); FD_CLR(m_chessGames[i].vViewers[j].intSocket, &readSockets); m_chessGames[i].vViewers.erase(m_chessGames[i].vViewers.begin() + j); continue; } } } } } } //***************************************************************** // // int ChessRoomManager::CreateServerSocket(unsigned short int intPort) // // Parameters: // // intPort - the port to create our socket on // // Description: // // This function creates the server socket and assigns it to // m_intServerSocket - it should be called only once. // // Returns: // // 0 if the socket was created successfully, -1 otherwise // //***************************************************************** int ChessRoomManager::CreateServerSocket(unsigned short int intPort) { struct sockaddr_in stSockAddress; // create the socket m_intServerSocket = socket(PF_INET, SOCK_STREAM, 0); if (m_intServerSocket < 0) { perror("socket"); return -1; } // assign the socket address stSockAddress.sin_family = AF_INET; stSockAddress.sin_port = htons(intPort); stSockAddress.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(m_intServerSocket, (struct sockaddr *)&stSockAddress, sizeof(stSockAddress)) < 0) { perror("bind"); return(-1); } // start listening on our new socket if (listen (m_intServerSocket, 5) < 0) { perror("listen"); return -1; } return 0;; } //***************************************************************** // // int ChessRoomManager::ReadFromClient(int intPlayerIndex) // // Parameters: // // lpPlayerIndex - the clients index into the m_players vector // // Description: // // This function reads data from the client socket. // // Returns: // // 0 - on success, -1 otherwise // //***************************************************************** int ChessRoomManager::ReadFromClient(int intPlayerIndex) { PLAYER_T *lpPlayer = &m_players[intPlayerIndex]; char receiveBuffer[512]; int intBytes; memset(&receiveBuffer, 0, sizeof(receiveBuffer)); // read from the socket intBytes = read(lpPlayer->intSocket, receiveBuffer, sizeof(receiveBuffer)); if (intBytes <= 0) { // socket closed or read error return -1; } // put a null character at the end of the data to make it a string receiveBuffer[intBytes < sizeof(receiveBuffer) ? intBytes : sizeof(receiveBuffer) - 1] = '\0'; // data was read from a client socket // first make a copy of the data read as a chat message since // receiveBuffer is about the be corrupted by strtok char *lpChatMessage = (char *)malloc(strlen(receiveBuffer) + 40); if (!lpChatMessage) { fprintf(stderr, "Insufficient Memory Error in ReadFromClient()\n"); return 0; } sprintf(lpChatMessage, "%s: %s", lpPlayer->szHandle, receiveBuffer); // check for keys char *szKey = strtok(receiveBuffer, ","); if (szKey == NULL) { // assign a dummy value so strcmp ahead doesn't crash szKey = (char *)&cszSuffix[0]; } if (!strcmp(szKey, cszClientSendingPlayerIDKey)) { // the user probobly just entered the room and is telling // us who he is by sending us his user ID - so, lets get it char *szID = strtok(NULL, ","); if (szID) { // first make sure the player isn't already in the room for (int i = 0; i < m_players.size(); ++i) { if (m_players[i].szID && (!strcmp(szID, m_players[i].szID))) { RemovePlayer(intPlayerIndex); return 0; } } // okay, look up szID in the datebase MYSQL_RES *lpResult; MYSQL_ROW row; char szQuery[256]; // first get info from table 'user' strcpy(szQuery, "SELECT * FROM user WHERE intID ="); strcat(szQuery, szID); mysql_query(&m_mysql, szQuery); lpResult = mysql_store_result(&m_mysql); if (mysql_num_rows(lpResult)) { row = mysql_fetch_row(lpResult); strcpy(lpPlayer->szID, row[0]); strcpy(lpPlayer->szHandle, row[1]); // if we're a computer, add a 'c' to the end of the handle if (lpPlayer->bIsComputer && (strlen(lpPlayer->szHandle) < 17)) { strcat(lpPlayer->szHandle, " (c)"); } } mysql_free_result(lpResult); // next, get info from table 'chessPlayer' strcpy(szQuery, "SELECT * FROM chessPlayer WHERE intID ="); strcat(szQuery, szID); mysql_query(&m_mysql, szQuery); lpResult = mysql_store_result(&m_mysql); if (mysql_num_rows(lpResult)) { row = mysql_fetch_row(lpResult); strcpy(lpPlayer->szRating, row[1]); strcpy(lpPlayer->szGamesWon, row[2]); strcpy(lpPlayer->szGamesLost, row[3]); strcpy(lpPlayer->szGamesDrawn, row[4]); lpPlayer->intStatus = STATUS_CHATTING; lpPlayer->intBlacksInARow = (int)strtol(row[5], NULL, 0); } mysql_free_result(lpResult); SendMessageToClient(lpPlayer->intSocket, cszServerWelcomeKey, lpPlayer->szHandle); // now send the player a list of the other peopel in this room if (!lpPlayer->bIsComputer) { SendPlayersList(lpPlayer->intSocket); } SendNewPlayerToAll(intPlayerIndex); } } else if (!strcmp(szKey, cszClientIAmAComputerKey)) { lpPlayer->bIsComputer = TRUE; } else if (!strcmp(szKey, cszClientIAmAChessGameKey)) { // the client's game applet has established a socket connection and is // telling us what player in the games vector he is. // get the player's handle char *szHandle = strtok(NULL, ","); if (!szHandle) { fprintf(stderr, "IAmAChessGame Error: no handle\n"); free(lpChatMessage); return 0; } // find the handle the the m_ChessGames vector for this player // assign to it the current socket. int intSize = m_chessGames.size(); for (int i = 0; i < intSize; ++i) { // are we a socket for the white player? if (!strcmp(szHandle, m_chessGames[i].stWhitePlayer.szHandle)) { m_chessGames[i].stWhitePlayer.intSocket = m_players[intPlayerIndex].intSocket; // if both clients have sent their IAmAChessGame keys then tell them it's okay // to start playing.... if (m_chessGames[i].stBlackPlayer.intSocket) { m_chessGames[i].pChessScoreKeeper->newGame(); SendMessageToGameClients(i, cszServerNewGameKey, NULL); } // remove the m_players[i] structer. It is empty except for the socket and // is no longer needed m_players.erase(m_players.begin() + intPlayerIndex); break; } // ..or are we a socket for the black player? else if (!strcmp(szHandle, m_chessGames[i].stBlackPlayer.szHandle)) { m_chessGames[i].stBlackPlayer.intSocket = m_players[intPlayerIndex].intSocket; // if both clients have sent their IAmAChessGame keys then tell them it's okay // to start playing.... if (m_chessGames[i].stWhitePlayer.intSocket) { m_chessGames[i].pChessScoreKeeper->newGame(); SendMessageToGameClients(i, cszServerNewGameKey, NULL); } // remove the m_players[i] structer. It is empty except for the socket and // is no longer needed m_players.erase(m_players.begin() + intPlayerIndex); break; } } } else if (!strcmp(szKey, cszClientIAmAViewerKey)) { // this is a game viewer who just requested initial viewer info // get the viewer's handle char *szHandle = strtok(NULL, ","); if (!szHandle) { fprintf(stderr, "IAmAChessAViewer Error: no handle\n"); free(lpChatMessage); return 0; } // get whites handle char *szPlayer = strtok(NULL, ","); if (!szPlayer) { fprintf(stderr, "IAmAChessAViewer Error: no Player\n"); free(lpChatMessage); return 0; } int intIndex = GetPlayerIndex(szHandle); if (intIndex == -1) { fprintf(stderr, "IAmAChessAViewer Error: could not find index for viewer %s\n", szHandle); free(lpChatMessage); return 0; } // get the viewer's PLAYER_T info and set it to this socket PLAYER_T stViewer = m_players[intIndex]; stViewer.intSocket = m_players[intPlayerIndex].intSocket; // find the game by looking for the white player int bFoundGame = FALSE; int intSize = m_chessGames.size(); for (int i = 0; i < intSize; ++i) { if (!strcmp(m_chessGames[i].stWhitePlayer.szHandle, szPlayer) || !strcmp(m_chessGames[i].stBlackPlayer.szHandle, szPlayer)) { // found the game info - send it off! char *szMoveHistory = m_chessGames[i].pChessScoreKeeper->getMoves(); if (!szMoveHistory) { szMoveHistory = ""; } char *szGameParams = (char *)malloc(64 + strlen(szMoveHistory)); if (!szGameParams) { fprintf(stderr, "IAmAViewerKey Error: out of memory\n"); free(lpChatMessage); return 0;; } sprintf(szGameParams, "%s,%s,%s,%s,%s,%s,%s,%s,%s", m_chessGames[i].stWhitePlayer.szHandle, m_chessGames[i].stBlackPlayer.szHandle, m_chessGames[i].stWhitePlayer.szRating, m_chessGames[i].stBlackPlayer.szRating, m_chessGames[i].szWhiteMillisLeft, m_chessGames[i].szBlackMillisLeft, m_chessGames[i].szCurGameMinutes, m_chessGames[i].szCurGameSeconds, szMoveHistory); SendMessageToClient(m_players[intPlayerIndex].intSocket, cszServerViewerInfoKey, szGameParams); free(szGameParams); if (strlen(szMoveHistory)) { free(szMoveHistory); } // add us to the games viewer vector m_chessGames[i].vViewers.push_back(stViewer); bFoundGame = TRUE; } } if (!bFoundGame) { SendMessageToClient(m_players[intPlayerIndex].intSocket, NULL, "Sorry, no game was found - make sure it's still in progress."); RemovePlayer(intPlayerIndex); } else { // remove the empty structer from the m_players vector m_players.erase(m_players.begin() + intPlayerIndex); } } /*else if (!strcmp(szKey, cszClientIAmReturningKey)) { // this is a returning player who lost his connection some how // (probabaly closed his applet) we'll just request updated time // from the opponenent. The applet will then request viewer info. char *szHandle = strtok(NULL, ","); if (!szHandle) { fprintf(stderr, "IAmReturning Error: no handle\n"); free(lpChatMessage); return 0; } // find the handle the the m_ChessGames vector for this player // assign to it the current socket. int intSize = m_chessGames.size(); for (int i = 0; i < intSize; ++i) { // are we a socket for the white player? if (!strcmp(szHandle, m_chessGames[i].stWhitePlayer.szHandle)) { m_chessGames[i].stWhitePlayer.intSocket = m_players[intPlayerIndex].intSocket; // if both clients have sent their IAmAChessGame keys then tell them it's okay // to start playing.... if (m_chessGames[i].stBlackPlayer.intSocket) { m_chessGames[i].pChessScoreKeeper->newGame(); SendMessageToGameClients(i, cszServerNewGameKey, NULL); } // remove the m_players[i] structer. It is empty except for the socket and // is no longer needed m_players.erase(m_players.begin() + intPlayerIndex); break; } // ..or are we a socket for the black player? else if (!strcmp(szHandle, m_chessGames[i].stBlackPlayer.szHandle)) { m_chessGames[i].stBlackPlayer.intSocket = m_players[intPlayerIndex].intSocket; // if both clients have sent their IAmAChessGame keys then tell them it's okay // to start playing.... if (m_chessGames[i].stWhitePlayer.intSocket) { m_chessGames[i].pChessScoreKeeper->newGame(); SendMessageToGameClients(i, cszServerNewGameKey, NULL); } // remove the m_players[i] structer. It is empty except for the socket and // is no longer needed m_players.erase(m_players.begin() + intPlayerIndex); break; } } } */ else if (!strcmp(szKey, cszClientChallengeKey)) { // Challenge from a player CHALLENGE_FROM_T stChallengeFrom = {0}; // copy the challenging player to szChallenge.szHandle strcpy(stChallengeFrom.szHandle, m_players[intPlayerIndex].szHandle); // get the challenged player from the socket data char *szChallengedPlayer = strtok(NULL, ","); if (!szChallengedPlayer) { fprintf(stderr, "Challenge Player Error: No player challenged\n"); free(lpChatMessage); return 0; } // get the index of the challenged player int intChallengedIndex = GetPlayerIndex(szChallengedPlayer); if (intChallengedIndex == -1) { fprintf(stderr, "Challenge Player Error: Could not get the index for %s\n", szChallengedPlayer); free(lpChatMessage); return 0; } // if the either player is involved in a game, reject the challenge if (m_players[intChallengedIndex].intStatus == STATUS_PLAYING || m_players[intPlayerIndex].intStatus == STATUS_PLAYING) { fprintf(stderr, "Challenge Player Error: player already in a game\n"); free(lpChatMessage); return 0; } // now get the time control char *szMinutes = strtok(NULL, ","); if (!szMinutes) { fprintf(stderr, "Challenge Player Error: No minutes\n"); free(lpChatMessage); return 0; } strcpy(stChallengeFrom.szMinutes, szMinutes); char *szSeconds = strtok(NULL, ","); if (!szSeconds) { fprintf(stderr, "Challenge Player Error: No seconds\n"); free(lpChatMessage); return 0; } strcpy(stChallengeFrom.szSeconds, szSeconds); // add the challenges to the vectors CHALLENGE_TO_T stChallengeTo; strcpy(stChallengeTo.szHandle, szChallengedPlayer); // we can only challenge or accept challenges from one player at a time // so delete any previous challenges in the To or From vectors for (int i = 0; i < m_players[intPlayerIndex].vChallengesTo.size(); ++i) { if (!strcmp(stChallengeTo.szHandle, m_players[intPlayerIndex].vChallengesTo[i].szHandle)) { m_players[intPlayerIndex].vChallengesTo.erase(m_players[intPlayerIndex].vChallengesTo.begin() + i); break; } } for (int i = 0; i < m_players[intChallengedIndex].vChallengesFrom.size(); ++i) { if (!strcmp(stChallengeFrom.szHandle, m_players[intChallengedIndex].vChallengesFrom[i].szHandle)) { m_players[intChallengedIndex].vChallengesFrom.erase(m_players[intChallengedIndex].vChallengesFrom.begin() + i); break; } } // add the TO challenge m_players[intPlayerIndex].vChallengesTo.push_back(stChallengeTo); // add the FROM challenge m_players[intChallengedIndex].vChallengesFrom.push_back(stChallengeFrom); // send the To and From players the challenge char szChallengeString[128]; strcpy(szChallengeString, "FROM,"); strcat(szChallengeString, stChallengeFrom.szHandle); strcat(szChallengeString, ","); strcat(szChallengeString, stChallengeFrom.szMinutes); strcat(szChallengeString, ","); strcat(szChallengeString, stChallengeFrom.szSeconds); SendMessageToClient(m_players[intChallengedIndex].intSocket, cszServerChallengeRecievedKey, szChallengeString); strcpy(szChallengeString, "TO,"); strcat(szChallengeString, m_players[intChallengedIndex].szHandle); strcat(szChallengeString, ","); strcat(szChallengeString, stChallengeFrom.szMinutes); strcat(szChallengeString, ","); strcat(szChallengeString, stChallengeFrom.szSeconds); SendMessageToClient(m_players[intPlayerIndex].intSocket, cszServerChallengeRecievedKey, szChallengeString); } else if (!strcmp(szKey, cszClientAcceptChallengeKey)) { char *szOpponent = strtok(NULL, ","); if (!szOpponent) { fprintf(stderr, "Challenge Accept Error: No Opponent\n"); free(lpChatMessage); return 0; } // find the challenge in our VChallengesFrom vector char szMinutes[10]; char szSeconds[10]; int bFoundChallenge = FALSE; int intSize = m_players[intPlayerIndex].vChallengesFrom.size(); for (int i = 0; i < intSize; ++i) { if (!strcmp(szOpponent, m_players[intPlayerIndex].vChallengesFrom[i].szHandle)) { bFoundChallenge = TRUE; // got it - now get the times control strcpy(szMinutes, m_players[intPlayerIndex].vChallengesFrom[i].szMinutes); strcpy(szSeconds, m_players[intPlayerIndex].vChallengesFrom[i].szSeconds); break; } } if (!bFoundChallenge) { fprintf(stderr, "Challenge Accept Error: Could not find challenge\n"); free(lpChatMessage); return 0; } // now find the challenger's m_players index int intOpponentIndex = -1; intSize = m_players.size(); for (int i = 0; i < intSize; ++i) { if (!strcmp(szOpponent, m_players[i].szHandle)) { intOpponentIndex = i; break; } } if (intOpponentIndex == -1) { fprintf(stderr, "Challenge Accept Error: Could not find opponent's index\n"); free(lpChatMessage); return 0; } // if the either player is involved in a game, reject the challenge if (m_players[intOpponentIndex].intStatus == STATUS_PLAYING || m_players[intPlayerIndex].intStatus == STATUS_PLAYING) { fprintf(stderr, "Challenge Accept Error: player already in a game\n"); free(lpChatMessage); return 0; } // remove the players' remaining challenges RemovePlayersChallenges(intPlayerIndex); RemovePlayersChallenges(intOpponentIndex); // update the players online status m_players[intPlayerIndex].intStatus = STATUS_PLAYING; m_players[intOpponentIndex].intStatus = STATUS_PLAYING; // get ready to create a new game CHESS_GAME_T chessGame; memset(&chessGame, 0, sizeof(chessGame)); // get the game parameters - assign white and black and the time control char szGameParams[64]; if (m_players[intPlayerIndex].intBlacksInARow > m_players[intOpponentIndex].intBlacksInARow) { // the opponent will get getting black sprintf(szGameParams, "%s,%s,%s,%s,%s,%s", m_players[intPlayerIndex].szHandle, szOpponent, m_players[intPlayerIndex].szRating, m_players[intOpponentIndex].szRating, szMinutes, szSeconds); ++m_players[intOpponentIndex].intBlacksInARow; m_players[intPlayerIndex].intBlacksInARow = 0; chessGame.stWhitePlayer = m_players[intPlayerIndex]; chessGame.stBlackPlayer = m_players[intOpponentIndex]; } else { // we (intPlayerIndex) will be getting black sprintf(szGameParams, "%s,%s,%s,%s,%s,%s", szOpponent, m_players[intPlayerIndex].szHandle, m_players[intOpponentIndex].szRating, m_players[intPlayerIndex].szRating, szMinutes, szSeconds); ++m_players[intPlayerIndex].intBlacksInARow; m_players[intOpponentIndex].intBlacksInARow = 0; chessGame.stWhitePlayer = m_players[intOpponentIndex]; chessGame.stBlackPlayer = m_players[intPlayerIndex]; } // initialize the sockets to zero. See the IAmAChessGame key to // see how and when these sockets are set. chessGame.stWhitePlayer.intSocket = 0; chessGame.stBlackPlayer.intSocket = 0; // initialize the play again flags to FALSE chessGame.stWhitePlayer.bPlayAgain = FALSE; chessGame.stBlackPlayer.bPlayAgain = FALSE; // set the games time control int intMinutes = atoi(szMinutes); sprintf(chessGame.szWhiteMillisLeft, "%u", intMinutes * 60 * 1000); sprintf(chessGame.szBlackMillisLeft, "%u", intMinutes * 60 * 1000); strcpy(chessGame.szCurGameMinutes, szMinutes); strcpy(chessGame.szCurGameSeconds, szSeconds); // set the players "current opponents" in thier m_players structer. This is for the // benifit of the players list status info. strcpy(m_players[intPlayerIndex].szCurrentOpponent, m_players[intOpponentIndex].szHandle); strcpy(m_players[intPlayerIndex].szOpponentRating, m_players[intOpponentIndex].szRating); strcpy(m_players[intOpponentIndex].szCurrentOpponent, m_players[intPlayerIndex].szHandle); strcpy(m_players[intOpponentIndex].szOpponentRating, m_players[intPlayerIndex].szRating); // add our new socket to the m_players vector chessGame.pChessScoreKeeper = new ChessScoreKeeper(); m_chessGames.push_back(chessGame); // tell the players to start a game! We'll wait till they both send // back an IAmAChessGame key before we start the game. SendMessageToClient(m_players[intPlayerIndex].intSocket, cszServerStartGameKey, szGameParams); SendMessageToClient(m_players[intOpponentIndex].intSocket, cszServerStartGameKey, szGameParams); // update the clients as the the above players new online status UpdatePlayer(intPlayerIndex, intOpponentIndex); UpdatePlayer(intOpponentIndex, intPlayerIndex); } else { // assume this is a chat message - pre-pend the players handle and // send the message of to all clients; SendMessageToAllClients(NULL, lpChatMessage); } free(lpChatMessage); return 0; } //***************************************************************** // // int ChessRoomManager::ReadFromGame(int intGameIndex, int intSocket) // // Parameters: // // lpGameIndex - the index into the m_chessGames vector // intSocket - the Socket we are reading from // // Description: // // This function reads data from am active chess game. // // Returns: // // 0 - on success, -1 otherwise // //***************************************************************** int ChessRoomManager::ReadFromGame(int intGameIndex, int intSocket) { CHESS_GAME_T *pChessGame = &m_chessGames[intGameIndex]; char receiveBuffer[512]; int intBytes; memset(&receiveBuffer, 0, sizeof(receiveBuffer)); // read from the socket intBytes = read(intSocket, receiveBuffer, sizeof(receiveBuffer)); // put a null character at the end of the data to make it a string receiveBuffer[intBytes < sizeof(receiveBuffer) ? intBytes : sizeof(receiveBuffer) - 1] = '\0'; if (intBytes <= 0) { // socket closed or read error return -1; } // data was read from a client game socket // first make a copy of the data read as a chat message since // receiveBuffer is about the be corrupted by strtok char *lpChatMessage = (char *)malloc(strlen(receiveBuffer) + 40); if (!lpChatMessage) { fprintf(stderr, "Insufficient Memory Error in ReadFromGame()\n"); return 0; } // get the sender's handle char szHandle[30]; // which socket was used will tell use who sent the data if (intSocket == pChessGame->stWhitePlayer.intSocket) { strcpy(szHandle, pChessGame->stWhitePlayer.szHandle); } else if (intSocket == pChessGame->stBlackPlayer.intSocket) { strcpy(szHandle, pChessGame->stBlackPlayer.szHandle); } else { int bFound = FALSE; int intSize = pChessGame->vViewers.size(); for (int i = 0; i < intSize; ++i) { if (intSocket == pChessGame->vViewers[i].intSocket) { strcpy(szHandle, pChessGame->vViewers[i].szHandle); bFound = TRUE; break; } } if (!bFound) { fprintf(stderr, "ReadFromGame Error: Could not find sender\n"); free(lpChatMessage); return 0; } } sprintf(lpChatMessage, "%s: %s", szHandle, receiveBuffer); // check for keys char *szKey = strtok(receiveBuffer, ","); if (!strcmp(szKey, cszClientSendingMoveKey)) { char *szMove = strtok(NULL, ","); if (!szMove) { fprintf(stderr, "ClientSendingMoveKey Error: no move\n"); free(lpChatMessage); return 0; } char *szMillisLeft = strtok(NULL, ","); if (!szMillisLeft) { fprintf(stderr, "ClientSendingMoveKey Error: no milliseconds\n"); free(lpChatMessage); return 0; } char szMoveAndMillis[80]; strcpy(szMoveAndMillis, szMove); strcat(szMoveAndMillis, ","); strcat(szMoveAndMillis, szMillisLeft); // make that move int intStatus = pChessGame->pChessScoreKeeper->move(szMove); // set the millies left for the player who just moved (if it was legal) if (intStatus) { if (pChessGame->pChessScoreKeeper->isWhitesMove()) { strcpy(pChessGame->szBlackMillisLeft, szMillisLeft); } else { strcpy(pChessGame->szWhiteMillisLeft, szMillisLeft); } } // send the move result the the game clients switch (intStatus) { case 0: // illegal move SendMessageToClient(intSocket, cszServerIllegalMoveKey, szMoveAndMillis); break; case 1: // legal move SendMessageToGameClients(intGameIndex, cszServerSendingMoveKey, szMoveAndMillis); break; case 2: // stalemate SendMessageToGameClients(intGameIndex, cszServerStalemateKey, szMoveAndMillis); GameOver(intGameIndex, 0); break; case 3: // white is mated SendMessageToGameClients(intGameIndex, cszServerWhiteIsMatedKey, szMoveAndMillis); GameOver(intGameIndex, 1); break; case 4: // black is mated SendMessageToGameClients(intGameIndex, cszServerBlackIsMatedKey, szMoveAndMillis); GameOver(intGameIndex, 2); break; case 5: // draw by repetition SendMessageToGameClients(intGameIndex, cszServerDrawByRepetitionKey, szMoveAndMillis); GameOver(intGameIndex, 0); break; default: fprintf(stderr, "pChessScoreKeeper->move Error: invalid return status: %d\n", intStatus); break; } } else if (!strcmp(szKey, cszClientMyTimeExpiredKey)) { // make sure the game is running.... if (!pChessGame->pChessScoreKeeper->isGameInProgress()) { return 0; } // did white lose on time? if (intSocket == pChessGame->stWhitePlayer.intSocket) { SendMessageToGameClients(intGameIndex, cszServerWhiteLostOnTimeKey, NULL); GameOver(intGameIndex, 1); } // or did black lose on time? else if (intSocket == pChessGame->stBlackPlayer.intSocket) { SendMessageToGameClients(intGameIndex, cszServerBlackLostOnTimeKey, NULL); GameOver(intGameIndex, 2); } // we shouldn't ever get into the else statement below else { fprintf(stderr, "ReadFromGame Error: Don't know who send MyTimeExpired Key\n"); } } else if (!strcmp(szKey, cszClientClaimFlagKey)) { // make sure the game is running.... if (!pChessGame->pChessScoreKeeper->isGameInProgress()) { return 0; } // did white call his opponent's flag? if (intSocket == pChessGame->stWhitePlayer.intSocket) { SendMessageToGameClients(intGameIndex, cszServerBlackLostOnTimeKey, NULL); GameOver(intGameIndex, 2); } // or did black call his opponent's flag? else if (intSocket == pChessGame->stBlackPlayer.intSocket) { SendMessageToGameClients(intGameIndex, cszServerWhiteLostOnTimeKey, NULL); GameOver(intGameIndex, 1); } // we shouldn't ever get into the else statement below else { fprintf(stderr, "ReadFromGame Error: Don't know who send MyTimeExpired Key\n"); } } else if (!strcmp(szKey, cszClientIResignKey)) { // make sure the game is running.... if (!pChessGame->pChessScoreKeeper->isGameInProgress()) { return 0; } // did white resign? if (intSocket == pChessGame->stWhitePlayer.intSocket) { SendMessageToGameClients(intGameIndex, cszServerWhiteResignedKey, NULL); GameOver(intGameIndex, 1); } // or did black resign? else if (intSocket == pChessGame->stBlackPlayer.intSocket) { SendMessageToGameClients(intGameIndex, cszServerBlackResignedKey, NULL); GameOver(intGameIndex, 2); } } else if (!strcmp(szKey, cszClientOfferDrawKey)) { // make sure the game is running.... if (!pChessGame->pChessScoreKeeper->isGameInProgress()) { return 0; } // did white offer a draw? if (intSocket == pChessGame->stWhitePlayer.intSocket) { SendMessageToClient(pChessGame->stBlackPlayer.intSocket, cszServerDrawOfferedKey, NULL); } // or did black offer a draw? else if (intSocket == pChessGame->stBlackPlayer.intSocket) { SendMessageToClient(pChessGame->stWhitePlayer.intSocket, cszServerDrawOfferedKey, NULL); } } else if (!strcmp(szKey, cszClientDrawAgreedKey)) { // make sure the game is running.... if (!pChessGame->pChessScoreKeeper->isGameInProgress()) { return 0; } SendMessageToGameClients(intGameIndex, cszServerDrawAgreedKey, NULL); GameOver(intGameIndex, 0); } else if (!strcmp(szKey, cszClientPlayAgainKey)) { // make sure the game is not running.... if (pChessGame->pChessScoreKeeper->isGameInProgress()) { return 0; } // did white send the request? if (intSocket == pChessGame->stWhitePlayer.intSocket) { // if black already hit his play again button - start a new game! // otherwise set your own flag if (pChessGame->stBlackPlayer.bPlayAgain) { // make the players switch colors PLAYER_T tempPlayer = pChessGame->stWhitePlayer; pChessGame->stWhitePlayer = pChessGame->stBlackPlayer; pChessGame->stBlackPlayer = tempPlayer; // now start the game m_chessGames[intGameIndex].pChessScoreKeeper->newGame(); SendMessageToGameClients(intGameIndex, cszServerNewGameKey, NULL); pChessGame->stBlackPlayer.bPlayAgain = FALSE; pChessGame->stWhitePlayer.bPlayAgain = FALSE; // return one here to tell the socket reading loop not to read the black // player's socket (which was just white's) free(lpChatMessage); return 1; } else { pChessGame->stWhitePlayer.bPlayAgain = TRUE; } } // or did black send the request? else if (intSocket == pChessGame->stBlackPlayer.intSocket) { // if white already hit his play again button - start a new game! // otherwise set your own flag if (pChessGame->stWhitePlayer.bPlayAgain) { // make the players switch colors PLAYER_T tempPlayer = pChessGame->stWhitePlayer; pChessGame->stWhitePlayer = pChessGame->stBlackPlayer; pChessGame->stBlackPlayer = tempPlayer; // now start the game m_chessGames[intGameIndex].pChessScoreKeeper->newGame(); SendMessageToGameClients(intGameIndex, cszServerNewGameKey, NULL); pChessGame->stBlackPlayer.bPlayAgain = FALSE; pChessGame->stWhitePlayer.bPlayAgain = FALSE; } else { pChessGame->stBlackPlayer.bPlayAgain = TRUE; } } } else { // chat message - send to all the game's clients SendMessageToGameClients(intGameIndex, NULL, lpChatMessage); } free(lpChatMessage); return 0; } //***************************************************************** // // void ChessRoomManager::GameOver(int intGameIndex, int intResult) // // Parameters: // // intGameIndex - the index into the m_chessGames vector // intResult - the game result according to the following: // 0 - draw, 1 - black won, 2 - white won // // Description: // // This records the game's result by updating the players' stats // // Returns: // // none // //***************************************************************** void ChessRoomManager::GameOver(int intGameIndex, int intResult) { // tell the score keeper that the game is over m_chessGames[intGameIndex].pChessScoreKeeper->gameOver(intResult); // get player stats as integers int intWhiteRating = atoi(m_chessGames[intGameIndex].stWhitePlayer.szRating); int intWhiteWins = atoi(m_chessGames[intGameIndex].stWhitePlayer.szGamesWon); int intWhiteLosses = atoi(m_chessGames[intGameIndex].stWhitePlayer.szGamesLost); int intWhiteDraws = atoi(m_chessGames[intGameIndex].stWhitePlayer.szGamesDrawn); int intBlackRating = atoi(m_chessGames[intGameIndex].stBlackPlayer.szRating); int intBlackWins = atoi(m_chessGames[intGameIndex].stBlackPlayer.szGamesWon); int intBlackLosses = atoi(m_chessGames[intGameIndex].stBlackPlayer.szGamesLost); int intBlackDraws = atoi(m_chessGames[intGameIndex].stBlackPlayer.szGamesDrawn); // save the old ratings so we can tell the players what the change was int intOldWhiteRating = intWhiteRating; int intOldBlackRating = intBlackRating; // now adjust ratings acccording to the game's result int intRatingDifference = intBlackRating - intWhiteRating; // max rating differece is 375 if (intRatingDifference > 375) { intRatingDifference = 375; } else if (intRatingDifference < -375) { intRatingDifference = -375; } switch (intResult) { case 0: // draw intWhiteRating = intWhiteRating + (intRatingDifference / 25); intBlackRating = intBlackRating + ((intRatingDifference * -1) / 25); ++intWhiteDraws; ++intBlackDraws; break; case 1: // black won intWhiteRating = intWhiteRating + (intRatingDifference / 25) - 16; intBlackRating = intBlackRating + ((intRatingDifference * -1) / 25) + 16; ++intBlackWins; ++intWhiteLosses; break; case 2: // white won intWhiteRating = intWhiteRating + (intRatingDifference / 25) + 16; intBlackRating = intBlackRating + ((intRatingDifference * -1) / 25) - 16; ++intWhiteWins; ++intBlackLosses; break; default: // error fprintf(stderr, "GameOver error: Invalid game result %d\n", intResult); return; } // update white - first the database record char szQuery[256]; sprintf(szQuery, "UPDATE chessPlayer SET wRating=%d, intGamesLost=%d, intGamesWon=%d, intGamesDrawn=%d WHERE intID=%s", intWhiteRating, intWhiteLosses, intWhiteWins, intWhiteDraws, m_chessGames[intGameIndex].stWhitePlayer.szID); mysql_query(&m_mysql, szQuery); // make sure the player was updated int intRowsAffected = mysql_affected_rows(&m_mysql); if (intRowsAffected != 1) { fprintf(stderr, "GameOver error: mysql_affected_rows() returned %d on the following query:\n\n %s\n", intRowsAffected, szQuery); } // update the game's PLAYER_T structer sprintf(m_chessGames[intGameIndex].stWhitePlayer.szRating, "%d", intWhiteRating); sprintf(m_chessGames[intGameIndex].stWhitePlayer.szGamesWon, "%d", intWhiteWins); sprintf(m_chessGames[intGameIndex].stWhitePlayer.szGamesLost, "%d", intWhiteLosses); sprintf(m_chessGames[intGameIndex].stWhitePlayer.szGamesDrawn, "%d", intWhiteDraws); // update the m_players structer int intWhitePlayerIndex = GetPlayerIndex(m_chessGames[intGameIndex].stWhitePlayer.szHandle); if (intWhitePlayerIndex != -1) { char szTempOpponent[22]; strcpy(szTempOpponent, m_players[intWhitePlayerIndex].szCurrentOpponent); int intTempSocket = m_players[intWhitePlayerIndex].intSocket; m_players[intWhitePlayerIndex] = m_chessGames[intGameIndex].stWhitePlayer; m_players[intWhitePlayerIndex].intSocket = intTempSocket; strcpy(m_players[intWhitePlayerIndex].szCurrentOpponent, szTempOpponent); sprintf(m_players[intWhitePlayerIndex].szOpponentRating, "%d", intBlackRating); } // update black - first the database record sprintf(szQuery, "UPDATE chessPlayer SET wRating=%d, intGamesLost=%d, intGamesWon=%d, intGamesDrawn=%d WHERE intID=%s", intBlackRating, intBlackLosses, intBlackWins, intBlackDraws, m_chessGames[intGameIndex].stBlackPlayer.szID); mysql_query(&m_mysql, szQuery); // make sure the player was updated intRowsAffected = mysql_affected_rows(&m_mysql); if (intRowsAffected != 1) { fprintf(stderr, "GameOver error: mysql_affected_rows() returned %d on the following query:\n\n %s\n", intRowsAffected, szQuery); } // update the game's PLAYER_T structer sprintf(m_chessGames[intGameIndex].stBlackPlayer.szRating, "%d", intBlackRating); sprintf(m_chessGames[intGameIndex].stBlackPlayer.szGamesWon, "%d", intBlackWins); sprintf(m_chessGames[intGameIndex].stBlackPlayer.szGamesLost, "%d", intBlackLosses); sprintf(m_chessGames[intGameIndex].stBlackPlayer.szGamesDrawn, "%d", intBlackDraws); // update the m_players structer int intBlackPlayerIndex = GetPlayerIndex(m_chessGames[intGameIndex].stBlackPlayer.szHandle); if (intBlackPlayerIndex != -1) { char szTempOpponent[22]; strcpy(szTempOpponent, m_players[intBlackPlayerIndex].szCurrentOpponent); int intTempSocket = m_players[intBlackPlayerIndex].intSocket; m_players[intBlackPlayerIndex] = m_chessGames[intGameIndex].stBlackPlayer; m_players[intBlackPlayerIndex].intSocket = intTempSocket; strcpy(m_players[intBlackPlayerIndex].szCurrentOpponent, szTempOpponent); sprintf(m_players[intBlackPlayerIndex].szOpponentRating, "%d", intWhiteRating); // update the clients with whites new info UpdatePlayer(intBlackPlayerIndex, intWhitePlayerIndex); } if (intWhitePlayerIndex != -1) { UpdatePlayer(intWhitePlayerIndex, intBlackPlayerIndex); } // now update game clients scoresheet ratings char szMessage[20]; sprintf(szMessage, "%d,%d", intWhiteRating, intBlackRating); SendMessageToGameClients(intGameIndex, cszServerUpdateRatingsKey, szMessage); } //***************************************************************** // // void ChessRoomManager::UpdatePlayer(int intPlayerIndex, int intOpponentIndex) // // Parameters: // // intPlayerIndex - the index of the player to update // intOpponentIndex - the index of the player's opponent if his online // status is STATUS_PLAYING // // Description: // // Send updated information on the given player to all clients // // Returns: // // none // //***************************************************************** void ChessRoomManager::UpdatePlayer(int intPlayerIndex, int intOpponentIndex) { char szMessage[128]; // update the clients with whites new info strcpy(szMessage, m_players[intPlayerIndex].szHandle); strcat(szMessage, ","); strcat(szMessage, m_players[intPlayerIndex].szRating); strcat(szMessage, ","); strcat(szMessage, m_players[intPlayerIndex].szGamesWon); strcat(szMessage, ","); strcat(szMessage, m_players[intPlayerIndex].szGamesLost); strcat(szMessage, ","); strcat(szMessage, m_players[intPlayerIndex].szGamesDrawn); strcat(szMessage, ","); switch (m_players[intPlayerIndex].intStatus) { case STATUS_CHATTING: strcat(szMessage, "Chatting"); break; case STATUS_PLAYING: strcat(szMessage, "Vs "); strcat(szMessage, m_players[intPlayerIndex].szCurrentOpponent); strcat(szMessage, " ("); strcat(szMessage, m_players[intPlayerIndex].szOpponentRating); strcat(szMessage, ")"); break; case STATUS_VIEWING: strcat(szMessage, "Viewing"); break; default: strcat(szMessage, "Error"); break; } strcat(szMessage, ","); SendMessageToAllClients(cszServerUpdatePlayerKey, szMessage); } //***************************************************************** // // void ChessRoomManager::CloseGame(int intGameIndex) // // Parameters: // // intGameIndex - the index into the m_chessGames vector // // Description: // // Closes the given game and cleans up it's resources. // // Returns: // // none // //***************************************************************** void ChessRoomManager::CloseGame(int intGameIndex) { // update the white's online status int intPlayerIndex = GetPlayerIndex(m_chessGames[intGameIndex].stWhitePlayer.szHandle); if (intPlayerIndex != -1) { m_players[intPlayerIndex].intStatus = STATUS_CHATTING; // update the clients with whites new info UpdatePlayer(intPlayerIndex, -1); } // update the blacks's online status intPlayerIndex = GetPlayerIndex(m_chessGames[intGameIndex].stBlackPlayer.szHandle); if (intPlayerIndex != -1) { m_players[intPlayerIndex].intStatus = STATUS_CHATTING; // update the clients with whites new info UpdatePlayer(intPlayerIndex, -1); } // free resources m_chessGames[intGameIndex].vViewers.clear(); delete m_chessGames[intGameIndex].pChessScoreKeeper; m_chessGames.erase(m_chessGames.begin() + intGameIndex); } //***************************************************************** // // void ChessRoomManager::SendMessageToGameClients // ( // int intGameIndex, // const char *cszKey, // const char *szMessage // ) // // Parameters: // // intGameIndex - the game's m_chessGames vector index // cszKey - the key that tells client what kind of message this is // szMessage - on optional message to follow the key // // Description: // // This function sends builds and sends a string all the clients // at the given game. // // Returns: // // nothing // //***************************************************************** void ChessRoomManager::SendMessageToGameClients(int intGameIndex, const char *cszKey, const char *szMessage) { CHESS_GAME_T *pChessGame = &m_chessGames[intGameIndex]; // send the message to all at the chess game SendMessageToClient(pChessGame->stWhitePlayer.intSocket, cszKey, szMessage); SendMessageToClient(pChessGame->stBlackPlayer.intSocket, cszKey, szMessage); int intSize = pChessGame->vViewers.size(); for (int i = 0; i < intSize; ++i) { SendMessageToClient(pChessGame->vViewers[i].intSocket, cszKey, szMessage); } } //***************************************************************** // // int ChessRoomManager::SendMessageToClient // ( // int intClientSocket, // const char *cszKey, // const char *szMessage // ) // // Parameters: // // intClientSocket - the socket to write to // cszKey - the key that tells client what kind of message this is // szMessage - on optional message to follow the key // // Description: // // This function sends builds and sends a string to the client. // // Returns: // // 0 - success, -1 otherwise // //***************************************************************** int ChessRoomManager::SendMessageToClient ( int intClientSocket, const char *cszKey, const char *szMessage ) { if (!intClientSocket) { return 0; } // build the string that will be sent to the client char szBuffer[1024]; if (cszKey) { strcpy(szBuffer, cszKey); strcat(szBuffer, ","); } else { szBuffer[0] = '\0'; } if (szMessage) { strcat(szBuffer, szMessage); } strcat(szBuffer, cszSuffix); // finally, send the string to the client if (write(intClientSocket, szBuffer, strlen(szBuffer)) != strlen(szBuffer)) { fprintf(stderr, "SendMessageToClient error - write error\n"); return -1; } return 0; } //***************************************************************** // // void ChessRoomManager::SendMessageToAllClients // ( // const char *cszKey, // const char *szMessage // ) // // Parameters: // // cszKey - the key that tells client what kind of message this is // szMessage - on optional message to follow the key // // Description: // // This function builds and sends a string to all active clients // // Returns: // // 0 - success, -1 otherwise // //***************************************************************** void ChessRoomManager::SendMessageToAllClients(const char *cszKey, char *szMessage) { int intSize = m_players.size(); for (int i = 0; i < intSize; ++i) { if (!m_players[i].bIsComputer) { SendMessageToClient(m_players[i].intSocket, cszKey, szMessage); } } } //***************************************************************** // // void ChessRoomManager::SendPlayersList(int intSocket) // // Parameters: // // intSocket - Socket to send to // // Description: // // This method sends the players and their info to intSocket // //***************************************************************** void ChessRoomManager::SendPlayersList(int intSocket) { // first allocate our buffer char *lpBuffer = (char *)malloc(sizeof(PLAYER_T) * m_players.size()); if (!lpBuffer) { fprintf(stderr, "Out of memory in SendPlayersList\n"); } // now build the info string lpBuffer[0] = '\0'; int intSize = m_players.size(); for (int i = 0; i < intSize; ++i) { if (m_players[i].szID[0]) { strcat(lpBuffer, m_players[i].szHandle); strcat(lpBuffer, ","); strcat(lpBuffer, m_players[i].szRating); strcat(lpBuffer, ","); strcat(lpBuffer, m_players[i].szGamesWon); strcat(lpBuffer, ","); strcat(lpBuffer, m_players[i].szGamesLost); strcat(lpBuffer, ","); strcat(lpBuffer, m_players[i].szGamesDrawn); strcat(lpBuffer, ","); switch (m_players[i].intStatus) { case STATUS_CHATTING: strcat(lpBuffer, "Chatting"); break; case STATUS_PLAYING: strcat(lpBuffer, "Vs "); strcat(lpBuffer, m_players[i].szCurrentOpponent); strcat(lpBuffer, " ("); strcat(lpBuffer, m_players[i].szOpponentRating); strcat(lpBuffer, ")"); break; case STATUS_VIEWING: strcat(lpBuffer, "Viewing"); break; default: strcat(lpBuffer, "Error"); break; } strcat(lpBuffer, ","); } } // send it off SendMessageToClient(intSocket, cszServerSendPlayersListKey, lpBuffer); free(lpBuffer); } //***************************************************************** // // void ChessRoomManager::SendNewPlayerToAll(int intPlayer) // // Parameters: // // intPlayer - the index into m_players of the player to send // // Description: // // This method sends a player to all the clients so they will know // he is in this room. // //***************************************************************** void ChessRoomManager::SendNewPlayerToAll(int intPlayerIndex) { char szMessage[128]; strcpy(szMessage, m_players[intPlayerIndex].szHandle); strcat(szMessage, ","); strcat(szMessage, m_players[intPlayerIndex].szRating); strcat(szMessage, ","); strcat(szMessage, m_players[intPlayerIndex].szGamesWon); strcat(szMessage, ","); strcat(szMessage, m_players[intPlayerIndex].szGamesLost); strcat(szMessage, ","); strcat(szMessage, m_players[intPlayerIndex].szGamesDrawn); strcat(szMessage, ","); switch (m_players[intPlayerIndex].intStatus) { case STATUS_CHATTING: strcat(szMessage, "Chatting"); break; case STATUS_PLAYING: strcat(szMessage, "Vs "); strcat(szMessage, m_players[intPlayerIndex].szCurrentOpponent); strcat(szMessage, " ("); strcat(szMessage, m_players[intPlayerIndex].szOpponentRating); strcat(szMessage, ")"); break; case STATUS_VIEWING: strcat(szMessage, "Viewing"); break; default: strcat(szMessage, "Error"); break; } strcat(szMessage, ","); int intSize = m_players.size(); for (int i = 0; i < intSize; ++i) { // send to everyone but the new player himself and computer players if (i != intPlayerIndex && !m_players[i].bIsComputer) { SendMessageToClient(m_players[i].intSocket, cszServerNewPlayerKey, szMessage); } } } //***************************************************************** // // void ChessRoomManager::RemovePlayer(int intPlayerIndex) // // Parameters: // // intPlayerIndex - index into the m_players array // // Description: // // This method closes the players socket connection and // erases the player from the m_player vector. It also // removes any open challenges connected with the player. // //***************************************************************** void ChessRoomManager::RemovePlayer(int intPlayerIndex) { // first get the handle of the player (if we have a handle) char szHandle[22]; if (strlen(m_players[intPlayerIndex].szHandle)) { strcpy(szHandle, m_players[intPlayerIndex].szHandle); } else { szHandle[0] = '\0'; } // close and remove the player's socket from the active set close(m_players[intPlayerIndex].intSocket); FD_CLR(m_players[intPlayerIndex].intSocket, &m_activeSockets); // set the player's socket to null so we don't write to it // when removing any open challenges m_players[intPlayerIndex].intSocket = 0; // remove the player's open "TO" challenges from other player's FROM vectors RemovePlayersChallenges(intPlayerIndex); // clear the player's challenge vectors m_players[intPlayerIndex].vChallengesTo.clear(); m_players[intPlayerIndex].vChallengesFrom.clear(); // remove the player form the m_players vector m_players.erase(m_players.begin() + intPlayerIndex); // Tell the other players in this room the the given player has left if (strlen(szHandle)) { SendMessageToAllClients(cszServerRemovePlayerKey, szHandle); } } //***************************************************************** // // void ChessRoomManager::RemoveChallenge(int intChallengePlayer, int intChallengingPlayer) // // Parameters: // // intChallengedPlayer - index of the player challenged // intChallengingPlayer - index of the challenging player // // Description: // // This method removes the challenge from the challenger's TO vector // and the challenged players FROM vector. It will also send a message // to the two clients letting them know the challenge was removed. // //***************************************************************** void ChessRoomManager::RemoveChallenge(int intChallengedIndex, int intChallengerIndex) { // remove the challenge from the challenged player's From vector first int intSize = m_players[intChallengedIndex].vChallengesFrom.size(); for (int i = 0; i < intSize; ++i) { if (!strcmp(m_players[intChallengedIndex].vChallengesFrom[i].szHandle, m_players[intChallengerIndex].szHandle)) { // remove the challenge from the vector m_players[intChallengedIndex].vChallengesFrom.erase(m_players[intChallengedIndex].vChallengesFrom.begin() + i); // send a message to the both challenger and the challenged player // to they can both update their status if (!m_players[intChallengedIndex].bIsComputer) { char szMessage[128]; strcpy(szMessage, "FROM,"); strcat(szMessage, m_players[intChallengerIndex].szHandle); SendMessageToClient(m_players[intChallengedIndex].intSocket, cszServerChallengeRemovedKey, szMessage); } break; } } // now remove the challenge from the challenger's TO vector intSize = m_players[intChallengerIndex].vChallengesTo.size(); for (int i = 0; i < intSize; ++i) { if (!strcmp(m_players[intChallengerIndex].vChallengesTo[i].szHandle, m_players[intChallengedIndex].szHandle)) { // remove the challenge from the vector m_players[intChallengerIndex].vChallengesTo.erase(m_players[intChallengerIndex].vChallengesTo.begin() + i); // send a status message to the challenger if (!m_players[intChallengerIndex].bIsComputer) { char szMessage[128]; strcpy(szMessage, "TO,"); strcat(szMessage, m_players[intChallengedIndex].szHandle); SendMessageToClient(m_players[intChallengerIndex].intSocket, cszServerChallengeRemovedKey, szMessage); } break; } } } //***************************************************************** // // void ChessRoomManager::RemovePlayersChallenges(int intPlayerIndex) // // Parameters: // // intPlayerIndex - index of the player // // Description: // // This method removes open challenges connected with the // given player // //***************************************************************** void ChessRoomManager::RemovePlayersChallenges(int intPlayerIndex) { // remove the player's open "TO" challenges from other player's FROM vectors int intSize; while (intSize = m_players[intPlayerIndex].vChallengesTo.size()) { int intChallengedIndex = GetPlayerIndex(m_players[intPlayerIndex].vChallengesTo[0].szHandle); if (intChallengedIndex == -1) { fprintf(stderr, "RemovePlayersChallenges Error: cannot get index for challenged player: %s\n", m_players[intPlayerIndex].vChallengesTo[0].szHandle); return; } else { RemoveChallenge(intChallengedIndex, intPlayerIndex); // the TO vector should be one less now - error out if not if (m_players[intPlayerIndex].vChallengesTo.size() >= intSize) { fprintf(stderr, "RemovePlayersChallenges Error: RemoveChallenge failed to reduce TO vector\n"); return; } } } // remove the player's open "FROM" challenges from other player's TO vectors while (intSize = m_players[intPlayerIndex].vChallengesFrom.size()) { int intChallengerIndex = GetPlayerIndex(m_players[intPlayerIndex].vChallengesFrom[0].szHandle); if (intChallengerIndex == -1) { fprintf(stderr, "RemovePlayersChallenges Error: cannot get index for challenging player: %s\n", m_players[intPlayerIndex].vChallengesFrom[0].szHandle); } else { RemoveChallenge(intPlayerIndex, intChallengerIndex); // the FROM vector should be one less now - error out if not if (m_players[intPlayerIndex].vChallengesFrom.size() >= intSize) { fprintf(stderr, "RemovePlayersChallenges Error: RemoveChallenge failed to reduce FROM vector\n"); return; } } } } //***************************************************************** // // int ChessRoomManager::GetPlayerIndex(char *lpszHandle); // // Parameters: // // lpszHandle - The player's handle // // Description: // // This method returns the given players's index in the // global m_players vector // // Returns: // // The player's index if found, -1 otherwise // //***************************************************************** int ChessRoomManager::GetPlayerIndex(char *lpszHandle) { int intSize = m_players.size(); for (int i = 0; i < intSize; ++i) { if (!strcmp(m_players[i].szHandle, lpszHandle)) { // found the player's index return i; } } // Failed to find the player's index return -1; } //***************************************************************** // // int ChessRoomManager::TimeKeeper(); // // Parameters: // // none // // Description: // // This method is the time keeper for all chess games that runs // in its own process. Note the following: // // - communicate with games via m_TimeKeeperToAppPipe and // m_AppToTimeKeeperPipe. // // - When the App exits, send "~quit" to this Time keeper to exit // its process. // // Returns: // // nothing // //***************************************************************** void ChessRoomManager::TimeKeeper() { // prepare our fd_sets for select() fd_set activeSockets, readSockets; FD_ZERO(&activeSockets); FD_SET(m_AppToTimeKeeperPipe[0], &activeSockets); struct timeval stTimeOut; char receiveBuffer[1024]; for (;;) { // block for half a second - save CPU cycles stTimeOut.tv_sec = 0; stTimeOut.tv_usec = 500000; readSockets = activeSockets; if (select(FD_SETSIZE, &readSockets, NULL, NULL, &stTimeOut) == -1) { perror("TimeKeeper select"); exit(EXIT_FAILURE); } if (FD_ISSET(m_AppToTimeKeeperPipe[0], &readSockets)) { int intBytesRead = read(m_AppToTimeKeeperPipe[0], receiveBuffer, sizeof(receiveBuffer)); receiveBuffer[intBytesRead] = '\0'; if (strstr(receiveBuffer, "~quit")) { fprintf(stderr, "TimeKeeper Exiting\n"); exit(EXIT_SUCCESS); } printf("We just read %s\n", receiveBuffer); // reset the "intSecondsLeft" fields here //time_t startTime = time(NULL); } // check for expired timers and notify gameClients } } void makeDaemon() { // make ourself a deamon // most of these deamon comments are take from: // // http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 // fork() so the parent can exit, this returns control to the command line // or shell invoking your program. This step is required so that the new // process is guaranteed not to be a process group leader. The next step, setsid(), // fails if you're a process group leader. switch (int pid = fork()) { case -1: perror("fork"); exit(EXIT_FAILURE); case 0: // child break; default: exit(EXIT_SUCCESS); // parent } // setsid() to become a process group and session group leader. Since // a controlling terminal is associated with a session, and this new // session has not yet acquired a controlling terminal our process now // has no controlling terminal, which is a Good Thing for daemons. if (setsid() == -1) { perror("setid"); exit(EXIT_FAILURE); } // fork() again so the parent, (the session group leader), can exit. // This means that we, as a non-session group leader, can never regain a // controlling terminal. switch (int pid = fork()) { case -1: perror("fork"); exit(EXIT_FAILURE); case 0: // child break; default: exit(EXIT_SUCCESS); // parent } // chdir("/") to ensure that our process doesn't keep any directory in use. // Failure to do this could make it so that an administrator couldn't // unmount a filesystem, because it was our current directory. [Equivalently, // we could change to any directory containing files important to the daemon's // operation.] chdir("/"); // umask(0) so that we have complete control over the permissions of anything // we write. We don't know what umask we may have inherited. [This step is // optional] umask(0); // close() fds 0, 1, and 2. This releases the standard in, out, and error // we inherited from our parent process. We have no way of knowing where // these fds might have been redirected to. Note that many daemons use // sysconf() to determine the limit _SC_OPEN_MAX. _SC_OPEN_MAX tells you // the maximun open files/process. Then in a loop, the daemon can close all // possible file descriptors. You have to decide if you need to do this or // not. If you think that there might be file-descriptors open you should // close them, since there's a limit on number of concurrent file descriptors. for (int fd = 0; fd < sysconf(_SC_OPEN_MAX); ++fd) { close(fd); } // Establish new open descriptors for stdin, stdout and stderr. Even if // you don't plan to use them, it is still a good idea to have them open. // The precise handling of these is a matter of taste; if you have // a logfile, for example, you might wish to open it as stdout or stderr, // and open `/dev/null' as stdin; alternatively, you could open // `/dev/console' as stderr and/or stdout, and `/dev/null' as stdin, or // any other combination that makes sense for your particular daemon. int fd = open("/dev/null", O_RDWR); dup2(fd, 0); dup2(fd, 1); } void terminationHandler(int intSignal) { bTerminateManager = TRUE; } void main() { // first we'll make ourself a deamon //makeDaemon(); // we'll catch SIGTERM so we can clean up properly on termination signal(SIGTERM, terminationHandler); signal(SIGINT, terminationHandler); // stderr will write to our log file //stderr = fopen("/var/log/ChessRoomManger.log", "w"); // now start the chess room manager fprintf(stderr, "Starting ChessRoomManager...\n"); ChessRoomManager *chessRoomManager = new ChessRoomManager(5555); // the program continuously loops in the chessRoomManager and // only gets to this point after recieving a SIGTERM signal fprintf(stderr, "Stopping ChessRoomManager...\n"); delete chessRoomManager; fclose(stderr); }