📄 controller.c
字号:
warn(em.error); exit(1); } for ( j=0; j < gextras(the_game)->histcount; j++ ) { int sleeptime; for ( i=0 ; i < NUM_SEATS; i++ ) { PlayerP p = the_game->players[i]; if ( ! pextras(p)->disconnected ) continue; resend(p->id,gextras(the_game)->history[j]); } /* this is an undocumented feature to make things a bit nicer for humans reconnecting: we'll unilaterally slow down the feed, by adding a 0.15 second delay between items, or 1 second after a claim implementation. */ switch ( gextras(the_game)->history[j]->type ) { case CMsgPlayerPairs: case CMsgPlayerChows: case CMsgPlayerPungs: case CMsgPlayerKongs: case CMsgPlayerSpecialSet: sleeptime = 1000; /* ms */ break; default: sleeptime = 150; } usleep(sleeptime*1000); } } /* and now mark those players connected again */ for ( i=0 ; i < NUM_SEATS; i++ ) { PlayerP p = the_game->players[i]; if ( ! pextras(p)->disconnected ) continue; pextras(p)->disconnected = 0; } /* Now we should tell somebody to do something. Namely: In state HandComplete: tell everybody, and start a hand if things aren't paused. In state Dealing: everybody. In Discarded, several players might want to do something, so we don't specify the id. Ditto in MahJonging. Otherwise, it's the player. */ { CMsgStartPlayMsg sm; CMsgPauseMsg pm; sm.type = CMsgStartPlay; switch (the_game->state) { case HandComplete: case Dealing: case Discarded: case MahJonging: sm.id = 0; break; default: sm.id = the_game->players[the_game->player]->id; break; } /* now we should ask the players whether they are ready to continue. We send the pause request *before* making the game active */ pm.type = CMsgPause; pm.reason = (the_game->state == HandComplete) ? "to start hand" : "to continue play"; pm.exempt = 0; pm.requestor = 0; handle_cmsg(the_game,&pm); send_all(the_game,&pm); send_all(the_game,&sm); /* and set the game active */ the_game->active = 1; } /* now set loadstate */ loadstate = 1; return; } /* end of case PMsgConnect */ case PMsgRequestPause: { PMsgRequestPauseMsg *m = (PMsgRequestPauseMsg *)pmp; PlayerP p; int id; seats seat; CMsgPauseMsg pm; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ pm.type = CMsgPause; pm.exempt = 0; pm.requestor = id; pm.reason = m->reason; em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; if ( !(the_game->state == Discarding || the_game->state == HandComplete) ) { em.error = "Not reasonable to pause at this point"; send_id(id,&em); return; } send_all(the_game,&pm); break; } /* end of PMsgRequestPauseMsg */ case PMsgSetPlayerOption: { PMsgSetPlayerOptionMsg *m = (PMsgSetPlayerOptionMsg *)pmp; PlayerP p; int id; seats seat; CMsgPlayerOptionSetMsg posm; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ posm.type = CMsgPlayerOptionSet; posm.option = m->option; posm.value = m->value; posm.text = m->text; posm.ack = 1; em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; if ( m->option == (unsigned)-1 ) m->option = POUnknown; if ( m->option == POUnknown ) em.error = "Unknown option"; else switch ( m->option ) { /* validity checking */ /* boolean options */ case POInfoTiles: if ( m->value < 0 || m->value > 1 ) em.error = "Bad value for InfoTiles"; break; case POLocalTimeouts: /* This is a bit messy. As a matter of policy, we only allow a client to do local timeouts if everybody else is too. So we simply remember the requests, unless all have requested, in which case we ack everybody. */ /* if this is an ack, ignore it. We should only get acks after we've forcibly set things to zero, and then we don't care about the ack. */ if ( m->ack ) break; /* if this is a request to start local timeouts: */ if ( m->value ) { int i,lt; pextras(p)->localtimeouts = 1; for ( i=0, lt=1; i < NUM_SEATS; i++ ) { lt = (lt && pextras(the_game->players[i])->localtimeouts); } if ( ! lt ) return; /* just wait */ localtimeouts = 1; timeout_time = get_timeout_time(the_game,localtimeouts); for ( i=0; i < NUM_SEATS; i++ ) { popts(the_game->players[i])[m->option] = 1; } send_all(the_game,&posm); /* and that's it -- we don't want to drop through */ return; } else { unsigned int i; /* request to disable local timeouts */ localtimeouts = 0; timeout_time = get_timeout_time(the_game,localtimeouts); pextras(p)->localtimeouts = 0; /* instruct all the other players to drop local timeouts */ posm.ack = 0; for ( i=0; i < NUM_SEATS; i++ ) { if ( i != seat ) { send_seat(the_game,i,&posm); } } posm.ack = 1; /* now drop through to ack and set this one. */ } break; /* non-boolean options */ case PODelayTime: /* players *can* request any positive value */ if ( m->value < 0 ) em.error = "Bad value for DelayTime"; break; default: ; } if ( em.error ) { send_id(id,&em); return; } if ( ! m->ack ) { send_id(id,&posm); } popts(p)[m->option] = m->value; /* action may now be required */ switch ( m->option ) { int i; case PODelayTime: /* find the highest value requested by any player, or option */ min_time = min_time_opt; for ( i = 0; i < NUM_SEATS; i++ ) { if ( popts(the_game->players[i])[PODelayTime] > min_time ) min_time = popts(the_game->players[i])[PODelayTime]; } /* highest reasonable value is 5 seconds */ if ( min_time > 50 ) min_time = 50; break; default: ; } return; } case PMsgReady: { PlayerP p; int id; seats seat; CMsgPlayerReadyMsg prm; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ prm.type = CMsgPlayerReady; prm.id = id; em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; /* if the player is already ready, ignore this message */ if ( the_game->ready[seat] ) return; if ( handle_cmsg(the_game,&prm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } send_all(the_game,&prm); /* if state is handcomplete and no longer pausing, start the next hand */ if ( the_game->state == HandComplete && ! the_game->paused ) start_hand(the_game); /* and in the discarded or konging state, reinstate the timeout */ if ( the_game->state == Discarded || (the_game->state == Discarding && the_game->konging ) ) timeout = 1000*timeout_time; return; } /* end of case PMsgReady */ case PMsgDeclareSpecial: { PMsgDeclareSpecialMsg *m = (PMsgDeclareSpecialMsg *) pmp; PlayerP p; int id; seats seat; CMsgErrorMsg em; CMsgPlayerDeclaresSpecialMsg pdsm; CMsgPlayerDrawsMsg pdlm; /* FIXME */ int res; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ pdlm.id = pdsm.id = id; em.type = CMsgError ; em.seqno = connections[cnx].seqno; pdsm.type = CMsgPlayerDeclaresSpecial; pdlm.type = CMsgPlayerDraws; em.error = NULL; /* Legality checking is done by handle cmsg, so just set up the cmsg and apply it */ pdsm.tile = m->tile; res = handle_cmsg(the_game,&pdsm); if ( res < -1 ) { warn("Consistency error: giving up"); exit(1); } if ( res < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&pdsm); if ( pdsm.tile == HiddenTile ) { /* if we've now started play (i.e. state == Discarding), ask everybody (except east) if they're ready */ if ( the_game->state == Discarding ) { CMsgPauseMsg pm; pm.type = CMsgPause; pm.reason = "to start play"; pm.exempt = the_game->players[east]->id; pm.requestor = 0; if ( handle_cmsg(the_game,&pm) >= 0 ) { send_all(the_game,&pm); } else { warn("Failed to pause at start of play: %s",the_game->cmsg_err); } } return; } /* and now draw the replacement */ if ( game_get_option_value(the_game,GOFlowersLoose,NULL) ) { pdlm.tile = game_peek_loose_tile(the_game); pdlm.type = CMsgPlayerDrawsLoose; } else { pdlm.tile = game_peek_tile(the_game); pdlm.type = CMsgPlayerDraws; } if ( pdlm.tile == ErrorTile ) { washout(NULL); return; } if ( the_game->state == Discarding ) { /* don't do this if declaring specials */ /* stash a copy of the player, in case it goes mahjong */ copy_player(gextras(the_game)->caller,p); } if ( handle_cmsg(the_game,&pdlm) < 0 ) { /* error should be impossible */ warn("Consistency error: giving up"); exit(1); } send_id(id,&pdlm); pdlm.tile = HiddenTile; check_min_time(1); send_others(the_game,id,&pdlm); send_infotiles(p); return; assert(0); } /* end of case PMsgDeclareSpecial */ case PMsgDiscard: { PMsgDiscardMsg *m = (PMsgDiscardMsg *) pmp; PlayerP p; int id; seats seat; CMsgErrorMsg em; CMsgPlayerDiscardsMsg pdm; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; pdm.type = CMsgPlayerDiscards; pdm.id = id; pdm.tile = m->tile; pdm.discard = the_game->serial+1; pdm.calling = m->calling; if ( handle_cmsg(the_game,&pdm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&pdm); send_infotiles(p); /* set the timeout */ timeout = 1000*timeout_time; return; } /* end of case PMsgDiscard */ case PMsgNoClaim: { PMsgNoClaimMsg *m = (PMsgNoClaimMsg *) pmp; PlayerP p; int id; seats seat; CMsgErrorMsg em; CMsgPlayerDoesntClaimMsg pdcm; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; pdcm.type = CMsgPlayerDoesntClaim; pdcm.id = id; pdcm.discard = m->discard; pdcm.timeout = 0; if ( handle_cmsg(the_game,&pdcm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } /* handle_cmsg ignores noclaims in wrong state, so we need to check it */ if ( ! (the_game->state == Discarded || ( (the_game->state == Discarding || the_game->state == DeclaringSpecials) && the_game->konging ) ) ) return; /* acknowledge to the player only */ send_id(id,&pdcm); /* if all claims received, process */ check_claims(the_game); return; } /* end of case PMsgNoClaim */ case PMsgPung: { PMsgPungMsg *m = (PMsgPungMsg *) pmp; PlayerP p; int id; seats seat; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; if ( the_game->state != MahJonging ) { CMsgPlayerClaimsPungMsg pcpm; pcpm.type = CMsgPlayerClaimsPung; pcpm.id = id; pcpm.discard = m->discard; if ( handle_cmsg(the_game,&pcpm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } send_all(the_game,&pcpm); /* if all claims received, process */ check_claims(the_game); return; } else { /* MahJonging */ CMsgPlayerPungsMsg ppm; ppm.type = CMsgPlayerPungs; ppm.id = id; ppm.tile = the_game->tile; if ( handle_cmsg(the_game,&ppm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&ppm); send_infotiles(p);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -