among-sus/main.c

1122 lines
30 KiB
C

#define _POSIX_C_SOURCE 200112L
#define _XOPEN_SOURCE 700
#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#define NUM_PLAYERS 10
#define NUM_SHORT 6
#define NUM_LONG 2
#define NUM_CHATS 50
#define MIN_NAME 2
#define MAX_NAME 10
enum game_stage {
STAGE_LOBBY,
STAGE_PLAYING,
STAGE_DISCUSS,
};
enum player_stage {
PLAYER_STAGE_NAME,
PLAYER_STAGE_LOBBY,
PLAYER_STAGE_MAIN,
PLAYER_STAGE_DISCUSS,
};
enum player_task_short {
TASK_CAFE_TRASH,
TASK_CAFE_COFFEE,
TASK_CAFE_WIRES,
TASK_STORAGE_TRASH,
TASK_ELECTRICAL_WIRES,
TASK_ELECTRICAL_BREAKERS,
TASK_ADMIN_WIRES,
TASK_NAVIGATION_WIRES,
TASK_WEAPONS_WIRES,
TASK_SHIELDS_WIRES,
TASK_O2_WIRES,
TASK_O2_CLEAN,
TASK_MEDBAY_WIRES,
TASK_UPPER_CATALYZER,
TASK_LOWER_CATALYZER,
TASK_UPPER_COMPRESSION_COIL,
TASK_LOWER_COMPRESSION_COIL,
TASK_SHORT_COUNT,
};
const char short_task_descriptions[][45] = {
"Empty the cafeteria trash",
"Start the coffee maker in the cafeteria",
"Fix wiring in cafeteria",
"Empty the storage trash chute",
"Fix wiring in electrical",
"Reset breakers in electrical",
"Fix wiring in admin",
"Fix wiring in navigation",
"Fix wiring in weapons",
"Fix wiring in shields",
"Fix wiring in o2",
"Clean oxygenator output in o2",
"Fix wiring in medbay",
"Check catalyzer in upper engine",
"Check catalyzer in lower engine",
"Replace compression coil in upper engine",
"Replace compression coil in lower engine",
};
enum player_task_long {
TASK_STORAGE_COUNT,
TASK_O2_LOG,
TASK_REACTOR_LOG,
TASK_ADMIN_PIN,
TASK_LONG_COUNT
};
const char long_task_descriptions[][45] = {
"Take count of the boxes in storage",
"Log o2 numbers",
"Log reactor numbers",
"Enter pin at admin",
};
enum player_location {
LOC_CAFETERIA,
LOC_REACTOR,
LOC_UPPER_ENGINE,
LOC_LOWER_ENGINE,
LOC_SECURITY,
LOC_MEDBAY,
LOC_ELECTRICAL,
LOC_STORAGE,
LOC_ADMIN,
LOC_COMMUNICATIONS,
LOC_O2,
LOC_WEAPONS,
LOC_SHIELDS,
LOC_NAVIGATION,
LOC_COUNT,
};
const char locations[][45] = {
[LOC_CAFETERIA] = "cafeteria",
[LOC_REACTOR] = "reactor",
[LOC_UPPER_ENGINE] = "upper",
[LOC_LOWER_ENGINE] = "lower",
[LOC_SECURITY] = "security",
[LOC_MEDBAY] = "medbay",
[LOC_ELECTRICAL] = "electrical",
[LOC_STORAGE] = "storage",
[LOC_ADMIN] = "admin",
[LOC_COMMUNICATIONS] = "communications",
[LOC_O2] = "o2",
[LOC_WEAPONS] = "weapons",
[LOC_SHIELDS] = "shields",
[LOC_NAVIGATION] = "navigation",
};
enum player_location doors[][10] = {
[LOC_CAFETERIA] = { LOC_MEDBAY, LOC_ADMIN, LOC_WEAPONS, -1 },
[LOC_REACTOR] = { LOC_UPPER_ENGINE, LOC_SECURITY, LOC_LOWER_ENGINE, -1 },
[LOC_UPPER_ENGINE] = { LOC_REACTOR, LOC_ELECTRICAL, LOC_MEDBAY, -1 },
[LOC_LOWER_ENGINE] = { LOC_REACTOR, LOC_ELECTRICAL, -1 },
[LOC_SECURITY] = { LOC_UPPER_ENGINE, LOC_REACTOR, LOC_LOWER_ENGINE, -1 },
[LOC_MEDBAY] = { LOC_UPPER_ENGINE, LOC_CAFETERIA, -1 },
[LOC_ELECTRICAL] = { LOC_LOWER_ENGINE, LOC_STORAGE, -1 },
[LOC_STORAGE] = { LOC_ELECTRICAL, LOC_ADMIN, LOC_COMMUNICATIONS, LOC_SHIELDS, -1 },
[LOC_ADMIN] = { LOC_CAFETERIA, LOC_STORAGE, -1 },
[LOC_COMMUNICATIONS] = { LOC_STORAGE, LOC_SHIELDS, -1 },
[LOC_O2] = { LOC_SHIELDS, LOC_WEAPONS, LOC_NAVIGATION, -1 },
[LOC_WEAPONS] = { LOC_CAFETERIA, LOC_O2, LOC_NAVIGATION, -1 },
[LOC_SHIELDS] = { LOC_STORAGE, LOC_COMMUNICATIONS, LOC_O2, LOC_NAVIGATION, -1 },
[LOC_NAVIGATION] = { LOC_WEAPONS, LOC_O2, LOC_SHIELDS, -1 },
};
const char descriptions[][256] = {
[LOC_CAFETERIA] = "You are standing in the middle of the cafeteria, in the center there's an emergency button\n",
[LOC_REACTOR] = "You are in the reactor room, it seems to be running normally\n",
[LOC_UPPER_ENGINE] = "You are in a small room, mostly filled up by an engine.\n",
[LOC_LOWER_ENGINE] = "You are in a small room, mostly filled up by an engine.\n",
[LOC_SECURITY] = "You are in a small room filled with monitors, the monitors are displaying camera images showing an overview of the ship\n",
[LOC_MEDBAY] = "You are in a room with beds and a medical scanner.\n",
[LOC_ELECTRICAL] = "You are in a room filled with equipment racks. Some of them have wires sticking out of them\n",
[LOC_STORAGE] = "You are in a large room filled with boxes. One of the walls has a large door to the outside\n",
[LOC_ADMIN] = "You are in a nice carpeted room with a holographic map in the middle\n",
[LOC_COMMUNICATIONS] = "You are in a small room with what looks like radio equipment\n",
[LOC_O2] = "You are in a room with plants in terrariums and life support equipment\n",
[LOC_WEAPONS] = "You are in a circular room with a targeting system in the middle and a view of outer space\n",
[LOC_SHIELDS] = "You are in a circular room with glowing tubes and a control panel for the shields\n",
[LOC_NAVIGATION] = "You are all the way in the front of the ship in a room with the ship controls and a great view of space\n",
};
const char map[][100] = {
"|\\----------------|--------------|----------------|--------------\\\n",
"| \\\n",
"| UPPER ENGINE CAFETERIA WEAPONS \\\n",
"| |- --------| | \\\n",
"|/--------| |--| MEDBAY | | \\\n",
" | | | | \\------\\\n",
"/---------| |-------\\ | |----------| | \\\n",
"| | | \\ |---| |------| | |\n",
"| \\ | | |\n",
"| REACTOR SECURITY | | ADMIN OFFICE | O2 NAVIGATION |\n",
"| | | | | |\n",
"| | | | |---| |----|-|----------| |\n",
"\\---------| |----------|------| | | /\n",
" | | | /------/\n",
"|\\--------| |--| | /\n",
"| | | |-- --| /\n",
"| LOWER ENGINE ELECTRICAL STORAGE | COMMS | SHIELDS /\n",
"| | | /\n",
"|/----------------|--------------|--------------|--------|-------/\n",
};
struct player {
int fd;
enum player_stage stage;
char name[MAX_NAME + 1];
int is_admin;
int is_imposter;
enum player_location location;
int in_vent;
int is_alive;
int is_found;
int has_cooldown;
int voted;
int votes;
enum player_task_short short_tasks[NUM_SHORT];
int short_tasks_done[NUM_SHORT];
enum player_task_long long_tasks[NUM_LONG];
int long_tasks_done[NUM_LONG];
};
struct gamestate {
enum game_stage stage;
int has_admin;
int players;
int is_reactor_meltdown;
int chats_left;
int skips;
};
struct gamestate state;
struct player players[NUM_PLAYERS];
fd_set rfds, afds;
void
broadcast(char* message, int notfd)
{
char buf[1024];
int pid;
printf("*> %s\n", message);
snprintf(buf, sizeof(buf), "%s\n", message);
for (pid = 0; pid < NUM_PLAYERS; pid++) {
if (players[pid].fd == -1 || players[pid].fd == notfd
|| players[pid].stage == PLAYER_STAGE_NAME)
continue;
write(players[pid].fd, buf, strlen(buf));
}
}
void
player_move(int pid, enum player_location location)
{
char buf[100];
printf("Moving player %d to %d\n", pid, location);
players[pid].location = location;
players[pid].has_cooldown = 0;
// body detection
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].location != players[pid].location || i == pid
|| players[i].fd == -1
|| players[i].is_alive == 1
|| players[i].is_found == 1)
continue;
snprintf(buf, sizeof(buf), "you enter the room and see the body of [%s] laying on the floor\n", players[i].name);
write(players[pid].fd, buf, strlen(buf));
}
}
void
end_game()
{
broadcast("------------------------", -1);
broadcast("The game has ended, returning to lobby", -1);
state.stage = STAGE_LOBBY;
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].fd == -1)
continue;
players[i].stage = PLAYER_STAGE_LOBBY;
}
}
void
check_win_condition(void)
{
char buf[100];
int alive = 0, iid = -1, tasks = 1;
for (size_t i = 0; i < NUM_PLAYERS; i++) {
if (players[i].fd != -1 && players[i].is_imposter)
iid = i;
if (players[i].is_imposter == 1 && players[i].is_alive == 0) {
broadcast("The crew won", -1);
end_game();
return;
}
if (players[i].fd != -1 &&
players[i].is_alive == 1 &&
players[i].is_imposter == 0)
alive++;
if (players[i].fd != -1 && !players[i].is_imposter
&& players[i].is_alive) {
for (size_t j = 0; j < NUM_SHORT; j++) {
if (!players[i].short_tasks_done[j]) {
tasks = 0;
break;
}
}
for (size_t j = 0; j < NUM_LONG; j++) {
if (!players[i].long_tasks_done[j]) {
tasks = 0;
break;
}
}
}
}
if (tasks == 1) {
broadcast("The crew won", -1);
end_game();
return;
}
if (alive == 1) {
broadcast("The imposter is alone with the last crewmate and murders him", -1);
snprintf(buf, sizeof(buf), "The imposter was [%s] all along...", players[iid].name);
broadcast(buf, -1);
end_game();
}
}
void
task_completed(int pid, int task_id, int long_task)
{
// Mark task completed for player
if (!long_task) {
for(int i=0; i<NUM_SHORT; i++) {
if (players[pid].short_tasks[i] == task_id) {
players[pid].short_tasks_done[i] = 1;
}
}
} else {
for(int i=0; i<NUM_LONG; i++) {
if (players[pid].long_tasks[i] == task_id) {
players[pid].long_tasks_done[i] = 1;
}
}
}
check_win_condition();
}
void
player_list_tasks(int pid)
{
char buf[100];
for(int i=0;i<TASK_SHORT_COUNT;i++){
for(int j=0;j<NUM_SHORT;j++) {
if(players[pid].short_tasks[j] == i) {
const char *cm;
if(players[pid].short_tasks_done[j]) {
cm = "*";
} else {
cm = " ";
}
snprintf(buf, sizeof(buf), " [%s] %s\n", cm,
short_task_descriptions[i]);
write(players[pid].fd, buf, strlen(buf));
}
}
}
for(int i=0;i<TASK_LONG_COUNT;i++){
for(int j=0;j<NUM_LONG;j++) {
if(players[pid].long_tasks[j] == i) {
const char *cm;
if(players[pid].long_tasks_done[j]) {
cm = "*";
} else {
cm = " ";
}
snprintf(buf, sizeof(buf), " [%s] %s\n", cm,
long_task_descriptions[i]);
write(players[pid].fd, buf, strlen(buf));
}
}
}
snprintf(buf, sizeof(buf), "# ");
write(players[pid].fd, buf, strlen(buf));
}
void
player_kill(int pid, int tid)
{
char buf[100];
if(players[pid].location != players[tid].location
|| players[tid].is_imposter)
return;
// so sad
players[tid].is_alive = 0;
// less murdering, reset by movement
players[pid].has_cooldown = 1;
// notify player of their recent death
snprintf(buf, sizeof(buf), "It turns out %s is the imposter, sadly the way you know is that you died.\n",
players[pid].name);
write(players[tid].fd, buf, strlen(buf));
// notify bystanders
for(int i=0; i<NUM_PLAYERS;i++) {
if (i == pid || players[i].fd == -1 || players[i].is_alive == 0
|| players[i].location != players[pid].location)
continue;
snprintf(buf, sizeof(buf), "someone killed [%s] while you were in the room\n",
players[tid].name);
write(players[i].fd, buf, strlen(buf));
}
check_win_condition();
}
void
start_discussion(int pid, int bid)
{
char buf[100];
state.stage = STAGE_DISCUSS;
state.skips = 0;
// switch everyone to the discussion state and mark bodies found
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].fd == -1)
continue;
players[i].stage = PLAYER_STAGE_DISCUSS;
players[i].voted = 0;
players[i].votes = 0;
if (!players[i].is_alive)
players[i].is_found = 1;
}
broadcast("------------------------", -1);
// Inform everyone
if(bid == -1) {
// Emergency button was pressed
snprintf(buf, sizeof(buf), "\nAn emergency meeting was called by [%s]", players[pid].name);
} else {
// Body was reported
snprintf(buf, sizeof(buf), "\nThe body of [%s] was found by [%s]", players[bid].name, players[pid].name);
}
broadcast(buf, -1);
// List the state of the players
broadcast("Players:", -1);
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].fd == -1)
continue;
if (players[i].is_alive) {
snprintf(buf, sizeof(buf), "* %d [%s]", i, players[i].name);
} else {
snprintf(buf, sizeof(buf), "* %d [%s] (dead)", i, players[i].name);
}
broadcast(buf, -1);
}
// Inform people of the chat limit
snprintf(buf, sizeof(buf), "Discuss, there are %d messages left", NUM_CHATS);
state.chats_left = NUM_CHATS;
broadcast(buf, -1);
}
void
back_to_playing()
{
state.stage = STAGE_PLAYING;
// switch everyone to the playing state
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].fd == -1)
continue;
players[i].stage = PLAYER_STAGE_MAIN;
}
broadcast("-- Voting has ended, back to the ship --\n\n# ", -1);
}
void
discussion(int pid, char* input)
{
char buf[300];
int vote = 0, max_votes = 0, tie = 0, winner = -1, crew_alive = 0;
char temp[5];
// TODO: implement broadcast to dead players
if (players[pid].is_alive == 0)
return;
if (input[0] == '/' && input[1] != '/') {
if (strncmp(input, "/vote ", 6) == 0 || strncmp(input, "/skip", 6) == 0) {
if (players[pid].voted) {
snprintf(buf, sizeof(buf), "You can only vote once\n");
write(players[pid].fd, buf, strlen(buf));
return;
}
if (input[1] == 's') {
vote = -1;
} else {
char *endptr = NULL;
strncpy(temp, &input[6], 4);
printf("Decoding '%s' now\n", temp);
vote = strtol(temp, &endptr, 10);
if (!endptr || endptr[0] != '\0') {
snprintf(buf, sizeof(buf), "Invalid vote, not an integer\n");
write(players[pid].fd, buf, strlen(buf));
return;
}
}
if (vote == -1) {
printf("[%s] voted to skip\n", players[pid].name);
} else {
printf("[%s] voted for %d\n", players[pid].name, vote);
}
if(vote < -1 || vote > NUM_PLAYERS-1 || players[vote].fd == -1) {
snprintf(buf, sizeof(buf), "Invalid vote, no such player\n");
write(players[pid].fd, buf, strlen(buf));
return;
}
players[pid].voted = 1;
if (vote == -1) {
state.skips++;
} else {
players[vote].votes++;
}
check_votes:
// Check if voting is complete
for(int i=0;i<NUM_PLAYERS;i++) {
if(players[i].fd != -1 &&
players[i].voted == 0 &&
players[i].is_alive == 1) {
printf("No vote from [%s] yet\n", players[i].name);
goto not_yet;
}
}
printf("Voting complete\n");
// Count votes
max_votes = state.skips;
for(int i=0;i<NUM_PLAYERS;i++) {
if(players[i].fd == -1)
continue;
if(players[i].votes > max_votes){
max_votes = players[i].votes;
tie = 0;
winner = i;
continue;
}
if(players[i].votes == max_votes) {
tie = 1;
}
}
printf("Vote winner: %d\n", winner);
if (tie) {
broadcast("The voting ended in a tie", -1);
back_to_playing();
return;
} else if (winner == -1) {
snprintf(buf, sizeof(buf), "The crew voted to skip\n");
broadcast(buf, -1);
back_to_playing();
return;
} else {
snprintf(buf, sizeof(buf), "The crew voted to eject [%s]\n", players[winner].name);
broadcast(buf, -1);
}
// dramatic pause
for(int i=0;i<5;i++) {
sleep(1);
broadcast(".", -1);
}
players[winner].is_alive = 0;
if (players[winner].is_imposter) {
snprintf(buf, sizeof(buf), "It turns out [%s] was an imposter", players[winner].name);
broadcast(buf, -1);
} else {
snprintf(buf, sizeof(buf), "Sadly, [%s] was not an imposter", players[winner].name);
broadcast(buf, -1);
}
check_win_condition();
back_to_playing();
return;
not_yet:
broadcast("A vote has been cast", -1);
} else if (strncmp(input, "/help", 6) == 0) {
snprintf(buf, sizeof(buf), "Commands: /vote [id], /skip, /list\n");
write(players[pid].fd, buf, strlen(buf));
} else if (strncmp(input, "/list", 6) == 0) {
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].fd == -1)
continue;
if (players[i].is_alive && players[i].voted) {
snprintf(buf, sizeof(buf), "* %d [%s] (voted) \n", i, players[i].name);
} else if (players[i].is_alive) {
snprintf(buf, sizeof(buf), "* %d [%s]\n", i, players[i].name);
} else {
snprintf(buf, sizeof(buf), "* %d [%s] (dead)\n", i, players[i].name);
}
write(players[pid].fd, buf, strlen(buf));
}
snprintf(buf, sizeof(buf), "Commands: /vote [id], /skip, /list\n");
write(players[pid].fd, buf, strlen(buf));
} else if (strncmp(input, "/kick", 5) == 0) {
if (!players[pid].is_admin) {
snprintf(buf, sizeof(buf), "You have no kicking permission\n");
write(players[pid].fd, buf, strlen(buf));
}
char *endptr = NULL;
strncpy(temp, &input[6], 4);
printf("Decoding '%s' now\n", temp);
vote = strtol(temp, &endptr, 10);
if (!endptr || endptr[0] != '\0') {
snprintf(buf, sizeof(buf), "Invalid kick, not an integer\n");
write(players[pid].fd, buf, strlen(buf));
return;
}
snprintf(buf, sizeof(buf), "Admin kicked [%s]\n", players[vote].name);
broadcast(buf, -1);
close(players[vote].fd);
FD_CLR(players[vote].fd, &afds);
players[vote].fd = -1;
players[vote].is_alive = 0;
goto check_votes;
} else {
snprintf(buf, sizeof(buf), "Invalid command\n");
write(players[pid].fd, buf, strlen(buf));
}
} else if (input[0] == '/') {
if (state.chats_left == 0) {
snprintf(buf, sizeof(buf), "No chats left, you can only vote now\n");
write(players[pid].fd, buf, strlen(buf));
return;
}
snprintf(buf, sizeof(buf), "(%d) [%s] %s", state.chats_left, players[pid].name, &input[1]);
broadcast(buf, -1);
state.chats_left--;
} else {
if (state.chats_left == 0) {
snprintf(buf, sizeof(buf), "No chats left, you can only vote now\n");
write(players[pid].fd, buf, strlen(buf));
return;
}
snprintf(buf, sizeof(buf), "(%d) [%s] %s", state.chats_left, players[pid].name, input);
broadcast(buf, -1);
state.chats_left--;
}
}
void
adventure(int pid, char* input)
{
char buf[1024];
const char *location;
int task_id;
int task_is_long;
if (input[0] == 'e' || strncmp(input, "ls", 3) == 0) {
enum player_location loc = players[pid].location;
strcpy(buf, "you can move to: ");
assert(doors[loc][0] != -1);
strncat(buf, locations[doors[loc][0]], sizeof(buf) - 1);
for (size_t i = 1; doors[loc][i] != -1; i++) {
strncat(strncat(buf, ", ", sizeof(buf) - 1),
locations[doors[loc][i]], sizeof(buf) - 1);
}
strncat(buf, "\n", sizeof(buf) - 1);
location = descriptions[loc];
if (loc == LOC_REACTOR && state.is_reactor_meltdown) {
location = "You are in the reactor room, there are red warning lights on and a siren is going off.\n";
}
write(players[pid].fd, location, strlen(location));
write(players[pid].fd, buf, strlen(buf));
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].location != players[pid].location
|| players[i].fd == -1 || i == pid)
continue;
snprintf(buf, sizeof(buf),
"you also see %s in the room with you\n",
players[i].name);
write(players[pid].fd, buf, strlen(buf));
}
snprintf(buf, sizeof(buf), "# ");
} else if (strncmp(input, "go ", 3) == 0 || strncmp(input, "cd ", 3) == 0) {
enum player_location new;
for (new = 0; new < LOC_COUNT; new++) {
if (strcmp(locations[new], &input[3]) == 0) {
break;
}
}
if (new == LOC_COUNT) {
snprintf(buf, sizeof(buf), "INVALID MOVEMENT\n# ");
} else {
for (size_t i = 0; doors[players[pid].location][i] != -1; i++) {
if (doors[players[pid].location][i] == new) {
player_move(pid, new);
snprintf(buf, sizeof(buf),
"successfully moved\n# ");
new = LOC_COUNT;
break;
}
}
if (new != LOC_COUNT) {
snprintf(buf, sizeof(buf), "INVALID MOVEMENT\n# ");
}
}
} else if (strcmp(input, "murder crewmate") == 0) {
if (players[pid].is_imposter == 0) {
snprintf(buf, sizeof(buf), "you might dislike them, but you can't kill them without weapon\n# ");
} else if (players[pid].has_cooldown) {
snprintf(buf, sizeof(buf), "you can't kill that quickly\n# ");
} else {
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].location != players[pid].location
|| i == pid || players[i].fd == -1
|| players[i].is_alive == 0)
continue;
// TODO: kill more randomly
player_kill(pid, i);
snprintf(buf, sizeof(buf), "you draw your weapon and brutally murder %s\n# ",
players[i].name);
break;
}
}
} else if (strcmp(input, "report") == 0) {
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].location != players[pid].location
|| i == pid || players[i].fd == -1
|| players[i].is_alive == 1)
continue;
start_discussion(pid, i);
return;
}
snprintf(buf, sizeof(buf), "Nothing to report here\n# ");
} else if (strcmp(input, "press emergency button") == 0) {
if (players[pid].location != LOC_CAFETERIA) {
snprintf(buf, sizeof(buf), "You can't do that here");
} else {
start_discussion(pid, -1);
return;
}
} else if (strcmp(input, "check tasks") == 0) {
player_list_tasks(pid);
return;
} else if (strcmp(input, "help") == 0) {
snprintf(buf, sizeof(buf), "Commands: help, examine room, go [room], murder crewmate, report, check tasks\n# ");
} else if (strncmp(input, "map", 3) == 0) {
for(int l=0;l<19;l++) {
write(players[pid].fd, map[l], strlen(map[l]));
}
snprintf(buf, sizeof(buf), "# ");
} else {
// check if it was a task
task_id = -1;
task_is_long = 0;
for(int i=0;i<TASK_SHORT_COUNT;i++) {
if(strcmp(input, short_task_descriptions[i]) == 0) {
task_id = i;
break;
}
}
for(int i=0;i<TASK_LONG_COUNT;i++) {
if(strcmp(input, long_task_descriptions[i]) == 0) {
task_id = i;
task_is_long = 1;
break;
}
}
if (task_id == -1) {
snprintf(buf, sizeof(buf), "Invalid instruction\n# ");
} else {
// check it was in the right room
if (strstr(input, locations[players[pid].location]) != NULL) {
task_completed(pid, task_id, task_is_long);
if (state.stage == STAGE_PLAYING) {
snprintf(buf, sizeof(buf), "Completed task\n# ");
} else {
buf[0] = '\0';
}
} else {
snprintf(buf, sizeof(buf), "You're in the wrong place for that\n# ");
}
}
}
write(players[pid].fd, buf, strlen(buf));
}
void
start_game()
{
int imposternum;
int assigned;
char buf[200];
int temp;
broadcast("---------- [ Game is starting ] ----------", -1);
state.stage = STAGE_PLAYING;
state.players = 0;
for(int i=0; i<NUM_PLAYERS; i++) {
if(players[i].fd != -1) {
state.players++;
}
}
// Pick an imposter
imposternum = rand() % state.players;
assigned = 0;
for(int i=0; i<NUM_PLAYERS; i++) {
if(players[i].fd == -1)
continue;
players[i].stage = PLAYER_STAGE_MAIN;
players[i].location = LOC_CAFETERIA;
players[i].is_alive = 1;
// Assign NUM_SHORT random short tasks
for(int j=0;j<NUM_SHORT;j++) {
retry:
temp = rand() % TASK_SHORT_COUNT;
for(int k=0;k<NUM_SHORT;k++) {
if(players[i].short_tasks[k] == temp)
goto retry;
}
players[i].short_tasks[j] = temp;
players[i].short_tasks_done[j] = 0;
}
// Assign NUM_LONG random long tasks
for(int j=0;j<NUM_LONG;j++) {
retry2:
temp = rand() % TASK_LONG_COUNT;
for(int k=0;k<NUM_LONG;k++) {
if(players[i].long_tasks[k] == temp)
goto retry2;
}
players[i].long_tasks[j] = temp;
players[i].long_tasks_done[j] = 0;
}
if (assigned == imposternum) {
players[i].is_imposter = 1;
snprintf(buf, sizeof(buf), "You are the imposter, kill everyone without getting noticed.\n");
} else {
players[i].is_imposter = 0;
snprintf(buf, sizeof(buf), "You are a crewmate, complete your tasks before everyone is killed.\n");
}
write(players[i].fd, buf, strlen(buf));
assigned++;
}
// dramatic pause
for(int i=0;i<5;i++) {
sleep(1);
broadcast(".", -1);
}
for(int i=0;i<NUM_PLAYERS;i++) {
if(players[i].fd == -1)
continue;
if(players[i].is_imposter) {
snprintf(buf, sizeof(buf), "You are in a spaceship, the other %d crew members think you're on of them\n# ", assigned - 1);
write(players[i].fd, buf, strlen(buf));
} else {
snprintf(buf, sizeof(buf), "You are in a spaceship, one of the crew of %d people\n", assigned);
write(players[i].fd, buf, strlen(buf));
snprintf(buf, sizeof(buf), "The tasks have been handed out and the daily routine is starting up, but there are rumors one of your fellow crewmates isn't a crewmate at all.\n# ");
write(players[i].fd, buf, strlen(buf));
}
}
}
int
handle_input(int fd)
{
char buf[200];
char buf2[300];
int len;
int pid;
// Find player for fd
for (pid = 0; pid < NUM_PLAYERS; pid++) {
if (players[pid].fd == fd) {
break;
}
}
// Get the input
len = read(fd, buf, 200);
if (len < 0) {
printf("Read error from player %d\n", pid);
players[pid].fd = -1;
return -1;
}
if (len == 0) {
printf("Received EOF from player %d\n", pid);
players[pid].fd = -1;
if (players[pid].stage != PLAYER_STAGE_NAME) {
snprintf(buf, sizeof(buf), "Player [%s] left the game.", players[pid].name);
printf("Sending parting message\n");
broadcast(buf, -1);
}
return -2;
}
for(int i=0; i<sizeof(buf); i++) {
if (buf[i] == '\n' || buf[i] == '\r') {
buf[i] = '\0';
break;
}
}
printf("%d: %s\n", pid, buf);
switch(players[pid].stage) {
case PLAYER_STAGE_NAME:
// Setting the name after connection and informing the lobby
if(strlen(buf) < MIN_NAME) {
snprintf(buf, sizeof(buf), "Too short, pick another name\n >");
write(fd, buf, strlen(buf));
return 0;
}
if(strlen(buf) > MAX_NAME) {
snprintf(buf, sizeof(buf), "Too long, pick another name\n >");
write(fd, buf, strlen(buf));
return 0;
}
for(int i=0;i<strlen(buf);i++){
if(!isascii(buf[i])) {
snprintf(buf, sizeof(buf), "Invalid char, pick another name\n >");
write(fd, buf, strlen(buf));
return 0;
}
}
strcpy(players[pid].name, buf);
snprintf(buf, sizeof(buf), "[%s] has joined the lobby", players[pid].name);
broadcast(buf, fd);
players[pid].stage = PLAYER_STAGE_LOBBY;
break;
case PLAYER_STAGE_LOBBY:
// Chat message in the lobby
if (buf[0] == '/') {
if (strcmp(buf, "/start") == 0) {
if(players[pid].is_admin) {
start_game();
} else {
const char *msg = "You don't have permission to /start\n";
write(fd, msg, strlen(msg));
}
} else if (strcmp(buf, "/shrug") == 0) {
snprintf(buf2, sizeof(buf2), "[%s] ¯\\_(ツ)_/¯", players[pid].name);
broadcast(buf2, fd);
} else if (strncmp(buf, "/me ", 4) == 0) {
snprintf(buf2, sizeof(buf2), " * [%s] %s", players[pid].name, &buf[4]);
broadcast(buf2, fd);
} else if (strcmp(buf, "/help") == 0) {
snprintf(buf, sizeof(buf), "Commands: /start, /list and more\n");
write(fd, buf, strlen(buf));
} else if (strcmp(buf, "/list") == 0) {
for(int i=0;i<NUM_PLAYERS;i++){
if (players[i].fd == -1)
continue;
if (players[i].stage == PLAYER_STAGE_NAME) {
snprintf(buf, sizeof(buf), " %d: -[ setting name ]-\n", i);
} else if (players[i].is_admin) {
snprintf(buf, sizeof(buf), " %d: %s (admin)\n", i, players[i].name);
} else {
snprintf(buf, sizeof(buf), " %d: %s\n", i, players[i].name);
}
write(fd, buf, strlen(buf));
}
}
} else {
for(int i=0;i<strlen(buf);i++){
if(!isascii(buf[i])) {
buf[i] = '\0';
}
}
snprintf(buf2, sizeof(buf2), "[%s]: %s", players[pid].name, buf);
broadcast(buf2, fd);
}
break;
case PLAYER_STAGE_MAIN:
// Main game adventure loop
adventure(pid, buf);
break;
case PLAYER_STAGE_DISCUSS:
// Main discussion loop
discussion(pid, buf);
break;
}
return 0;
}
int
welcome_player(int fd)
{
int i;
char buf[100];
if(state.stage != STAGE_LOBBY) {
snprintf(buf, sizeof(buf), "There is a game in progress, try again later\n");
write(fd, buf, strlen(buf));
close(fd);
return -1;
}
for (i = 0; i < sizeof(players); i++) {
if (players[i].fd > 0) {
continue;
}
players[i].fd = fd;
if (!state.has_admin) {
state.has_admin = 1;
players[i].is_admin = 1;
}
players[i].stage = PLAYER_STAGE_NAME;
snprintf(buf, sizeof(buf), "Welcome player %d!\n\nEnter your name:\n> ", i);
write(fd, buf, strlen(buf));
printf("Assigned player to spot %d\n", i);
return 0;
}
snprintf(buf, sizeof(buf), "There are no spots available, goodbye!\n");
write(fd, buf, strlen(buf));
close(fd);
return -1;
}
int
main(void)
{
int listen_fd;
int new_fd;
socklen_t client_size;
struct sockaddr_in listen_addr, client_addr;
int port = 1234;
int i;
for (i = 0; i < NUM_PLAYERS; i++) {
players[i].fd = -1;
};
srand(time(NULL));
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
listen_addr.sin_family = AF_INET;
listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);
listen_addr.sin_port = htons(port);
if (bind(listen_fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) < 0) {
perror("bind");
return -1;
}
listen(listen_fd, 5);
printf("Listening on :%d\n", port);
state.stage = STAGE_LOBBY;
FD_ZERO(&afds);
FD_SET(listen_fd, &afds);
while (1) {
rfds = afds;
if (select(FD_SETSIZE, &rfds, NULL, NULL, NULL) < 0) {
perror("select");
exit(EXIT_FAILURE);
}
for (i = 0; i < FD_SETSIZE; ++i) {
if (FD_ISSET (i, &rfds)) {
if (i == listen_fd) {
printf("welcome client!\n");
client_size = sizeof(client_addr);
new_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_size);
if (new_fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
printf("New connection from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
FD_SET(new_fd, &afds);
if(welcome_player(new_fd)<0){
FD_CLR(new_fd, &afds);
}
} else {
if(handle_input(i) < 0) {
close(i);
FD_CLR(i, &afds);
}
}
}
}
}
return 0;
}