C-Multiplayer-Game-Server / Multiplayer Game Server GH / battle.c
battle.c
Raw
/*
 * 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 = &top; *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);
    }
}