/* * a multiplayer battle game server program running on a private port (52934). * Client interface is handled by the Netcat networking utility */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #include <arpa/inet.h> #include <time.h> #include <ctype.h> // game stats constants #define MIN_HP 20 #define MIN_PM 1 #define MIN_DMG 2 // port number #ifndef PORT #define PORT 52934 #endif #define SECONDS 10 #define MAX_NAME 20 #define MAX_SPEAK 256 /* client information, including internet address, is stored in a linked list of structs * each struct member holds a reference to the next client, as well as game stats info */ struct client { int fd; // client socket file descriptor 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; }; // function prototypes 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 struct client **matchclient(struct client *head); static struct client *moveclienttoend(struct client **top, int fd); static void broadcast(struct client *top, char *s, int size); static void roundstartroutine(struct client *player, struct client *opponent); static void commandmenuroutine(struct client *active_player); char *process_move(struct client *player); int handleclient(struct client *p, struct client *top); 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; // sets to store file descriptors fd_set allset; fd_set rset; int i; time_t t; // seeding srand() for randomization srand((unsigned) time(&t)); // creates, binds and listens to client socket file descriptor 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; // blocks the server while waiting for information from clients 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; } // use the accept system call once a connecting client is detected if (FD_ISSET(listenfd, &rset)) { // print statements are visible to the server host 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 USERNAME char input_name[MAX_NAME]; char big_name[MAX_SPEAK]; char *join_msg = "What is your name?"; write(clientfd, join_msg, strlen(join_msg)); // collect client login username, store in 'big_name' int num_chars = read(clientfd, big_name, MAX_SPEAK); input_name[num_chars] = '\0'; // null terminate username 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 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 WELCOME MESSAGE TO CLIENT WHEN USERNAME 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 NOTIFY ALL OTHER CLIENTS OF NEWCOMER if (head_copy == NULL) { // head does not 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 'matchingClients' pointers are null */ struct client **matchingClients = matchclient(head); // this if block contains the main single-match logic and starts a match 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; 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 clients names beforehand in case they drop 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, the match continues 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 and stores it in 'result' char *result = process_move(matchingClients[0]); // if client has dropped, indicated by 'd' 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]); } // make the client input another move 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 and stores it in 'result' char *result = process_move(matchingClients[1]); // if client has dropped, indicated by 'd' 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]); } // make the client input another move result = process_move(matchingClients[1]); } // switch the active player for next round active_player = matchingClients[0]->fd; } } #pragma endregion #pragma region PRINT APPROPRIATE MESSAGES WHEN 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 } // a match is not ready yet else { continue; } // prints server diagnostics 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) { int result = handleclient(p, head); // if a client has dropped if (result == -1) { int tmp_fd = p->fd; head = removeclient(head, p->fd); FD_CLR(tmp_fd, &allset); close(tmp_fd); } break; } } } } } return 0; } /* * outputs client information to the server host */ 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 to a client socket while aborting 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; // setsockopt() releases the server port immediately after the server terminates 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); } // listenfd now listens for incoming connections from clients if (listen(listenfd, 5)) { perror("listen"); exit(1); } return listenfd; } /* * adds a new client with file descriptor 'fd' to the client linked list */ static struct client *addclient(struct client *top, int fd, struct in_addr addr, char name[MAX_NAME]) { // allocate memory for the new client struct client *p = malloc(sizeof(struct client)); if (!p) { perror("malloc"); exit(1); } // note to server 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; } /* * remove a client with file descriptor 'fd' from the client linked list */ static struct client *removeclient(struct client *top, int fd) { struct client **p; for (p = ⊤ *p && (*p)->fd != fd; p = &(*p)->next); // traverse to where p points to (1) top, or (2) a pointer to another client // this avoids an edge 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, "Error encountered trying to remove fd %d\n", fd); } return top; } /* * moves a client to the end of the linked list, returns head of the linked list */ static struct client *moveclienttoend(struct client **top, int fd) { struct client *prev = NULL; struct client *current = *top; // traverse to find position 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; } // else 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; } /* * returns a list of two viable clients who can be matched with one another in a game */ struct client **matchclient(struct client *head) { struct client *first = NULL, *second = NULL; struct client *current = head; while (current != NULL) { // find the first available player that is not in an active match if (!current->in_match) { first = current; break; // found first } current = current->next; } // traverse after first current = first; while (current != NULL) { // avoid matching players who just played one another 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 list 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)); } /* * print available commands that a player can make at any given moment */ 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 the player has powermoves left 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 the player has shields left and is not shielded if (active_player->shield > 0 && active_player->shielded == 0) { char *shields = "(x)shield\r\n"; write(active_player->fd, shields, strlen(shields)); } // if the player has heals left if (active_player->health_potion > 0) { char *health = "(h)ealth pot\r\n"; write(active_player->fd, health, strlen(health)); } } /* * executes game logic in response to player commands, return the command a player inputted */ char *process_move(struct client *player) { struct client *opponent = player->matched; int socket = player->fd; // read player command char move[MAX_SPEAK]; int num_chars = read(socket, move, MAX_SPEAK); if (num_chars <= 0) { // socket is closed, client dropped return "d"; } // null terminate the command 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]); } /* player chose to use a power move * does not run the power move command if player does not have any remaining pms */ if (strcmp(move, "p") == 0 && (player->pm > 0) ) { // 50% chance to hit the power move int random_number = (rand() % 2) + 1; // if the power move hit if (random_number == 1 && player->pm >= 1){ // if the opponent was not shielded if (opponent->shielded == 0) { player->pm -= 1; int damage = 3 * ((rand() % 5) + MIN_DMG); opponent->hp = opponent->hp - damage; char powerhit[MAX_SPEAK]; sprintf(powerhit, "\nYou hit %s for %i damage!\r\n", opponent->name, damage); write(socket, powerhit, strlen(powerhit)); 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 { // opponent was shielded char attackmiss[MAX_SPEAK]; sprintf(attackmiss, "\n%s used a shield and negated the damage!\r\n", opponent->name); write(socket, attackmiss, strlen(attackmiss)); 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 lost their shield opponent->shielded = 0; } } else { // 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"; } // player chose to speak 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"; } // player chose to basic attack else if (strcmp(move, "a") == 0) { // opponent was not shielded if (opponent->shielded == 0) { int damage = (rand() % 5) + MIN_DMG; opponent->hp = opponent->hp - damage; char attackhit[MAX_SPEAK]; sprintf(attackhit, "\nYou hit %s for %i damage!\r\n", opponent->name, damage); write(socket, attackhit, strlen(attackhit)); 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 { // opponent was shielded char attackmiss[MAX_SPEAK]; sprintf(attackmiss, "\n%s used a shield and negated the damage!\r\n", opponent->name); write(socket, attackmiss, strlen(attackmiss)); 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 lost their shield opponent->shielded = 0; } return "a"; } // player chose to shield 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"; } // player chose to heal 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"; } // player inputted invalid move else { return "f"; } } /* * send a message to all clients in the server */ 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); } }