1 /* $Header: /home/jcb/MahJong/newmj/RCS/controller.c,v 12.10 2020/05/16 13:23:44 jcb Exp $
2  * controller.c
3  * This program implements the master controller, which accepts
4  * connections from players and runs the game.
5  * At present, this is designed around the assumption that it is
6  * only running one game. Realistically, I see no immediate need
7  * to be more general. However, I trust that nothing in the design
8  * would make that awkward.
9  */
10 /****************** COPYRIGHT STATEMENT **********************
11  * This file is Copyright (c) 2000 by J. C. Bradfield.       *
12  * Distribution and use is governed by the LICENCE file that *
13  * accompanies this file.                                    *
14  * The moral rights of the author are asserted.              *
15  *                                                           *
16  ***************** DISCLAIMER OF WARRANTY ********************
17  * This code is not warranted fit for any purpose. See the   *
18  * LICENCE file for further information.                     *
19  *                                                           *
20  *************************************************************/
21 
22 static const char rcs_id[] = "$Header: /home/jcb/MahJong/newmj/RCS/controller.c,v 12.10 2020/05/16 13:23:44 jcb Exp $";
23 
24 #include <stdio.h>
25 #include <assert.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <sys/stat.h>
29 #include "controller.h"
30 #include "scoring.h"
31 
32 #include "sysdep.h"
33 
34 #include "version.h"
35 
36 /* extra data in players */
37 typedef struct {
38   /* option settings */
39   int options[PONumOptions];
40   /* this variable is set to one to indicate that the player
41      has been disconnected or otherwise out of touch, and therefore needs
42      to be told the current state of the game. */
43   int disconnected;
44   int auth_state; /* authorization state */
45 #define AUTH_NONE 0
46 #define AUTH_PENDING 1
47 #define AUTH_DONE 2
48   char *auth_data; /* point to relevant auth data */
49   int protversion; /* protocol version supported by this player */
50   int localtimeouts; /* 1 if this player has requested LocalTimeouts */
51 } PlayerExtras;
52 
53 #define popts(p) ((PlayerExtras *)(p->userdata))->options
54 #define pextras(p) ((PlayerExtras *)(p->userdata))
55 
56 /* authorization data (basic only) */
57 typedef struct {
58   int id;
59   char passwd[16];
60 } authinfo;
61 
62 #define AUTH_NUM 4
63 authinfo auth_info[AUTH_NUM];
64 
65 /* this is The Game that we will use */
66 
67 Game *the_game = NULL;
68 
69 int num_connected_players;
70 #define NUM_PLAYERS 4
71 
72 int first_id = 0; /* id assigned to first player to connect */
73 
74 /* This array stores information about current connections.
75    When a new connection arrives, we use the first free slot.
76 */
77 #define MAX_CONNECTIONS 8
78 struct {
79   int inuse; /* slot in use */
80   SOCKET skt; /* the system level socket of this connection. */
81   PlayerP player; /* player, if any */
82   int seqno; /* sequence number of messages on this connection */
83   PMsgMsg *cm; /* stored copy of the player's connect message */
84 } connections[MAX_CONNECTIONS];
85 
86 
87 /* This is the fd of the socket on which we listen */
88 SOCKET socket_fd;
89 
90 /* This is the master set of fds on which we are listening
91    for events. If we're on some foul system that doesn't have
92    this type, it is sysdep.h's responsibility to make sure we do.
93 */
94 
95 fd_set event_fds;
96 
97 /* this is used by auxiliary functions to pass back an error message */
98 char *failmsg;
99 
100 /* This is a logfile, if specified */
101 FILE *logfile = NULL;
102 
103 /* This is the name of a game option file */
104 char *optfilename = NULL;
105 
106 /* forward declarations */
107 static void despatch_line(int cnx, char *line);
108 static int close_connection(int cnx);
109 static int new_connection(SOCKET skt); /* returns new connection number */
110 static void setup_maps(int cnx, PlayerP p);
111 static void remove_from_maps(int cnx);
112 /* if logit is 2, print with cnx* to indicate being sent to all players */
113 static void send_packet(int cnx,CMsgMsg *m, int logit); /* not much used except in next two */
114 static void _send_id(int id,CMsgMsg *m, int logit);
115 static void _send_all(Game *g,CMsgMsg *m);
116 static void _send_others(Game *g, int id,CMsgMsg *m);
117 static void _send_seat(Game *g, seats s, CMsgMsg *m);
118 static void _send_other_seats(Game *g, seats s, CMsgMsg *m);
119 /* always casting is boring, so ... */
120 /* send_id logs (if logging is enabled */
121 #define send_id(id,m) _send_id(id,(CMsgMsg *)(m),1)
122 #define send_all(g,m) _send_all(g,(CMsgMsg *)(m))
123 #define send_others(g,id,m) _send_others(g,id,(CMsgMsg *)(m))
124 #define send_seat(g,s,m) _send_seat(g,s,(CMsgMsg *)(m))
125 #define send_other_seats(g,s,m) _send_other_seats(g,s,(CMsgMsg *)(m))
126 static void resend(int id,CMsgMsg *m);
127 static int load_state(Game *g);
128 static char *save_state(Game *g, char *filename);
129 static void save_and_exit(void); /* save if save-on-exit, and quit */
130 static int load_wall(char *wfname, Game *g);
131 static int id_to_cnx(int id);
132 static int cnx_to_id(int cnx);
133 static void clear_history(Game *g);
134 static void history_add(Game *g, CMsgMsg *m);
135 
136 /* N.B. we do not use the game_id_to_player function, since
137    we don't always have players in a game.
138 */
139 static PlayerP id_to_player(int id);
140 
141 /* A convenience definition. */
142 #define id_to_seat(id) game_id_to_seat(the_game,id)
143 /* and another */
144 #define handle_cmsg(g,m) game_handle_cmsg(g,(CMsgMsg *)m)
145 
146 static int start_hand(Game *g, int rotate_only);
147 static void check_claims(Game *g);
148 static void send_infotiles(PlayerP p);
149 static void score_hand(Game *g, seats s);
150 static void washout(char *reason);
151 static void handle_cnx_error(int cnx);
152 
153 static int hand_history = 0; /* dump history file for each hand */
154 static int loadstate = 0; /* if we have to load state */
155 /* order in which to seat players */
156 typedef enum {
157   SODefault = 0,
158   SORandom = 1,
159   SOIdOrder = 2,
160 } SeatingOrder;
161 static SeatingOrder seating_order = SODefault;
162 static int noidrequired = 0; /* allow arbitrary joining on resumption */
163 static int nomanager = 0; /* forbid managers */
164 static char *wallfilename = NULL; /*file to load wall from*/
165 static char loadfilename[1024]; /* file to load game from */
166 static int debug = 0; /* allow debugging messages */
167 static int usehist = 1; /* if we keep history to allow resumption */
168 static int end_on_disconnect = 0; /* end game (gracefully) on disconnect */
169 /* penalties for disconnection */
170 static int disconnect_penalty_end_of_round = 0;
171 static int disconnect_penalty_end_of_hand = 0;
172 static int disconnect_penalty_in_hand = 0;
173 static int exit_on_disconnect = 0; /* if we die on losing a player */
174 static int save_on_exit = 0; /* save on exit other than at end of game */
175 static int localtimeouts = 0; /* are we using local timeouts ? */
176 
177 static int game_over = 0; /* we hang around so players can still exchange
178 			     messages, and quit on first disconnect */
179 
usage(char * pname,char * msg)180 static void usage(char *pname,char *msg) {
181   fprintf(stderr,"%s: %s\nUsage: %s [ --server ADDR ]"
182 "  [ --timeout N ]\n"
183 "  [ --pause N ]\n"
184 "  [ --random-seats | --id-order-seats ]\n"
185 "  [ --debug ]\n"
186 "  [ --disconnect-penalties N1,N2,N3 ]\n"
187 "  [ --end-on-disconnect ]\n"
188 "  [ --exit-on-disconnect ]\n"
189 "  [ --save-on-exit ]\n"
190 "  [ --option-file FILE ]\n"
191 "  [ --load-game FILE ]\n"
192 "  [ --no-id-required ]\n"
193 "  [ --auth-basic AUTHINFO ]\n"
194 "  [ --no-manager ]\n"
195 "  [ --logfile FILE]\n"
196 "  [ --hand-history ]\n"
197 "  [ --no-special-scores ]\n"
198 "  [ --seed N ]\n"
199 "  [ --wallfile FILE ]\n"
200 "  [ --nohist ]\n",
201 	  pname,msg,pname);
202   exit(1);
203 }
204 
205 /* This global is used to specify a timeout for claims
206    in milliseconds. If it is non-zero on entry to the select
207    call, then it will be used as the timeout for select; and if
208    data is read before timeout, it will be adjusted appropriately
209    for the next call. If a select expires due to timeout, then
210    the timeout_handler is called. */
211 static void timeout_handler(Game *g);
212 static int timeout = 0;
213 /* this is the user level timeout in seconds.*/
214 static int timeout_time = 15;
215 /* this function extracts the timeout_time from a game
216    according the Timeout and TimeoutGrace options, and
217    the passed in value of localtimeouts */
218 static int get_timeout_time(Game *g,int localtimeouts);
219 /* this is the minimum time between discards and draws,
220    keep the game down to human speed */
221 static int min_time = 0; /* deciseconds */
222 /* and this is the value requested on the command line */
223 static int min_time_opt = 0;
224 /* A player has disconnected, and we're cleaning up the
225    mess. Used to keep the game going during MahJonging */
226 static int end_on_disconnect_in_progress = 0;
227 /* This is for a hack: if we get two savestate requests while
228    a hand is completed, on the second one we save the history of
229    the hand, rather than just the game state. So this variable
230    gets incremented when save_state is called in handcomplete,
231    and is reset by start_hand.
232 */
233 static int save_state_hack = 0;
234 
235 
main(int argc,char * argv[])236 int main(int argc, char *argv[]) {
237   char *address = ":5000";
238   char *logfilename = NULL;
239   int seed = 0;
240   int nfds;
241   struct timeval timenow, timethen, timetimeout;
242   fd_set rfds, efds;
243   int i;
244 
245   /* clear auth_info */
246   for ( i=0; i<AUTH_NUM; i++ ) {
247     auth_info[i].id = 0;
248     auth_info[i].passwd[0] = 0;
249   }
250 
251   /* options. I should use getopt ... */
252   for (i=1;i<argc;i++) {
253     if ( strcmp(argv[i],"--nohist") == 0 ) {
254       usehist = 0;
255     } else if ( strcmp(argv[i],"--debug") == 0 ) {
256       debug = 1;
257     } else if ( strcmp(argv[i],"--random-seats") == 0 ) {
258       seating_order = SORandom;
259     } else if ( strcmp(argv[i],"--id-order-seats") == 0 ) {
260       seating_order = SOIdOrder;
261     } else if ( strcmp(argv[i],"--no-id-required") == 0 ) {
262       noidrequired = 1;
263     } else if ( strcmp(argv[i],"--no-manager") == 0 ) {
264       nomanager = 1;
265     } else if ( strcmp(argv[i],"--disconnect-penalties") == 0 ) {
266       if ( ++i == argc ) usage(argv[0],"missing argument to --disconnect-penalties");
267       sscanf(argv[i],"%d,%d,%d",
268 	&disconnect_penalty_in_hand,
269 	&disconnect_penalty_end_of_hand,
270 	&disconnect_penalty_end_of_round);
271     } else if ( strcmp(argv[i],"--end-on-disconnect") == 0 ) {
272       end_on_disconnect = 1;
273     } else if ( strcmp(argv[i],"--exit-on-disconnect") == 0 ) {
274       exit_on_disconnect = 1;
275     } else if ( strcmp(argv[i],"--save-on-exit") == 0 ) {
276       save_on_exit = 1;
277     } else if ( strcmp(argv[i],"--no-special-scores") == 0 ) {
278       no_special_scores = 1;
279     } else if ( strcmp(argv[i],"--hand-history") == 0 ) {
280       hand_history = 1 ;
281     } else if ( strcmp(argv[i],"--auth-basic") == 0 ) {
282       int j = 0;
283       while ( auth_info[j].id != 0 && j < AUTH_NUM ) j++;
284       if ( j == AUTH_NUM ) usage(argv[0], "too many --auth-basic's");
285       if ( ++i == argc ) usage(argv[0],"missing argument to --auth-basic");
286       sscanf(argv[i],"%d:%15s",&auth_info[j].id,auth_info[j].passwd);
287     } else if ( strcmp(argv[i],"--address") == 0 ) {
288       /* N.B. the --address option is retained only for backward compatibility */
289       if ( ++i == argc ) usage(argv[0],"missing argument to --address");
290       address = argv[i];
291     } else if ( strcmp(argv[i],"--server") == 0 ) {
292       if ( ++i == argc ) usage(argv[0],"missing argument to --server");
293       address = argv[i];
294     } else if ( strcmp(argv[i],"--timeout") == 0 ) {
295       if ( ++i == argc ) usage(argv[0],"missing argument to --timeout");
296       timeout_time = atoi(argv[i]);
297     } else if ( strcmp(argv[i],"--pause") == 0 ) {
298       if ( ++i == argc ) usage(argv[0],"missing argument to --pause");
299       min_time_opt = min_time = atoi(argv[i]); /* N.B. Time in deciseconds */
300     } else if ( strcmp(argv[i],"--seed") == 0 ) {
301       if ( ++i == argc ) usage(argv[0],"missing argument to --seed");
302       seed = atoi(argv[i]);
303     } else if ( strcmp(argv[i],"--logfile") == 0 ) {
304       if ( ++i == argc ) usage(argv[0],"missing argument to --logfile");
305       logfilename = argv[i];
306     } else if ( strcmp(argv[i],"--option-file") == 0 ) {
307       if ( ++i == argc ) usage(argv[0],"missing argument to --option-file");
308       optfilename = argv[i];
309     } else if ( strcmp(argv[i],"--load-game") == 0 ) {
310       if ( ++i == argc ) usage(argv[0],"missing argument to --load-game");
311       strmcpy(loadfilename,argv[i],1023);
312       loadstate = 1;
313     } else if ( strcmp(argv[i],"--wallfile") == 0 ) {
314       if ( ++i == argc ) usage(argv[0],"missing argument to --wallfile");
315       wallfilename = argv[i];
316     } else usage(argv[0],"unknown option or argument");
317   }
318 
319   if ( logfilename ) {
320     if ( strcmp(logfilename,"-") == 0 ) {
321       logfile = stdout;
322     } else {
323       logfile = fopen(logfilename,"a");
324       if ( ! logfile ) {
325 	perror("couldn't open logfile");
326 	exit(1);
327       }
328     }
329   }
330 
331   /* we don't want to get SIGPIPE */
332   ignore_sigpipe();
333 
334   socket_fd = set_up_listening_socket(address);
335   if ( socket_fd == INVALID_SOCKET ) {
336     printf("FAILED: couldn't set up socket\n");
337     perror("couldn't set up listening socket");
338     exit(1);
339   }
340   printf("OK: %s\n",address);
341   fflush(stdout);
342 
343   /* stick it in the connection list */
344   new_connection(socket_fd);
345 
346   /* seed the random number generator */
347   rand_seed(seed);
348 
349   /* initialize data */
350   num_connected_players = 0;
351 
352   /* malloc space for the game and four players.
353      We will shortly load the state if we're resuming; if we're not
354      resuming, initialization happens later */
355   the_game = (Game *) malloc(sizeof(Game));
356   if ( the_game == NULL ) {
357     warn("Couldn't malloc game structure");
358     exit(1);
359   }
360   memset((void *)the_game,0,sizeof(Game));
361   the_game->userdata = malloc(sizeof(GameExtras));
362   if ( the_game->userdata == NULL ) {
363     warn("Couldn't malloc game extra structure");
364     exit(1);
365   }
366   gextras(the_game)->histcount = gextras(the_game)->prehistcount = 0;
367   gextras(the_game)->caller = (PlayerP) malloc(sizeof(Player));
368   if ( gextras(the_game)->caller == NULL ) {
369     warn("Couldn't malloc game extra structure");
370     exit(1);
371   }
372   gextras(the_game)->completed_rounds = 0;
373 
374   for ( i=0 ; i < NUM_SEATS ; i++ ) {
375     void *tmp;
376     if ( (the_game->players[i] = (PlayerP) malloc(sizeof(Player)))
377 	 == NULL ) {
378       warn("couldn't malloc player structure");
379       exit(1);
380     }
381     memset((void *)the_game->players[i],0,sizeof(Player));
382     if ( (tmp = malloc(sizeof(PlayerExtras)))
383 	 == NULL ) {
384       warn("couldn't malloc player options");
385       exit(1);
386     }
387     memset((void *)tmp,0,sizeof(PlayerExtras));
388     set_player_userdata(the_game->players[i],tmp);
389   }
390 
391   /* some game initialization happens here */
392   the_game->protversion = PROTOCOL_VERSION; /* may be reduced by players */
393   game_set_options_from_defaults(the_game);
394 
395   if ( loadstate ) {
396     if ( load_state(the_game) == 0 ) {
397       warn("Couldn't load the game state");
398       loadstate = 0;
399     }
400   }
401 
402   /* enter the main loop */
403   while ( 1 ) {
404     struct timeval *tvp;
405     rfds = efds = event_fds;
406 
407     tvp = NULL;
408     if ( the_game->active && !the_game->paused && timeout > 0 ) {
409       gettimeofday(&timethen,NULL);
410       tvp = &timetimeout;
411       tvp->tv_sec = timeout/1000; tvp->tv_usec = (timeout%1000)*1000;
412     }
413 
414     nfds = select(32,&rfds,NULL,&efds,tvp); /* n, read, write, except, timeout */
415 
416     if ( tvp ) {
417       gettimeofday(&timenow,NULL);
418       timeout -= (timenow.tv_sec*1000 + timenow.tv_usec/1000)
419 	- (timethen.tv_sec*1000 + timethen.tv_usec/1000);
420     }
421 
422     if ( nfds < 0 ) {
423       perror("select failed");
424       exit(1);
425     }
426 
427     /* if the timeout has gone negative, that means that something
428        is throwing input at us so fast we never get round to cancelling
429        the timeout. This probably means something is in a loop. So
430        call the timeout handler anyway */
431 
432     if ( nfds == 0 || timeout < 0 ) {
433       timeout_handler(the_game);
434       continue;
435     }
436 
437     for ( i = 0 ; i < MAX_CONNECTIONS ; i++ ) {
438       SOCKET fd;
439       if ( ! connections[i].inuse ) continue;
440       fd = connections[i].skt;
441 
442       if ( FD_ISSET(fd,&efds) ) {
443 	handle_cnx_error(i);
444 	continue;
445       }
446       if ( FD_ISSET(fd,&rfds) ) {
447 	if ( fd == socket_fd ) {
448 	  SOCKET newfd;
449 
450           newfd = accept_new_connection(fd);
451 	  if (newfd == INVALID_SOCKET) {
452 	    warn("failure in accepting connection");
453 	    exit(1);
454 	  }
455 	  /* if we are shutting down after a disconnect,
456 	     accept no more connections */
457 	  if ( end_on_disconnect_in_progress ) {
458 	    CMsgErrorMsg em;
459 	    em.type = CMsgError;
460 	    em.seqno = 0;
461 	    em.error = "Game shutting down; no reconnect allowed";
462 	    put_line(newfd,encode_cmsg((CMsgMsg *)&em));
463 	    closesocket(newfd);
464 	  } else
465 	  /* add to the connection list */
466 	  if ( new_connection(newfd) < 0) {
467 	    CMsgErrorMsg em;
468 	    em.type = CMsgError;
469 	    em.seqno = 0;
470 	    em.error = "Unable to accept new connections";
471 	    put_line(newfd,encode_cmsg((CMsgMsg *)&em));
472 	    closesocket(newfd);
473 	  }
474 	}
475 	else {
476 	  char *line;
477 	  /* get_line will fail if there is only a partial line
478 	     available. This is wrong, since it is possible for
479 	     lines to arrive fragmentedly (not that it should
480 	     normally happen). However, it would also be wrong to
481 	     wait forever for a partial line to complete. There
482 	     should be a timeout.
483 	  */
484 	  /* get_line provides us with a pointer to the text line,
485 	     which we can mess with as we like, provided we don't
486 	     expect it to survive the next call to get_line
487 	  */
488 	  line = get_line(connections[i].skt); /* errors dealt with by despatch line */
489 	  /* except that we want to know the error code */
490 	  if ( line == NULL ) {
491 	    info("get_line returned null: %s",strerror(errno));
492 	  }
493 	  despatch_line(i,line);
494 	}
495       }
496     }
497   }
498 }
499 
500 /* function to act on Player Messages. NB it takes a connection number,
501    not an id, since ids may be unassigned.
502 */
503 static void handle_pmsg(PMsgMsg *pmp, int cnx);
504 
505 /* despatch_line: process the line of input received on cnx. */
506 
despatch_line(int cnx,char * line)507 static void despatch_line(int cnx, char *line) {
508   PMsgMsg *pmp;
509 
510   if ( logfile ) {
511     fprintf(logfile,"<cnx%d %s",cnx,line);
512   }
513 
514   if ( line == NULL ) {
515     if ( game_over ) exit(0);
516     warn("receive error on cnx %d, player id %d\n",cnx, cnx_to_id(cnx));
517     handle_cnx_error(cnx);
518     return;
519   }
520 
521   pmp = decode_pmsg(line);
522   /* increment the sequence number */
523   connections[cnx].seqno++;
524 
525   if ( pmp == NULL ) {
526     CMsgErrorMsg em;
527     em.type = CMsgError;
528     em.error = "Protocol error";
529     em.seqno = connections[cnx].seqno;
530     send_packet(cnx,(CMsgMsg *)&em,1);
531     warn("Protocol error on cnx %d, player id %d; ignoring\n",cnx, cnx_to_id(cnx));
532     return;
533   }
534 
535   handle_pmsg(pmp,cnx);
536 }
537 
538 /* little function to check that the minimum time has passed
539    since the last tile moving activity.
540    The argument multiplies the usual delay time between the current
541    call and the next call. It's used to increase the delay after
542    claim implementations. bloody emacs ' */
check_min_time(float factor)543 static void check_min_time(float factor) {
544   static struct timeval disctime, timenow;
545   static int min_time_this_time;
546   if ( min_time_this_time > 0 ) {
547     int n;
548     if ( disctime.tv_sec > 0 ) { /* not firsttime */
549       gettimeofday(&timenow,NULL);
550       n = (timenow.tv_sec * 1000 + timenow.tv_usec/1000)
551 	- (disctime.tv_sec * 1000 + disctime.tv_usec/1000);
552       n = 100*min_time_this_time - n;
553       if ( n > 0 ) usleep(1000*n);
554     }
555     gettimeofday(&disctime,NULL);
556   }
557   min_time_this_time = (int)(min_time*factor);
558 }
559 
handle_pmsg(PMsgMsg * pmp,int cnx)560 static void handle_pmsg(PMsgMsg *pmp, int cnx)
561 {
562 
563   /* We mustn't act on most requests if the game is not active.
564      There's a race possible otherwise: we can suspend the game, and
565      meanwhile a player sends a request: we then act on this, but it
566      doesn't get into the history records of the suspended players. */
567   if ( ! the_game->active ) {
568     CMsgErrorMsg em;
569     em.type = CMsgError;
570     em.error = "Game not active";
571     em.seqno = connections[cnx].seqno;
572     switch ( pmp->type ) {
573     case PMsgSaveState:
574     case PMsgLoadState:
575     case PMsgConnect:
576     case PMsgDisconnect:
577     case PMsgAuthInfo:
578     case PMsgRequestReconnect:
579     case PMsgSetPlayerOption:
580     case PMsgSendMessage:
581     case PMsgQueryGameOption:
582     case PMsgListGameOptions:
583       break; /* these are OK */
584     default:
585       send_packet(cnx,(CMsgMsg *)&em,0);
586       return;
587     }
588   }
589 
590   /* check for debugging messages */
591   if ( ! debug && pmp->type >= DebugMsgsStart ) {
592     CMsgErrorMsg em;
593     em.type = CMsgError;
594     em.seqno = connections[cnx].seqno;
595     em.error = "Debugging not enabled";
596     send_packet(cnx,(CMsgMsg *)&em,0);
597     return;
598   }
599 
600   switch ( pmp->type ) {
601   case PMsgSaveState:
602     {
603       PMsgSaveStateMsg *m = (PMsgSaveStateMsg *)pmp;
604       CMsgErrorMsg em;
605       PlayerP p; int id; seats seat;
606       char *res;
607 
608       id = cnx_to_id(cnx);
609       p = id_to_player(id);
610       seat = id_to_seat(id);
611 
612       em.type = CMsgError;
613       em.seqno = connections[cnx].seqno;
614 
615       if ( (res = save_state(the_game,m->filename)) ) {
616 	if ( the_game->protversion >= 1025 ) {
617 	  CMsgStateSavedMsg ssm;
618 	  ssm.type = CMsgStateSaved;
619 	  ssm.id = id;
620 	  ssm.filename = res;
621 	  send_all(the_game,&ssm);
622 	}
623       } else {
624 	em.error = "Unable to save state";
625 	send_id(id,&em);
626       }
627       return;
628     }
629   case PMsgLoadState:
630     {
631       PMsgLoadStateMsg *m = (PMsgLoadStateMsg *) pmp;
632       CMsgErrorMsg em;
633       PlayerP p; int id; seats seat;
634 
635       id = cnx_to_id(cnx);
636       p = id_to_player(id);
637       seat = id_to_seat(id);
638 
639       em.type = CMsgError;
640       em.seqno = connections[cnx].seqno;
641       em.error = NULL;
642 
643       /* I think we can load state any time before the game starts,
644 	 and only then */
645       if ( protocol_version < 1038 ) {
646 	em.error = "Not all players support dynamic state loading";
647       } else if ( game_has_started(the_game) ) {
648 	em.error = "Can't load game state when already playing";
649       } else if ( m->filename == NULL || m->filename[0] == 0 ) {
650 	em.error = "No game file specified in LoadState";
651       } else {
652 	int i;
653 	CMsgPlayerMsg pm;
654 	loadstate = 1;
655 	strmcpy(loadfilename,m->filename,1023);
656 	/* first delete all players from the clients */
657 	pm.type = CMsgPlayer;
658 	pm.name = NULL;
659 	for ( i = 0 ; i < MAX_CONNECTIONS ; i++ ) {
660 	  if ( connections[i].inuse && connections[i].player ) {
661 	    pm.id = connections[i].player->id;
662 	    send_all(the_game,&pm);
663 	  }
664 	}
665 	/* now remove from maps ... */
666 	for ( i = 0 ; i < MAX_CONNECTIONS ; i++ ) {
667 	  if ( connections[i].inuse && connections[i].player ) {
668 	    remove_from_maps(i);
669 	    num_connected_players--;
670 	  }
671 	}
672 	if ( ! load_state(the_game) ) {
673 	  em.error = "Loading game state failed";
674 	}
675 	the_game->active = 0;
676 	/* now reprocess connection messages */
677 	for ( i = 0 ; i < MAX_CONNECTIONS ; i++ ) {
678 	  if ( connections[i].inuse && connections[i].cm) {
679 	    handle_pmsg(connections[i].cm,i);
680 	  }
681 	}
682       }
683       if ( em.error ) {
684 	send_id(id,&em);
685       }
686       return;
687     }
688   case PMsgDisconnect:
689     {
690       close_connection(cnx);
691       return;
692     }
693   case PMsgRequestReconnect:
694     {
695       CMsgReconnectMsg rm;
696       CMsgErrorMsg em;
697       PlayerP p; int id; seats seat;
698 
699       id = cnx_to_id(cnx);
700       p = id_to_player(id);
701       seat = id_to_seat(id);
702 
703       rm.type = CMsgReconnect;
704       em.type = CMsgError;
705 
706       /* no reason to refuse, so far */
707 
708       /* first, stop play */
709       { char msg[1024];
710 	CMsgStopPlayMsg spm;
711 	spm.type = CMsgStopPlay;
712 	sprintf(msg,"Player %s requested reconnect - please wait...",
713 		p->name);
714 	spm.reason = msg;
715 	send_all(the_game,&spm);
716 	the_game->active = 0;
717       }
718 
719       /* now send the reconnect */
720       send_id(id,&rm);
721 
722       /* remove player from maps and decrement player count */
723       remove_from_maps(cnx);
724       num_connected_players--;
725 
726       return;
727     }
728   case PMsgNewAuthInfo:
729     {
730       CMsgErrorMsg em;
731       int id;
732 
733       id = cnx_to_id(cnx);
734 
735       em.type = CMsgError ;
736       em.seqno = connections[cnx].seqno;
737       em.error = "Password changing not supported";
738       send_id(id,&em);
739       return;
740     }
741   case PMsgAuthInfo:
742     {
743       PMsgAuthInfoMsg *m = (PMsgAuthInfoMsg *) pmp;
744       int id;
745       PlayerP p = NULL;
746       CMsgConnectReplyMsg cm;
747 
748       id = cnx_to_id(cnx);
749       p = id_to_player(id);
750 
751       /* if auth fails, we'll send a negative connection reply.
752 	 Otherwise, we'll drop through and re-process the connect.
753       */
754       cm.type = CMsgConnectReply;
755       if ( strcmp(m->authdata,pextras(p)->auth_data) != 0 ) {
756 	cm.reason = "Authentication failure";
757 	send_packet(cnx,(CMsgMsg *)&cm,1);
758 	close_connection(cnx);
759 	return;
760       }
761       pextras(p)->auth_state = AUTH_DONE;
762       /* now fake reprocessing the connect message */
763       pmp = connections[cnx].cm;
764     }
765   case PMsgConnect:
766     {
767       PMsgConnectMsg *m = (PMsgConnectMsg *) pmp;
768       PlayerP p = NULL;
769       CMsgConnectReplyMsg cm;
770       CMsgPlayerMsg thisplayer;
771       CMsgGameMsg gamemsg;
772       char *refusal = NULL;
773       static int player_id = 0;
774       int i;
775 
776       /* start filling in reply message */
777       cm.type = CMsgConnectReply;
778       cm.pvers = PROTOCOL_VERSION;
779       if ( m->pvers/1000 != PROTOCOL_VERSION/1000 ) refusal = "Protocol major version mismatch";
780       /* we may be seeing the message for the second time, so allocation
781 	 may already have been done */
782       if ( cnx_to_id(cnx) < 0 && num_connected_players == NUM_PLAYERS )
783 	refusal = "Already have players";
784       /* it's reasonable to insist on a name being supplied */
785       if ( m->name == NULL )
786 	refusal = "A non-empty name must be given";
787       if ( cnx_to_id(cnx) >= 0 && cnx_to_id(cnx) != m->last_id )
788         refusal = "You already have a player ID";
789 
790       /* If we have a previous game state loaded, we need to associate
791 	 this player with one in the game. Match on ids */
792       int foundbyname = 0;
793       if ( loadstate && !refusal ) {
794 	/* if no id specified, match on names */
795 	if ( m->last_id != 0 ) {
796 	  for ( i = 0; i < NUM_SEATS
797 		  && m->last_id != the_game->players[i]->id ; i++ ) ;
798 	} else {
799 	  for ( i = 0; i < NUM_SEATS
800 		  && (strcmp(m->name,the_game->players[i]->name) != 0) ; i++ ) ;
801 	  if ( i < NUM_SEATS ) {
802 	    m->last_id = the_game->players[i]->id;
803 	    foundbyname = 1;
804 	  }
805 	}
806 	/* if no id required, just match this to the first player
807 	   not currently connected */
808 	if ( i == NUM_SEATS && noidrequired ) {
809 	  for ( i = 0; i < NUM_SEATS
810 		  && id_to_cnx(the_game->players[i]->id) >= 0 ; i++ );
811 	}
812 	if ( i == NUM_SEATS ) {
813 	  refusal = "Can't find you in the resumed game";
814 	} else {
815 	  /* better check that this player isn't already connected */
816 	  if ( id_to_cnx(m->last_id) >= 0 && id_to_cnx(m->last_id) != cnx ) {
817 	    if ( foundbyname ) {
818 	      refusal = "Your name is connected elsewhere - please give your id";
819 	    } else {
820 	      refusal = "Your id is already connected elsewhere";
821 	    }
822 	  } else {
823 	    p = the_game->players[i];
824 	    set_player_name(p,m->name); /* not preserved by saving */
825 	    if ( cnx_to_id(cnx) < 0 ) {
826 	      setup_maps(cnx,p); /* set up maps between id and cnx etc*/
827 	      num_connected_players++;
828 	    }
829 	  }
830 	}
831       } else if ( ! refusal) { /* not loadstate */
832 	/* in change from previous versions, honour the last_id field */
833 	if ( m->last_id ) {
834 	  /* better check that this player isn't already connected */
835 	  if ( id_to_cnx(m->last_id) >= 0 && id_to_cnx(m->last_id) != cnx )
836 	    refusal = "Your id is already connected elsewhere";
837 	} else {
838 	  while ( id_to_cnx(++player_id) >= 0 ) { }
839 	  m->last_id = player_id;
840 	}
841 	if ( ! refusal && cnx_to_id(cnx) < 0 ) {
842 	  /* the second test is because we may already be initialized */
843 	  p = game_id_to_player(the_game,0); /* get free player structure */
844 	  if ( p == NULL ) {
845 	    warn("can't get player structure; exiting");
846 	    exit(1);
847 	  }
848 	  initialize_player(p);
849 	  num_connected_players++;
850 	  set_player_id(p,m->last_id);
851 	  set_player_name(p,m->name);
852 	  /* store the id of the first human player to connect.
853 	     This assumes that computer players are called Robot.. */
854 	  if ( ! first_id
855 	    && strncmp(p->name,"Robot",5) != 0 ) first_id = num_connected_players;
856 	  setup_maps(cnx,p); /* set up maps between id and cnx etc*/
857 	}
858       }
859       if (refusal) {
860 	cm.id = 0;
861 	cm.reason = refusal;
862 	send_packet(cnx,(CMsgMsg *)&cm,1);
863 	close_connection(cnx);
864 	return;
865       }
866       /* set the player in case this is second pass */
867       if ( ! p ) p = game_id_to_player(the_game,m->last_id);
868       /* now do basic auth checking */
869       if ( auth_info[0].id && pextras(p)->auth_state != AUTH_DONE ) {
870 	int j = 0;
871 	while ( j < AUTH_NUM && auth_info[j].id != p->id ) j++;
872 	if ( j == AUTH_NUM ) {
873 	  refusal = "id not authorized for this game";
874 	} else {
875 	  pextras(p)->auth_state = AUTH_PENDING;
876 	  pextras(p)->auth_data = auth_info[j].passwd;
877 	  if ( pextras(p)->auth_data[0] == 0 )
878 	    pextras(p)->auth_state = AUTH_DONE;
879 	}
880       } else {
881 	pextras(p)->auth_state = AUTH_DONE;
882       }
883       if (refusal) {
884 	cm.id = 0;
885 	cm.reason = refusal;
886 	send_packet(cnx,(CMsgMsg *)&cm,1);
887 	close_connection(cnx);
888 	return;
889       }
890 
891       /* store the protocol version of this player */
892       pextras(p)->protversion = m->pvers;
893 
894       /* Yes, this is right: the fact that this player is connecting
895 	 means that it has been disconnected! */
896       pextras(p)->disconnected = 1;
897 
898       /* keep a copy of the message if not already there */
899       if ( connections[cnx].cm == NULL ) {
900 	connections[cnx].cm = pmsg_deepcopy(pmp);
901       }
902 
903       if ( pextras(p)->auth_state == AUTH_DONE ) {
904 	/* send the reply */
905 	cm.id = p->id;
906 	cm.reason = NULL;
907 	send_packet(cnx,(CMsgMsg *)&cm,1);
908       } else {
909 	/* ask for authorization, and then bail out.
910 	   When we get authorization, we'll come back through
911 	   all this code again. */
912 	CMsgAuthReqdMsg cm;
913 	cm.type = CMsgAuthReqd;
914 	strcpy(cm.authtype,"basic");
915 	cm.authdata = NULL;
916 	send_packet(cnx,(CMsgMsg *)&cm,0); /* don't log this */
917 	return;
918       }
919 
920       /* if we had localtimeouts in operation, turn them off,
921 	 and (in the loop below ) tell the other players to */
922       pextras(p)->localtimeouts = 0;
923 
924       /* Now we need to tell this player who's already here, and
925 	 tell the others about this one */
926       thisplayer.type = CMsgPlayer;
927       thisplayer.id = p->id;
928       thisplayer.name = p->name;
929 
930       for ( i = 0; i < NUM_SEATS; i++ ) {
931 	CMsgPlayerMsg pm;
932 	PlayerP q;
933 
934 	q = the_game->players[i];
935 
936 	if ( q->id == 0 ) continue;
937 
938 	if ( q->id == p->id ) continue;
939 
940 	pm.type = CMsgPlayer;
941 	pm.id = q->id;
942 	pm.name = q->name;
943 
944 	send_packet(cnx,(CMsgMsg *)&pm,1);
945 	send_id(q->id,&thisplayer); /* fails harmlessly if not connected */
946 
947 	CMsgPlayerOptionSetMsg posm;
948 	posm.type = CMsgPlayerOptionSet;
949 	posm.option = POLocalTimeouts;
950 	posm.value = 0;
951 	posm.ack = 0;
952 	posm.text = NULL;
953 	send_id(q->id,(CMsgMsg *)&posm);
954       }
955       localtimeouts = 0;
956       timeout_time = get_timeout_time(the_game,localtimeouts);
957 
958       /* set the protocol version to the greatest supported version.
959 	 We inspect all players again in case somebody disconnected
960 	 before the game was set up */
961       protocol_version = PROTOCOL_VERSION;
962       for ( i = 0; i < NUM_SEATS ; i++ ) {
963 	PlayerP q = the_game->players[i];
964 	if ( q->id == 0 ) continue;
965 	if ( protocol_version > pextras(q)->protversion )
966 	  protocol_version = pextras(q)->protversion;
967       }
968       /* if not everybody is connected, just wait for the next */
969       if ( num_connected_players < NUM_SEATS ) return;
970 
971       /* otherwise, set up the game state (if we aren't
972 	 already in the middle of an interrupted game */
973 
974       if ( ! loadstate ) {
975 	GameOptionEntry goe;
976 	/* set up the seating order */
977 	switch ( seating_order ) {
978 	default: break;
979 	case SORandom:
980 	  { int i,j,n;
981 	    PlayerP temp[NUM_SEATS];
982 	    for (i=0; i<NUM_SEATS; i++) temp[i] = the_game->players[i];
983 	    for (i=0; i<NUM_SEATS; i++) {
984 	      /* random number between 0 and seatsleft-1 */
985 	      n = rand_index(NUM_SEATS-(i+1));
986 	      /* skip already used */
987 	      j = 0;
988 	      while ( (!temp[j]) || j < n ) {
989 		if ( !temp[j] ) n++;
990 		j++;
991 	      }
992 	      the_game->players[i] = temp[j];
993 	      temp[j] = NULL;
994 	    }
995 	  }
996 	  break;
997 	case SOIdOrder:
998 	  { int i,j; PlayerP t;
999 	    for ( i = 0 ; i < NUM_SEATS-1 ; i++ ) {
1000 	      if ( the_game->players[i+1]->id < the_game->players[i]->id ) {
1001 		/* sink it to the bottom */
1002 		for ( j = i+1 ;
1003 		      j > 0 &&
1004 		      the_game->players[j]->id < the_game->players[j-1]->id;
1005 		      j-- ) {
1006 		  t =  the_game->players[j-1];
1007 		  the_game->players[j-1] =  the_game->players[j];
1008 		  the_game->players[j] = t;
1009 		}
1010 	      }
1011 	    }
1012 	  }
1013 	  break;
1014 	}
1015 	the_game->state = HandComplete;
1016 	the_game->player = noseat;
1017 	the_game->round = EastWind;
1018 	the_game->hands_as_east = 0;
1019 	the_game->firsteast = the_game->players[east]->id;
1020 	/* protocol_version has been maintained during the
1021 	   connection process */
1022 	the_game->protversion = protocol_version;
1023 	the_game->manager = nomanager ? -1 : first_id;
1024 	game_set_options_from_defaults(the_game);
1025 	/* initialize the timeout time from the command line option */
1026 	goe = *game_get_option_entry(the_game,GOTimeout,NULL);
1027 	goe.value.optint = timeout_time;
1028 	game_set_option(the_game,&goe);
1029 	/* And I think we will have the normal dead wall as default,
1030 	   rather than millington style */
1031 	goe = *game_get_option_entry(the_game,GODeadWall16,NULL);
1032 	if ( goe.enabled ) {
1033 	  goe.value.optbool = 0;
1034 	  game_set_option(the_game,&goe);
1035 	}
1036 	/* Now load the game option file, if there is one */
1037 	if ( optfilename ) {
1038 	  FILE *optfile;
1039 	  char buf[1024];
1040 	  optfile = fopen(optfilename,"r");
1041 	  if ( optfile ) {
1042 	    CMsgMsg *m;
1043 	    while ( ! feof(optfile) ) {
1044 	      fgets(buf,1024,optfile);
1045 	      m = decode_cmsg(buf);
1046 	      if ( ! m ) {
1047 		warn("Error decoding game option file entry %s",buf);
1048 	      } else {
1049 		if ( handle_cmsg(the_game,m) < 0 ) {
1050 		  warn("Error applying game option (%s)",the_game->cmsg_err);
1051 		}
1052 		cmsg_deepfree(m);
1053 	      }
1054 	    }
1055 	  } else {
1056 	    warn("couldn't open game option file %s (%s)",
1057 		 optfilename,strerror(errno));
1058 	  }
1059 	}
1060       }
1061       the_game->cmsg_check = 1;
1062 
1063       gamemsg.type = CMsgGame;
1064       gamemsg.east = the_game->players[east]->id;
1065       gamemsg.south = the_game->players[south]->id;
1066       gamemsg.west = the_game->players[west]->id;
1067       gamemsg.north = the_game->players[north]->id;
1068       gamemsg.round = the_game->round;
1069       gamemsg.hands_as_east = the_game->hands_as_east;
1070       gamemsg.firsteast = the_game->firsteast;
1071       gamemsg.east_score = the_game->players[east]->cumulative_score;
1072       gamemsg.south_score = the_game->players[south]->cumulative_score;
1073       gamemsg.west_score = the_game->players[west]->cumulative_score;
1074       gamemsg.north_score = the_game->players[north]->cumulative_score;
1075       gamemsg.protversion = the_game->protversion;
1076       gamemsg.manager = the_game->manager;
1077 
1078       /* we only send the game message to the players who
1079 	 have been disconnected. (In the usual case, this
1080 	 is all players...) */
1081 
1082       for ( i=0 ; i < NUM_SEATS; i++ ) {
1083 	PlayerP p = the_game->players[i];
1084 	PMsgListGameOptionsMsg plgo;
1085 	if ( ! pextras(p)->disconnected ) continue;
1086 
1087 	send_id(p->id,&gamemsg);
1088 	/* we also have to tell them the game options now, so that
1089 	   they do any necessary setup corrrectly before the new
1090 	   hand message. This is most easily done by faking a
1091 	   ListOptions request. */
1092 	plgo.type = PMsgListGameOptions;
1093 	plgo.include_disabled = 0;
1094 	handle_pmsg((PMsgMsg *)&plgo,id_to_cnx(p->id));
1095 
1096 	/* we don't actually clear the history records
1097 	   until the NewHand message is sent. But we don't
1098 	   want to send history for a complete hand.
1099 	   However, if the game is paused, we need to tell the player so. */
1100 	if ( the_game->state == HandComplete ) {
1101 	  if ( the_game->paused ) {
1102 	    CMsgPauseMsg pm;
1103 	    CMsgPlayerReadyMsg prm;
1104 	    int i;
1105 	    pm.type = CMsgPause;
1106 	    pm.exempt = 0;
1107 	    pm.requestor = 0;
1108 	    pm.reason = the_game->paused;
1109 	    send_id(p->id,&pm);
1110 	    prm.type = CMsgPlayerReady;
1111 	    for ( i = 0; i < NUM_SEATS; i++ ) {
1112 	      if ( the_game->ready[i] ) {
1113 		prm.id = the_game->players[i]->id;
1114 		send_id(p->id,&prm);
1115 	      }
1116 	    }
1117 	  }
1118 	}
1119       }
1120       /* now we need to send the history to disconnected players.*/
1121       if ( the_game->state != HandComplete ) {
1122 	int j;
1123 	if ( ! usehist ) {
1124 	  CMsgErrorMsg em;
1125 	  em.type = CMsgError;
1126 	  em.seqno = connections[cnx].seqno;
1127 	  em.error = "No history kept: resumption not supported";
1128 	  send_all(the_game,&em);
1129 	  warn(em.error);
1130 	  exit(1);
1131 	}
1132 	for ( j=0; j < gextras(the_game)->histcount; j++ ) {
1133 	  int sleeptime;
1134 	  for ( i=0 ; i < NUM_SEATS; i++ ) {
1135 	    PlayerP p = the_game->players[i];
1136 	    if ( ! pextras(p)->disconnected ) continue;
1137 	    resend(p->id,gextras(the_game)->history[j]);
1138 	  }
1139 	  /* this is an undocumented feature to make things
1140 	     a bit nicer for humans reconnecting: we'll
1141 	     unilaterally slow down the feed, by adding a
1142 	     0.15 second delay between items, or 1 second after
1143 	     a claim implementation. */
1144 	  switch ( gextras(the_game)->history[j]->type ) {
1145 	  case CMsgPlayerPairs:
1146 	  case CMsgPlayerChows:
1147 	  case CMsgPlayerPungs:
1148 	  case CMsgPlayerKongs:
1149 	  case CMsgPlayerSpecialSet:
1150 	    sleeptime = 1000; /* ms */
1151 	    break;
1152 	  default:
1153 	    sleeptime = 150;
1154 	  }
1155 	  usleep(sleeptime*1000);
1156 	}
1157       }
1158       /* and now mark those players connected again */
1159       for ( i=0 ; i < NUM_SEATS; i++ ) {
1160 	PlayerP p = the_game->players[i];
1161 	if ( ! pextras(p)->disconnected ) continue;
1162 	pextras(p)->disconnected = 0;
1163       }
1164 
1165       /* Now we should tell somebody to do something. Namely:
1166 	 In state HandComplete: tell everybody, and start a hand
1167 	 if things aren't paused.
1168 	 In state Dealing: everybody.
1169 	 In Discarded, several players might want to do something,
1170 	 so we don't specify the id.
1171 	 Ditto in MahJonging.
1172          Otherwise, it's the player.
1173       */
1174       {
1175 	CMsgStartPlayMsg sm;
1176 	CMsgPauseMsg pm;
1177 	sm.type = CMsgStartPlay;
1178 	switch (the_game->state) {
1179 	case HandComplete:
1180 	case Dealing:
1181 	case Discarded:
1182 	case MahJonging:
1183 	  sm.id = 0;
1184 	  break;
1185 	default:
1186 	  sm.id = the_game->players[the_game->player]->id;
1187 	  break;
1188 	}
1189 	/* now we should ask the players whether they are ready to
1190 	   continue. We send the pause request *before* making the
1191 	   game active */
1192 	pm.type = CMsgPause;
1193 	pm.reason = (the_game->state == HandComplete)
1194 	  ? "to start hand" : "to continue play";
1195 	pm.exempt = 0;
1196 	pm.requestor = 0;
1197 	handle_cmsg(the_game,&pm);
1198 	send_all(the_game,&pm);
1199 	send_all(the_game,&sm);
1200 	/* and set the game active */
1201 	the_game->active = 1;
1202       }
1203 
1204 
1205       /* now set loadstate */
1206       loadstate = 1;
1207 
1208       return;
1209     } /* end of case PMsgConnect */
1210   case PMsgRequestPause:
1211     { PMsgRequestPauseMsg *m = (PMsgRequestPauseMsg *)pmp;
1212       PlayerP p; int id; seats seat;
1213       CMsgPauseMsg pm;
1214       CMsgErrorMsg em;
1215 
1216       id = cnx_to_id(cnx);
1217       p = id_to_player(id);
1218       seat = id_to_seat(id);
1219 
1220       /* fill in basic fields of possible replies */
1221       pm.type = CMsgPause;
1222       pm.exempt = 0;
1223       pm.requestor = id;
1224       pm.reason = m->reason;
1225 
1226       em.type = CMsgError ;
1227       em.seqno = connections[cnx].seqno;
1228       em.error = NULL;
1229 
1230       if ( !(the_game->state == Discarding
1231 	     || the_game->state == HandComplete) ) {
1232 	em.error = "Not reasonable to pause at this point";
1233 	send_id(id,&em);
1234 	return;
1235       }
1236 
1237       send_all(the_game,&pm);
1238       break;
1239     } /* end of PMsgRequestPauseMsg */
1240   case PMsgSetPlayerOption:
1241     {
1242       PMsgSetPlayerOptionMsg *m = (PMsgSetPlayerOptionMsg *)pmp;
1243       PlayerP p; int id; seats seat;
1244       CMsgPlayerOptionSetMsg posm;
1245       CMsgErrorMsg em;
1246 
1247       id = cnx_to_id(cnx);
1248       p = id_to_player(id);
1249       seat = id_to_seat(id);
1250 
1251       /* fill in basic fields of possible replies */
1252       posm.type = CMsgPlayerOptionSet;
1253       posm.option = m->option;
1254       posm.value = m->value;
1255       posm.text = m->text;
1256       posm.ack = 1;
1257       em.type = CMsgError ;
1258       em.seqno = connections[cnx].seqno;
1259       em.error = NULL;
1260 
1261       if ( m->option == (unsigned)-1 ) m->option = POUnknown;
1262       if ( m->option == POUnknown ) em.error = "Unknown option";
1263       else switch ( m->option ) {
1264 	/* validity checking  */
1265 	/* boolean options */
1266       case POInfoTiles:
1267 	if ( m->value < 0 || m->value > 1 ) em.error = "Bad value for InfoTiles";
1268 	break;
1269       case POLocalTimeouts:
1270 	/* This is a bit messy. As a matter of policy, we only allow
1271 	   a client to do local timeouts if everybody else is too.
1272 	   So we simply remember the requests, unless all have requested,
1273 	   in which case we ack everybody. */
1274 	/* if this is an ack, ignore it. We should only get acks
1275 	   after we've forcibly set things to zero, and then
1276 	   we don't care about the ack. */
1277 	if ( m->ack ) break;
1278 	/* if this is a request to start local timeouts: */
1279 	if ( m->value ) {
1280 	  int i,lt;
1281 	  pextras(p)->localtimeouts = 1;
1282 	  for ( i=0, lt=1; i < NUM_SEATS; i++ ) {
1283 	    lt = (lt && pextras(the_game->players[i])->localtimeouts);
1284 	  }
1285 	  if ( ! lt ) return; /* just wait */
1286 	  localtimeouts = 1;
1287 	  timeout_time = get_timeout_time(the_game,localtimeouts);
1288 	  for ( i=0; i < NUM_SEATS; i++ ) {
1289 	    popts(the_game->players[i])[m->option] = 1;
1290 	  }
1291 	  send_all(the_game,&posm);
1292 	  /* and that's it -- we don't want to drop through */
1293 	  return;
1294 	} else {
1295 	  seats i;
1296 	  /* request to disable local timeouts */
1297 	  localtimeouts = 0;
1298 	  timeout_time = get_timeout_time(the_game,localtimeouts);
1299 	  pextras(p)->localtimeouts = 0;
1300 	  /* instruct all the other players to drop local timeouts */
1301 	  posm.ack = 0;
1302 	  for ( i=0; i < NUM_SEATS; i++ ) {
1303 	    if ( i != seat ) { send_seat(the_game,i,&posm); }
1304 	  }
1305 	  posm.ack = 1;
1306 	  /* now drop through to ack and set this one. */
1307 	}
1308 	break;
1309 	/* non-boolean options */
1310       case PODelayTime:
1311 	/* players *can* request any positive value */
1312 	if ( m->value < 0 ) em.error = "Bad value for DelayTime";
1313 	break;
1314       default:
1315 	;
1316       }
1317       if ( em.error ) {
1318 	send_id(id,&em);
1319 	return;
1320       }
1321       if ( ! m->ack ) { send_id(id,&posm); }
1322       popts(p)[m->option] = m->value;
1323       /* action may now be required */
1324       switch ( m->option ) {
1325 	int i;
1326       case PODelayTime:
1327 	/* find the highest value requested by any player, or option */
1328 	min_time = min_time_opt;
1329 	for ( i = 0; i < NUM_SEATS; i++ ) {
1330 	  if ( popts(the_game->players[i])[PODelayTime] > min_time )
1331 	    min_time = popts(the_game->players[i])[PODelayTime];
1332 	}
1333 	/* highest reasonable value is 5 seconds */
1334 	if ( min_time > 50 ) min_time = 50;
1335 	break;
1336       default:
1337 	;
1338       }
1339       return;
1340     }
1341   case PMsgReady:
1342     {
1343       PlayerP p; int id; seats seat;
1344       CMsgPlayerReadyMsg prm;
1345       CMsgErrorMsg em;
1346 
1347       id = cnx_to_id(cnx);
1348       p = id_to_player(id);
1349       seat = id_to_seat(id);
1350 
1351       /* fill in basic fields of possible replies */
1352       prm.type = CMsgPlayerReady;
1353       prm.id = id;
1354       em.type = CMsgError ;
1355       em.seqno = connections[cnx].seqno;
1356       em.error = NULL;
1357 
1358       /* if the player is already ready, ignore this message */
1359       if ( the_game->ready[seat] ) return;
1360 
1361       if ( handle_cmsg(the_game,&prm) < 0 ) {
1362 	em.error = the_game->cmsg_err;
1363 	send_id(id,&em);
1364 	return;
1365       }
1366 
1367       send_all(the_game,&prm);
1368 
1369       /* if state is handcomplete and no longer pausing,
1370 	 start the next hand */
1371       if ( the_game->state == HandComplete && ! the_game->paused ) start_hand(the_game,0);
1372       /* and in the discarded or konging state, reinstate the timeout */
1373       if ( the_game->state == Discarded
1374 	   || (the_game->state == Discarding && the_game->konging ) )
1375 	timeout = 1000*timeout_time;
1376       return;
1377     } /* end of case PMsgReady */
1378   case PMsgDeclareSpecial:
1379     {
1380       PMsgDeclareSpecialMsg *m = (PMsgDeclareSpecialMsg *) pmp;
1381       PlayerP p; int id; seats seat;
1382       CMsgErrorMsg em;
1383       CMsgPlayerDeclaresSpecialMsg pdsm;
1384       CMsgPlayerDrawsMsg  pdlm; /* FIXME */
1385       int res;
1386 
1387       id = cnx_to_id(cnx);
1388       p = id_to_player(id);
1389       seat = id_to_seat(id);
1390 
1391       /* fill in basic fields of possible replies */
1392       pdlm.id = pdsm.id = id;
1393       em.type = CMsgError ;
1394       em.seqno = connections[cnx].seqno;
1395       pdsm.type = CMsgPlayerDeclaresSpecial;
1396       pdlm.type = CMsgPlayerDraws;
1397       em.error = NULL;
1398 
1399       /* Legality checking is done by handle cmsg, so
1400 	 just set up the cmsg and apply it */
1401       pdsm.tile = m->tile;
1402 
1403       res = handle_cmsg(the_game,&pdsm);
1404 
1405       if ( res < -1 ) {
1406 	warn("Consistency error: giving up");
1407 	exit(1);
1408       }
1409       if ( res < 0 ) {
1410 	em.error = the_game->cmsg_err;
1411 	send_id(id,&em);
1412 	return;
1413       }
1414       check_min_time(1);
1415       send_all(the_game,&pdsm);
1416       if ( pdsm.tile == HiddenTile ) {
1417 	/* if we've now started play (i.e. state == Discarding),
1418 	   ask everybody (except east) if they're ready */
1419 	if ( the_game->state == Discarding ) {
1420 	  CMsgPauseMsg pm;
1421 	  pm.type = CMsgPause;
1422 	  pm.reason = "to start play";
1423 	  pm.exempt = the_game->players[east]->id;
1424 	  pm.requestor = 0;
1425 	  if ( handle_cmsg(the_game,&pm) >= 0 ) {
1426 	    send_all(the_game,&pm);
1427 	  } else {
1428 	    warn("Failed to pause at start of play: %s",the_game->cmsg_err);
1429 	  }
1430 	}
1431 	return;
1432       }
1433       /* and now draw the replacement */
1434       if ( game_get_option_value(the_game,GOFlowersLoose,NULL).optbool ) {
1435 	pdlm.tile = game_peek_loose_tile(the_game);
1436 	pdlm.type = CMsgPlayerDrawsLoose;
1437       } else {
1438 	pdlm.tile = game_peek_tile(the_game);
1439 	pdlm.type = CMsgPlayerDraws;
1440       }
1441       if ( pdlm.tile == ErrorTile ) {
1442 	washout(NULL);
1443 	return;
1444       }
1445 
1446       if ( the_game->state == Discarding ) {
1447 	/* don't do this if declaring specials */
1448 	/* stash a copy of the player, in case it goes mahjong */
1449 	copy_player(gextras(the_game)->caller,p);
1450       }
1451 
1452       if ( handle_cmsg(the_game,&pdlm) < 0 ) {
1453 	/* error should be impossible */
1454 	warn("Consistency error: giving up");
1455 	exit(1);
1456       }
1457       send_id(id,&pdlm);
1458       pdlm.tile = HiddenTile;
1459       check_min_time(1);
1460       send_others(the_game,id,&pdlm);
1461       send_infotiles(p);
1462       return;
1463       assert(0);
1464     } /* end of case PMsgDeclareSpecial */
1465   case PMsgDiscard:
1466     {
1467       PMsgDiscardMsg *m = (PMsgDiscardMsg *) pmp;
1468       PlayerP p; int id; seats seat;
1469       CMsgErrorMsg em;
1470       CMsgPlayerDiscardsMsg pdm;
1471 
1472       id = cnx_to_id(cnx);
1473       p = id_to_player(id);
1474       seat = id_to_seat(id);
1475 
1476       /* fill in basic fields of possible replies */
1477       em.type = CMsgError ;
1478       em.seqno = connections[cnx].seqno;
1479       em.error = NULL;
1480       pdm.type = CMsgPlayerDiscards;
1481       pdm.id = id;
1482       pdm.tile = m->tile;
1483       pdm.discard = the_game->serial+1;
1484       pdm.calling = m->calling;
1485       if ( handle_cmsg(the_game,&pdm) < 0 ) {
1486 	em.error = the_game->cmsg_err;
1487 	send_id(id,&em);
1488 	return;
1489       }
1490       check_min_time(1);
1491       send_all(the_game,&pdm);
1492       send_infotiles(p);
1493       /* set the timeout */
1494       timeout = 1000*timeout_time;
1495       return;
1496     } /* end of case PMsgDiscard */
1497   case PMsgNoClaim:
1498     {
1499       PMsgNoClaimMsg *m = (PMsgNoClaimMsg *) pmp;
1500       PlayerP p; int id; seats seat;
1501       CMsgErrorMsg em;
1502       CMsgPlayerDoesntClaimMsg pdcm;
1503 
1504       id = cnx_to_id(cnx);
1505       p = id_to_player(id);
1506       seat = id_to_seat(id);
1507 
1508       /* fill in basic fields of possible replies */
1509       em.type = CMsgError ;
1510       em.seqno = connections[cnx].seqno;
1511       em.error = NULL;
1512       pdcm.type = CMsgPlayerDoesntClaim;
1513       pdcm.id = id;
1514       pdcm.discard = m->discard;
1515       pdcm.timeout = 0;
1516 
1517       if ( handle_cmsg(the_game,&pdcm) < 0 ) {
1518 	em.error = the_game->cmsg_err;
1519 	send_id(id,&em);
1520 	return;
1521       }
1522       /* handle_cmsg ignores noclaims in wrong state, so
1523 	 we need to check it */
1524       if ( ! (the_game->state == Discarded
1525 	   || ( (the_game->state == Discarding
1526 		 || the_game->state == DeclaringSpecials)
1527 		&& the_game->konging ) ) ) return;
1528       /* acknowledge to the player only */
1529       /* No, now we send to all players, so they can see who's thinking */
1530       send_all(the_game,&pdcm);
1531       /* if all claims received, process */
1532       check_claims(the_game);
1533       return;
1534     } /* end of case PMsgNoClaim */
1535   case PMsgPung:
1536     {
1537       PMsgPungMsg *m = (PMsgPungMsg *) pmp;
1538       PlayerP p; int id; seats seat;
1539       CMsgErrorMsg em;
1540 
1541       id = cnx_to_id(cnx);
1542       p = id_to_player(id);
1543       seat = id_to_seat(id);
1544 
1545       /* fill in basic fields of possible replies */
1546       em.type = CMsgError ;
1547       em.seqno = connections[cnx].seqno;
1548       em.error = NULL;
1549 
1550       if ( the_game->state != MahJonging ) {
1551 	CMsgPlayerClaimsPungMsg pcpm;
1552 	pcpm.type = CMsgPlayerClaimsPung;
1553 	pcpm.id = id;
1554 	pcpm.discard = m->discard;
1555 	if ( handle_cmsg(the_game,&pcpm) < 0 ) {
1556 	  em.error = the_game->cmsg_err;
1557 	  send_id(id,&em);
1558 	  return;
1559 	}
1560 	send_all(the_game,&pcpm);
1561 	/* if all claims received, process */
1562 	check_claims(the_game);
1563 	return;
1564       } else { /* MahJonging */
1565 	CMsgPlayerPungsMsg ppm;
1566 
1567 	ppm.type = CMsgPlayerPungs;
1568 	ppm.id = id;
1569 	ppm.tile = the_game->tile;
1570 
1571 	if ( handle_cmsg(the_game,&ppm) < 0 ) {
1572 	  em.error = the_game->cmsg_err;
1573 	  send_id(id,&em);
1574 	  return;
1575 	}
1576 	check_min_time(1);
1577 	send_all(the_game,&ppm);
1578 	send_infotiles(p);
1579 	/* if that came from a dangerous discard, tell the other players */
1580 	if ( game_flag(the_game,GFDangerousDiscard) ) {
1581 	  CMsgDangerousDiscardMsg ddm;
1582 	  ddm.type = CMsgDangerousDiscard;
1583 	  ddm.id = the_game->players[the_game->supplier]->id;
1584 	  ddm.discard = the_game->serial;
1585 	  ddm.nochoice = game_flag(the_game,GFNoChoice);
1586 	  send_all(the_game,&ddm);
1587 	}
1588 	/* do scoring if we've finished */
1589 	if ( pflag(p,HandDeclared) ) score_hand(the_game,seat);
1590 	return;
1591       }
1592     } /* end of case PMsgPung */
1593   case PMsgPair:
1594     {
1595       PlayerP p; int id; seats seat;
1596       CMsgErrorMsg em;
1597       CMsgPlayerPairsMsg ppm;
1598 
1599       id = cnx_to_id(cnx);
1600       p = id_to_player(id);
1601       seat = id_to_seat(id);
1602 
1603       /* fill in basic fields of possible replies */
1604       em.type = CMsgError ;
1605       em.seqno = connections[cnx].seqno;
1606       em.error = NULL;
1607       ppm.type = CMsgPlayerPairs;
1608       ppm.id = id;
1609       ppm.tile = the_game->tile;
1610 
1611       if ( handle_cmsg(the_game,&ppm) < 0 ) {
1612 	em.error = the_game->cmsg_err;
1613 	send_id(id,&em);
1614 	return;
1615       }
1616       check_min_time(1);
1617       send_all(the_game,&ppm);
1618       send_infotiles(p);
1619       /* if that came from a dangerous discard, tell the other players */
1620       if ( game_flag(the_game,GFDangerousDiscard) ) {
1621 	CMsgDangerousDiscardMsg ddm;
1622 	ddm.type = CMsgDangerousDiscard;
1623 	ddm.id = the_game->players[the_game->supplier]->id;
1624 	ddm.discard = the_game->serial;
1625 	ddm.nochoice = game_flag(the_game,GFNoChoice);
1626 	send_all(the_game,&ddm);
1627       }
1628       /* do scoring if we've finished */
1629       if ( pflag(p,HandDeclared) ) score_hand(the_game,seat);
1630       return;
1631     } /* end of case PMsgPair */
1632   case PMsgSpecialSet:
1633     {
1634       PlayerP p; int id; seats seat;
1635       CMsgErrorMsg em;
1636       CMsgPlayerSpecialSetMsg pssm;
1637       char tiles[100];
1638       int i;
1639 
1640       id = cnx_to_id(cnx);
1641       p = id_to_player(id);
1642       seat = id_to_seat(id);
1643 
1644       /* fill in basic fields of possible replies */
1645       em.type = CMsgError ;
1646       em.seqno = connections[cnx].seqno;
1647       em.error = NULL;
1648       pssm.type = CMsgPlayerSpecialSet;
1649       pssm.id = id;
1650       pssm.tile = the_game->tile;
1651 
1652       tiles[0] = '\000';
1653       for ( i = 0; i < p->num_concealed; i++ ) {
1654 	if ( i > 0 ) strcat(tiles," ");
1655 	strcat(tiles,tile_code(p->concealed[i]));
1656       }
1657       pssm.tiles = tiles;
1658 
1659       if ( handle_cmsg(the_game,&pssm) < 0 ) {
1660 	em.error = the_game->cmsg_err;
1661 	send_id(id,&em);
1662 	return;
1663       }
1664       send_all(the_game,&pssm);
1665       send_infotiles(p);
1666       /* do scoring if we've finished */
1667       if ( pflag(p,HandDeclared) ) score_hand(the_game,seat);
1668       return;
1669     } /* end of case PMsgSpecialSet */
1670   case PMsgFormClosedPair:
1671     {
1672       PMsgFormClosedPairMsg *m = (PMsgFormClosedPairMsg *) pmp;
1673       CMsgPlayerFormsClosedPairMsg pfcpm;
1674       PlayerP p; int id; seats seat;
1675       CMsgErrorMsg em;
1676 
1677       id = cnx_to_id(cnx);
1678       p = id_to_player(id);
1679       seat = id_to_seat(id);
1680 
1681       /* fill in basic fields of possible replies */
1682       em.type = CMsgError ;
1683       em.seqno = connections[cnx].seqno;
1684       em.error = NULL;
1685       pfcpm.type = CMsgPlayerFormsClosedPair;
1686       pfcpm.id = id;
1687       pfcpm.tile = m->tile;
1688 
1689       if ( handle_cmsg(the_game,&pfcpm) < 0 ) {
1690 	em.error = the_game->cmsg_err;
1691 	send_id(id,&em);
1692 	return;
1693       }
1694       check_min_time(1);
1695       send_all(the_game,&pfcpm);
1696       send_infotiles(p);
1697       /* do scoring if we've finished */
1698       if ( pflag(p,HandDeclared) ) score_hand(the_game,seat);
1699       return;
1700     } /* end of case PMsgFormClosedPair */
1701   case PMsgFormClosedSpecialSet:
1702     {
1703       PlayerP p; int id; seats seat;
1704       CMsgErrorMsg em;
1705       CMsgPlayerFormsClosedSpecialSetMsg pfcssm;
1706       char tiles[100];
1707       int i;
1708 
1709       id = cnx_to_id(cnx);
1710       p = id_to_player(id);
1711       seat = id_to_seat(id);
1712 
1713       /* fill in basic fields of possible replies */
1714       em.type = CMsgError ;
1715       em.seqno = connections[cnx].seqno;
1716       em.error = NULL;
1717       pfcssm.type = CMsgPlayerFormsClosedSpecialSet;
1718       pfcssm.id = id;
1719       tiles[0] = '\000';
1720       for ( i = 0; i < p->num_concealed; i++ ) {
1721 	if ( i > 0 ) strcat(tiles," ");
1722 	strcat(tiles,tile_code(p->concealed[i]));
1723       }
1724       pfcssm.tiles = tiles;
1725 
1726       if ( handle_cmsg(the_game,&pfcssm) < 0 ) {
1727 	em.error = the_game->cmsg_err;
1728 	send_id(id,&em);
1729 	return;
1730       }
1731       check_min_time(1);
1732       send_all(the_game,&pfcssm);
1733       send_infotiles(p);
1734       /* do scoring if we've finished */
1735       if ( pflag(p,HandDeclared) ) score_hand(the_game,seat);
1736       return;
1737     } /* end of case PMsgFormClosedSpecialSet */
1738   case PMsgFormClosedPung:
1739     {
1740       PMsgFormClosedPungMsg *m = (PMsgFormClosedPungMsg *) pmp;
1741       CMsgPlayerFormsClosedPungMsg pfcpm;
1742       PlayerP p; int id; seats seat;
1743       CMsgErrorMsg em;
1744 
1745       id = cnx_to_id(cnx);
1746       p = id_to_player(id);
1747       seat = id_to_seat(id);
1748 
1749       /* fill in basic fields of possible replies */
1750       em.type = CMsgError ;
1751       em.seqno = connections[cnx].seqno;
1752       em.error = NULL;
1753       pfcpm.type = CMsgPlayerFormsClosedPung;
1754       pfcpm.id = id;
1755       pfcpm.tile = m->tile;
1756 
1757       if ( handle_cmsg(the_game,&pfcpm) < 0 ) {
1758 	em.error = the_game->cmsg_err;
1759 	send_id(id,&em);
1760 	return;
1761       }
1762 
1763       check_min_time(1);
1764       send_all(the_game,&pfcpm);
1765       send_infotiles(p);
1766       /* do scoring if we've finished */
1767       if ( pflag(p,HandDeclared) ) score_hand(the_game,seat);
1768       return;
1769     } /* end of case PMsgFormClosedPung */
1770   case PMsgFormClosedChow:
1771     {
1772       PMsgFormClosedChowMsg *m = (PMsgFormClosedChowMsg *) pmp;
1773       CMsgPlayerFormsClosedChowMsg pfccm;
1774       PlayerP p; int id; seats seat;
1775       CMsgErrorMsg em;
1776 
1777       id = cnx_to_id(cnx);
1778       p = id_to_player(id);
1779       seat = id_to_seat(id);
1780 
1781       /* fill in basic fields of possible replies */
1782       em.type = CMsgError ;
1783       em.seqno = connections[cnx].seqno;
1784       em.error = NULL;
1785       pfccm.type = CMsgPlayerFormsClosedChow;
1786       pfccm.id = id;
1787       pfccm.tile = m->tile;
1788 
1789       if ( handle_cmsg(the_game,&pfccm) < 0 ) {
1790 	em.error = the_game->cmsg_err;
1791 	send_id(id,&em);
1792 	return;
1793       }
1794 
1795       check_min_time(1);
1796       send_all(the_game,&pfccm);
1797       send_infotiles(p);
1798       /* do scoring if we've finished */
1799       if ( pflag(p,HandDeclared) ) score_hand(the_game,seat);
1800       return;
1801     } /* end of case PMsgFormClosedChow */
1802   case PMsgKong:
1803     {
1804       PMsgKongMsg *m = (PMsgKongMsg *) pmp;
1805       PlayerP p; int id; seats seat;
1806       CMsgPlayerClaimsKongMsg pckm;
1807       CMsgErrorMsg em;
1808 
1809       id = cnx_to_id(cnx);
1810       p = id_to_player(id);
1811       seat = id_to_seat(id);
1812 
1813       /* fill in basic fields of possible replies */
1814       em.type = CMsgError ;
1815       em.seqno = connections[cnx].seqno;
1816       em.error = NULL;
1817       pckm.type = CMsgPlayerClaimsKong;
1818       pckm.id = id;
1819       pckm.discard = m->discard;
1820       if ( handle_cmsg(the_game,&pckm) < 0 ) {
1821 	em.error = the_game->cmsg_err;
1822 	send_id(id,&em);
1823 	return;
1824       }
1825       send_all(the_game,&pckm);
1826       /* if all claims received, process */
1827       check_claims(the_game);
1828       return;
1829     } /* end of case PMsgKong */
1830   case PMsgChow:
1831     {
1832       PMsgChowMsg *m = (PMsgChowMsg *) pmp;
1833       PlayerP p; int id; seats seat;
1834       CMsgPlayerClaimsChowMsg pccm;
1835       CMsgErrorMsg em;
1836 
1837       id = cnx_to_id(cnx);
1838       p = id_to_player(id);
1839       seat = id_to_seat(id);
1840 
1841       /* fill in basic fields of possible replies */
1842       em.type = CMsgError ;
1843       em.seqno = connections[cnx].seqno;
1844       em.error = NULL;
1845       pccm.type = CMsgPlayerClaimsChow;
1846       pccm.id = id;
1847       pccm.discard = m->discard;
1848       pccm.cpos = m->cpos;
1849 
1850       if ( the_game->state == Discarded ) {
1851 	/* special case: if chowpending is set, then this claim should
1852 	   be to specify the previously unspecified position
1853 	   after we've told the player that its claim has
1854 	   succeeded.
1855 	   So implement the chow and announce */
1856 	if ( the_game->chowpending ) {
1857 	  CMsgPlayerChowsMsg pcm;
1858 
1859 	  pcm.type = CMsgPlayerChows;
1860 	  pcm.id = id;
1861 	  pcm.tile = the_game->tile;
1862 	  pcm.cpos = m->cpos;
1863 	  if ( handle_cmsg(the_game,&pcm) < 0 ) {
1864 	    em.error = the_game->cmsg_err;
1865 	    send_id(id,&em);
1866 	    return;
1867 	  }
1868 	  check_min_time(1);
1869 	  send_all(the_game,&pcm);
1870 	  return;
1871 	}
1872 	if ( handle_cmsg(the_game,&pccm) < 0 ) {
1873 	  em.error = the_game->cmsg_err;
1874 	  send_id(id,&em);
1875 	  return;
1876 	}
1877 	send_all(the_game,&pccm);
1878 	/* if all claims received, process */
1879 	check_claims(the_game);
1880 	return;
1881       } else { /* This had better be mahjonging */
1882 	CMsgPlayerChowsMsg pcm;
1883 
1884 	pcm.type = CMsgPlayerChows;
1885 	pcm.id = id;
1886 	pcm.tile = the_game->tile;
1887 	pcm.cpos = m->cpos;
1888 	if ( handle_cmsg(the_game,&pcm) < 0 ) {
1889 	  em.error = the_game->cmsg_err;
1890 	  send_id(id,&em);
1891 	  return;
1892 	}
1893 	/* if any pos, just tell the player */
1894 	if ( m->cpos == AnyPos ) {
1895 	  send_id(id,&pcm);
1896 	} else {
1897 	  check_min_time(1);
1898 	  send_all(the_game,&pcm);
1899 	  send_infotiles(p);
1900 	  /* if that came from a dangerous discard, tell the other players */
1901 	  if ( game_flag(the_game,GFDangerousDiscard) ) {
1902 	    CMsgDangerousDiscardMsg ddm;
1903 	    ddm.type = CMsgDangerousDiscard;
1904 	    ddm.id = the_game->players[the_game->supplier]->id;
1905 	    ddm.discard = the_game->serial;
1906 	    ddm.nochoice = game_flag(the_game,GFNoChoice);
1907 	    send_all(the_game,&ddm);
1908 	  }
1909 	  if ( pflag(p,HandDeclared) ) score_hand(the_game,seat);
1910 	}
1911 	return;
1912       }
1913     } /* end of case PMsgChow */
1914   case PMsgDeclareWashOut:
1915     {
1916       PlayerP p; int id; seats seat;
1917       CMsgErrorMsg em;
1918 
1919       id = cnx_to_id(cnx);
1920       p = id_to_player(id);
1921       seat = id_to_seat(id);
1922 
1923       /* fill in basic fields of possible replies */
1924       em.type = CMsgError ;
1925       em.seqno = connections[cnx].seqno;
1926       em.error = NULL;
1927 
1928       /* at present, we don't have any rules allowing a player
1929 	 to declare a washout */
1930       em.error = "Can't declare a Wash-Out";
1931       send_id(id,&em);
1932       return;
1933     } /* end of case PMsgDeclareWashOut */
1934   case PMsgMahJong:
1935     {
1936       PMsgMahJongMsg *m = (PMsgMahJongMsg *) pmp;
1937       PlayerP p; int id; seats seat;
1938       CMsgPlayerClaimsMahJongMsg pcmjm;
1939       CMsgPlayerMahJongsMsg pmjm;
1940       CMsgErrorMsg em;
1941 
1942       id = cnx_to_id(cnx);
1943       p = id_to_player(id);
1944       seat = id_to_seat(id);
1945 
1946       /* fill in basic fields of possible replies */
1947       em.type = CMsgError ;
1948       em.seqno = connections[cnx].seqno;
1949       em.error = NULL;
1950       pcmjm.type = CMsgPlayerClaimsMahJong;
1951       pcmjm.id = id;
1952       pmjm.type = CMsgPlayerMahJongs;
1953       pmjm.id = id;
1954 
1955       if ( the_game->state == Discarded
1956 	   || ((the_game->state == Discarding
1957 		|| the_game->state == DeclaringSpecials)
1958 	       && the_game->konging ) ) {
1959 	pcmjm.discard = m->discard;
1960 	if ( handle_cmsg(the_game,&pcmjm) < 0 ) {
1961 	  em.error = the_game->cmsg_err;
1962 	  send_id(id,&em);
1963 	  return;
1964 	}
1965 	send_all(the_game,&pcmjm);
1966 	/* if all claims received, process */
1967 	check_claims(the_game);
1968 	return;
1969       } else { /* this should be discarding */
1970 	pmjm.tile = the_game->tile; /* that's the tile drawn */
1971 	if ( handle_cmsg(the_game,&pmjm) < 0 ) {
1972 	  em.error = the_game->cmsg_err;
1973 	  send_id(id,&em);
1974 	  return;
1975 	}
1976 	send_all(the_game,&pmjm);
1977 	/* the players will now start declaring their hands */
1978 	return;
1979       }
1980     } /* end of case PMsgMahJong */
1981   case PMsgDeclareClosedKong:
1982     {
1983       PMsgDeclareClosedKongMsg *m = (PMsgDeclareClosedKongMsg *) pmp;
1984       PlayerP p; int id; seats seat;
1985       CMsgErrorMsg em;
1986       CMsgPlayerDeclaresClosedKongMsg pdckm;
1987 
1988       id = cnx_to_id(cnx);
1989       p = id_to_player(id);
1990       seat = id_to_seat(id);
1991 
1992       /* fill in basic fields of possible replies */
1993       pdckm.id = id;
1994       em.type = CMsgError ;
1995       em.seqno = connections[cnx].seqno;
1996       pdckm.type = CMsgPlayerDeclaresClosedKong;
1997       em.error = NULL;
1998 
1999       pdckm.tile = m->tile;
2000       pdckm.discard = the_game->serial+1;
2001 
2002       if ( handle_cmsg(the_game,&pdckm) < 0 ) {
2003 	em.error = the_game->cmsg_err;
2004 	send_id(id,&em);
2005 	return;
2006       }
2007 
2008       check_min_time(1);
2009       send_all(the_game,&pdckm);
2010       send_infotiles(p);
2011 
2012       /* now we need to wait for people to try to rob the kong */
2013       timeout = 1000*timeout_time;
2014       return;
2015 
2016     } /* end of case PMsgDeclareClosedKong */
2017   case PMsgAddToPung:
2018     {
2019       PMsgAddToPungMsg *m = (PMsgAddToPungMsg *) pmp;
2020       PlayerP p; int id; seats seat;
2021       CMsgErrorMsg em;
2022       CMsgPlayerAddsToPungMsg patpm;
2023 
2024       id = cnx_to_id(cnx);
2025       p = id_to_player(id);
2026       seat = id_to_seat(id);
2027 
2028       /* fill in basic fields of possible replies */
2029       patpm.id = id;
2030       em.type = CMsgError ;
2031       em.seqno = connections[cnx].seqno;
2032       patpm.type = CMsgPlayerAddsToPung;
2033       em.error = NULL;
2034 
2035       patpm.tile = m->tile;
2036       patpm.discard = the_game->serial+1;
2037       if ( handle_cmsg(the_game,&patpm) < 0 ) {
2038 	em.error = the_game->cmsg_err;
2039 	send_id(id,&em);
2040 	return;
2041       }
2042 
2043       check_min_time(1);
2044       send_all(the_game,&patpm);
2045       send_infotiles(p);
2046 
2047       /* now we need to wait for people to try to rob the kong */
2048       timeout = 1000*timeout_time;
2049       return;
2050 
2051       assert(0);
2052     } /* end of case PMsgAddToPung */
2053   case PMsgQueryMahJong:
2054     {
2055       PMsgQueryMahJongMsg *m = (PMsgQueryMahJongMsg *) pmp;
2056       PlayerP p; int id; seats seat;
2057       CMsgCanMahJongMsg cmjm;
2058       MJSpecialHandFlags mjf;
2059 
2060       id = cnx_to_id(cnx);
2061       p = id_to_player(id);
2062       seat = id_to_seat(id);
2063 
2064       cmjm.type = CMsgCanMahJong;
2065       cmjm.tile = m->tile;
2066       mjf = 0;
2067       if ( (int)game_get_option_value(the_game,GOSevenPairs,NULL).optbool )
2068 	mjf |= MJSevenPairs;
2069       cmjm.answer = player_can_mah_jong(p,m->tile,mjf);
2070       send_id(id,&cmjm);
2071       return;
2072     }
2073   case PMsgShowTiles:
2074     {
2075       PlayerP p; int id; seats seat;
2076       CMsgErrorMsg em;
2077       CMsgPlayerShowsTilesMsg pstm;
2078 
2079       id = cnx_to_id(cnx);
2080       p = id_to_player(id);
2081       seat = id_to_seat(id);
2082 
2083       /* fill in basic fields of possible replies */
2084       pstm.id = id;
2085       em.type = CMsgError ;
2086       em.seqno = connections[cnx].seqno;
2087       pstm.type = CMsgPlayerShowsTiles;
2088       em.error = NULL;
2089 
2090       /* if there are no concealed tiles, just ignore the message;
2091 	 this can only happen from the mahjonging player or by duplication */
2092       if ( p->num_concealed > 0 ) {
2093 	char tiles[100];
2094 	int i;
2095 
2096 	tiles[0] = '\000';
2097 	for ( i = 0; i < p->num_concealed; i++ ) {
2098 	  if ( i > 0 ) strcat(tiles," ");
2099 	  strcat(tiles,tile_code(p->concealed[i]));
2100 	}
2101 	pstm.tiles = tiles;
2102 	if ( handle_cmsg(the_game,&pstm) < 0 ) {
2103 	  em.error = the_game->cmsg_err;
2104 	  send_id(id,&em);
2105 	  return;
2106 	}
2107 	check_min_time(1);
2108 	send_all(the_game,&pstm);
2109 	score_hand(the_game,seat);
2110 	return;
2111       }
2112       return;
2113     } /* end of case PMsgShowTiles */
2114   case PMsgSetGameOption:
2115     {
2116       PMsgSetGameOptionMsg *m = (PMsgSetGameOptionMsg *)pmp;
2117       PlayerP p; int id;
2118       CMsgErrorMsg em;
2119       CMsgGameOptionMsg gom;
2120       GameOptionEntry *goe;
2121 
2122       id = cnx_to_id(cnx);
2123 
2124       /* fill in basic fields of possible replies */
2125       gom.type = CMsgGameOption;
2126       gom.id = id;
2127       p = id_to_player(id);
2128       em.type = CMsgError ;
2129       em.seqno = connections[cnx].seqno;
2130       em.error = NULL;
2131 
2132       /* do we have the option ? */
2133       goe = game_get_option_entry(the_game,GOUnknown,m->optname);
2134 
2135       if ( ! goe ) {
2136 	em.error = "Trying to set unknown option";
2137 	send_id(id,&em);
2138 	return;
2139       }
2140       /* if the option is actually unknown or end, ignore it */
2141       if ( goe->option == GOUnknown || goe->option == GOEnd ) break;
2142 
2143       if ( goe->enabled == 0 ) {
2144 	em.error = "Option not available in this game";
2145 	send_id(id,&em);
2146 	return;
2147       }
2148 
2149       gom.optentry = *goe;
2150 
2151       switch ( goe->type ) {
2152       case GOTBool:
2153 	if ( sscanf(m->optvalue,"%d",&gom.optentry.value.optbool) == 0 ) {
2154 	  em.error = "No boolean value found for option";
2155 	  send_id(id,&em);
2156 	  return;
2157 	}
2158 	if ( gom.optentry.value.optbool != 0
2159 	     && gom.optentry.value.optbool != 1 ) {
2160 	  em.error = "No boolean value found for option";
2161 	  send_id(id,&em);
2162 	  return;
2163 	}
2164 	break;
2165       case GOTNat:
2166 	if ( sscanf(m->optvalue,"%u",&gom.optentry.value.optnat) == 0 ) {
2167 	  em.error = "No integer value found for option";
2168 	  send_id(id,&em);
2169 	  return;
2170 	}
2171 	break;
2172       case GOTInt:
2173 	if ( sscanf(m->optvalue,"%d",&gom.optentry.value.optint) == 0 ) {
2174 	  em.error = "No integer value found for option";
2175 	  send_id(id,&em);
2176 	  return;
2177 	}
2178 	break;
2179       case GOTScore:
2180 	if ( sscanf(m->optvalue,"%d",&gom.optentry.value.optscore) == 0 ) {
2181 	  em.error = "No score value found for option";
2182 	  send_id(id,&em);
2183 	  return;
2184 	}
2185 	break;
2186       case GOTString:
2187 	strmcpy(gom.optentry.value.optstring,
2188             m->optvalue,
2189             sizeof(gom.optentry.value));
2190 	break;
2191       }
2192 
2193       /* specific validity checking not done by handle_cmsg */
2194       if ( gom.optentry.option == GOTimeout
2195 	   && gom.optentry.value.optint < 0 ) {
2196 	em.error = "Can't set negative timeout!";
2197 	send_id(id,&em);
2198 	return;
2199       }
2200 
2201       if ( gom.optentry.option == GONumRounds
2202 	&& ( gom.optentry.value.optnat == 0
2203 	  || gom.optentry.value.optnat == 3
2204 	  || (gom.optentry.value.optnat > 4
2205 	    && gom.optentry.value.optnat % 4 != 0) ) ) {
2206 	em.error = "Number of rounds must be 1, 2 or a multiple of 4";
2207 	send_id(id,&em);
2208 	return;
2209       }
2210       if ( handle_cmsg(the_game,&gom) < 0 ) {
2211 	em.error = the_game->cmsg_err;
2212 	send_id(id,&em);
2213       }
2214       send_all(the_game,&gom);
2215       if ( gom.optentry.option == GOTimeout
2216 	   || gom.optentry.option == GOTimeoutGrace ) {
2217 	timeout_time = get_timeout_time(the_game,localtimeouts);
2218       }
2219       break;
2220     }
2221   case PMsgQueryGameOption:
2222     {
2223       PMsgQueryGameOptionMsg *m = (PMsgQueryGameOptionMsg *)pmp;
2224       int id;
2225       CMsgErrorMsg em;
2226       CMsgGameOptionMsg gom;
2227       GameOptionEntry *goe;
2228 
2229       id = cnx_to_id(cnx);
2230 
2231       /* fill in basic fields of possible replies */
2232       gom.type = CMsgGameOption;
2233       gom.id = 0;
2234       em.type = CMsgError ;
2235       em.seqno = connections[cnx].seqno;
2236       em.error = NULL;
2237 
2238       goe = game_get_option_entry(the_game,GOUnknown,m->optname);
2239       if ( goe == NULL ) {
2240 	em.error = "Option not known";
2241 	send_id(id,&em);
2242 	return;
2243       }
2244       gom.optentry = *goe;
2245       send_id(id,&gom);
2246       break;
2247     }
2248   case PMsgListGameOptions:
2249     {
2250       PMsgListGameOptionsMsg *m = (PMsgListGameOptionsMsg *)pmp;
2251       int id;
2252       CMsgGameOptionMsg gom;
2253       GameOptionEntry *goe;
2254       unsigned int i;
2255 
2256       id = cnx_to_id(cnx);
2257 
2258       /* fill in basic fields of possible replies */
2259       gom.type = CMsgGameOption;
2260       gom.id = 0;
2261 
2262       /* this relies on the fact that we know our option table
2263 	 contains only known options in numerical order. This
2264 	 would not necessarily be the case for clients, but it
2265 	 is for us, since we don't allow unknown options to be set */
2266       for ( i = 1 ; i <= GOEnd ; i++ ) {
2267 	goe = &the_game->option_table.options[i];
2268 	if ( !goe->enabled && ! m->include_disabled ) continue;
2269 	gom.optentry = *goe;
2270 	send_id(id,&gom);
2271       }
2272       break;
2273     }
2274   case PMsgChangeManager:
2275     {
2276       PMsgChangeManagerMsg *m = (PMsgChangeManagerMsg *)pmp;
2277       int id;
2278       CMsgChangeManagerMsg cmm;
2279       CMsgErrorMsg em;
2280 
2281       id = cnx_to_id(cnx);
2282 
2283       /* fill in basic fields of possible replies */
2284       cmm.type = CMsgChangeManager;
2285       cmm.id = id;
2286       cmm.manager = m->manager;
2287       em.type = CMsgError ;
2288       em.seqno = connections[cnx].seqno;
2289       em.error = NULL;
2290 
2291       if ( handle_cmsg(the_game,&cmm) < 0 ) {
2292 	em.error = the_game->cmsg_err;
2293 	send_id(id,&em);
2294 	return;
2295       }
2296       send_all(the_game,&cmm);
2297       break;
2298     }
2299   case PMsgSendMessage:
2300     {
2301       PMsgSendMessageMsg *m = (PMsgSendMessageMsg *)pmp;
2302       int id;
2303       CMsgMessageMsg mm;
2304       CMsgErrorMsg em;
2305 
2306       id = cnx_to_id(cnx);
2307 
2308       mm.type = CMsgMessage;
2309       mm.sender = id;
2310       mm.addressee = m->addressee;
2311       mm.text = m->text;
2312       em.type = CMsgError;
2313       em.seqno = connections[cnx].seqno;
2314       em.error = NULL;
2315 
2316       if ( mm.addressee == 0 ) {
2317 	send_all(the_game,&mm);
2318       } else {
2319 	if ( id_to_cnx(mm.addressee) < 0 ) {
2320 	  em.error = "Addressee not found in game";
2321 	  send_id(id,&em);
2322 	  return;
2323 	} else {
2324 	  send_id(mm.addressee,&mm);
2325 	}
2326       }
2327       break;
2328     }
2329   case PMsgSwapTile:
2330     {
2331       PMsgSwapTileMsg *m = (PMsgSwapTileMsg *) pmp;
2332       PlayerP p; int id; seats seat;
2333       CMsgErrorMsg em;
2334       CMsgSwapTileMsg stm;
2335       int i;
2336 
2337       id = cnx_to_id(cnx);
2338 
2339       /* fill in basic fields of possible replies */
2340       stm.type = CMsgSwapTile;
2341       stm.id = m->id;
2342       stm.oldtile = m->oldtile;
2343       stm.newtile = m->newtile;
2344       p = id_to_player(stm.id);
2345       seat = id_to_seat(stm.id);
2346       em.type = CMsgError ;
2347       em.seqno = connections[cnx].seqno;
2348       em.error = NULL;
2349 
2350       if ( p == NULL ) {
2351 	em.error = "SwapTile: no such player";
2352 	send_id(id,&em);
2353 	return;
2354       }
2355 
2356       /* find the new tile in the wall. Because this is only used
2357 	 for debugging and testing, we don't care that we're diving
2358 	 inside the game structure!
2359 	 Look for the tile from the end, so as to avoid depleting
2360 	 the early wall.
2361       */
2362       for ( i = the_game->wall.dead_end-1 ;
2363 	    the_game->wall.tiles[i] != m->newtile
2364 	      && i >= the_game->wall.live_used ;
2365 	    i-- ) ;
2366       if ( i < the_game->wall.live_used ) em.error = "No new tile in wall";
2367       else {
2368 	if ( handle_cmsg(the_game,&stm) < 0 ) {
2369 	  em.error = the_game->cmsg_err;
2370 	} else {
2371 	  send_id(stm.id,&stm);
2372 	  the_game->wall.tiles[i] = stm.oldtile;
2373 	}
2374       }
2375       if ( em.error )
2376 	send_id(id,&em);
2377       return;
2378     } /* end of case PMsgSwapTile */
2379   }
2380 }
2381 
2382 
2383 
2384 /* setup_maps: we need to be able to map between connections,
2385    player structures, and player ids.
2386    player to id is in the player structure, so we need the others.
2387    It's a pity we can't declare private variables here.
2388 */
2389 
2390 /* set up data for a new connection */
new_connection(SOCKET skt)2391 static int new_connection(SOCKET skt) {
2392   int i;
2393 
2394   for (i= 0; i < MAX_CONNECTIONS; i++) {
2395     if ( connections[i].inuse ) continue;
2396     connections[i].inuse = 1;
2397     connections[i].skt = skt;
2398     connections[i].player = NULL;
2399     connections[i].seqno = 0;
2400     /* add to the event set */
2401     FD_SET(skt,&event_fds);
2402     return i;
2403   }
2404   warn("No space for new connection");
2405   return -1;
2406 }
2407 
2408 /* close connection */
close_connection(int cnx)2409 static int close_connection(int cnx) {
2410   if ( connections[cnx].player ) {
2411     /* clear maps and decrement counter */
2412     remove_from_maps(cnx);
2413     num_connected_players--;
2414   }
2415   FD_CLR(connections[cnx].skt,&event_fds);
2416   closesocket(connections[cnx].skt);
2417   connections[cnx].inuse = 0;
2418   return 1;
2419 }
cnx_to_id(int cnx)2420 static int cnx_to_id(int cnx) {
2421   PlayerP p;
2422 
2423   p = connections[cnx].player;
2424   return p ? p->id : -1;
2425 }
2426 
id_to_cnx(int id)2427 static int id_to_cnx(int id) {
2428   int i;
2429 
2430   i = 0;
2431   while ( i < MAX_CONNECTIONS ) {
2432     if ( connections[i].inuse
2433 	 && connections[i].player
2434 	 && connections[i].player->id == id ) return i;
2435     i++;
2436   }
2437   return -1;
2438 }
2439 
id_to_player(int id)2440 static PlayerP id_to_player(int id) {
2441   int f;
2442 
2443   f = id_to_cnx(id);
2444   if ( f < 0 ) return NULL;
2445   return connections[f].player;
2446 }
2447 
setup_maps(int cnx,PlayerP p)2448 static void setup_maps(int cnx, PlayerP p) {
2449   connections[cnx].player = p;
2450 }
2451 
remove_from_maps(int cnx)2452 static void remove_from_maps(int cnx) {
2453   /* clear authorization data */
2454   pextras(connections[cnx].player)->auth_state = AUTH_NONE;
2455   connections[cnx].player = (PlayerP) 0;
2456 }
2457 
2458 /* send_packet: send the given packet out on the given cnx, perhaps logging */
2459 /* logit == 2 flags a message to all players of which this is one copy */
send_packet(int cnx,CMsgMsg * m,int logit)2460 static void send_packet(int cnx,CMsgMsg *m, int logit) {
2461   char *l;
2462 
2463   if ( cnx < 0 ) return;
2464 
2465   l = encode_cmsg(m);
2466   if ( l == NULL ) {
2467     /* this shouldn't happen */
2468     warn("send_packet: protocol conversion failed");
2469     /* in fact, it so much shouldn't happen that we'll dump core */
2470     assert(0);
2471     return;
2472   }
2473   if ( logit && logfile ) {
2474     if ( logit > 1 ) {
2475       fprintf(logfile,">cnx* %s",l);
2476     } else {
2477       fprintf(logfile,">cnx%d %s",cnx,l);
2478     }
2479     fflush(logfile);
2480   }
2481   if ( put_line(connections[cnx].skt,l) < 0 ) {
2482     warn("send_packet: write on cnx %d failed",cnx);
2483     /* maybe we should shutdown the descriptor here? */
2484     return;
2485   }
2486 }
2487 
2488 /* send player id a packet. Maybe log it.
2489    Enter the packet into the player's history, if appropriate */
_send_id(int id,CMsgMsg * m,int logit)2490 static void _send_id(int id,CMsgMsg *m,int logit) {
2491   int cnx = id_to_cnx(id);
2492   if ( cnx < 0 ) return;
2493   send_packet(cnx,m,logit);
2494   if ( usehist && logit && the_game->active ) {
2495     history_add(the_game,m);
2496   }
2497 }
2498 
2499 /* send to all players in a game, logging one copy */
_send_all(Game * g,CMsgMsg * m)2500 static void _send_all(Game *g,CMsgMsg *m) {
2501   _send_id(g->players[east]->id,m,2);
2502   _send_id(g->players[south]->id,m,0);
2503   _send_id(g->players[west]->id,m,0);
2504   _send_id(g->players[north]->id,m,0);
2505 }
2506 
2507 /* send to all players in a game EXCEPT the id. Don't log it. */
_send_others(Game * g,int id,CMsgMsg * m)2508 static void _send_others(Game *g, int id,CMsgMsg *m) {
2509   if ( g->players[east]->id != id ) _send_id(g->players[east]->id,m,0);
2510   if ( g->players[south]->id != id ) _send_id(g->players[south]->id,m,0);
2511   if ( g->players[west]->id != id ) _send_id(g->players[west]->id,m,0);
2512   if ( g->players[north]->id != id ) _send_id(g->players[north]->id,m,0);
2513 }
2514 
2515 /* send to a particular seat in a game. Logit. */
_send_seat(Game * g,seats s,CMsgMsg * m)2516 static void _send_seat(Game *g, seats s, CMsgMsg *m) {
2517   _send_id(g->players[s]->id,m,1);
2518 }
2519 
2520 /* send to seats other than s.
2521    Don't log it: a more informative copy will have gone to
2522    another player. */
_send_other_seats(Game * g,seats s,CMsgMsg * m)2523 static void _send_other_seats(Game *g, seats s, CMsgMsg *m) {
2524   seats i;
2525   for ( i = 0 ; i < NUM_SEATS ; i++ )
2526     if ( i != s ) _send_id(g->players[i]->id,m,0);
2527 }
2528 
2529 
2530 /* finish the game: send GameOver message, and print scores */
finish_game(Game * g)2531 static void finish_game(Game *g) {
2532   seats i,j;
2533   CMsgGameOverMsg gom;
2534   gom.type = CMsgGameOver;
2535   send_all(g,&gom);
2536   fprintf(stdout,"Game over. Final scores:\n");
2537   /* May as well print these in the order of original easts */
2538   for (i=0;i<NUM_SEATS;i++) {
2539     j = (i+1)%NUM_SEATS;
2540     fprintf(stdout,"%5d (%-20s)  %5d\n",
2541       g->players[j]->id,
2542       g->players[j]->name,
2543       g->players[j]->cumulative_score);
2544   }
2545 #ifdef WIN32
2546   sleep(10); /* give the client time to close the connection */
2547 #endif
2548   game_over = 1;
2549   the_game->active = 0;
2550 }
2551 
2552 /* start_hand: start a new hand.
2553    The game should be in state HandComplete.
2554    Send a NewHand message to each player.
2555    Shuffle and build the new wall.
2556    Deal the initial tiles to each player.
2557    Draw east's 14th tile.
2558    Enter state DeclaringSpecials, with player set to east.
2559 
2560    If the second argument is true, then don't send messages or
2561    actually start the hand; just rotate the deal (to get from a "final"
2562    HandComplete to an "initial" HandComplete, for use in game
2563    saving and resumption code).
2564 
2565    Return 1 on success.
2566    On error, return 0, and put human readable explanation in
2567    global variable failmsg.
2568 */
2569 
start_hand(Game * g,int rotate_only)2570 static int start_hand(Game *g, int rotate_only) {
2571   int stack,wall;
2572   CMsgNewHandMsg cm;
2573 
2574   cm.type = CMsgNewHand;
2575 
2576   if ( g->state != HandComplete ) {
2577     failmsg = "start_hand called when hand not complete.";
2578     return 0;
2579   }
2580 
2581   if ( ! rotate_only ) {
2582     save_state_hack = 0; /* see explanation by variable declaration */
2583   }
2584 
2585   cm.east = g->players[east]->id; /* assume no change */
2586 
2587   /* Does the deal rotate? */
2588   if ( g->player != noseat /* there was a mah jong */
2589        && (g->player != east /* and either it wasn't east */
2590 	   || g->hands_as_east == 13)) { /* or this was east's 13th win */
2591 
2592     /* does the round change? (the current south will be the next east;
2593        if that is the firsteast, the round changes) */
2594     if ( g->players[south]->id == g->firsteast ) {
2595       CMsgNewRoundMsg nrm;
2596 
2597       /* with num_rounds code, this is more complex. Don't duplicate */
2598 #if 0
2599       if ( g->round == NorthWind ) {
2600 	assert(0); /* this should have been caught earlier, now */
2601 	finish_game(g);
2602       }
2603 #endif
2604       nrm.round = next_wind(g->round);
2605       nrm.type = CMsgNewRound;
2606       handle_cmsg(g,&nrm);
2607       gextras(g)->completed_rounds++;
2608       if ( ! rotate_only ) {
2609 	send_all(g,&nrm);
2610       }
2611     }
2612     /* rotate the seats. The new east is the current south */
2613     cm.east = g->players[south]->id;
2614   }
2615 
2616   /* we haven't yet processed the newhand message */
2617 
2618   if ( ! rotate_only ) { clear_history(g); }
2619 
2620   /* now set up the prehistory: game message and options */
2621   if ( ! rotate_only )
2622   {
2623     CMsgGameMsg gamemsg;
2624     CMsgGameOptionMsg gom;
2625     GameOptionEntry *goe;
2626     int i;
2627 
2628     gamemsg.type = CMsgGame;
2629     gamemsg.east = g->players[east]->id;
2630     gamemsg.south = g->players[south]->id;
2631     gamemsg.west = g->players[west]->id;
2632     gamemsg.north = g->players[north]->id;
2633     gamemsg.round = g->round;
2634     gamemsg.hands_as_east = g->hands_as_east;
2635     gamemsg.firsteast = g->firsteast;
2636     gamemsg.east_score = g->players[east]->cumulative_score;
2637     gamemsg.south_score = g->players[south]->cumulative_score;
2638     gamemsg.west_score = g->players[west]->cumulative_score;
2639     gamemsg.north_score = g->players[north]->cumulative_score;
2640     gamemsg.protversion = g->protversion;
2641     gamemsg.manager = g->manager;
2642 
2643     gextras(g)->prehistory[gextras(g)->prehistcount++] = cmsg_deepcopy((CMsgMsg *)&gamemsg);
2644 
2645     gom.type = CMsgGameOption;
2646     gom.id = 0;
2647 
2648     /* this relies on the fact that we know our option table
2649        contains only known options in numerical order. This
2650        would not necessarily be the case for clients, but it
2651        is for us, since we don't allow unknown options to be set */
2652     for ( i = 1 ; i <= GOEnd ; i++ ) {
2653       goe = &g->option_table.options[i];
2654       gom.optentry = *goe;
2655       gextras(g)->prehistory[gextras(g)->prehistcount++] = cmsg_deepcopy((CMsgMsg *)&gom);
2656     }
2657   }
2658 
2659   /* process and send a new hand msg */
2660   if ( wallfilename ) {
2661     if ( load_wall(wallfilename,g) == 0 ) {
2662       warn("Unable to load wall");
2663       exit(1);
2664     }
2665   } else {
2666     /* set up the wall. Should this be done by the game routine?
2667        I think not, on the whole. */
2668     random_tiles(g->wall.tiles,game_get_option_value(g,GOFlowers,NULL).optbool);
2669   }
2670   /* we're also supposed to choose the place where the deal starts.
2671      Just for fun, we'll follow the real procedure. */
2672   wall = 1+rand_index(5) + 1+rand_index(5);
2673   stack = wall + 1+rand_index(5) + 1+rand_index(5);
2674   stack++; /* the dice give the end of the dead wall */
2675   if ( stack > (g->wall.size/NUM_SEATS)/2 ) {
2676     stack -= (g->wall.size/NUM_SEATS)/2;
2677     wall++;
2678   }
2679 
2680   cm.start_wall = (wall-1) % NUM_SEATS;
2681   cm.start_stack = stack -1; /* convert from 1 counting to 0 counting */
2682   if ( handle_cmsg(g,&cm) < 0 ) {
2683     warn("handle_cmsg of NewHand message return error; dumping core");
2684     abort();
2685     }
2686   if ( rotate_only ) { return 1; }
2687   send_all(g,&cm);
2688 
2689   /* deal out the tiles into deal messages.
2690      We will actually deal in the traditional way: if we ever get
2691      round to implementing a semi-random shuffle, instead of a really
2692      random re-deal, this will make a difference.
2693   */
2694   { CMsgPlayerDrawsMsg pdm;
2695   int i,j;
2696   seats s;
2697   pdm.type = CMsgPlayerDraws;
2698 
2699   for ( j=0; j < 3; j++ ) {
2700     for ( s=east; s<=north; s++) {
2701       check_min_time(1);
2702       pdm.id = g->players[s]->id;
2703       for ( i = 0; i < 4; i++ ) {
2704 	pdm.tile = game_draw_tile(g);
2705 	send_id(pdm.id,&pdm);
2706 	player_draws_tile(g->players[s],pdm.tile);
2707 	pdm.tile = HiddenTile;
2708 	send_others(g,pdm.id,&pdm);
2709       }
2710     }
2711   }
2712   for ( s=east; s<=north; s++) {
2713     check_min_time(1);
2714     pdm.id = g->players[s]->id;
2715     pdm.tile = game_draw_tile(g);
2716     send_id(pdm.id,&pdm);
2717     player_draws_tile(g->players[s],pdm.tile);
2718     pdm.tile = HiddenTile;
2719     send_others(g,pdm.id,&pdm);
2720   }
2721   /* at this point, we should copy east to the caller slot in case
2722      it wins with heaven's blessing; otherwise scoring.c will be
2723      unhappy. (Yes, this happened.) */
2724   copy_player(gextras(g)->caller,g->players[east]);
2725 
2726   s=east;
2727   pdm.id = g->players[s]->id;
2728   pdm.tile = game_draw_tile(g);
2729   check_min_time(1);
2730   send_id(pdm.id,&pdm);
2731   player_draws_tile(g->players[s],pdm.tile);
2732   pdm.tile = HiddenTile;
2733   send_others(g,pdm.id,&pdm);
2734   }
2735 
2736   /* send out info messages */
2737   send_infotiles(g->players[east]);
2738   send_infotiles(g->players[south]);
2739   send_infotiles(g->players[west]);
2740   send_infotiles(g->players[north]);
2741 
2742   /* Enter the next state and return */
2743   g->state = DeclaringSpecials;
2744   g->player = east;
2745   return 1;
2746 }
2747 
2748 
2749 
2750 /* timeout_handler: force no claims to be sent, and
2751    then check claims */
timeout_handler(Game * g)2752 static void timeout_handler(Game *g) {
2753   seats i;
2754   CMsgPlayerDoesntClaimMsg pdcm;
2755 
2756   if ( g->state != Discarded
2757        && ! ( g->state == Discarding && g->konging ) ) {
2758     warn("timeout_handler called in unexpected state");
2759     return;
2760   }
2761 
2762   pdcm.type = CMsgPlayerDoesntClaim;
2763   pdcm.discard = g->serial;
2764   pdcm.timeout = 1;
2765   for ( i=0; i < NUM_SEATS; i++ ) {
2766     if ( i == g->player ) continue;
2767     if ( g->claims[i] != UnknownClaim ) continue;
2768     pdcm.id = g->players[i]->id;
2769     handle_cmsg(g,&pdcm);
2770     send_id(pdcm.id,&pdcm);
2771   }
2772   check_claims(g);
2773 }
2774 
2775 
2776 /* check_claims: when a player makes a claim on the discard,
2777    (or tries to rob a kong)
2778    we need to check whether all claims have been lodged, and
2779    if so, process them. This function does that */
2780 
check_claims(Game * g)2781 static void check_claims(Game *g) {
2782   seats i, s, ds;
2783   CMsgClaimDeniedMsg cdm;
2784 
2785   if ( g->state != Discarded
2786        && ! ( (g->state == Discarding
2787 	       || g->state == DeclaringSpecials)
2788 	      && g->konging ) ) {
2789     warn("check_claims: called in wrong game state");
2790     return;
2791   }
2792 
2793   /* ds is the seat of the discarder/konger */
2794   ds = g->player;
2795 
2796   /* return if not all claims are lodged */
2797   for ( i = 0 ; i < NUM_SEATS ; i++ )
2798     if ( i != ds && g->claims[i] == UnknownClaim ) return;
2799 
2800   /* if we are in a chowpending state, return silently now: it must be
2801      a duplicate claim that triggered this */
2802   if ( g->chowpending ) {
2803     info("check_claim: Duplicate claim received, ignoring");
2804     return;
2805   }
2806 
2807   /* OK, process. First, cancel timeout */
2808   timeout = 0;
2809 
2810   /* s will be the successful claim */
2811   s = noseat ;
2812   /* is there a mah-jong? */
2813   for ( i = 0 ; i < NUM_SEATS ; i++ )
2814     if ( g->claims[(ds+i)%NUM_SEATS]
2815 	  == MahJongClaim ) { s = (ds+i)%NUM_SEATS ; break ; }
2816 
2817   /* is there a kong? */
2818   if ( s == noseat )
2819     for ( i = 0 ; i < NUM_SEATS ; i++ )
2820       if ( g->claims[(ds+i)%NUM_SEATS]
2821 	   == KongClaim ) { s = (ds+i)%NUM_SEATS ; break ; }
2822 
2823   /* is there a pung ? */
2824   if ( s == noseat )
2825     for ( i = 0 ; i < NUM_SEATS ; i++ )
2826       if ( g->claims[(ds+i)%NUM_SEATS]
2827 	   == PungClaim ) { s = (ds+i)%NUM_SEATS ; break ; }
2828 
2829   /* or is there a chow? */
2830   if ( s == noseat )
2831     if ( g->claims[(ds+1)%NUM_SEATS]
2832 	   == ChowClaim ) { s = (ds+1)%NUM_SEATS ; }
2833 
2834   /* finished checking; now process */
2835 
2836   if ( s == noseat ) {
2837     if ( g->state == Discarded ) {
2838       /* no claim; play passes to the next player, who draws */
2839       CMsgPlayerDrawsMsg m;
2840       s = (ds+1)%NUM_SEATS;
2841       m.type = CMsgPlayerDraws;
2842       m.id = g->players[s]->id;
2843       /* peek at the tile: we'll pass the CMsg to the game
2844 	 to handle state changes */
2845       m.tile = game_peek_tile(g);
2846 
2847       if ( m.tile == ErrorTile ) {
2848 	/* run out of wall; washout */
2849 	washout(NULL);
2850 	return;
2851       }
2852 
2853       /* stash a copy of the player, in case it goes mahjong */
2854       copy_player(gextras(g)->caller,g->players[s]);
2855 
2856       if ( handle_cmsg(g,&m) < 0 ) {
2857 	warn("Consistency error drawing tile");
2858 	abort();
2859       }
2860       check_min_time(1);
2861       send_seat(g,s,&m);
2862       m.tile = HiddenTile;
2863       send_other_seats(g,s,&m);
2864       send_infotiles(g->players[s]);
2865       return;
2866     } else {
2867       /* the player formed a kong, and (surprise) nobody tried to
2868 	 rob it */
2869       CMsgPlayerDrawsLooseMsg pdlm;
2870 
2871       pdlm.type = CMsgPlayerDrawsLoose;
2872       pdlm.id = g->players[ds]->id;
2873       /* find the loose tile. */
2874       pdlm.tile = game_peek_loose_tile(the_game);
2875       if ( pdlm.tile == ErrorTile ) {
2876 	washout(NULL);
2877 	return;
2878       }
2879 
2880       /* stash a copy of the player, in case it goes mahjong */
2881       copy_player(gextras(g)->caller,g->players[ds]);
2882 
2883       if ( handle_cmsg(the_game,&pdlm) < 0 ) {
2884 	/* error should be impossible */
2885 	warn("Consistency error: giving up");
2886 	exit(1);
2887       }
2888       check_min_time(1);
2889       send_id(pdlm.id,&pdlm);
2890       pdlm.tile = HiddenTile;
2891       send_others(the_game,pdlm.id,&pdlm);
2892       send_infotiles(g->players[ds]);
2893       return;
2894     }
2895   }
2896 
2897   if ( g->claims[s] == ChowClaim ) {
2898     /* there can be no other claims, so just implement this one */
2899     CMsgPlayerChowsMsg pcm;
2900 
2901     pcm.type = CMsgPlayerChows;
2902     pcm.id = g->players[s]->id;
2903     pcm.tile = g->tile;
2904     pcm.cpos = g->cpos;
2905 
2906     /* if the cpos is AnyPos, then we instruct the player (only)
2907        to send in another chow claim, and just wait for it */
2908     if ( pcm.cpos == AnyPos ) {
2909       if ( handle_cmsg(g,&pcm) < 0 ) {
2910 	CMsgErrorMsg em;
2911 	em.type = CMsgError;
2912 	em.seqno = connections[id_to_cnx(pcm.id)].seqno;
2913 	em.error = g->cmsg_err;
2914 	send_id(pcm.id,&em);
2915 	return;
2916       }
2917       send_id(pcm.id,&pcm);
2918       return;
2919     }
2920 
2921     /* stash a copy of the player, in case it goes mahjong */
2922     copy_player(gextras(g)->caller,g->players[s]);
2923 
2924     if ( handle_cmsg(g,&pcm) < 0 ) {
2925       warn("Consistency error: failed to implement claimed chow.");
2926       exit(1);
2927     }
2928 
2929     check_min_time(2);
2930     send_all(g,&pcm);
2931     send_infotiles(g->players[s]);
2932     /* if that came from a dangerous discard, tell the other players */
2933     if ( game_flag(the_game,GFDangerousDiscard) ) {
2934       CMsgDangerousDiscardMsg ddm;
2935       ddm.type = CMsgDangerousDiscard;
2936       ddm.discard = the_game->serial;
2937       ddm.id = g->players[g->supplier]->id;
2938       ddm.nochoice = game_flag(g,GFNoChoice);
2939       send_all(the_game,&ddm);
2940     }
2941     return;
2942   }
2943 
2944   /* In the remaining cases, there may be other claims; send
2945      denial messages to them */
2946   cdm.type = CMsgClaimDenied;
2947   cdm.reason = "There was a prior claim";
2948 
2949   for ( i = 0 ; i < NUM_SEATS ; i++ ) {
2950     if ( i != ds && i != s && g->claims[i] != NoClaim) {
2951       cdm.id = g->players[i]->id;
2952       send_id(cdm.id,&cdm);
2953     }
2954   }
2955 
2956   if ( g->claims[s] == PungClaim ) {
2957     CMsgPlayerPungsMsg ppm;
2958 
2959     ppm.type = CMsgPlayerPungs;
2960     ppm.id = g->players[s]->id;
2961     ppm.tile = g->tile;
2962 
2963     /* stash a copy of the player, in case it goes mahjong */
2964     copy_player(gextras(g)->caller,g->players[s]);
2965 
2966     if ( handle_cmsg(g,&ppm) < 0 ) {
2967       warn("Consistency error: failed to implement claimed pung.");
2968       exit(1);
2969     }
2970 
2971     check_min_time(2);
2972     send_all(g,&ppm);
2973     send_infotiles(g->players[s]);
2974     /* if that came from a dangerous discard, tell the other players */
2975     if ( game_flag(the_game,GFDangerousDiscard) ) {
2976       CMsgDangerousDiscardMsg ddm;
2977       ddm.type = CMsgDangerousDiscard;
2978       ddm.id = g->players[g->supplier]->id;
2979       ddm.discard = the_game->serial;
2980       ddm.nochoice = game_flag(g,GFNoChoice);
2981       send_all(the_game,&ddm);
2982     }
2983     return;
2984   }
2985 
2986   if ( g->claims[s] == KongClaim ) {
2987     CMsgPlayerKongsMsg pkm;
2988     CMsgPlayerDrawsLooseMsg pdlm;
2989 
2990     pdlm.type = CMsgPlayerDrawsLoose;
2991     pkm.type = CMsgPlayerKongs;
2992     pdlm.id = pkm.id = g->players[s]->id;
2993     pkm.tile = g->tile;
2994 
2995     if ( handle_cmsg(g,&pkm) < 0 ) {
2996       warn("Consistency error: failed to implement claimed kong.");
2997       exit(1);
2998     }
2999 
3000     check_min_time(2);
3001     send_all(g,&pkm);
3002 
3003     /* find the loose tile. */
3004     pdlm.tile = game_peek_loose_tile(g);
3005     if ( pdlm.tile == ErrorTile ) {
3006       washout(NULL);
3007       return;
3008     }
3009 
3010     /* stash a copy of the player, in case it goes mahjong */
3011     copy_player(gextras(g)->caller,g->players[s]);
3012 
3013     if ( handle_cmsg(the_game,&pdlm) < 0 ) {
3014       /* error should be impossible */
3015       warn("Consistency error: giving up");
3016       exit(1);
3017     }
3018     check_min_time(1);
3019     send_id(pdlm.id,&pdlm);
3020     pdlm.tile = HiddenTile;
3021     send_others(g,pdlm.id,&pdlm);
3022     send_infotiles(g->players[s]);
3023     return;
3024   }
3025 
3026   if ( g->claims[s] == MahJongClaim ) {
3027     /* stash a copy of the player before it grabs the discard */
3028     copy_player(gextras(g)->caller,g->players[s]);
3029 
3030     if ( g->state == Discarded ) {
3031       /* the normal situation */
3032       CMsgPlayerMahJongsMsg pmjm;
3033 
3034       pmjm.type = CMsgPlayerMahJongs;
3035       pmjm.id = g->players[s]->id;
3036       pmjm.tile = HiddenTile;
3037 
3038       if ( handle_cmsg(g,&pmjm) < 0 ) {
3039 	/* this should be impossible */
3040 	CMsgErrorMsg em;
3041 	em.type = CMsgError;
3042 	em.seqno = connections[id_to_cnx(pmjm.id)].seqno;
3043 	em.error = g->cmsg_err;
3044 	send_id(pmjm.id,&em);
3045 	return;
3046       }
3047       send_all(g,&pmjm);
3048       return;
3049     } else {
3050       /* the kong's been robbed */
3051       CMsgPlayerRobsKongMsg prkm;
3052 
3053       prkm.type = CMsgPlayerRobsKong;
3054       prkm.id = g->players[s]->id;
3055       prkm.tile = g->tile;
3056 
3057       if ( handle_cmsg(g,&prkm) < 0 ) {
3058 	/* this should be impossible */
3059 		CMsgErrorMsg em;
3060 	em.type = CMsgError;
3061 	em.seqno = connections[id_to_cnx(prkm.id)].seqno;
3062 	em.error = g->cmsg_err;
3063 	send_id(prkm.id,&em);
3064 	return;
3065       }
3066       check_min_time(2);
3067       send_all(g,&prkm);
3068       return;
3069     }
3070   }
3071 
3072   /* NOTREACHED */
3073 }
3074 
3075 
3076 /* This function sends an InfoTiles message about the given player
3077    to all players who are receiving info tiles */
send_infotiles(PlayerP p)3078 static void send_infotiles(PlayerP p) {
3079   static CMsgInfoTilesMsg itm;
3080   PlayerP q;
3081   static char buf[128];
3082   int i;
3083 
3084   itm.type = CMsgInfoTiles;
3085   itm.id = p->id;
3086   if ( popts(p)[POInfoTiles] ) {
3087     player_print_tiles(buf,p,0);
3088     itm.tileinfo = buf;
3089     send_id(itm.id,&itm);
3090   }
3091   player_print_tiles(buf,p,1);
3092   for ( i = 0 ; i < NUM_SEATS ; i++ ) {
3093     q = the_game->players[i];
3094     if ( q != p && popts(q)[POInfoTiles] ) {
3095       send_id(q->id,&itm);
3096     }
3097   }
3098 }
3099 
3100 
3101 /* Save state in the file.
3102    The state is saved as a sequence of CMsgs.
3103    First, four Players;
3104    then a Game;
3105    then GameOptions;
3106    then if a hand is in progress,
3107    Wall
3108    NewHand
3109    the history of the hand.
3110 */
save_state(Game * g,char * filename)3111 static char *save_state(Game *g, char *filename) {
3112   int fd;
3113   int i,j; /* unsigned to suppress warnings */
3114   static char gfile[512];
3115   CMsgGameMsg gamemsg;
3116   CMsgGameOptionMsg gom;
3117   GameOptionEntry *goe;
3118 
3119   if ( filename && filename[0] ) {
3120     char *f;
3121     /* strip any directories, for security */
3122     f = strrchr(filename,'/');
3123     if ( f ) filename = f+1;
3124     f = strrchr(filename,'\\');
3125     if ( f ) filename = f+1;
3126     strmcpy(gfile,filename,500);
3127     /* if it doesn't already end with .mjs, add it */
3128     if ( strlen(gfile) < 4 || strcmp(gfile+strlen(gfile)-4,".mjs") ) {
3129       strcat(gfile,".mjs");
3130     }
3131   } else {
3132     /* if we have no name already in use, construct one */
3133     if ( gfile[0] == 0 ) {
3134       if ( loadfilename[0] ) {
3135 	strmcpy(gfile,loadfilename,510);
3136       } else {
3137 	struct tm *ts;
3138 	time_t t;
3139 	int i;
3140 	struct stat junk;
3141 	t = time(NULL);
3142 	ts = localtime(&t);
3143 	i = 0;
3144 	while ( i < 10 ) {
3145 	  sprintf(gfile,"game-%d-%02d-%02d-%d.mjs",
3146 	    ts->tm_year+1900,ts->tm_mon+1,ts->tm_mday,i);
3147 	  if ( stat(gfile,&junk) != 0 ) break;
3148 	  i++;
3149 	}
3150       }
3151     }
3152   }
3153   fd = open(gfile,O_WRONLY|O_CREAT|O_TRUNC,0777);
3154   if ( fd < 0 ) {
3155     warn("Unable to write game state file: %s",strerror(errno));
3156     return 0;
3157   }
3158   for ( i = 0; i < NUM_SEATS; i++ ) {
3159     CMsgPlayerMsg pm;
3160     PlayerP q;
3161 
3162     q = g->players[i];
3163     if ( q->id == 0 ) continue;
3164     pm.type = CMsgPlayer;
3165     pm.id = q->id;
3166     pm.name = q->name;
3167     fd_put_line(fd,encode_cmsg((CMsgMsg *)&pm));
3168   }
3169 
3170   /* Normally, we write out a game message describing the current
3171      state of the game, and then the history. However, if this
3172      is the second time we've been called at the end of the same
3173      hand, we need to print the prehistory instead. */
3174   if ( g->state == HandComplete ) save_state_hack++;
3175 
3176   if ( save_state_hack > 1 ) {
3177     int j;
3178     /* save the number of completed rounds in a comment */
3179     CMsgCommentMsg cm;
3180     cm.type = CMsgComment;
3181     char c[] = "CompletedRounds nnnnnn";
3182     sprintf(c,"CompletedRounds %6d",gextras(g)->completed_rounds);
3183     cm.comment = c;
3184     fd_put_line(fd,encode_cmsg((CMsgMsg *)&cm));
3185 
3186     for ( j=0; j < gextras(g)->prehistcount; j++ ) {
3187       fd_put_line(fd,encode_cmsg(gextras(g)->prehistory[j]));
3188     }
3189   } else {
3190     /* This is a mess. Because of our initial design error,
3191        the protocol can't distinguish between an "initial" HandComplete
3192        and a "final" HandComplete. So if we're saving state in
3193        HandComplete, we need to rotate the deal before saving, so that the
3194        resumed game loads in an "initial" HandComplete for the next hand.
3195        But we can't do this to the real game, because that would mess
3196        up the game in progress. So we have to make a deep copy of the game.
3197        As this is only done here, we won't encapsulate it. */
3198 
3199     Game ng = *g;
3200     GameExtras ngx = *(gextras(g));
3201     ng.userdata = (void *)&ngx;
3202     Player pp[NUM_SEATS];
3203     int j;
3204     Game *gg;
3205     if ( g->state == HandComplete ) {
3206       for (j = 0; j < NUM_SEATS; j++) {
3207 	copy_player(&pp[j],g->players[j]);
3208 	ng.players[j] = &pp[j];
3209       }
3210       gg = &ng;
3211       start_hand(gg,1);
3212     } else {
3213       gg = g;
3214     }
3215 
3216     gamemsg.type = CMsgGame;
3217     gamemsg.east = gg->players[east]->id;
3218     gamemsg.south = gg->players[south]->id;
3219     gamemsg.west = gg->players[west]->id;
3220     gamemsg.north = gg->players[north]->id;
3221     gamemsg.round = gg->round;
3222     gamemsg.hands_as_east = gg->hands_as_east;
3223     gamemsg.firsteast = gg->firsteast;
3224     gamemsg.east_score = gg->players[east]->cumulative_score;
3225     gamemsg.south_score = gg->players[south]->cumulative_score;
3226     gamemsg.west_score = gg->players[west]->cumulative_score;
3227     gamemsg.north_score = gg->players[north]->cumulative_score;
3228     gamemsg.protversion = gg->protversion;
3229     gamemsg.manager = gg->manager;
3230 
3231     fd_put_line(fd,encode_cmsg((CMsgMsg *)&gamemsg));
3232 
3233     /* save the number of completed rounds in a comment */
3234     CMsgCommentMsg cm;
3235     cm.type = CMsgComment;
3236     char c[] = "CompletedRounds nnnnnn";
3237     sprintf(c,"CompletedRounds %6d",gextras(gg)->completed_rounds);
3238     cm.comment = c;
3239     fd_put_line(fd,encode_cmsg((CMsgMsg *)&cm));
3240 
3241     gom.type = CMsgGameOption;
3242     gom.id = 0;
3243 
3244     /* this relies on the fact that we know our option table
3245        contains only known options in numerical order. This
3246        would not necessarily be the case for clients, but it
3247        is for us, since we don't allow unknown options to be set */
3248     for ( i = 1 ; i <= GOEnd ; i++ ) {
3249       goe = &gg->option_table.options[i];
3250       gom.optentry = *goe;
3251       fd_put_line(fd,encode_cmsg((CMsgMsg *)&gom));
3252     }
3253   }
3254 
3255   /* We are saving state for resumption here, so we do not need
3256      to save the history of a complete hand */
3257   if ( g->state != HandComplete || save_state_hack > 1 ) {
3258     CMsgWallMsg wm;
3259     char wall[MAX_WALL_SIZE*3+1];
3260     wm.type = CMsgWall;
3261     wm.wall = wall;
3262     wall[0] = 0;
3263     for ( i = 0 ; i < g->wall.size ; i++ ) {
3264       if ( i ) strcat(wall," ");
3265       strcat(wall,tile_code(g->wall.tiles[i]));
3266     }
3267     fd_put_line(fd,encode_cmsg((CMsgMsg *)&wm));
3268 
3269     for ( j=0; j < gextras(g)->histcount; j++ ) {
3270       fd_put_line(fd,encode_cmsg(gextras(g)->history[j]));
3271     }
3272   }
3273 
3274   close(fd);
3275   return gfile;
3276 }
3277 
3278 /* Load the state into the (pre-allocated) game pointed to by g.
3279    N.B. The player structures must also be allocated.
3280 */
load_state(Game * g)3281 static int load_state(Game *g) {
3282   int fd;
3283   char *l;
3284   CMsgMsg *m;
3285   int manager = 0;
3286 
3287   fd = open(loadfilename,O_RDONLY);
3288   if ( fd < 0 ) {
3289     warn("unable to open game state file: %s",strerror(errno));
3290     return 0;
3291   }
3292 
3293   clear_history(g);
3294   g->cmsg_check = 1;
3295   while ( (l = fd_get_line(fd)) ) {
3296     m = decode_cmsg(l);
3297     if ( ! m ) {
3298       warn("Bad cmsg in game state file, line %s",l);
3299       return 0;
3300     }
3301     if ( m->type == CMsgComment ) {
3302       char *p = ((CMsgCommentMsg *)m)->comment;
3303       if ( strncmp("CompletedRounds ",p,16) == 0 ) {
3304 	if ( sscanf(p+16,"%d",&(gextras(g)->completed_rounds)) == 0 )
3305 	  warn("unable to read number of completed rounds in game state file line %s",l);
3306       }
3307       continue;
3308     }
3309     if ( game_handle_cmsg(g,m) < 0 ) {
3310       warn("Error handling cmsg: line %s, error %s",l,g->cmsg_err);
3311       return 0;
3312     }
3313     /* if the game has a manager, clear it temporarily, so that
3314        the gameoptions we're about to process do get processed */
3315     if ( m->type == CMsgGame ) {
3316       manager = g->manager;
3317       g->manager = 0;
3318     }
3319     history_add(g,m);
3320   }
3321 
3322   /* reinstate manager */
3323   g->manager = manager;
3324   close(fd);
3325   /* We need to copy the timeout option value from the resumed game */
3326   timeout_time = get_timeout_time(g,localtimeouts);
3327   return 1;
3328 }
3329 
3330 /* This function is responsible for handling the scoring.
3331    It is called in the following circumstances:
3332    The mahjonging player has made all their sets, or
3333    a non-mahjonging player has issued a ShowTiles message
3334    (the showing of the tiles will already have been done).
3335    It then (a) computes the score for the hand, together
3336    with a text description of the computation, and announces
3337    it.
3338    (b) if all players are now declared, computes the settlements,
3339    announces the net change for each player, together with
3340    a text description of the calculation; implements the settlements;
3341    and moves to the state HandComplete, incrementing hands_as_east
3342    if necessary.
3343    If the game is being terminated early because of disconnect,
3344    it will finish the game at Settlement.
3345    Arguments: g is the game, s is the *seat* of the player on
3346    which something has happened.
3347 */
score_hand(Game * g,seats s)3348 static void score_hand(Game *g, seats s) {
3349   PlayerP p = g->players[s];
3350   Score score;
3351   char buf[1000], tmp[100];
3352   static char *seatnames[] = { "East ", "South", "West ", "North" };
3353   static int changes[NUM_SEATS];
3354   CMsgHandScoreMsg hsm;
3355   CMsgSettlementMsg sm;
3356   seats i,j;
3357   int m;
3358   int cannon=0;
3359   seats cannoner = noseat;
3360   bool eastdoubles;
3361   bool loserssettle;
3362   bool discarderdoubles;
3363 
3364   buf[0] = 0;
3365   score = score_of_hand(g,s);
3366   hsm.type = CMsgHandScore;
3367   hsm.id = p->id;
3368   hsm.score = score.value;
3369   hsm.explanation = score.explanation;
3370 
3371   send_all(g,&hsm);
3372 
3373   if ( s == g->player ) {
3374     psetflag(p,MahJongged);
3375   }
3376   set_player_hand_score(p,score.value);
3377 
3378   /* have we scored all the players ? */
3379   for (i=0; i < NUM_SEATS; i++)
3380     if ( g->players[i]->hand_score < 0 ) return;
3381 
3382   /* now compute the settlements. East pays and receives double, etc? */
3383   eastdoubles = game_get_option_value(g,GOEastDoubles,NULL).optbool;
3384   loserssettle = game_get_option_value(g,GOLosersSettle,NULL).optbool;
3385   discarderdoubles = game_get_option_value(g,GODiscDoubles,NULL).optbool;
3386 
3387   for ( i = 0; i < NUM_SEATS; i++ ) changes[i] = 0;
3388 
3389   /* first the winner; let's re-use s and p */
3390   s = g->player;
3391   p = g->players[s];
3392 
3393   /* Firstly, check to see if somebody let off a cannon. At this point,
3394      we assume we know that the dflags field is a bit field! */
3395   for ( i = 0; i < NUM_SEATS; i++ ) {
3396     if ( i == s ) continue;
3397     if ( p->dflags[i] & p->dflags[s] ) {
3398       /* Only one person can be liable, so let's have a little
3399 	 consistency check */
3400       if ( cannon ) {
3401 	warn("Two people found letting off a cannon!");
3402 	exit(1);
3403       }
3404       cannon=1;
3405       cannoner=i;
3406     }
3407   }
3408 
3409   if ( cannon ) {
3410     sprintf(tmp,"%s let off a cannon\n\n",seatnames[cannoner]);
3411     strcat(buf,tmp);
3412   }
3413   for ( i = 0; i < NUM_SEATS; i++ ) {
3414     if ( i == s ) continue;
3415     m = g->players[s]->hand_score;
3416     if ( eastdoubles && i*s == 0 ) m *= 2; /* sneaky test for one being east */
3417     /* singaporean rules: discarder pays double, on self-draw
3418        (including loose - query, is this right?) everybody pays double */
3419     if ( discarderdoubles
3420       && (g->whence != FromDiscard
3421 	|| i == g->supplier) ) m *= 2;
3422     changes[s] += m; changes[cannon ? cannoner : i] -= m;
3423     sprintf(tmp,"%s pays %s",seatnames[cannon ? cannoner : i],seatnames[s]);
3424     strcat(buf,tmp);
3425     if ( cannon && cannoner != i ) {
3426       sprintf(tmp," (for %s)",seatnames[i]);
3427       strcat(buf,tmp);
3428     }
3429     sprintf(tmp," %5d\n",m);
3430     strcat(buf,tmp);
3431   }
3432   sprintf(tmp,"%s GAINS %5d\n\n",seatnames[s],changes[s]);
3433   strcat(buf,tmp);
3434 
3435   /* and now the others */
3436   for ( i = 0; i < NUM_SEATS; i++ ) {
3437     if ( i == s ) continue;
3438     for ( j = i+1; j < NUM_SEATS; j++ ) {
3439       if ( j == s ) continue;
3440       if ( cannon ) continue;
3441       if ( ! loserssettle ) continue;
3442       m = g->players[i]->hand_score - g->players[j]->hand_score;
3443       if ( eastdoubles && i*j == 0 ) m *= 2;
3444       changes[i] += m; changes[j] -= m;
3445       if ( m > 0 ) {
3446 	sprintf(tmp,"%s pays %s %5d\n",seatnames[j],seatnames[i],m);
3447       } else if ( m < 0 ) {
3448 	sprintf(tmp,"%s pays %s %5d\n",seatnames[i],seatnames[j],-m);
3449       } else {
3450 	sprintf(tmp,"%s and %s are even\n",seatnames[i],seatnames[j]);
3451       }
3452       strcat(buf,tmp);
3453     }
3454     sprintf(tmp,"%s %s %5d\n\n",seatnames[i],
3455 	    changes[i] >= 0 ? "GAINS" : "LOSES",
3456 	    abs(changes[i]));
3457     strcat(buf,tmp);
3458   }
3459 
3460   sm.type = CMsgSettlement;
3461   sm.east = changes[east];
3462   sm.south = changes[south];
3463   sm.west = changes[west];
3464   sm.north = changes[north];
3465   sm.explanation = buf;
3466   handle_cmsg(g,&sm);
3467   send_all(g,&sm);
3468 
3469   /* dump the hand history to a log file if requested */
3470   if ( hand_history ) {
3471     char buf[256];
3472     /* hack hack hack FIXME */
3473     static int handnum = 1;
3474     sprintf(buf,"hand-%02d.mjs",handnum++);
3475     save_state_hack = 1;
3476     save_state(g,buf);
3477   }
3478 
3479   /* finish game if terminating on disconnect */
3480   if ( end_on_disconnect_in_progress ) finish_game(g);
3481 
3482   /* is the game over? */
3483   if ( g->player != noseat /* there was a mah jong */
3484     && (g->player != east /* and either it wasn't east */
3485       || g->hands_as_east == 13) /* or this was east's 13th win */
3486     /* does the round change? (the current south will be the next east;
3487        if that is the firsteast, the round changes) */
3488     && g->players[south]->id == g->firsteast
3489     && gextras(g)->completed_rounds == game_get_option_value(g,GONumRounds,NULL).optnat - 1 ) // it's the last round
3490     ////    && g->round == NorthWind ) /* and it's the last round already */
3491     finish_game(g);
3492 
3493   /* otherwise pause for the next hand */
3494   {
3495     CMsgPauseMsg pm;
3496     pm.type = CMsgPause;
3497     pm.exempt = 0;
3498     pm.requestor = 0;
3499     pm.reason = "to start next hand";
3500     handle_cmsg(g,&pm);
3501     send_all(g,&pm);
3502   }
3503 }
3504 
3505 /* implement washout. The argument is the reason for the washout;
3506    if NULL, the default reason of "wall exhausted" is assumed. */
washout(char * reason)3507 static void washout(char *reason) {
3508   CMsgPauseMsg pm;
3509   CMsgWashOutMsg wom;
3510   wom.type = CMsgWashOut;
3511   if ( reason ) wom.reason = reason;
3512   else wom.reason = "wall exhausted";
3513   handle_cmsg(the_game,&wom);
3514   send_all(the_game,&wom);
3515   /* if ShowOnWashout is set, show all tiles */
3516   if ( game_get_option_value(the_game,GOShowOnWashout,NULL).optbool ) {
3517     int i;
3518     PlayerP p;
3519     CMsgPlayerShowsTilesMsg pstm;
3520 
3521     /* fill in basic fields of possible replies */
3522     pstm.type = CMsgPlayerShowsTiles;
3523 
3524     for ( i = 0; i < NUM_SEATS; i++ ) {
3525       p = the_game->players[i];
3526 
3527       /* if there are no concealed tiles, skip */
3528       if ( p->num_concealed > 0 ) {
3529 	char tiles[100];
3530 	int j;
3531 
3532 	tiles[0] = '\000';
3533 	for ( j = 0; j < p->num_concealed; j++ ) {
3534 	  if ( j > 0 ) strcat(tiles," ");
3535 	  strcat(tiles,tile_code(p->concealed[j]));
3536 	}
3537 	pstm.id = p->id;
3538 	pstm.tiles = tiles;
3539 	if ( handle_cmsg(the_game,&pstm) < 0 ) {
3540 	  warn("Error handling cmsg in ShowOnWashout: error %s",the_game->cmsg_err);
3541 	  return;
3542 	}
3543 	check_min_time(1);
3544 	send_all(the_game,&pstm);
3545       }
3546     }
3547   }
3548   /* and now pause for the next hand */
3549   pm.type = CMsgPause;
3550   pm.exempt = 0;
3551   pm.requestor = 0;
3552   pm.reason = "to start next hand";
3553   handle_cmsg(the_game,&pm);
3554   send_all(the_game,&pm);
3555 }
3556 
3557 /* called for an error on an cnx. Removes the cnx,
3558    suspends the game if appropriate, etc */
handle_cnx_error(int cnx)3559 static void handle_cnx_error(int cnx) {
3560   CMsgStopPlayMsg spm;
3561   char emsg[100];
3562   int id;
3563   /* for the moment, die noisily. Ultimately, just notify
3564      other players, and re-open the listening socket */
3565   if ( connections[cnx].skt == socket_fd ) {
3566     /* hard to see how this could happen, but ... */
3567     warn("Exception condition on listening fd\n");
3568     exit(1);
3569   }
3570   warn("Exception/eof on cnx %d, player %d\n",cnx, cnx_to_id(cnx));
3571   spm.type = CMsgStopPlay;
3572   /* We clear the player's entry before sending the message
3573      so that it fails harmlessly on the player's file instead
3574      of writing to a broken pipe. */
3575   id = cnx_to_id(cnx);
3576   /* we need to keep the connection in the table for use below.
3577      This will cause some writes to broken pipes, but we're
3578      ignoring those anyway */
3579   if ( id >= 0 ) {
3580     /* if the game hasn't yet started, and all the players support
3581        the necessary protocol version, we do not need to exit;
3582        we can just delete the disconnecting player.
3583        Of course, we don't need the disconnecting player to support
3584        the necessary protocol version, but I can't be bothered to
3585        do that.
3586        However, I think if --exit-on-disconnect is given, we
3587        should exit anyway, since the purpose of that option is
3588        to stop unwanted servers hanging around.
3589     */
3590     if ( the_game->round == UnknownWind
3591       && ! exit_on_disconnect
3592       && protocol_version > 1034 ) {
3593       CMsgPlayerMsg pm;
3594       pm.type = CMsgPlayer;
3595       pm.id = id;
3596       pm.name = NULL;
3597       handle_cmsg(the_game,&pm);
3598       close_connection(cnx);
3599       send_all(the_game,&pm);
3600       return;
3601     }
3602     sprintf(emsg,"Lost connection to player %d%s",
3603 	    id,exit_on_disconnect ? "quitting" : loadstate ? "; game suspended" : "");
3604     spm.reason = emsg;
3605     if ( ! end_on_disconnect ) {
3606       send_all(the_game,&spm);
3607       the_game->active = 0;
3608     } else {
3609       int washed_out = 0;
3610       int dcpen = 0; /* apply disconnection penalty ? */
3611       CMsgMessageMsg mm;
3612       mm.type = CMsgMessage;
3613       mm.sender = mm.addressee = 0;
3614       mm.text = emsg;
3615       sprintf(emsg,"Lost connection to player %d - ",id);
3616       /* to handle disconnection gracefully ... */
3617       if ( the_game->state == HandComplete ) {
3618 	/* nothing to do at this point */
3619 	strcat(emsg,"game will end now");
3620       } else if ( the_game->state == MahJonging ) {
3621 	PlayerP p;
3622 	seats s;
3623 	/* if it's the mahjonging player we've lost, and they
3624 	   haven't yet declared their hand, I think that's
3625 	   just tough -- we don't want to do auto-declaration */
3626 	s = id_to_seat(id);
3627 	p = id_to_player(id);
3628 	if ( the_game->player == s && !pflag(p,HandDeclared) ) {
3629 	  CMsgWashOutMsg wom;
3630 	  wom.type = CMsgWashOut;
3631 	  wom.reason = "Lost the winner";
3632 	  washed_out = 1;
3633 	  handle_cmsg(the_game,&wom);
3634 	  send_all(the_game,&wom);
3635 	  strcat(emsg,"ending game");
3636 	} else {
3637 	  PMsgShowTilesMsg stm;
3638 	  /* we have to continue processing, since other players
3639 	     may continue to declare tiles. We fake a showtiles
3640 	     for this player, and handle the end of game later */
3641 	  end_on_disconnect_in_progress = 1;
3642 	  if ( !pflag(p,HandDeclared) ) {
3643 	    stm.type = PMsgShowTiles;
3644 	    handle_pmsg((PMsgMsg *)&stm,cnx);
3645 	  }
3646 	  strcat(emsg,"game will end after scoring this hand");
3647 	}
3648       } else {
3649 	/* not much we can do except declare a washout */
3650 	CMsgWashOutMsg wom;
3651 	wom.type = CMsgWashOut;
3652 	wom.reason = "Lost a player";
3653 	handle_cmsg(the_game,&wom);
3654 	send_all(the_game,&wom);
3655 	strcat(emsg,"ending game");
3656 	washed_out = 1;
3657       }
3658       send_all(the_game,&mm);
3659       /* do we need to apply a disconnection penalty?
3660 	 If the state is MahJonging, then it's morally the
3661 	 same as HandComplete. Well, not if it's the winner
3662 	 who dc'ed, but we'll quietly pass over that.
3663 	 The washouts will have set handcomplete, so we must
3664 	 apply the usual logic */
3665 	/* are we at the end of a hand ? */
3666       /* If the game hasn't actually got under way, we won't charge
3667 	 a penalty */
3668       if ( the_game->state == HandComplete
3669 	&& the_game->round == EastWind
3670 	&& the_game->players[east]->id == the_game->firsteast
3671 	&& the_game->hands_as_east == 0
3672 	   && ! washed_out ) {
3673 	dcpen = 0;
3674       } else if ( (the_game->state == HandComplete
3675 	     || the_game->state == MahJonging)
3676 	/* It's a pain that I can't test for this easily
3677 	   at the *end* of a hand. Have to duplicate logic
3678 	   elsewhere */
3679 	&& the_game->player != noseat /* there was a mah jong */ ) {
3680 	/* are we at the end of a round? */
3681 	if ( (the_game->player != east /* and either it wasn't east */
3682 	   || the_game->hands_as_east == 13) /* or this was east's 13th win */
3683 	  /* does the round change? (the current south will be the next east;
3684 	     if that is the firsteast, the round changes) */
3685 	  && the_game->players[south]->id == the_game->firsteast ) {
3686 	  if ( the_game->round == NorthWind ) {
3687 	    /* end of game. No penalty can apply */
3688 	  } else {
3689 	    dcpen = disconnect_penalty_end_of_round;
3690 	  }
3691 	} else {
3692 	  /* in middle of round, but end of hand */
3693 	  dcpen = disconnect_penalty_end_of_hand;
3694 	}
3695       } else {
3696 	dcpen = disconnect_penalty_in_hand;
3697       }
3698       if ( dcpen > 0 ) {
3699 	info("Disconnect penalty for player %d: %d",id,dcpen);
3700 	change_player_cumulative_score(id_to_player(id),-dcpen);
3701       }
3702       /* if we have nothing further to do, we should end the game */
3703       if ( ! end_on_disconnect_in_progress ) {
3704 	finish_game(the_game);
3705       }
3706     }
3707     timeout = 0; /* clear timeout */
3708   }
3709   if ( exit_on_disconnect ) save_and_exit() ;
3710   /* and close the offending connection */
3711   close_connection(cnx);
3712 }
3713 
3714 /* loads a wall from a file; puts stack and wall of start in last two
3715    arguments */
load_wall(char * wfname,Game * g)3716 static int load_wall(char *wfname, Game *g) {
3717   FILE *wfp;
3718   int i;
3719   char tn[5];
3720   Tile t;
3721 
3722   wfp = fopen(wfname,"r");
3723   if ( wfp == NULL ) {
3724     warn("couldn't open wall file");
3725     return 0;
3726   }
3727   for ( i = 0 ; i < g->wall.size ; i++ ) {
3728     fscanf(wfp,"%2s",tn);
3729     t = tile_decode(tn);
3730     if ( t == ErrorTile ) {
3731       warn("Error parsing wall file");
3732       return 0;
3733     }
3734     g->wall.tiles[i] = t;
3735   }
3736   return 1;
3737 }
3738 
3739 /* resend: given a player id and a message, send it to the player
3740    if appropriate, censoring as required. */
resend(int id,CMsgMsg * m)3741 static void resend(int id, CMsgMsg *m)
3742 {
3743   switch ( m->type ) {
3744     /* The following messages are always sent */
3745   case CMsgNewHand:
3746   case CMsgPlayerDeclaresSpecial:
3747   case CMsgPause:
3748   case CMsgPlayerReady:
3749   case CMsgPlayerDiscards:
3750   case CMsgDangerousDiscard:
3751   case CMsgPlayerClaimsPung:
3752   case CMsgPlayerPungs:
3753   case CMsgPlayerFormsClosedPung:
3754   case CMsgPlayerClaimsKong:
3755   case CMsgPlayerKongs:
3756   case CMsgPlayerDeclaresClosedKong:
3757   case CMsgPlayerAddsToPung:
3758   case CMsgPlayerRobsKong:
3759   case CMsgPlayerClaimsChow:
3760   case CMsgPlayerChows:
3761   case CMsgPlayerFormsClosedChow:
3762   case CMsgWashOut:
3763   case CMsgPlayerClaimsMahJong:
3764   case CMsgPlayerMahJongs:
3765   case CMsgPlayerPairs:
3766   case CMsgPlayerFormsClosedPair:
3767   case CMsgPlayerShowsTiles:
3768   case CMsgPlayerSpecialSet:
3769   case CMsgPlayerFormsClosedSpecialSet:
3770   case CMsgHandScore:
3771   case CMsgSettlement:
3772     send_id(id,m);
3773     break;
3774     /* The following messages are sent only to the player concerned */
3775   case CMsgPlayerDoesntClaim:
3776     if ( id == ((CMsgPlayerDoesntClaimMsg *)m)->id ) send_id(id,m);
3777     break;
3778   case CMsgSwapTile:
3779     if ( id == ((CMsgSwapTileMsg *)m)->id ) send_id(id,m);
3780     break;
3781     /* The following messages are sent as is to the player concerned,
3782        and with censoring to other players */
3783   case CMsgPlayerDraws:
3784     if ( id == ((CMsgPlayerDrawsMsg *)m)->id ) {
3785       send_id(id,m);
3786     } else {
3787       CMsgPlayerDrawsMsg mm = *(CMsgPlayerDrawsMsg *)m;
3788       mm.tile = HiddenTile;
3789       send_id(id,&mm);
3790     }
3791     break;
3792   case CMsgPlayerDrawsLoose:
3793     if ( id == ((CMsgPlayerDrawsLooseMsg *)m)->id ) {
3794       send_id(id,m);
3795     } else {
3796       CMsgPlayerDrawsLooseMsg mm = *(CMsgPlayerDrawsLooseMsg *)m;
3797       mm.tile = HiddenTile;
3798       send_id(id,&mm);
3799     }
3800     break;
3801     /* The following message can occur in history, but
3802        is not sent to clients */
3803   case CMsgComment:
3804     break;
3805     /* The following should not occur in history */
3806   case CMsgCanMahJong:
3807   case CMsgClaimDenied:
3808   case CMsgConnectReply:
3809   case CMsgReconnect:
3810   case CMsgRedirect:
3811   case CMsgAuthReqd:
3812   case CMsgError:
3813   case CMsgGame:
3814   case CMsgGameOption:
3815   case CMsgGameOver:
3816   case CMsgInfoTiles:
3817   case CMsgNewRound:
3818   case CMsgPlayer:
3819   case CMsgPlayerOptionSet:
3820   case CMsgMessage:
3821   case CMsgChangeManager:
3822   case CMsgStartPlay:
3823   case CMsgStopPlay:
3824   case CMsgWall:
3825   case CMsgStateSaved:
3826     abort();
3827   }
3828 }
3829 
clear_history(Game * g)3830 static void clear_history(Game *g) {
3831   /* now clear the history records */
3832   while ( gextras(g)->histcount > 0 ) {
3833       cmsg_deepfree(gextras(g)->history[--gextras(g)->histcount]);
3834   }
3835   /* and the prehistory */
3836   while ( gextras(g)->prehistcount > 0 ) {
3837       cmsg_deepfree(gextras(g)->prehistory[--gextras(g)->prehistcount]);
3838   }
3839 }
3840 
3841 /* add a cmsg (if appropriate) to the history of a game */
history_add(Game * g,CMsgMsg * m)3842 static void history_add(Game *g, CMsgMsg *m) {
3843   switch ( m->type ) {
3844     /* These messages do go into history */
3845   case CMsgNewHand:
3846   case CMsgPlayerDeclaresSpecial:
3847   case CMsgPause:
3848   case CMsgPlayerReady:
3849   case CMsgPlayerDraws:
3850   case CMsgPlayerDrawsLoose:
3851   case CMsgPlayerDiscards:
3852   case CMsgPlayerDoesntClaim:
3853   case CMsgDangerousDiscard:
3854   case CMsgPlayerClaimsPung:
3855   case CMsgPlayerPungs:
3856   case CMsgPlayerFormsClosedPung:
3857   case CMsgPlayerClaimsKong:
3858   case CMsgPlayerKongs:
3859   case CMsgPlayerDeclaresClosedKong:
3860   case CMsgPlayerAddsToPung:
3861   case CMsgPlayerRobsKong:
3862   case CMsgPlayerClaimsChow:
3863   case CMsgPlayerChows:
3864   case CMsgPlayerFormsClosedChow:
3865   case CMsgWashOut:
3866   case CMsgPlayerClaimsMahJong:
3867   case CMsgPlayerMahJongs:
3868   case CMsgPlayerPairs:
3869   case CMsgPlayerFormsClosedPair:
3870   case CMsgPlayerShowsTiles:
3871   case CMsgPlayerSpecialSet:
3872   case CMsgPlayerFormsClosedSpecialSet:
3873   case CMsgHandScore:
3874   case CMsgSettlement:
3875   case CMsgComment:
3876   case CMsgSwapTile:
3877     gextras(g)->history[gextras(g)->histcount++] = cmsg_deepcopy(m);
3878     break;
3879     /* These messages do not go into history */
3880   case CMsgStateSaved:
3881   case CMsgCanMahJong:
3882   case CMsgClaimDenied:
3883   case CMsgConnectReply:
3884   case CMsgReconnect:
3885   case CMsgAuthReqd:
3886   case CMsgRedirect:
3887   case CMsgError:
3888   case CMsgGame:
3889   case CMsgGameOption:
3890   case CMsgGameOver:
3891   case CMsgInfoTiles:
3892   case CMsgNewRound:
3893   case CMsgPlayer:
3894   case CMsgPlayerOptionSet:
3895   case CMsgMessage:
3896   case CMsgChangeManager:
3897   case CMsgStartPlay:
3898   case CMsgStopPlay:
3899   case CMsgWall:
3900     ;
3901   }
3902 }
3903 
save_and_exit()3904 static void save_and_exit() {
3905   if ( save_on_exit ) save_state(the_game,NULL);
3906   exit(0);
3907 }
3908 
3909 /* this function extracts the timeout_time from a game
3910    according the Timeout and TimeoutGrace options, and
3911    the passed in value of localtimeouts */
get_timeout_time(Game * g,int localtimeouts)3912 static int get_timeout_time(Game *g,int localtimeouts) {
3913   int t = game_get_option_value(g,GOTimeout,NULL).optnat;
3914   if ( t > 0 && localtimeouts )
3915     t += game_get_option_value(g,GOTimeoutGrace,NULL).optnat;
3916   return t;
3917 }
3918