#include #include #include #include #include #include #include #include #include #include #include // GAME STATS CONSTANTS #define MIN_HP 20 #define MAX_HP 30 #define MIN_PM 1 #define MAX_PM 3 #define MIN_DMG 2 #define MAX_DMG 6 #ifndef PORT #define PORT 52934 #endif #define SECONDS 10 #define MAX_NAME 20 #define MAX_SPEAK 256 // linked list struct client { int fd; struct in_addr ipaddr; struct client *next; struct client *matched; // pointer to another client that this client most recently matched with char name[MAX_NAME]; int hp; // 20-30 int pm; // 50% to hit; 3x dmg int in_match; int shield; int shielded; int health_potion; }; static struct client *addclient(struct client *top, int fd, struct in_addr addr, char name[MAX_NAME]); static struct client *removeclient(struct client *top, int fd); static void broadcast(struct client *top, char *s, int size); static struct client **matchclient(struct client *head); int handleclient(struct client *p, struct client *top); static struct client *moveclienttoend(struct client **top, int fd); static void roundstartroutine(struct client *player, struct client *opponent); static void commandmenuroutine(struct client *active_player); char *process_move(struct client *player); int bindandlisten(void); int main() { int clientfd, maxfd, nready; struct client *p; struct client *head = NULL; socklen_t len; struct sockaddr_in q; struct timeval tv; fd_set allset; fd_set rset; int i; time_t t; // using srand() for randomization srand((unsigned) time(&t)); // creates the socket, binds and listens as the server socket should do int listenfd = bindandlisten(); // initialize allset and add listenfd to the // set of file descriptors passed into select FD_ZERO(&allset); FD_SET(listenfd, &allset); // we want to know the max file descriptor number for use in select() maxfd = listenfd; while (1) { // make a copy of the set before we pass it into select rset = allset; // timeout tv.tv_sec = SECONDS; tv.tv_usec = 0; /* and microseconds */ nready = select(maxfd + 1, &rset, NULL, NULL, &tv); if (nready == 0) { printf("No response from clients in %d seconds\n", SECONDS); continue; } if (nready == -1) { perror("select"); continue; } // does accept sys call as a server socket should do if (FD_ISSET(listenfd, &rset)) { printf("a new client is connecting\n"); len = sizeof(q); // clientfd is the file descriptor representing the client socket connected to if ((clientfd = accept(listenfd, (struct sockaddr *)&q, &len)) < 0) { perror("accept"); exit(1); } // adds clientfd to the allset file descriptor set FD_SET(clientfd, &allset); if (clientfd > maxfd) { maxfd = clientfd; } printf("connection from %s\n", inet_ntoa(q.sin_addr)); #pragma region ASK NEW CLIENT FOR NAME char input_name[MAX_NAME]; char big_name[MAX_SPEAK]; char *join_msg = "What is your name?"; write(clientfd, join_msg, strlen(join_msg)); int num_chars = read(clientfd, big_name, MAX_SPEAK); input_name[num_chars] = '\0'; while (strstr(big_name, "\r\n") == NULL) { num_chars += read(clientfd, &big_name[num_chars], MAX_SPEAK - num_chars); big_name[num_chars] = '\0'; } big_name[num_chars - 2] = '\0'; memcpy(input_name, big_name, MAX_NAME - 1); #pragma endregion // copy of all clients before adding new one struct client *head_copy = head; // head is the previous head of the linked list we can traverse through, returns newest head head = addclient(head, clientfd, q.sin_addr, input_name); #pragma region SEND MESSAGE TO CLIENT WHEN NAME IS GIVEN char success_msg[MAX_SPEAK]; sprintf(success_msg, "Welcome, %s! Awaiting opponent...\r\n", input_name); write(clientfd, success_msg, strlen(success_msg)); #pragma endregion #pragma region SEND LOGIN MESSAGE TO ALL OTHER CLIENTS if (head_copy == NULL) { // head doesn't have anything prior to first client continue; } // traversing linked list to send message to all clients excluding recent login else if (head_copy->fd != head->fd) { while (head_copy != NULL) { char all_join_msg[MAX_SPEAK]; sprintf(all_join_msg, "**%s enters the arena**\r\n", input_name); write(head_copy->fd, all_join_msg, strlen(all_join_msg)); head_copy = head_copy->next; } } } #pragma endregion /* find two clients that can be matched to each other, otherwise if there are no * suitable players, the pointers are null */ struct client **matchingClients = matchclient(head); // main single-match logic if (matchingClients[0] != NULL && matchingClients[1] != NULL) { #pragma region PRINT ENGAGE MESSAGE TO MATCHED CLIENTS // these clients are now matched with each other matchingClients[0]->matched = matchingClients[1]; matchingClients[1]->matched = matchingClients[0]; matchingClients[0]->in_match = 1; matchingClients[1]->in_match = 1; char engage1[MAX_SPEAK]; sprintf(engage1, "You engage %s!\r\n", matchingClients[0]->name); write(matchingClients[1]->fd, engage1, strlen(engage1)); char engage2[MAX_SPEAK]; sprintf(engage2, "You engage %s!\r\n", matchingClients[1]->name); write(matchingClients[0]->fd, engage2, strlen(engage2)); #pragma endregion #pragma region USE RAND() TO DECIDE PLAYER HP, POWERMOVES, FIRST ACTIVE PLAYER, SHIELD, HEALTH int hp_player_1 = rand() % 11 + MIN_HP; int hp_player_2 = rand() % 11 + MIN_HP; matchingClients[0]->hp = hp_player_1; matchingClients[1]->hp = hp_player_2; int pm_player_1 = rand() % 3 + MIN_PM; int pm_player_2 = rand() % 3 + MIN_PM; matchingClients[0]->pm = pm_player_1; matchingClients[1]->pm = pm_player_2; int shield_player_1 = rand() % 3 + MIN_PM; int shield_player_2 = rand() % 3 + MIN_PM; matchingClients[0]->shield = shield_player_1; matchingClients[1]->shield = shield_player_2; int health_player_1 = rand() % 3 + MIN_PM; int health_player_2 = rand() % 3 + MIN_PM; matchingClients[0]->health_potion = health_player_1; matchingClients[1]->health_potion = health_player_2; matchingClients[0] ->shielded = 0; matchingClients[1] ->shielded = 0; int choose_active_player = rand() % 2; // active_player stores the fd of the current attacking player, which starts randomized int active_player; if (choose_active_player == 0) { active_player = matchingClients[0]->fd; } else { active_player = matchingClients[1]->fd; } #pragma endregion #pragma region ACTUAL GAME LOGIC // stores info about client drops before the client can be removed from linked list int p0_drop = 0; char p0_name[MAX_NAME]; strncpy(p0_name, matchingClients[0]->name, MAX_NAME); int p1_drop = 0; char p1_name[MAX_NAME]; strncpy(p1_name, matchingClients[1]->name, MAX_NAME); // while both players are alive while ( (matchingClients[0]->hp > 0) && (matchingClients[1]->hp > 0) ) { // prints round start player info to a player; hp, pms, shields, heals and opponent hp roundstartroutine(matchingClients[0], matchingClients[1]); roundstartroutine(matchingClients[1], matchingClients[0]); // logic for the current attacking player if (active_player == matchingClients[0]->fd) { char strike_wait[MAX_SPEAK]; sprintf(strike_wait, "Waiting for %s to strike...\r\n", matchingClients[0]->name); write(matchingClients[1]->fd, strike_wait, strlen(strike_wait)); // prints a menu of valid commands that the active player can make commandmenuroutine(matchingClients[0]); // processes the move that the active player makes char *result = process_move(matchingClients[0]); // if client has dropped if ( (strcmp(result, "d") == 0) ) { p0_drop = 1; int tmp_fd = matchingClients[0]->fd; head = removeclient(head, matchingClients[0]->fd); FD_CLR(tmp_fd, &allset); close(tmp_fd); break; } // if player gave an invalid move 'f' or chose to speak/shield do not end round while ( (strcmp(result, "f") == 0) || (strcmp(result, "s") == 0) || (strcmp(result, "x") == 0) ) { // if they spoke or shielded we need to print routines again if ( (strcmp(result, "s") == 0) || (strcmp(result, "x") == 0) ) { roundstartroutine(matchingClients[0], matchingClients[1]); roundstartroutine(matchingClients[1], matchingClients[0]); write(matchingClients[1]->fd, strike_wait, strlen(strike_wait)); commandmenuroutine(matchingClients[0]); } result = process_move(matchingClients[0]); } // switch the active player for next round active_player = matchingClients[1]->fd; // repeat code for current attacking player } else { char strike_wait[MAX_SPEAK]; sprintf(strike_wait, "Waiting for %s to strike...\r\n", matchingClients[1]->name); write(matchingClients[0]->fd, strike_wait, strlen(strike_wait)); // prints a menu of valid commands that the active player can make commandmenuroutine(matchingClients[1]); // processes the move that the active player makes char *result = process_move(matchingClients[1]); // if client has dropped if ( (strcmp(result, "d") == 0) ) { p1_drop = 1; int tmp_fd = matchingClients[1]->fd; head = removeclient(head, matchingClients[1]->fd); FD_CLR(tmp_fd, &allset); close(tmp_fd); break; } // if player gave an invalid move 'f' or chose to speak/shield do not end round while ( (strcmp(result, "f") == 0) || (strcmp(result, "s") == 0) || (strcmp(result, "x") == 0) ) { // if they spoke or shielded we need to print routines again if ( (strcmp(result, "s") == 0) || (strcmp(result, "x") == 0) ) { roundstartroutine(matchingClients[1], matchingClients[0]); roundstartroutine(matchingClients[0], matchingClients[1]); write(matchingClients[0]->fd, strike_wait, strlen(strike_wait)); commandmenuroutine(matchingClients[1]); } result = process_move(matchingClients[1]); } // switch the active player for next round active_player = matchingClients[0]->fd; } } #pragma endregion #pragma region A PLAYER HAS WON if ( (matchingClients[0]->hp > 0) && p0_drop == 0 && p1_drop == 0) { char victory[MAX_SPEAK]; sprintf(victory, "%s gives up. You win!\r\n\n", matchingClients[1]->name); write(matchingClients[0]->fd, victory, strlen(victory)); char loss[MAX_SPEAK]; sprintf(loss, "You are no match for %s. You scurry away...\r\n\n", matchingClients[0]->name); write(matchingClients[1]->fd, loss, strlen(loss)); char awaiting[MAX_SPEAK]; sprintf(awaiting, "Awaiting next opponent...\r\n"); write(matchingClients[0]->fd, awaiting, strlen(awaiting)); write(matchingClients[1]->fd, awaiting, strlen(awaiting)); // both players no longer in a match matchingClients[0]->in_match = 0; matchingClients[1]->in_match = 0; // move both clients to end of linked list, rematching preference given to winner head = moveclienttoend(&head, matchingClients[0]->fd); head = moveclienttoend(&head, matchingClients[1]->fd); } else if ( (matchingClients[1]->hp > 0) && p0_drop == 0 && p1_drop == 0) { char victory[MAX_SPEAK]; sprintf(victory, "%s gives up. You win!\r\n\n", matchingClients[0]->name); write(matchingClients[1]->fd, victory, strlen(victory)); char loss[MAX_SPEAK]; sprintf(loss, "You are no match for %s. You scurry away...\r\n\n", matchingClients[1]->name); write(matchingClients[0]->fd, loss, strlen(loss)); char awaiting[MAX_SPEAK]; sprintf(awaiting, "Awaiting next opponent...\r\n"); write(matchingClients[0]->fd, awaiting, strlen(awaiting)); write(matchingClients[1]->fd, awaiting, strlen(awaiting)); // both players no longer in a match matchingClients[0]->in_match = 0; matchingClients[1]->in_match = 0; // move both clients to end of linked list, rematching preference given to winner head = moveclienttoend(&head, matchingClients[1]->fd); head = moveclienttoend(&head, matchingClients[0]->fd); } else { // if one of the clients has dropped, game ended unnaturally if (p0_drop == 1) { char dropped[MAX_SPEAK]; sprintf(dropped, "\n--%s dropped. You win!\r\n\n", p0_name); write(matchingClients[1]->fd, dropped, strlen(dropped)); char awaiting[MAX_SPEAK]; sprintf(awaiting, "Awaiting next opponent...\r\n"); write(matchingClients[1]->fd, awaiting, strlen(awaiting)); // broadcast to all players that player dropped char outbuf[512]; printf("Disconnect from %s\n", p0_name); sprintf(outbuf, "**%s leaves**\r\n", p0_name); broadcast(head, outbuf, strlen(outbuf)); } else if (p1_drop == 1) { char dropped[MAX_SPEAK]; sprintf(dropped, "\n--%s dropped. You win!\r\n\n", p1_name); write(matchingClients[0]->fd, dropped, strlen(dropped)); char awaiting[MAX_SPEAK]; sprintf(awaiting, "Awaiting next opponent...\r\n"); write(matchingClients[0]->fd, awaiting, strlen(awaiting)); // broadcast to all players that player dropped char outbuf[512]; printf("Disconnect from %s\n", p1_name); sprintf(outbuf, "**%s leaves**\r\n", p1_name); broadcast(head, outbuf, strlen(outbuf)); } } #pragma endregion } else { continue; } for (i = 0; i <= maxfd; i++) { // if the fd is one of all the file descriptors we've encountered so far if (FD_ISSET(i, &rset)) { // traverse the linked list for (p = head; p != NULL; p = p->next) { // if fd in the linked list is the same as i if (p->fd == i) { // read from the client socket, broadcast messages to all clients int result = handleclient(p, head); if (result == -1) { int tmp_fd = p->fd; head = removeclient(head, p->fd); FD_CLR(tmp_fd, &allset); close(tmp_fd); } break; } } } } } return 0; } int handleclient(struct client *p, struct client *top) { char buf[256]; char outbuf[512]; int len = read(p->fd, buf, sizeof(buf) - 1); if (len > 0) { buf[len] = '\0'; printf("Received %d bytes: %s", len, buf); sprintf(outbuf, "%s says: %s", inet_ntoa(p->ipaddr), buf); broadcast(top, outbuf, strlen(outbuf)); return 0; } else if (len <= 0) { // socket is closed printf("Disconnect from %s\n", inet_ntoa(p->ipaddr)); sprintf(outbuf, "Goodbye %s\r\n", inet_ntoa(p->ipaddr)); broadcast(top, outbuf, strlen(outbuf)); return -1; } return -1; } /* bind and listen, abort on error * returns FD of listening socket */ int bindandlisten(void) { struct sockaddr_in r; int listenfd; if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); exit(1); } int yes = 1; if ((setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))) == -1) { perror("setsockopt"); } memset(&r, '\0', sizeof(r)); r.sin_family = AF_INET; r.sin_addr.s_addr = INADDR_ANY; r.sin_port = htons(PORT); if (bind(listenfd, (struct sockaddr *)&r, sizeof r)) { perror("bind"); exit(1); } if (listen(listenfd, 5)) { perror("listen"); exit(1); } return listenfd; } static struct client *addclient(struct client *top, int fd, struct in_addr addr, char name[MAX_NAME]) { struct client *p = malloc(sizeof(struct client)); if (!p) { perror("malloc"); exit(1); } printf("Adding client %s\n", inet_ntoa(addr)); p->fd = fd; p->ipaddr = addr; p->next = top; strncpy(p->name, name, MAX_NAME); top = p; return top; } static struct client *removeclient(struct client *top, int fd) { struct client **p; for (p = ⊤ *p && (*p)->fd != fd; p = &(*p)->next) ; // Now, p points to (1) top, or (2) a pointer to another client // This avoids a special case for removing the head of the list if (*p) { struct client *t = (*p)->next; printf("Removing client %d %s\n", fd, inet_ntoa((*p)->ipaddr)); free(*p); *p = t; } else { fprintf(stderr, "Trying to remove fd %d, but I don't know about it\n", fd); } return top; } // returns head of linked list static struct client *moveclienttoend(struct client **top, int fd) { struct client *prev = NULL; struct client *current = *top; // traverse to find pos of client with given fd while (current != NULL && current->fd != fd) { prev = current; current = current->next; } // if client not found or client is already at the end, do nothing if (current == NULL || current->next == NULL) { return *top; } // detach client from its current position if (prev != NULL) { prev->next = current->next; } else { *top = current->next; } // traverse to end of the list struct client *last = *top; while (last->next != NULL) { last = last->next; } // attach the target client to the end of the list last->next = current; current->next = NULL; return *top; } struct client **matchclient(struct client *head) { struct client *first = NULL, *second = NULL; //initialize two players struct client *current = head; // struct client *temp = NULL; while (current != NULL) { //find the first client that is not in an active match if (!current->in_match) { first = current; break; // Found first } current = current->next; } current = first; while (current != NULL){ if ( (!current->in_match) && (current->matched != first) && (current->fd != first->fd) ){ second = current; break; //Found opponent for first } current = current->next; } // Allocate memory for the array of pointers to clients struct client** matched_players = malloc(2 * sizeof(struct client)); if (matched_players != NULL) { matched_players[0] = first; matched_players[1] = second; } return matched_players; } // prints round start player info to a player; hp, pms, shields, heals and opponent hp static void roundstartroutine(struct client *player, struct client *opponent) { char hitpoints[MAX_SPEAK]; sprintf(hitpoints, "Your hitpoints: %i\r\n", player->hp); write(player->fd, hitpoints, strlen(hitpoints)); char power_moves[MAX_SPEAK]; sprintf(power_moves, "Your powermoves: %i\r\n\n", player->pm); write(player->fd, power_moves, strlen(power_moves)); char shields[MAX_SPEAK]; sprintf(shields, "Your shields: %i\r\n\n", player->shield); write(player->fd, shields, strlen(shields)); char health[MAX_SPEAK]; sprintf(health, "Your health potions: %i\r\n\n", player->health_potion); write(player->fd, health, strlen(health)); char opp_hitpoints[MAX_SPEAK]; sprintf(opp_hitpoints, "%s's hitpoints: %i\r\n", opponent->name, opponent->hp); write(player->fd, opp_hitpoints, strlen(opp_hitpoints)); } static void commandmenuroutine(struct client *active_player) { char *newline = "\r\n"; write(active_player->fd, newline, strlen(newline)); char *attack = "(a)ttack\r\n"; write(active_player->fd, attack, strlen(attack)); if (active_player->pm > 0) { char *powermoves = "(p)owermove\r\n"; write(active_player->fd, powermoves, strlen(powermoves)); } char *speak = "(s)peak something\r\n"; write(active_player->fd, speak, strlen(speak)); if (active_player->shield > 0 && active_player->shielded == 0) { char *shields = "(x)shield\r\n"; write(active_player->fd, shields, strlen(shields)); } if (active_player->health_potion > 0) { char *health = "(h)ealth pot\r\n"; write(active_player->fd, health, strlen(health)); } } char *process_move(struct client *player) { struct client *opponent = player->matched; int socket = player->fd; // read command char move[MAX_SPEAK]; int num_chars = read(socket, move, MAX_SPEAK); if (num_chars <= 0) { // socket is closed, client dropped return "d"; } move[num_chars] = '\0'; // converts the move char to lowercase if it was given uppercase for (int i = 0; move[i] != '\0'; i++) { move[i] = tolower(move[i]); } // add a check to not even run the p command if the active player doesnt have pm > 0 if (strcmp(move, "p") == 0 && (player->pm > 0) ){ //50% chance to hit the power move int random_number = (rand() % 2) + 1; //power move hit if (random_number == 1 && player->pm >= 1){ if (opponent->shielded == 0) { player->pm -= 1; // int damage = (player->dmg * player->pm); int damage = 3 * ((rand() % 5) + MIN_DMG); opponent->hp = opponent->hp - damage; //write that power move hit char powerhit[MAX_SPEAK]; sprintf(powerhit, "\nYou hit %s for %i damage!\r\n", opponent->name, damage); write(socket, powerhit, strlen(powerhit)); //write that power move hit opponent char powerhit2[MAX_SPEAK]; sprintf(powerhit2, "\n%s powermoves you for %i damage!\r\n", player->name, damage); write(opponent->fd, powerhit2, strlen(powerhit2)); } else { //write that player attack got blocked char attackmiss[MAX_SPEAK]; sprintf(attackmiss, "\n%s used a shield and negated the damage!\r\n", opponent->name); write(socket, attackmiss, strlen(attackmiss)); //write to opponent player attacked and did no damage char attackmiss2[MAX_SPEAK]; sprintf(attackmiss2, "\n%s powermoves you but you block all damage!\r\n", player->name); write(opponent->fd, attackmiss2, strlen(attackmiss2)); opponent->shielded = 0; } } else { //write that the power move failed player->pm -= 1; char powerfail[MAX_SPEAK]; sprintf(powerfail, "\nYou missed!\r\n"); write(socket, powerfail, strlen(powerfail)); char powerfail2[MAX_SPEAK]; sprintf(powerfail2, "%s missed you!\r\n", player->name); write(opponent->fd, powerfail2, strlen(powerfail2)); } return "p"; } else if (strcmp(move, "s") == 0) { char *smsg = "\n\nSpeak: "; write(socket, smsg, strlen(smsg)); //take player message char msg[MAX_SPEAK - 60]; char big_msg[4 * MAX_SPEAK]; int num_chars = read(socket, big_msg, 4 * MAX_SPEAK); if (num_chars <= 0) { // socket is closed, client dropped return "d"; } big_msg[num_chars] = '\0'; // it may take more than one read to get all of the data that was written while(strstr(big_msg, "\r\n") == NULL) { num_chars += read(socket, &big_msg[num_chars], (4 * MAX_SPEAK)-num_chars); big_msg[num_chars]='\0'; } big_msg[num_chars-2] = '\0'; memcpy(msg, big_msg, MAX_SPEAK - 60); char msg2[MAX_SPEAK]; sprintf(msg2, "You speak: %s\r\n\n", msg); write(socket, msg2, strlen(msg2)); //send message to opponent char msg3[MAX_SPEAK]; sprintf(msg3, "%s takes a break to tell you:\n%s\r\n\n", player->name, msg); write(opponent->fd, msg3, strlen(msg3)); return "s"; } else if (strcmp(move, "a") == 0){ if (opponent->shielded == 0) { int damage = (rand() % 5) + MIN_DMG; opponent->hp = opponent->hp - damage; //write that player attacked opponent char attackhit[MAX_SPEAK]; sprintf(attackhit, "\nYou hit %s for %i damage!\r\n", opponent->name, damage); write(socket, attackhit, strlen(attackhit)); //write to opponent player attacked char attackhit2[MAX_SPEAK]; sprintf(attackhit2, "\n%s hits you for %i damage!\r\n", player->name, damage); write(opponent->fd, attackhit2, strlen(attackhit2)); } else { //write that player attack got blocked char attackmiss[MAX_SPEAK]; sprintf(attackmiss, "\n%s used a shield and negated the damage!\r\n", opponent->name); write(socket, attackmiss, strlen(attackmiss)); //write to opponent player attacked and did no damage char attackmiss2[MAX_SPEAK]; sprintf(attackmiss2, "\n%s hits you but you block all damage!\r\n", player->name); write(opponent->fd, attackmiss2, strlen(attackmiss2)); opponent->shielded = 0; } return "a"; } else if (strcmp(move, "x") == 0 && player->shield > 0 && player->shielded == 0) { player->shield -= 1; player->shielded = 1; char *xmsg = "\n\nShield has been activated.\n"; write(socket, xmsg, strlen(xmsg)); return "x"; } else if (strcmp(move, "h") == 0 && player-> health_potion > 0) { player->health_potion -= 1; int hp = (rand() % 5) + MIN_DMG; player->hp += hp; char healmsg[MAX_SPEAK]; sprintf(healmsg, "\nYou healed for %i hp!\r\n", hp); write(player->fd, healmsg, strlen(healmsg)); char healmsg2[MAX_SPEAK]; sprintf(healmsg2, "\n%s healed for %i hp!\r\n", player->name, hp); write(opponent->fd, healmsg2, strlen(healmsg2)); return "h"; } else { // player inputted invalid move return "f"; } } static void broadcast(struct client *top, char *s, int size) { struct client *p; for (p = top; p; p = p->next) { write(p->fd, s, size); } /* should probably check write() return value and perhaps remove client */ } // finished