⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 controller.c

📁 支持网络和单机的麻将游戏
💻 C
📖 第 1 页 / 共 5 页
字号:
/* $Header: /home/jcb/newmj/RCS/controller.c,v 11.42 2003/09/29 23:29:54 jcb Rel $ * controller.c * This program implements the master controller, which accepts * connections from players and runs the game. * At present, this is designed around the assumption that it is * only running one game. Realistically, I see no immediate need * to be more general. However, I trust that nothing in the design * would make that awkward. *//****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield.       * * Distribution and use is governed by the LICENCE file that * * accompanies this file.                                    * * The moral rights of the author are asserted.              * *                                                           * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the   * * LICENCE file for further information.                     * *                                                           * *************************************************************/static const char rcs_id[] = "$Header: /home/jcb/newmj/RCS/controller.c,v 11.42 2003/09/29 23:29:54 jcb Rel $";#include <stdio.h>#include <assert.h>#include <errno.h>#include <fcntl.h>#include <sys/stat.h>#include "controller.h"#include "scoring.h"#include "sysdep.h"#include "version.h"/* extra data in players */typedef struct {  /* option settings */  int options[PONumOptions];  /* this variable is set to one to indicate that the player     has been disconnected or otherwise out of touch, and therefore needs     to be told the current state of the game. */  int disconnected;  int protversion; /* protocol version supported by this player */  int localtimeouts; /* 1 if this player has requested LocalTimeouts */} PlayerExtras;#define popts(p) ((PlayerExtras *)(p->userdata))->options#define pextras(p) ((PlayerExtras *)(p->userdata))/* this is The Game that we will use */Game *the_game = NULL;int num_connected_players;#define NUM_PLAYERS 4int first_id = 0; /* id assigned to first player to connect *//* This array stores information about current connections.   When a new connection arrives, we use the first free slot.*/#define MAX_CONNECTIONS 8struct {  int inuse; /* slot in use */  SOCKET skt; /* the system level socket of this connection. */  PlayerP player; /* player, if any */  int seqno; /* sequence number of messages on this connection */  PMsgMsg *cm; /* stored copy of the player's connect message */} connections[MAX_CONNECTIONS];/* This is the fd of the socket on which we listen */SOCKET socket_fd;/* This is the master set of fds on which we are listening   for events. If we're on some foul system that doesn't have   this type, it is sysdep.h's responsibility to make sure we do.*/fd_set event_fds;/* this is used by auxiliary functions to pass back an error message */char *failmsg;/* This is a logfile, if specified */FILE *logfile = NULL;/* This is the name of a game option file */char *optfilename = NULL;/* forward declarations */static void despatch_line(int cnx, char *line);static int close_connection(int cnx);static int new_connection(SOCKET skt); /* returns new connection number */static void setup_maps(int cnx, PlayerP p);static void remove_from_maps(int cnx);static void send_packet(int cnx,CMsgMsg *m, int logit); /* not much used except in next two */static void _send_id(int id,CMsgMsg *m, int logit);static void _send_all(Game *g,CMsgMsg *m);static void _send_others(Game *g, int id,CMsgMsg *m);static void _send_seat(Game *g, seats s, CMsgMsg *m);static void _send_other_seats(Game *g, seats s, CMsgMsg *m);/* always casting is boring, so ... *//* send_id logs (if logging is enabled */#define send_id(id,m) _send_id(id,(CMsgMsg *)(m),1)#define send_all(g,m) _send_all(g,(CMsgMsg *)(m))#define send_others(g,id,m) _send_others(g,id,(CMsgMsg *)(m))#define send_seat(g,s,m) _send_seat(g,s,(CMsgMsg *)(m))#define send_other_seats(g,s,m) _send_other_seats(g,s,(CMsgMsg *)(m))static void resend(int id,CMsgMsg *m);static int load_state(Game *g);static char *save_state(Game *g, char *filename);static void save_and_exit(void); /* save if save-on-exit, and quit */static int load_wall(char *wfname, Game *g);static int id_to_cnx(int id);static int cnx_to_id(int cnx);static void clear_history(Game *g);static void history_add(Game *g, CMsgMsg *m);/* N.B. we do not use the game_id_to_player function, since   we don't always have players in a game.*/static PlayerP id_to_player(int id);/* A convenience definition. */#define id_to_seat(id) game_id_to_seat(the_game,id)/* and another */#define handle_cmsg(g,m) game_handle_cmsg(g,(CMsgMsg *)m)static int start_hand(Game *g);static void check_claims(Game *g);static void send_infotiles(PlayerP p);static void score_hand(Game *g, seats s);static void washout(char *reason);static void handle_cnx_error(int cnx);static int loadstate = 0; /* if we have to load state */static int randomseats = 0; /* assign seats randomly */static int noidrequired = 0; /* allow arbitrary joining on resumption */static int nomanager = 0; /* forbid managers */static char *wallfilename = NULL; /*file to load wall from*/static char loadfilename[1024]; /* file to load game from */static int debug = 0; /* allow debugging messages */static int usehist = 1; /* if we keep history to allow resumption */static int end_on_disconnect = 0; /* end game (gracefully) on disconnect *//* penalties for disconnection */static int disconnect_penalty_end_of_round = 0;static int disconnect_penalty_end_of_hand = 0;static int disconnect_penalty_in_hand = 0;static int exit_on_disconnect = 0; /* if we die on losing a player */static int save_on_exit = 0; /* save on exit other than at end of game */static int localtimeouts = 0; /* are we using local timeouts ? */static void usage(char *pname,char *msg) {  fprintf(stderr,"%s: %s\nUsage: %s [ --server ADDR ]""  [ --timeout N ]\n""  [ --pause N ]\n""  [ --random-seats ]\n""  [ --debug ]\n""  [ --disconnect-penalties N1,N2,N3 ]\n""  [ --end-on-disconnect ]\n""  [ --exit-on-disconnect ]\n""  [ --save-on-exit ]\n""  [ --option-file FILE ]\n""  [ --load-game FILE ]\n""  [ --no-id-required ]\n""  [ --no-manager ]\n""  [ --logfile FILE]\n""  [ --no-special-scores ]\n""  [ --seed N ]\n""  [ --wallfile FILE ]\n""  [ --nohist ]\n",	  pname,msg,pname);  exit(1);}/* This global is used to specify a timeout for claims   in milliseconds. If it is non-zero on entry to the select   call, then it will be used as the timeout for select; and if   data is read before timeout, it will be adjusted appropriately   for the next call. If a select expires due to timeout, then   the timeout_handler is called. */static void timeout_handler(Game *g);static int timeout = 0;/* this is the user level timeout in seconds.*/static int timeout_time = 15;/* this function extracts the timeout_time from a game   according the Timeout and TimeoutGrace options, and   the passed in value of localtimeouts */static int get_timeout_time(Game *g,int localtimeouts);/* this is the minimum time between discards and draws,   keep the game down to human speed */static int min_time = 0; /* deciseconds *//* and this is the value requested on the command line */static int min_time_opt = 0;/* A player has disconnected, and we're cleaning up the   mess. Used to keep the game going during MahJonging */static int end_on_disconnect_in_progress = 0;/* This is for a hack: if we get two savestate requests while   a hand is completed, on the second one we save the history of    the hand, rather than just the game state. So this variable   gets incremented when save_state is called in handcomplete,   and is reset by start_hand.*/static int save_state_hack = 0;int main(int argc, char *argv[]) {  char *address = ":5000";  char *logfilename = NULL;  int seed = 0;  int nfds;   struct timeval timenow, timethen, timetimeout;  fd_set rfds, efds;  int i;  /* options. I should use getopt ... */  for (i=1;i<argc;i++) {    if ( strcmp(argv[i],"--nohist") == 0 ) {      usehist = 0;    } else if ( strcmp(argv[i],"--debug") == 0 ) {      debug = 1;    } else if ( strcmp(argv[i],"--random-seats") == 0 ) {      randomseats = 1;    } else if ( strcmp(argv[i],"--no-id-required") == 0 ) {      noidrequired = 1;    } else if ( strcmp(argv[i],"--no-manager") == 0 ) {      nomanager = 1;    } else if ( strcmp(argv[i],"--disconnect-penalties") == 0 ) {      if ( ++i == argc ) usage(argv[0],"missing argument to --disconnect-penalties");      sscanf(argv[i],"%d,%d,%d",	&disconnect_penalty_in_hand,	&disconnect_penalty_end_of_hand,	&disconnect_penalty_end_of_round);    } else if ( strcmp(argv[i],"--end-on-disconnect") == 0 ) {      end_on_disconnect = 1;    } else if ( strcmp(argv[i],"--exit-on-disconnect") == 0 ) {      exit_on_disconnect = 1;    } else if ( strcmp(argv[i],"--save-on-exit") == 0 ) {      save_on_exit = 1;    } else if ( strcmp(argv[i],"--no-special-scores") == 0 ) {      no_special_scores = 1;    } else if ( strcmp(argv[i],"--address") == 0 ) {      /* N.B. the --address option is retained only for backward compatibility */      if ( ++i == argc ) usage(argv[0],"missing argument to --address");      address = argv[i];    } else if ( strcmp(argv[i],"--server") == 0 ) {      if ( ++i == argc ) usage(argv[0],"missing argument to --server");      address = argv[i];    } else if ( strcmp(argv[i],"--timeout") == 0 ) {      if ( ++i == argc ) usage(argv[0],"missing argument to --timeout");      timeout_time = atoi(argv[i]);    } else if ( strcmp(argv[i],"--pause") == 0 ) {      if ( ++i == argc ) usage(argv[0],"missing argument to --pause");      min_time_opt = min_time = atoi(argv[i]); /* N.B. Time in deciseconds */    } else if ( strcmp(argv[i],"--seed") == 0 ) {      if ( ++i == argc ) usage(argv[0],"missing argument to --seed");      seed = atoi(argv[i]);    } else if ( strcmp(argv[i],"--logfile") == 0 ) {      if ( ++i == argc ) usage(argv[0],"missing argument to --logfile");      logfilename = argv[i];    } else if ( strcmp(argv[i],"--option-file") == 0 ) {      if ( ++i == argc ) usage(argv[0],"missing argument to --option-file");      optfilename = argv[i];    } else if ( strcmp(argv[i],"--load-game") == 0 ) {      if ( ++i == argc ) usage(argv[0],"missing argument to --load-game");      strmcpy(loadfilename,argv[i],1023);      loadstate = 1;    } else if ( strcmp(argv[i],"--wallfile") == 0 ) {      if ( ++i == argc ) usage(argv[0],"missing argument to --wallfile");      wallfilename = argv[i];    } else usage(argv[0],"unknown option or argument");  }  if ( logfilename ) {    if ( strcmp(logfilename,"-") == 0 ) {      logfile = stdout;    } else {      logfile = fopen(logfilename,"a");      if ( ! logfile ) {	perror("couldn't open logfile");	exit(1);      }    }  }  /* we don't want to get SIGPIPE */  ignore_sigpipe();  socket_fd = set_up_listening_socket(address);  if ( socket_fd == INVALID_SOCKET ) {    printf("FAILED: couldn't set up socket\n");    perror("couldn't set up listening socket");    exit(1);  }  printf("OK: %s\n",address);  fflush(stdout);  /* stick it in the connection list */  new_connection(socket_fd);    /* seed the random number generator */  rand_seed(seed);  /* initialize data */  num_connected_players = 0;  /* malloc space for the game and four players.     We will shortly load the state if we're resuming; if we're not     resuming, initialization happens later */  the_game = (Game *) malloc(sizeof(Game));  if ( the_game == NULL ) {    warn("Couldn't malloc game structure");    exit(1);  }  memset((void *)the_game,0,sizeof(Game));  the_game->userdata = malloc(sizeof(GameExtras));  if ( the_game->userdata == NULL ) {    warn("Couldn't malloc game extra structure");    exit(1);  }  gextras(the_game)->histcount = gextras(the_game)->prehistcount = 0;  gextras(the_game)->caller = (PlayerP) malloc(sizeof(Player));  if ( gextras(the_game)->caller == NULL ) {    warn("Couldn't malloc game extra structure");    exit(1);  }  for ( i=0 ; i < NUM_SEATS ; i++ ) {    void *tmp;    if ( (the_game->players[i] = (PlayerP) malloc(sizeof(Player)))	 == NULL ) {      warn("couldn't malloc player structure");      exit(1);    }    memset((void *)the_game->players[i],0,sizeof(Player));    if ( (tmp = malloc(sizeof(PlayerExtras)))	 == NULL ) {      warn("couldn't malloc player options");      exit(1);    }    memset((void *)tmp,0,sizeof(PlayerExtras));    set_player_userdata(the_game->players[i],tmp);  }  /* some game initialization happens here */  the_game->protversion = PROTOCOL_VERSION; /* may be reduced by players */  game_set_options_from_defaults(the_game);  if ( loadstate ) {    if ( load_state(the_game) == 0 ) {      warn("Couldn't load the game state");      loadstate = 0;    }  }  /* enter the main loop */  while ( 1 ) {    struct timeval *tvp;    rfds = efds = event_fds;    tvp = NULL;    if ( the_game->active && !the_game->paused && timeout > 0 ) {      gettimeofday(&timethen,NULL);      tvp = &timetimeout;      tvp->tv_sec = timeout/1000; tvp->tv_usec = (timeout%1000)*1000;    }          nfds = select(32,&rfds,NULL,&efds,tvp); /* n, read, write, except, timeout */    if ( tvp ) {      gettimeofday(&timenow,NULL);      timeout -= (timenow.tv_sec*1000 + timenow.tv_usec/1000)	- (timethen.tv_sec*1000 + timethen.tv_usec/1000);    }    if ( nfds < 0 ) {      perror("select failed");      exit(1);    }    /* if the timeout has gone negative, that means that something       is throwing input at us so fast we never get round to cancelling       the timeout. This probably means something is in a loop. So       call the timeout handler anyway */    if ( nfds == 0 || timeout < 0 ) {      timeout_handler(the_game);      continue;    }    for ( i = 0 ; i < MAX_CONNECTIONS ; i++ ) {      SOCKET fd;      if ( ! connections[i].inuse ) continue;      fd = connections[i].skt;      if ( FD_ISSET(fd,&efds) ) {	handle_cnx_error(i);	continue;      }      if ( FD_ISSET(fd,&rfds) ) {	if ( fd == socket_fd ) {	  SOCKET newfd;          newfd = accept_new_connection(fd);	  if (newfd == INVALID_SOCKET) {	    warn("failure in accepting connection");	    exit(1);	  }	  /* if we are shutting down after a disconnect,	     accept no more connections */	  if ( end_on_disconnect_in_progress ) {	    CMsgErrorMsg em;	    em.type = CMsgError;	    em.seqno = 0;	    em.error = "Game shutting down; no reconnect allowed";	    put_line(newfd,encode_cmsg((CMsgMsg *)&em));	    closesocket(newfd);	  } else	  /* add to the connection list */	  if ( new_connection(newfd) < 0) {	    CMsgErrorMsg em;	    em.type = CMsgError;	    em.seqno = 0;	    em.error = "Unable to accept new connections";	    put_line(newfd,encode_cmsg((CMsgMsg *)&em));	    closesocket(newfd);	  }	}	else {	  char *line;	  /* get_line will fail if there is only a partial line	     available. This is wrong, since it is possible for	     lines to arrive fragmentedly (not that it should 	     normally happen). However, it would also be wrong to	     wait forever for a partial line to complete. There	     should be a timeout.	  */	  /* get_line provides us with a pointer to the text line,	     which we can mess with as we like, provided we don't	     expect it to survive the next call to get_line	  */	  line = get_line(connections[i].skt); /* errors dealt with by despatch line */	  /* except that we want to know the error code */	  if ( line == NULL ) {	    info("get_line returned null: %s",strerror(errno));	  }

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -