📄 controller.c
字号:
despatch_line(i,line); } } } }}/* function to act on Player Messages. NB it takes a connection number, not an id, since ids may be unassigned.*/static void handle_pmsg(PMsgMsg *pmp, int cnx);/* despatch_line: process the line of input received on cnx. */static void despatch_line(int cnx, char *line) { PMsgMsg *pmp; if ( logfile ) { fprintf(logfile,"<cnx%d %s",cnx,line); } if ( line == NULL ) { warn("receive error on cnx %d, player id %d\n",cnx, cnx_to_id(cnx)); handle_cnx_error(cnx); return; } pmp = decode_pmsg(line); /* increment the sequence number */ connections[cnx].seqno++; if ( pmp == NULL ) { CMsgErrorMsg em; em.type = CMsgError; em.error = "Protocol error"; em.seqno = connections[cnx].seqno; send_packet(cnx,(CMsgMsg *)&em,1); warn("Protocol error on cnx %d, player id %d; ignoring\n",cnx, cnx_to_id(cnx)); return; } handle_pmsg(pmp,cnx);}/* little function to check that the minimum time has passed since the last tile moving activity. The argument multiplies the usual delay time between the current call and the next call. It's used to increase the delay after claim implementations. bloody emacs ' */static void check_min_time(float factor) { static struct timeval disctime, timenow; static int min_time_this_time; if ( min_time_this_time > 0 ) { int n; if ( disctime.tv_sec > 0 ) { /* not firsttime */ gettimeofday(&timenow,NULL); n = (timenow.tv_sec * 1000 + timenow.tv_usec/1000) - (disctime.tv_sec * 1000 + disctime.tv_usec/1000); n = 100*min_time_this_time - n; if ( n > 0 ) usleep(1000*n); } gettimeofday(&disctime,NULL); } min_time_this_time = (int)(min_time*factor);}static void handle_pmsg(PMsgMsg *pmp, int cnx) { /* We mustn't act on most requests if the game is not active. There's a race possible otherwise: we can suspend the game, and meanwhile a player sends a request: we then act on this, but it doesn't get into the history records of the suspended players. */ if ( ! the_game->active ) { CMsgErrorMsg em; em.type = CMsgError; em.error = "Game not active"; em.seqno = connections[cnx].seqno; switch ( pmp->type ) { case PMsgSaveState: case PMsgLoadState: case PMsgConnect: case PMsgSetPlayerOption: case PMsgSendMessage: case PMsgQueryGameOption: case PMsgListGameOptions: break; /* these are OK */ default: send_packet(cnx,(CMsgMsg *)&em,0); return; } } /* check for debugging messages */ if ( ! debug && pmp->type >= DebugMsgsStart ) { CMsgErrorMsg em; em.type = CMsgError; em.seqno = connections[cnx].seqno; em.error = "Debugging not enabled"; send_packet(cnx,(CMsgMsg *)&em,0); return; } switch ( pmp->type ) { case PMsgSaveState: { PMsgSaveStateMsg *m = (PMsgSaveStateMsg *)pmp; CMsgErrorMsg em; PlayerP p; int id; seats seat; char *res; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); em.type = CMsgError; em.seqno = connections[cnx].seqno; if ( (res = save_state(the_game,m->filename)) ) { if ( the_game->protversion >= 1025 ) { CMsgStateSavedMsg ssm; ssm.type = CMsgStateSaved; ssm.id = id; ssm.filename = res; send_all(the_game,&ssm); } } else { em.error = "Unable to save state"; send_id(id,&em); } return; } case PMsgLoadState: { PMsgLoadStateMsg *m = (PMsgLoadStateMsg *) pmp; CMsgErrorMsg em; PlayerP p; int id; seats seat; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); em.type = CMsgError; em.seqno = connections[cnx].seqno; em.error = NULL; /* I think we can load state any time before the game starts, and only then */ if ( protocol_version < 1038 ) { em.error = "Not all players support dynamic state loading"; } else if ( game_has_started(the_game) ) { em.error = "Can't load game state when already playing"; } else if ( m->filename == NULL || m->filename[0] == 0 ) { em.error = "No game file specified in LoadState"; } else { int i; CMsgPlayerMsg pm; loadstate = 1; strmcpy(loadfilename,m->filename,1023); /* first delete all players from the clients */ pm.type = CMsgPlayer; pm.name = NULL; for ( i = 0 ; i < MAX_CONNECTIONS ; i++ ) { if ( connections[i].inuse && connections[i].player ) { pm.id = connections[i].player->id; send_all(the_game,&pm); } } /* now remove from maps ... */ for ( i = 0 ; i < MAX_CONNECTIONS ; i++ ) { if ( connections[i].inuse && connections[i].player ) { remove_from_maps(i); num_connected_players--; } } if ( ! load_state(the_game) ) { em.error = "Loading game state failed"; } the_game->active = 0; /* now reprocess connection messages */ for ( i = 0 ; i < MAX_CONNECTIONS ; i++ ) { if ( connections[i].inuse && connections[i].cm) { handle_pmsg(connections[i].cm,i); } } } if ( em.error ) { send_id(id,&em); } return; } case PMsgConnect: { PMsgConnectMsg *m = (PMsgConnectMsg *) pmp; PlayerP p = NULL; CMsgConnectReplyMsg cm; CMsgPlayerMsg thisplayer; CMsgGameMsg gamemsg; char *refusal = NULL; static int player_id = 0; int i; /* start filling in reply message */ cm.type = CMsgConnectReply; cm.pvers = PROTOCOL_VERSION; if ( m->pvers/1000 != PROTOCOL_VERSION/1000 ) refusal = "Protocol major version mismatch"; if ( num_connected_players == NUM_PLAYERS ) refusal = "Already have players"; /* it's reasonable to insist on a name being supplied */ if ( m->name == NULL ) refusal = "A non-empty name must be given"; /* If we have a previous game state loaded, we need to associate this player with one in the game. Match on ids */ if ( loadstate && !refusal ) { /* if no id specified, match on names */ if ( m->last_id != 0 ) { for ( i = 0; i < NUM_SEATS && m->last_id != the_game->players[i]->id ; i++ ) ; } else { for ( i = 0; i < NUM_SEATS && (strcmp(m->name,the_game->players[i]->name) != 0) ; i++ ) ; } /* if no id required, just match this to the first player not currently connected */ if ( i == NUM_SEATS && noidrequired ) { for ( i = 0; i < NUM_SEATS && id_to_cnx(the_game->players[i]->id) >= 0 ; i++ ); } if ( i == NUM_SEATS ) { refusal = "Can't find you in the resumed game"; } else { /* better check that this player isn't already connected */ if ( id_to_cnx(m->last_id) >= 0 ) refusal = "Your id is already connected"; else { p = the_game->players[i]; set_player_name(p,m->name); /* not preserved by saving */ setup_maps(cnx,p); /* set up maps between id and cnx etc*/ num_connected_players++; } } } else { /* ignore the lastid field, and assign this player a new id */ p = game_id_to_player(the_game,0); /* get free player structure */ if ( p == NULL ) { warn("can't get player structure; exiting"); exit(1); } initialize_player(p); num_connected_players++; set_player_id(p,++player_id); set_player_name(p,m->name); /* store the id of the first human player to connect. This assumes that computer players are called Robot.. */ if ( ! first_id && strncmp(p->name,"Robot",5) != 0 ) first_id = num_connected_players; setup_maps(cnx,p); /* set up maps between id and cnx etc*/ } if (refusal) { cm.id = 0; cm.reason = refusal; send_packet(cnx,(CMsgMsg *)&cm,1); close_connection(cnx); return; } /* send the reply */ cm.id = p->id; cm.reason = NULL; send_packet(cnx,(CMsgMsg *)&cm,1); /* store the protocol version of this player */ pextras(p)->protversion = m->pvers; /* Yes, this is right: the fact that this player is connecting means that it has been disconnected! */ pextras(p)->disconnected = 1; /* keep a copy of the message if not already there */ if ( connections[cnx].cm == NULL ) { connections[cnx].cm = pmsg_deepcopy(pmp); } /* Now we need to tell this player who's already here, and tell the others about this one */ thisplayer.type = CMsgPlayer; thisplayer.id = p->id; thisplayer.name = p->name; for ( i = 0; i < NUM_SEATS; i++ ) { CMsgPlayerMsg pm; PlayerP q; q = the_game->players[i]; if ( q->id == 0 ) continue; if ( q->id == p->id ) continue; pm.type = CMsgPlayer; pm.id = q->id; pm.name = q->name; send_packet(cnx,(CMsgMsg *)&pm,1); send_id(q->id,&thisplayer); /* fails harmlessly if not connected */ } /* set the protocol version to the greatest supported version. We inspect all players again in case somebody disconnected before the game was set up */ protocol_version = PROTOCOL_VERSION; for ( i = 0; i < NUM_SEATS ; i++ ) { PlayerP q = the_game->players[i]; if ( q->id == 0 ) continue; if ( protocol_version > pextras(q)->protversion ) protocol_version = pextras(q)->protversion; } /* if not everybody is connected, just wait for the next */ if ( num_connected_players < NUM_SEATS ) return; /* otherwise, set up the game state (if we aren't already in the middle of an interrupted game */ if ( ! loadstate ) { GameOptionEntry goe; /* randomize the seats if desired */ if ( randomseats ) { int i,j,n; PlayerP temp[NUM_SEATS]; for (i=0; i<NUM_SEATS; i++) temp[i] = the_game->players[i]; for (i=0; i<NUM_SEATS; i++) { /* random number between 0 and seatsleft-1 */ n = rand_index(NUM_SEATS-(i+1)); /* skip already used */ j = 0; while ( (!temp[j]) || j < n ) { if ( !temp[j] ) n++; j++; } the_game->players[i] = temp[j]; temp[j] = NULL; } } the_game->state = HandComplete; the_game->player = noseat; the_game->round = EastWind; the_game->hands_as_east = 0; the_game->firsteast = the_game->players[east]->id; /* protocol_version has been maintained during the connection process */ the_game->protversion = protocol_version; the_game->manager = nomanager ? -1 : first_id; game_set_options_from_defaults(the_game); /* initialize the timeout time from the command line option */ goe = *game_get_option_entry(the_game,GOTimeout,NULL); goe.value.optint = timeout_time; game_set_option(the_game,&goe); /* And I think we will have the normal dead wall as default, rather than millington style */ goe = *game_get_option_entry(the_game,GODeadWall16,NULL); if ( goe.enabled ) { goe.value.optbool = 0; game_set_option(the_game,&goe); } /* Now load the game option file, if there is one */ if ( optfilename ) { FILE *optfile; char buf[1024]; optfile = fopen(optfilename,"r"); if ( optfile ) { CMsgMsg *m; while ( ! feof(optfile) ) { fgets(buf,1024,optfile); m = decode_cmsg(buf); if ( ! m ) { warn("Error decoding game option file entry %s",buf); } else { if ( handle_cmsg(the_game,m) < 0 ) { warn("Error applying game option (%s)",the_game->cmsg_err); } cmsg_deepfree(m); } } } else { warn("couldn't open game option file %s (%s)", optfilename,strerror(errno)); } } } the_game->cmsg_check = 1; gamemsg.type = CMsgGame; gamemsg.east = the_game->players[east]->id; gamemsg.south = the_game->players[south]->id; gamemsg.west = the_game->players[west]->id; gamemsg.north = the_game->players[north]->id; gamemsg.round = the_game->round; gamemsg.hands_as_east = the_game->hands_as_east; gamemsg.firsteast = the_game->firsteast; gamemsg.east_score = the_game->players[east]->cumulative_score; gamemsg.south_score = the_game->players[south]->cumulative_score; gamemsg.west_score = the_game->players[west]->cumulative_score; gamemsg.north_score = the_game->players[north]->cumulative_score; gamemsg.protversion = the_game->protversion; gamemsg.manager = the_game->manager; /* we only send the game message to the players who have been disconnected. (In the usual case, this is all players...) */ for ( i=0 ; i < NUM_SEATS; i++ ) { PlayerP p = the_game->players[i]; if ( ! pextras(p)->disconnected ) continue; send_id(p->id,&gamemsg); /* we don't actually clear the history records until the NewHand message is sent. But we don't want to send history for a complete hand. However, if the game is paused, we need to tell the player so. */ if ( the_game->state == HandComplete ) { if ( the_game->paused ) { CMsgPauseMsg pm; CMsgPlayerReadyMsg prm; int i; pm.type = CMsgPause; pm.exempt = 0; pm.requestor = 0; pm.reason = the_game->paused; send_id(p->id,&pm); prm.type = CMsgPlayerReady; for ( i = 0; i < NUM_SEATS; i++ ) { if ( the_game->ready[i] ) { prm.id = the_game->players[i]->id; send_id(p->id,&prm); } } } } } /* now we need to send the history to disconnected players.*/ if ( the_game->state != HandComplete ) { int j; if ( ! usehist ) { CMsgErrorMsg em; em.type = CMsgError; em.seqno = connections[cnx].seqno; em.error = "No history kept: resumption not supported"; send_all(the_game,&em);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -