/* * gtkatlantic - the gtk+ monopd client, enjoy network monopoly games * * * Copyright (C) 2002-2004 Rochet Sylvain * * gtkatlantic 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "global.h" #include "game.h" #include "load.h" #include "interface.h" #include "client.h" #include "trade.h" #include "engine.h" /* create home directory (~/.gtkatlantic) * * return TRUE if exist/created, return FALSE if cannot be created */ gboolean create_home_directory() { guchar *path; struct stat s; path = g_strconcat( getenv("HOME"), "/", ".", PACKAGE, "/", NULL); global->path_home = g_strdup(path); if(stat(path, &s) < 0) { mkdir(path, 0777); if(stat(path, &s) < 0) { g_free(path); return FALSE; } } g_free(path); path = g_strconcat( global->path_home, "themes/", NULL); if(stat(path, &s) < 0) { mkdir(path, 0777); if(stat(path, &s) < 0) { g_free(path); return FALSE; } } g_free(path); return TRUE; } /* read config files */ void read_config_files() { guchar *filename; struct stat s; /* read default config file */ filename = g_strconcat(PACKAGE_DATA_DIR, "/default.conf", NULL); if(! parse_file_config(filename) ) { printf("Error opening config file %s\n",filename); exit(-1); } g_free(filename); /* read user config file */ filename = g_strconcat(global->path_home, "gtkatlantic.conf", NULL); if(stat(filename, &s) >= 0) parse_file_config(filename); g_free(filename); } /* return a valid slot for a connection * * else return FALSE */ gboolean connect_get_valid_id(guint16 *idconnect) { guint16 i; for(i = 1; i < MAX_CONNECTION; i++) if(!connection_is_open[i]) { *idconnect = i; return(TRUE); } return(FALSE); } /* this function made a new connection for metaserver (list & games) */ void create_connection_metaserver(guint32 gametype) { guint16 connectid; gchar *sendstr; if(! client_connect(&connectid, config->metaserver_host, config->metaserver_port) ) return; connection[connectid]->type = gametype; /* send client version (to retrieve info about release) */ if(config->metaserver_sendclientversion) { sendstr = g_strconcat("CHECKCLIENT", PACKAGE, VERSION, "\n", NULL); client_send(connectid, sendstr, strlen(sendstr)); g_free(sendstr); } if(gametype == CONNECT_TYPE_META_GETLIST) { sendstr = g_strdup("SERVERLIST\n"); client_send(connectid, sendstr, strlen(sendstr)); g_free(sendstr); } else { sendstr = g_strdup("GAMELIST\n"); client_send(connectid, sendstr, strlen(sendstr)); g_free(sendstr); } } /* this function made a new connection for get games only */ void create_connection_get_games(guchar *host, guint32 port) { guint16 connectid; gchar *sendstr; if(! client_connect(&connectid, host, port) ) return; connection[connectid]->type = CONNECT_TYPE_MONOPD_GETGAME; sendstr = g_strdup(".n_client_\n.gl\n"); client_send(connectid, sendstr, strlen(sendstr)); g_free(sendstr); } /* insert test in Message Box */ void text_insert_message(gchar* text, guint32 length) { GtkTextBuffer *textbuff; GtkTextIter textiter; GtkTextMark *textmark; guint32 i, len; if(global->phase < PHASE_GETGAMES) return; textbuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(global->Message)); gtk_text_buffer_get_end_iter(textbuff, &textiter); gtk_text_buffer_insert(textbuff, &textiter, text, length); gtk_text_buffer_insert(textbuff, &textiter, "\n", -1); /* Scroll the text */ textmark = gtk_text_buffer_get_mark(textbuff, "endmark"); gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(global->Message), textmark, 0.0, FALSE, 0.0, 0.0); global->message_nb_lines++; // while(global->message_nb_lines > config->message_max_lines) { /* if unlimited */ /* if(config->message_max_lines <= 0) break; len = gtk_text_get_length( GTK_TEXT(global->Message) ); for(i = 0 ; GTK_TEXT_INDEX( GTK_TEXT(global->Message), i) != '\n' ; i++) { if(i >= len) { /* argl... no CR founded */ /* gtk_text_set_point( GTK_TEXT(global->Message), 0); gtk_text_forward_delete( GTK_TEXT(global->Message), len); global->message_nb_lines = 0; break; } } if(global->message_nb_lines) { i++; gtk_text_set_point( GTK_TEXT(global->Message), 0); gtk_text_forward_delete( GTK_TEXT(global->Message), i); global->message_nb_lines--; } } len = gtk_text_get_length( GTK_TEXT(global->Message) ); gtk_text_set_point( GTK_TEXT(global->Message), len); gtk_text_thaw( GTK_TEXT(global->Message) ); gtk_adjustment_set_value( GTK_ADJUSTMENT( GTK_TEXT( global->Message )->vadj ), GTK_ADJUSTMENT( GTK_TEXT( global->Message )->vadj )->upper ); */ } /* insert test in Chat Box */ void text_insert_chat(gchar* text, guint32 length) { GtkTextBuffer *textbuff; GtkTextIter textiter; GtkTextMark *textmark; guint32 i, len; if(global->phase < PHASE_GAMECREATE) return; textbuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(game->Chat)); gtk_text_buffer_get_end_iter(textbuff, &textiter); gtk_text_buffer_insert(textbuff, &textiter, text, length); gtk_text_buffer_insert(textbuff, &textiter, "\n", -1); /* Scroll to the end mark */ textmark = gtk_text_buffer_get_mark(textbuff, "endmark"); gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(game->Chat), textmark, 0.0, FALSE, 0.0, 0.0); game->chat_nb_lines++; /* while(game->chat_nb_lines > config->chat_max_lines) { /* if unlimited */ /* if(config->chat_max_lines <= 0) break; len = gtk_text_get_length( GTK_TEXT(game->Chat) ); for(i = 0 ; GTK_TEXT_INDEX( GTK_TEXT(game->Chat), i) != '\n' ; i++) { if(i >= len) { /* argl... no CR founded */ /* gtk_text_set_point( GTK_TEXT(game->Chat), 0); gtk_text_forward_delete( GTK_TEXT(game->Chat), len); game->chat_nb_lines = 0; break; } } if(game->chat_nb_lines) { i++; gtk_text_set_point( GTK_TEXT(game->Chat), 0); gtk_text_forward_delete( GTK_TEXT(game->Chat), i); game->chat_nb_lines--; } } len = gtk_text_get_length( GTK_TEXT(game->Chat) ); gtk_text_set_point( GTK_TEXT(game->Chat), len); gtk_text_thaw( GTK_TEXT(game->Chat) ); // gtk_adjustment_set_value( GTK_ADJUSTMENT( GTK_TEXT( game->Chat )->vadj ), GTK_ADJUSTMENT( GTK_TEXT( game->Chat )->vadj )->upper ); */ } /* alloc all memory for a game */ void game_alloc() { game = g_malloc0(sizeof (_game) ); } /* free entirely a game, return to get games page */ void game_free() { guint32 i; client_disconnect(game->connectid); interface_destroy_message(); interface_destroy_chat(); if(game->WinEstateTree) gtk_widget_destroy(game->WinEstateTree); if(game->timeout_token) gtk_timeout_remove(game->timeout_token); interface_create_getgamespage(); for(i = 0 ; i < MAX_PLAYERS ; i++) { if(! global->player[i].playerid) continue; if(global->player[i].name) g_free(global->player[i].name); if(global->player[i].host) g_free(global->player[i].host); if(global->player[i].image) g_free(global->player[i].image); frame_destroy( global->player[i].playerlist_token_frame ); frame_destroy( global->player[i].playerlist_cards_frame ); } for(i = 0 ; i < data->number_estates ; i++) { if(game->estate[i].name) g_free(game->estate[i].name); } for(i = 0 ; i < MAX_GROUPS ; i++) { if(game->group[i].name) g_free(game->group[i].name); } for(i = 0 ; i < MAX_TRADES ; i++) { trade_destroy_slot(i); } for(i = 0 ; i < MAX_COMMANDS ; i++) { if(game->command[i].open && game->command[i].command) g_free(game->command[i].command); } frame_destroy( game->board_frame ); game_free_pngs(); g_free(game); game = 0; } /* return a valid game id */ gboolean game_get_valid_player_slot(guint8 *playerslot) { guint32 i; for(i = 0; i < MAX_PLAYERS; i++) if(!global->player[i].playerid) { *playerslot = i; return TRUE; } return FALSE; } /* return the player slot of playerid */ gint8 get_player_slot_with_playerid(guint32 playerid) { guint32 i; for(i = 0 ; i < MAX_PLAYERS ; i++) { if(global->player[i].playerid == playerid) { return(i); break; } } return(-1); } gint8 get_playerlistcard_id_with_estate(guint32 estate) { guint32 i; for(i = 0 ; i < data->number_playerlist_card ; i++) { if(data->playerlist_card[i].estateid == estate) { return(i); break; } } return(-1); } /* return a valid command slotid */ gboolean game_get_valid_command_slot(guint8 *commandslot) { guint8 i; for(i = 0; i < MAX_COMMANDS ; i++) if(!game->command[i].open) { *commandslot = i; return TRUE; } return FALSE; } /* return commandslot if button command is displayed return <0 if button not is not displayed */ gint8 get_command_button_slot_with_command(guchar *command) { guint32 i; for(i = 0 ; i < MAX_COMMANDS ; i ++) { if(game->command[i].open) { if(! strncmp(command, game->command[i].command, strlen(command)) ) return i; } } return -1; } /* send specific chat message, like version */ void parse_specific_chat_message(guchar *message) { guchar *msg, *sendstr; guint32 pslot; struct timeval tv; time_t t_now; pslot = get_player_slot_with_playerid( game->my_playerid ); if(message[0] != '!') return; msg = message; msg++; if(! strncmp(msg, "version", 7)) { msg += 7; while( strlen(msg) ) { if(msg[0] != ' ') break; else msg++; } if(strlen(msg) == 0 || !strcmp(msg, global->player[pslot].name) ) { sendstr = g_strdup_printf("GtkAtlantic %s\n", VERSION); client_send(game->connectid, sendstr, strlen(sendstr)); g_free(sendstr); } } else if(! strncmp(msg, "date", 4)) { msg += 4; while( strlen(msg) ) { if(msg[0] != ' ') break; else msg++; } if(strlen(msg) == 0 || !strcmp(msg, global->player[pslot].name) ) { gettimeofday(&tv, NULL); t_now = tv.tv_sec; sendstr = g_strdup_printf("%s", ctime(&t_now) ); client_send(game->connectid, sendstr, strlen(sendstr)); g_free(sendstr); } } else if(! strncmp(msg, "ping", 4)) { msg += 4; while( strlen(msg) ) { if(msg[0] != ' ') break; else msg++; } if(strlen(msg) == 0 || !strcmp(msg, global->player[pslot].name) ) { sendstr = g_strdup_printf("pong\n"); client_send(game->connectid, sendstr, strlen(sendstr)); g_free(sendstr); } } } /* parse specific send message */ void parse_specific_send_message(guchar *message) { guchar *msg, *tmp; guint32 i; gint32 playerid, pslot; struct timeval tv; struct tm *local_tm; time_t t_now; if(message[0] != '/') return; msg = message; msg++; if(! strncmp(msg, "assets", 6)) { msg += 6; while( strlen(msg) ) { if(msg[0] != ' ') break; else msg++; } if(strlen(msg) == 0) { for(i = 0 ; i < MAX_PLAYERS ; i++) { if(! global->player[i].playerid) continue; if(global->player[i].game != game->gameid) continue; tmp = g_strdup_printf("<%s> %d", global->player[i].name, game_get_assets_player(global->player[i].playerid) ); text_insert_message(tmp, strlen(tmp) ); g_free(tmp); } } if( (playerid = get_playerid_by_playername(msg)) > 0) { pslot = get_player_slot_with_playerid(playerid); tmp = g_strdup_printf("<%s> %d", global->player[pslot].name, game_get_assets_player(global->player[pslot].playerid) ); text_insert_message(tmp, strlen(tmp) ); g_free(tmp); } } else if(! strncmp(msg, "nick", 4)) { msg += 4; while( strlen(msg) ) { if(msg[0] != ' ') break; else msg++; } if(strlen(msg) != 0 && strcmp(global->player[ get_player_slot_with_playerid(game->my_playerid) ].name, msg) ) { tmp = g_strdup_printf(".n%s\n", msg); client_send(game->connectid, tmp, strlen(tmp)); g_free(tmp); } } else if(! strncmp(msg, "date", 4)) { gettimeofday(&tv, NULL); t_now = tv.tv_sec; tmp = g_strdup_printf("%s", ctime(&t_now) ); for(i = 0 ; i < strlen(tmp) ; i++) { if(tmp[i] == '\n') { tmp[i] = '\0'; break; } } text_insert_message(tmp, strlen(tmp) ); g_free(tmp); } else if(! strncmp(msg, "elapsed", 7)) { if(game->status >= GAME_STATUS_RUN) { gettimeofday(&tv, NULL); t_now = tv.tv_sec - game->start_time; } else t_now = 0; local_tm = gmtime(&t_now); tmp = g_strdup_printf("%.2d:%.2d:%.2d", local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec); text_insert_message(tmp, strlen(tmp) ); g_free(tmp); } else if(! strncmp(msg, "me", 2)) { msg += 2; while( strlen(msg) ) { if(msg[0] != ' ') break; else msg++; } if(strlen(msg) != 0) { tmp = g_strdup_printf("[ACTION] %s\n", msg); client_send(game->connectid, tmp, strlen(tmp)); g_free(tmp); } } } /* sort playerlist by playerid (ascending) */ void game_sort_playerlist_by_playerid() { guint32 i, j; _player playertmp; for(i = 0 ; i < MAX_PLAYERS ; i++) { if(! global->player[i].playerid) continue; for(j = i+1 ; j < MAX_PLAYERS ; j++) { if(! global->player[j].playerid) continue; if(global->player[j].playerid < global->player[i].playerid) { memcpy(&playertmp, &global->player[i], sizeof(_player) ); memcpy(&global->player[i], &global->player[j], sizeof(_player) ); memcpy(&global->player[j], &playertmp, sizeof(_player) ); } } } } /* write cookie on disk */ void game_write_cookie(guchar *cookie) { guchar *filename, *errormsg; FILE *file; filename = g_strconcat(global->path_home, "cookie", NULL); file = fopen(filename, "wt"); if(!file) { errormsg = g_strdup_printf("[ERROR] Can't write cookie file at \"%s\"", filename); text_insert_message(errormsg, strlen(errormsg)); g_free(errormsg); g_free(filename); return; } fprintf(file, "%s\n", connection[ game->connectid ]->host ); fprintf(file, "%d\n", connection[ game->connectid ]->port ); fprintf(file, "%s\n", cookie); g_free(filename); fclose(file); } /* return a valid card slotid */ gboolean game_get_valid_card_slot(guint8 *cardslot) { guint8 i; for(i = 0; i < MAX_CARDS ; i++) if(!game->card[i].owner) { *cardslot = i; return TRUE; } return FALSE; } /* return the card slot by cardid */ gint8 get_card_slot_with_cardid(guint32 cardid) { guint32 i; for(i = 0 ; i < MAX_CARDS ; i++) { if(game->card[i].cardid == cardid) { return(i); break; } } return(-1); } /* return player identifier by name of this player */ gint32 get_playerid_by_playername(guchar *name) { guint32 i; if(!name) return(-1); if(strlen(name) <= 0) return(-1); for(i = 0 ; i < MAX_PLAYERS ; i++) { if(! global->player[i].playerid) continue; if(! strcmp(global->player[i].name, name) ) { return( global->player[i].playerid ); break; } } return(-1); } /* return a valid trade slotid */ gboolean game_get_valid_trade_slot(guint32 *tradeslot) { guint32 i; for(i = 0; i < MAX_TRADES ; i++) if(! game->trade[i].open) { *tradeslot = i; return TRUE; } return FALSE; } /* return trade slot by tradeid */ gint32 get_trade_slot_with_tradeid(guint32 tradeid) { guint32 i; for(i = 0 ; i < MAX_TRADES ; i++) { if(! game->trade[i].open) continue; if(game->trade[i].tradeid == tradeid) { return(i); break; } } return(-1); } /* return estate identifier by name of this estate */ gint32 get_estateid_by_estatename(guchar *name) { guint32 i; for(i = 0 ; i < data->number_estates ; i++) { if(! strcmp(game->estate[i].name, name) ) { return(i); break; } } return(-1); } /* build player list in game config phase */ void game_buildplayerlist() { guint32 i; gint32 row; gchar *txt[10]; game_sort_playerlist_by_playerid(); /* build player list */ if(global->phase == PHASE_GAMECREATE) { gtk_clist_freeze(GTK_CLIST(game->PlayerCList)); gtk_clist_clear(GTK_CLIST(game->PlayerCList)); for(i = 0 ; i < MAX_PLAYERS ; i++) { if(global->player[i].playerid && global->player[i].game == game->gameid) { txt[0] = global->player[i].name; txt[1] = global->player[i].host; row = gtk_clist_append(GTK_CLIST(game->PlayerCList), txt); gtk_clist_set_selectable(GTK_CLIST(game->PlayerCList), row ,FALSE); } } gtk_clist_thaw(GTK_CLIST(game->PlayerCList)); } } /* return assets of a player, * assets is: money + sale price of houses + unmortgaged value of estates */ gint32 game_get_assets_player(guint32 playerid) { guint32 i; gint32 pslot, assets; pslot = get_player_slot_with_playerid( playerid ); if(pslot < 0) return 0; /* money */ assets = global->player[pslot].money; /* estates + houses */ for(i = 0 ; i < data->number_estates ; i++) { if(game->estate[i].owner != playerid) continue; /* estates */ if(! game->estate[i].mortgaged) assets += game->estate[i].mortgageprice; /* houses */ if(game->estate[i].houses <= 0) continue; assets += game->estate[i].houses * game->estate[i].sellhouseprice; } return assets; } /* update token position, handle superposed token */ void game_update_tokens() { gint32 i, j, nb_tokens, x1, y1, x2, y2, xs, ys, xp, yp, pp, k; /* === UNJAILED === */ for(i = 0 ; i < data->number_estates ; i++) { /* search number of unjailed tokens on this estate */ for(j = 0, nb_tokens = 0 ; j < MAX_PLAYERS ; j++) { if(! global->player[j].playerid) continue; if(global->player[j].game != game->gameid) continue; if(global->player[j].bankrupt) continue; if(global->player[j].jailed) continue; if(global->player[j].location != i) continue; nb_tokens++; } /* move token */ if(nb_tokens == 0) continue; for(j = 0, k = 0, xp = -1, yp = -1, pp = -1 ; j < MAX_PLAYERS ; j++) { if(! global->player[j].playerid) continue; if(global->player[j].game != game->gameid) continue; if(global->player[j].bankrupt) continue; if(global->player[j].jailed) continue; if(global->player[j].location != i) continue; x1 = data->estate[ global->player[j].location ].x1token; x2 = data->estate[ global->player[j].location ].x2token; y1 = data->estate[ global->player[j].location ].y1token; y2 = data->estate[ global->player[j].location ].y2token; if(nb_tokens == 1) { pic_set_x(game->board_frame, global->player[j].token_pic, x1); pic_set_y(game->board_frame, global->player[j].token_pic, y1); break; } xs = ( ( (x2 - x1) * k) / (nb_tokens -1) ) + x1; if(pp >= 0 && abs(xs - xp) > (data->pngfile_token_width[ global->player[pp].buffer_token ] + 2) ) { if(xs - xp > 0) xs = xp + data->pngfile_token_width[ global->player[pp].buffer_token ] + 2; else xs = xp - data->pngfile_token_width[ global->player[pp].buffer_token ] - 2; } ys = ( ( (y2 - y1) * k) / (nb_tokens -1) ) + y1; if(pp >= 0 && abs(ys - yp) > (data->pngfile_token_height[ global->player[pp].buffer_token ] + 2) ) { if(ys - yp > 0) ys = yp + data->pngfile_token_height[ global->player[pp].buffer_token ] + 2; else ys = yp - data->pngfile_token_height[ global->player[pp].buffer_token ] - 2; } pic_set_x(game->board_frame, global->player[j].token_pic, xs); pic_set_y(game->board_frame, global->player[j].token_pic, ys); xp = xs; yp = ys; pp = j; k++; } } /* === JAILED === */ for(i = 0 ; i < data->number_estates ; i++) { /* search number of jailed tokens on this estate */ for(j = 0, nb_tokens = 0 ; j < MAX_PLAYERS ; j++) { if(! global->player[j].playerid) continue; if(global->player[j].game != game->gameid) continue; if(global->player[j].bankrupt) continue; if(! global->player[j].jailed) continue; if(global->player[j].location != i) continue; nb_tokens++; } /* move token */ if(nb_tokens == 0) continue; for(j = 0, k = 0, xp = -1, yp = -1, pp = -1 ; j < MAX_PLAYERS ; j++) { if(! global->player[j].playerid) continue; if(global->player[j].game != game->gameid) continue; if(global->player[j].bankrupt) continue; if(! global->player[j].jailed) continue; if(global->player[j].location != i) continue; x1 = data->estate[ global->player[j].location ].x1jail; x2 = data->estate[ global->player[j].location ].x2jail; y1 = data->estate[ global->player[j].location ].y1jail; y2 = data->estate[ global->player[j].location ].y2jail; if(nb_tokens == 1) { pic_set_x(game->board_frame, global->player[j].token_pic, x1); pic_set_y(game->board_frame, global->player[j].token_pic, y1); break; } xs = ( ( (x2 - x1) * k) / (nb_tokens -1) ) + x1; if(pp >= 0 && abs(xs - xp) > (data->pngfile_token_width[ global->player[pp].buffer_token ] + 2) ) { if(xs - xp > 0) xs = xp + data->pngfile_token_width[ global->player[pp].buffer_token ] + 2; else xs = xp - data->pngfile_token_width[ global->player[pp].buffer_token ] - 2; } ys = ( ( (y2 - y1) * k) / (nb_tokens -1) ) + y1; if(pp >= 0 && abs(ys - yp) > (data->pngfile_token_height[ global->player[pp].buffer_token ] + 2) ) { if(ys - yp > 0) ys = yp + data->pngfile_token_height[ global->player[pp].buffer_token ] + 2; else ys = yp - data->pngfile_token_height[ global->player[pp].buffer_token ] - 2; } pic_set_x(game->board_frame, global->player[j].token_pic, xs); pic_set_y(game->board_frame, global->player[j].token_pic, ys); xp = xs; yp = ys; pp = j; k++; } } } /* start token movement */ void game_start_move_token(guint32 playerid) { guchar *sendstr; gint32 pslot; pslot = get_player_slot_with_playerid(playerid); if(pslot < 0) return; if(global->player[pslot].directmove) { global->player[pslot].location = global->player[pslot].location_to; sendstr = g_strdup_printf(".t%d\n", global->player[pslot].location); client_send(game->connectid, sendstr, strlen(sendstr)); g_free(sendstr); } game_update_tokens(); return; } /* animate move token */ gboolean game_move_tokens() { guint32 i; guchar *sendstr; gboolean update = 0; for(i = 0 ; i < MAX_PLAYERS ; i++) { if(! global->player[i].playerid) continue; if(global->player[i].game != game->gameid) continue; if(global->player[i].location == global->player[i].location_to) continue; global->player[i].location++; if(global->player[i].location >= data->number_estates) global->player[i].location = 0; sendstr = g_strdup_printf(".t%d\n", global->player[i].location); client_send(game->connectid, sendstr, strlen(sendstr)); g_free(sendstr); update = TRUE; } if(update) game_update_tokens(); return TRUE; } void game_delete_player(guint32 id) { /* delete a player */ if(global->player[id].name) g_free(global->player[id].name); if(global->player[id].host) g_free(global->player[id].host); if(global->player[id].image) g_free(global->player[id].image); memset(&global->player[id], 0, sizeof(_player) ); game->nb_players--; }