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