/* * Biloba * Copyright (C) 2004-2005 Guillaume Demougeot, Colin Leroy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /** * Biloba - Q1 2005 * Game by Guillaume Demougeot * Code by Colin Leroy * * This file contains the network server code. */ #include #include #include #include #include #include #include #include #include #include "netops.h" #include "llist.h" #include "game.h" static FILE *logfp = NULL; /* redefinitions to avoid sdl dependancy */ typedef enum { INPUT_LOCAL, INPUT_NETWORK, INPUT_AI, NUM_INPUT_SYSTEMS } InputSystemMethod; typedef enum { PAWN_ORANGE = 0, PAWN_BLUE, PAWN_RED, PAWN_GREEN, PAWN_NUM_COLORS } PawnColor; static unsigned int cur_id = 0; pthread_mutex_t gamelist_mutex; LList *gamelist = NULL; #define LOCK_MUTEX() { \ fprintf(logfp, "locking mutex from " __FUNCTION__":%d\n", __LINE__); \ fflush(logfp); \ pthread_mutex_lock(&gamelist_mutex); \ } #define UNLOCK_MUTEX() { \ fprintf(logfp, "unlocking mutex from " __FUNCTION__":%d\n", __LINE__); \ fflush(logfp); \ pthread_mutex_unlock(&gamelist_mutex); \ } static void free_game(Game *game) { int i = 0; if (!game) return; if (game->name) free(game->name); for (i = 0; i < game->num_players; i++) { if (game->player_name[i]) free(game->player_name[i]); } free(game); } /* must be called with gamelist_mutex held */ static LList *get_games(int num_players, int max) { LList *cur = gamelist; LList *results = NULL; int i = 0; while (cur) { Game *game = (Game *)cur->data; if (game->num_players == num_players && game->first_avail_spot > 0 && game->started == 0 && game->killed == 0) { results = llist_append(results, game); i++; } if (i >= max) break; cur = cur->next; } return results; } static Game *get_game(int id) { LList *cur = gamelist; while (cur) { Game *game = (Game *)cur->data; if (game->id == id) return game; cur = cur->next; } return NULL; } static int get_waiting_players(Game *game) { int i = 0; int r = 0; if (game == NULL) return 0; for (i = 0; i < game->num_players; i++) { if (game->player_name[i] == NULL) r++; } return r; } static void *handleconn(void *data) { int myfd = (int)data; char buf[255]; unsigned char len = 0, i; int numplayers = 0; char *p0name, *p1name, *p2name, *p3name; int gameid = 1; Game *new_game = NULL; Game *reserved = NULL; int sent_start = 0; char server = '?'; int my_move_id = 0; fprintf(logfp, "accepted connection %d\n",myfd); fflush(logfp); /* init connection */ if (read_msg(myfd, buf, 255, &len) < 0) goto endme; buf[254] = '\0'; fprintf(logfp, "MSGTYPE: got %d bytes: %s\n", len, buf); fflush(logfp); if (len == 0) goto endme; if (!strcmp(buf, "NEWGAME")) { new_game = malloc(sizeof(Game)); memset(new_game, 0, sizeof(Game)); if (send_msg(myfd, "OK", 3) < 0) goto endme; fprintf(logfp, "NEWGAME> OK\n"); fflush(logfp); /* read game name */ if (read_msg(myfd, buf, 255, &len) < 0) goto endme; buf[254] = '\0'; fprintf(logfp, "NEWGAME< %s\n", buf); fflush(logfp); pthread_mutex_lock (&gamelist_mutex); new_game->name = strdup(buf); cur_id++; gameid = htonl(cur_id); if (send_msg(myfd, &gameid, 4) < 0) { UNLOCK_MUTEX(); goto endme; } fprintf(logfp, "NEWGAME> %d\n", cur_id); fflush(logfp); new_game->id = cur_id; for (i = 0; i < 4; i++) { new_game->player_name[i] = NULL; } /* read player info */ if (read_msg(myfd, buf, 255, &len) < 0) { UNLOCK_MUTEX(); goto endme; } buf[254] = '\0'; fprintf(logfp, "NEWGAME< got %d bytes:\n", len); fflush(logfp); i = 0; numplayers = (int) buf[i]; i++; fprintf(logfp, " numplayers %d\n", numplayers); fflush(logfp); new_game->num_players = numplayers; new_game->first_avail_spot = -1; fprintf(logfp, "NEWGAME< player 0 type:%d\n", buf[i]); fflush(logfp); new_game->player_type[0] = (InputSystemMethod)buf[i]; i++; p0name = buf+i; fprintf(logfp, "NEWGAME< player 0 name:%s\n", p0name); fflush(logfp); new_game->player_name[0] = strdup(p0name); i+=strlen(p0name)+1; fprintf(logfp, "NEWGAME< player 1 type:%d\n", buf[i]); fflush(logfp); new_game->player_type[1] = (InputSystemMethod)buf[i]; i++;p1name = buf+i; fprintf(logfp, "NEWGAME< player 1 name:%s\n", p1name); fflush(logfp); new_game->player_name[1] = strdup(p1name); i+=strlen(p1name)+1; if (numplayers > 2) { fprintf(logfp, "NEWGAME< player 2 type:%d\n", buf[i]); fflush(logfp); new_game->player_type[2] = (InputSystemMethod)buf[i]; i++;p2name = buf+i; fprintf(logfp, "NEWGAME< player 2 name:%s\n", p2name); fflush(logfp); new_game->player_name[2] = strdup(p2name); i+=strlen(p2name)+1; } if (numplayers > 3) { fprintf(logfp, "NEWGAME< player 3 type:%d\n", buf[i]); fflush(logfp); new_game->player_type[3] = (InputSystemMethod)buf[i]; i++;p3name = buf+i; fprintf(logfp, "NEWGAME< player 3 name:%s\n", p3name); fflush(logfp); new_game->player_name[3] = strdup(p3name); i+=strlen(p3name)+1; } new_game->num_remote = 0; for (i = 0; i < numplayers; i++) { fprintf(logfp, "%s (%d,%d)\n", new_game->player_name[i], i, new_game->player_type[i]); fflush(logfp); if (new_game->player_type[i] == INPUT_NETWORK) { free(new_game->player_name[i]); new_game->player_name[i] = NULL; new_game->num_remote++; } } for (i = 1; i <= numplayers; i++) { if (new_game->player_type[i-1] == INPUT_NETWORK) { new_game->first_avail_spot = i; fprintf(logfp, "first_avail_spot %d\n", i); fflush(logfp); break; } } if (new_game->first_avail_spot < 0) { fprintf(logfp, "NEWGAME ERROR: first_avail_spot %d\n", new_game->first_avail_spot); fflush(logfp); if (send_msg(myfd, "ERROR", 6) < 0) { UNLOCK_MUTEX(); goto endme; } free_game(new_game); pthread_mutex_unlock (&gamelist_mutex); goto endme; } if (send_msg(myfd, "READY",6) < 0) { UNLOCK_MUTEX(); goto endme; } fprintf(logfp, "NEWGAME> READY\n"); fflush(logfp); gamelist = llist_append(gamelist, new_game); server = 'S'; pthread_mutex_unlock (&gamelist_mutex); } else if (!strcmp("LISTGAME", buf)) { int nump = 0; LList *avail = NULL, *cur; int tmp = 0; if (send_msg(myfd, "NUMP", 5) < 0) { goto endme; } fprintf(logfp, "LISTGAME> NUMP\n"); fflush(logfp); if (read_msg(myfd, &nump, 4, &len) < 0) { goto endme; } fprintf(logfp, "LISTGAME< %d (nump)\n", ntohl(nump)); fflush(logfp); if (len > 4) { goto endme; } nump = ntohl(nump); if (nump < 1 || nump > 4) goto endme; pthread_mutex_lock (&gamelist_mutex); avail = get_games(nump, 6); tmp = htonl(llist_length(avail)); if (send_msg(myfd, &tmp, 4) < 0) { UNLOCK_MUTEX(); goto endme; } cur = avail; fprintf(logfp, "LISTGAME> %d (list len)\n", llist_length(avail)); fflush(logfp); /* send up to 6 games: id, name */ while (cur) { Game *game = cur->data; tmp = htonl(game->id); if (send_msg(myfd, &tmp, 4) < 0) { UNLOCK_MUTEX(); goto endme; } fprintf(logfp, "LISTGAME> %d (game id)\n", game->id); fflush(logfp); if (send_msg(myfd, game->name, strlen(game->name) + 1) < 0) { UNLOCK_MUTEX(); goto endme; } fprintf(logfp, "LISTGAME> %s (game name)\n", game->name); fflush(logfp); cur = cur->next; } llist_free(avail); pthread_mutex_unlock (&gamelist_mutex); join_wait_cmd: if (read_msg(myfd, buf, 255, &len) < 0) { goto endme; } buf[254] = '\0'; fprintf(logfp, "LISTGAME< %s (command)\n", buf); fflush(logfp); if (!strcmp("PRELJOIN", buf)) { /* preliminary join. user clicked on game. * let him fill his name, bump first_avail_spot */ Game *game = NULL; int tmp = 0; int j = 0; fprintf(logfp, " got PRELJOIN\n"); fflush(logfp); if (read_msg(myfd, &tmp, 4, &len) < 0) { goto endme; } tmp = ntohl(tmp); if (tmp < 0) goto endme; fprintf(logfp, "PRELJOIN> %d (game id)\n", tmp); fprintf(logfp, "looking for game %d\n", tmp); fflush(logfp); pthread_mutex_lock (&gamelist_mutex); if (reserved != NULL) { /* put back in pool */ reserved->first_avail_spot --; if (reserved->first_avail_spot < 0) reserved->first_avail_spot = reserved->num_players; reserved = NULL; } game = get_game(tmp); if (game == NULL) { fprintf(logfp, "PRELJOIN: ERROR (game not found)\n"); fflush(logfp); if (send_msg(myfd, "ERROR1", 7) < 0) { UNLOCK_MUTEX(); goto endme; } pthread_mutex_unlock (&gamelist_mutex); goto endme; } if (send_msg(myfd, "OK", 3) < 0) { UNLOCK_MUTEX(); goto endme; } fprintf(logfp, "PRELJOIN> OK\n"); fflush(logfp); for (j = 0; j < game->num_players; j++) { if (game->player_name[j] != NULL) { if (send_msg(myfd, game->player_name[j], strlen(game->player_name[j])+1) < 0) { UNLOCK_MUTEX(); goto endme; } fprintf(logfp, "PRELJOIN> %s (player name)\n", game->player_name[j]); fflush(logfp); } else { if (send_msg(myfd, "", 1) < 0) { UNLOCK_MUTEX(); goto endme; } fprintf(logfp, "PRELJOIN> \"\" (empty player name)\n"); fflush(logfp); } } tmp = htonl(game->first_avail_spot); if (send_msg(myfd, &tmp, 4) < 0) { UNLOCK_MUTEX(); goto endme; } fprintf(logfp, "PRELJOIN> %d (first_avail_spot)\n", game->first_avail_spot); fflush(logfp); game->first_avail_spot ++; if (game->first_avail_spot > game->num_players) game->first_avail_spot = -1; reserved = game; pthread_mutex_unlock (&gamelist_mutex); } else if (!strcmp("JOIN", buf)) { /* player joins reserved game. */ int mynump = 0; fprintf(logfp, "< JOIN\n"); fflush(logfp); if (read_msg(myfd, &mynump, 4, &len) < 0) { goto endme; } fprintf(logfp, "JOIN< %d (mynump)\n", ntohl(mynump)); fflush(logfp); if (!reserved) { if (send_msg(myfd, "ERROR2", 7) < 0) { goto endme; } goto endme; } if (send_msg(myfd, "OK", 3) < 0) { goto endme; } fprintf(logfp, "JOIN> OK\n"); fflush(logfp); pthread_mutex_lock (&gamelist_mutex); mynump = ntohl(mynump); if (read_msg(myfd, buf, 255, &len) < 0) { UNLOCK_MUTEX(); goto endme; } buf[254] = '\0'; fprintf(logfp, "JOIN> %s (player name)\n", buf); fflush(logfp); reserved->player_name[mynump] = strdup(buf); new_game = reserved; new_game->num_remote = new_game->num_players - 1; new_game->last_joined = mynump; pthread_mutex_unlock (&gamelist_mutex); /* see if we can start game */ server = 'C'; goto playme; } else if (!strcmp("CANCEL", buf)) { fprintf(logfp, "< CANCEL\n"); fflush(logfp); pthread_mutex_lock (&gamelist_mutex); if (reserved != NULL) { /* put back in pool */ reserved->first_avail_spot --; if (reserved->first_avail_spot < 0) reserved->first_avail_spot = reserved->num_players; reserved = NULL; } pthread_mutex_unlock (&gamelist_mutex); goto endme; } goto join_wait_cmd; } else { goto endme; } playme: while (1) { int tmp = 0; int waiting_players = 10; char *last_joined = NULL; int n_last_joined = 0; LOCK_MUTEX(); waiting_players = get_waiting_players(new_game); fprintf(logfp, "game %d-%c: waiting for %d players...\n", new_game->id, server, waiting_players); fflush(logfp); if (read_msg(myfd, buf, 255, &len) < 0) { UNLOCK_MUTEX(); goto endme; } buf[254] = '\0'; fprintf(logfp, "WAIT> %s\n", buf); fflush(logfp); if (strcmp("READY?", buf)) { fprintf(logfp, "protocol error (got %s)\n", buf); fflush(logfp); UNLOCK_MUTEX(); goto endme; } tmp = htonl(waiting_players); if (send_msg(myfd, &tmp, 4) < 0) { UNLOCK_MUTEX(); goto endme; } n_last_joined = new_game->last_joined; last_joined = new_game->player_name[n_last_joined]; if (!last_joined) last_joined = ""; n_last_joined = htonl(n_last_joined); if (send_msg(myfd, &n_last_joined, 4) < 0) { UNLOCK_MUTEX(); goto endme; } if (send_msg(myfd, last_joined, strlen(last_joined)+1) < 0) { UNLOCK_MUTEX(); goto endme; } fprintf(logfp, "WAIT< %d (num waiting) %s\n", waiting_players, last_joined); fflush(logfp); if (waiting_players == 0) { fprintf(logfp, "game %d-%c: all players there!\n", new_game->id, server); fflush(logfp); new_game->started = 1; UNLOCK_MUTEX(); goto playgame; } UNLOCK_MUTEX(); sleep(1); } playgame: new_game->move_id = 0; new_game->cur_x = -1; new_game->cur_y = -1; new_game->quit_players = 0; new_game->killed = 0; for (i = 0; i < new_game->num_players; i++) { if (send_msg(myfd, new_game->player_name[i], strlen(new_game->player_name[i])+1) < 0) goto endme; } sent_start = 0; while (1) { int playercolor, x, y; char action; fprintf(logfp, "%c: reading action\n", server); fflush(logfp); if (read_msg(myfd, &action, 1, &len) < 0) { goto endme; } fprintf(logfp, "%c: did read %c\n", server, action); fflush(logfp); if (action == 'r') { /* receive */ int dest_nump = -1; if (read_msg(myfd, &playercolor, 4, &len) < 0) goto endme; dest_nump = ntohl(dest_nump); LOCK_MUTEX(); if (new_game->killed) { x = htonl(-2); y = htonl(-2); goto send_right_now; } if (new_game->move_id <= my_move_id) { fprintf(logfp, "%d-%c: waiting for ID to ++ (%d <= %d)\n", new_game->id, server, new_game->move_id, my_move_id); fflush(logfp); } while (new_game && new_game->move_id <= my_move_id) { pthread_mutex_unlock(&gamelist_mutex); usleep(80); if (new_game == NULL) goto endme; pthread_mutex_lock(&gamelist_mutex); } fprintf(logfp, "%c(%d): receiving new move (me %d, game %d)\n", server, ntohl(playercolor), my_move_id, new_game->move_id); fflush(logfp); x = htonl(new_game->cur_x); y = htonl(new_game->cur_y); send_right_now: if (send_msg(myfd, &x, 4) < 0) { new_game->waiting --; UNLOCK_MUTEX(); goto endme; } if (send_msg(myfd, &y, 4) < 0) { new_game->waiting --; UNLOCK_MUTEX(); goto endme; } my_move_id ++; fprintf(logfp, "%c: got a move (now: me %d, game %d)\n", server, my_move_id, new_game->move_id); fflush(logfp); new_game->waiting --; UNLOCK_MUTEX(); } else if (action == 'p' || action == 'q') { /* play or quit */ LOCK_MUTEX(); fprintf(logfp, "game %p, wait %d, killed %d\n", new_game, new_game->waiting, new_game->killed); if (new_game->waiting) { fprintf(logfp, "%c: waiting for %d players\n", server, new_game->waiting); } fflush(logfp); send_again: if (new_game->waiting > 0) { /* wait for last move to be received by all */ pthread_mutex_unlock(&gamelist_mutex); usleep(100); if (new_game->killed) goto endme; pthread_mutex_lock(&gamelist_mutex); goto send_again; } if (read_msg(myfd, &playercolor, 4, &len) < 0) { UNLOCK_MUTEX(); goto endme; } fprintf(logfp, "%c (%d): wait done\n", server, ntohl(playercolor)); fflush(logfp); if (action == 'p') { if (read_msg(myfd, &x, 4, &len) < 0) { UNLOCK_MUTEX(); goto endme; } if (read_msg(myfd, &y, 4, &len) < 0) { UNLOCK_MUTEX(); goto endme; } x = ntohl(x); y = ntohl(y); } else { x = -2; y = -2; } playercolor = ntohl(playercolor); new_game->cur_player = playercolor; new_game->cur_x = x; new_game->cur_y = y; new_game->move_id ++; new_game->waiting = new_game->num_remote; fprintf(logfp, "%c: player %d plays %d,%d\n", server, playercolor, x, y); fprintf(logfp, "%c: got a move (%d), %d players waiting it\n", server, new_game->move_id, new_game->waiting); fflush(logfp); if (action == 'q') { UNLOCK_MUTEX(); goto endme; } /* wait here that everyone gets the move. * this avoids deadlocks */ while (new_game->waiting > 0) { pthread_mutex_unlock(&gamelist_mutex); usleep(100); if (new_game->killed) goto endme; pthread_mutex_lock(&gamelist_mutex); } UNLOCK_MUTEX(); } usleep(100); } endme: fprintf(logfp, "thread done with %d\n",myfd); fflush(logfp); if (reserved != NULL) { /* put back in pool */ reserved->first_avail_spot --; if (reserved->first_avail_spot < 0) reserved->first_avail_spot = reserved->num_players; reserved = NULL; } if (new_game) { new_game->quit_players ++; if (new_game->quit_players > 0) { fprintf(logfp, "killing game %d\n", new_game->id); fflush(logfp); new_game->killed = 1; } if (new_game->quit_players >= new_game->num_players) { fprintf(logfp, "killed game %d\n", new_game->id); fflush(logfp); LOCK_MUTEX(); gamelist = llist_remove(gamelist, new_game); UNLOCK_MUTEX(); free(new_game); new_game = NULL; } } close(myfd); pthread_exit(NULL); } int main(int argc, char *argv[]) { int portno = 8000; int sockfd, clifd, clilen; struct sockaddr_in serv_addr, cli_addr; pthread_t threadid; int optOn = 1; if (argc > 1) { logfp = fopen(argv[1], "wb"); if (logfp == NULL) { printf("can't open %s\n", argv[1]); logfp = stdout; } } else logfp = stdout; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("Error on opening socket"); exit(1); } memset((char *) &serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(portno); setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optOn, sizeof(optOn)); if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { perror("Error on binding"); exit(1); } listen(sockfd, 1000); pthread_mutex_init(&gamelist_mutex, NULL); clilen = sizeof(cli_addr); while ((clifd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen)) >= 0) { setsockopt(clifd, SOL_SOCKET, SO_REUSEADDR, &optOn, sizeof(optOn)); fprintf(logfp, "New connection from %s\n", inet_ntoa(cli_addr.sin_addr)); pthread_create(&threadid, NULL, handleconn, (void *)clifd); pthread_detach(threadid); clilen = sizeof(cli_addr); } close(sockfd); pthread_mutex_destroy(&gamelist_mutex); return 0; }