/* $Id: CLISuniclues.c 53 2008-05-01 05:50:20Z edsko $ */ /* $Source: /home/skochine/RCS/uniclues.c,v $ */ /* * usage: uniclues [-i] configFile transcriptFile * * where -i indicates that interactive prompting should be used. This will violate the protocol, but make it * clear to an interactive human user when input is expected. */ /* * NOTE TO JUDGES: stderr from this server program contains the predetermined judged response * if there was an error. It is expected that stderr be redirected to the * filtered output file for diffing with the expected output. A zero-length * stderr is the correct output. * * A proper invocation of this program will look something similar to (for C/C++ clients): * * mkfifo server2client * clientBin < server2client 2> clientStderr | serverBin configFile transcriptFile > server2client 2> serverStderr * * ... and for java clients: * * mkfifo server2client * java clientClassFile < server2client 2> clientStderr | serverBin configFile transcriptFile > server2client 2> serverStderr */ #include #include #include #include #include #define MINPLAYERS 3 #define MAXPLAYERS 6 #define MAXSLEN 80 #define MAXROOMS 324 /* 6*6*9 combinations of suspect,weapon,room. Max number of rooms config file can specify */ #define FALSE 0 #define TRUE 1 #define CARDS 21 /* 6 suspects, 6 weapons, 9 rooms */ #define QUERY_LIMIT 42 /* maximum number of Q commands. Exceeding this number elicits TIME LIMIT EXCEEDED */ #define MAX_DELAY 1000 #define EXIT_CONFIGURATION_ERROR -1 /* these return codes when negative will cause abort actions. Setting them nonnegative may be better for the judging environment */ #define EXIT_PRESENTATION_ERROR 2 #define EXIT_TIME_LIMIT_EXCEEDED 3 typedef struct Card { int card; int shown; struct Card *next; } Card; typedef Card *CardPtr; CardPtr hand[MAXPLAYERS + 1]; /* 1-based array is a bit easier here */ char cardSpec[4][6]; char *cardToIndex="S1S2S3S4S5S6W1W2W3W4W5W6R1R2R3R4R5R6R7R8R9"; int currentRoomIndex; int murderer; int murderLocation; int murderWeapon; int P; int room[MAXROOMS]; int roomCount; int state; FILE *transcriptFile=NULL; void AppendCard(int c, CardPtr *h) { if (*h != NULL) { AppendCard(c,&((*h)->next)); } else { CardPtr new; new=calloc(1,sizeof(*new)); new->card=c; new->shown=FALSE; new->next=NULL; *h=new; } } CardPtr InHand(int c, CardPtr cptr) { if (cptr == NULL) { return cptr; } else { if (cptr->card == c) { return cptr; } else { return InHand(c,cptr->next); } } } int CheckAlibi(int suspect, int weapon, int room, int p) { CardPtr rptr; CardPtr sptr; CardPtr wptr; /* * Determine if any of the queried cards are in player p's hand. If so, show cards already divulged. */ sptr=InHand(suspect,hand[p]); if ((sptr != NULL) && (sptr->shown)) { return sptr->card; } wptr=InHand(weapon,hand[p]); if ((wptr != NULL) && (wptr->shown)) { return wptr->card; } rptr=InHand(room,hand[p]); if ((rptr != NULL) && (rptr->shown)) { return rptr->card; } /* If an alibi card is present but not yet divulged, show only the first occurrence */ if ((sptr != NULL) || (wptr != NULL) || (rptr != NULL)) { CardPtr cptr; cptr=hand[p]; while (cptr != NULL) { int c; c=cptr->card; if ((c == suspect) || (c == weapon) || (c == room)) { cptr->shown=TRUE; return c; } cptr=cptr->next; } assert("Programming Error; alibi should be present" == 0); } else { return -1; } } void DisplayCard(FILE *f, int c) { c=c*2; fputc(cardToIndex[c],f); fputc(cardToIndex[c+1],f); } void DisplayHand(FILE *f, CardPtr p) { DisplayCard(f,p->card); if (p->next != NULL) { fputc(' ',f); DisplayHand(f,p->next); } } int NextRoom(int *currentRoomIndex) { static int r=0; int rr; /* effect room cycling 1..9,1..9 if predetermined rooms exhausted */ if (*currentRoomIndex >= roomCount) { rr=r + 1; r++; r=r%9; } /* otherwise, just issue next room in sequence and advance the counter */ else { rr=room[*currentRoomIndex]; (*currentRoomIndex)++; } return rr; } void usage() { fprintf(stderr,"usage: uniclues [-i] configurationFile transcriptFile\n"); exit(EXIT_CONFIGURATION_ERROR); } void Exit(int e) { if ((transcriptFile != NULL) && (e != 0)) { fprintf(transcriptFile,"--> Server terminated before accusation. <--\n"); } exit(e); } int main(int argc, char *argv[]) { int ch; int dealt; FILE *f; int i; int interactivePrompt=FALSE; int j; int n; extern char *optarg; extern int opterr; extern int optind; extern int optopt; int p; int queryCount; char s[MAXSLEN + 1]; int used[CARDS]; /* process arguments */ while ((ch=getopt(argc,argv,"i")) != -1) { switch(ch) { case 'i': interactivePrompt=TRUE; break; case '?': default: fprintf(stderr,"unknown option: %c\n",ch); usage(); } } /* initialize hands and whether the card has been seen in the deal */ for (j=0; j < CARDS; j++) { used[j]=FALSE; } for (j=1; j <= P; j++) { hand[j]=NULL; } /* open config and transcript file. stderr for the transcript is a lazy man's default */ if (optind < argc) { f=fopen(argv[optind],"r"); /* open configuration file */ if (f == NULL) { perror(argv[optind]); Exit(EXIT_CONFIGURATION_ERROR); } } else { usage(); } optind++; if (optind < argc) { transcriptFile=fopen(argv[optind],"w"); if (transcriptFile == NULL) { perror(argv[optind]); Exit(EXIT_CONFIGURATION_ERROR); } } else { transcriptFile=stderr; } /* read number of players, P */ if (fgets(s,MAXSLEN,f) == NULL) { fprintf(stderr,"Premature EOF on the configuration file trying to read P, the number of players.\n"); Exit(EXIT_CONFIGURATION_ERROR); }; n=sscanf(s,"%d",&P); if (n != 1) { fprintf(stderr,"Line one of the configurationFile must contain P, the number of players.\n"); Exit(EXIT_CONFIGURATION_ERROR); } /* for each player p=1..P, read in hand */ dealt=0; for (p=1; p <= P; p++) { if (fgets(s,MAXSLEN,f) == NULL) { fprintf(stderr,"Premature EOF on the configuration file trying to read player %d's hand.\n",p); Exit(EXIT_CONFIGURATION_ERROR); } n=sscanf(s,"%s %s %s %s %s %s" ,cardSpec[0],cardSpec[1],cardSpec[2] ,cardSpec[3],cardSpec[4],cardSpec[5] ); for (j=0; j < n; j++) { int c; char *cptr; cptr=strstr(cardToIndex,cardSpec[j]); if (cptr == NULL) { fprintf(stderr,"unknown card specification '%s'\n",cardSpec[j]); Exit(EXIT_CONFIGURATION_ERROR); } c=(int)(cptr - cardToIndex); if ((c%2) != 0) { fprintf(stderr,"unknown card specification '%s'\n",cardSpec[j]); Exit(EXIT_CONFIGURATION_ERROR); } c=c/2; /* convert c to card number, 0..CARDS-1 */ if (used[c]) { fprintf(stderr,"card '%s' previously dealt this hand.\n",cardSpec[j]); Exit(EXIT_CONFIGURATION_ERROR); } AppendCard(c,&hand[p]); used[c]=TRUE; dealt++; } #ifdef DEBUG DisplayHand(stderr,hand[p]); fputc('\n',stderr); fflush(stderr); #endif } if (dealt != 18) { fprintf(stderr,"18 cards must be dealt; %d were actually dealt.\n",dealt); Exit(EXIT_CONFIGURATION_ERROR); } /* determine which suspect is absent (in the envelope) */ murderer=-1; for (i=0; i < 6; i++) { if (!used[i]) { murderer=i; } } if (murderer < 0) { fprintf(stderr,"No suspect card in the envelope.\n"); Exit(EXIT_CONFIGURATION_ERROR); } /* determine which weapon is absent (in the envelope) */ murderWeapon=-1; for (i=6; i < 12; i++) { if (!used[i]) { murderWeapon=i; } } if (murderWeapon < 0) { fprintf(stderr,"No weapon card in the envelope.\n"); Exit(EXIT_CONFIGURATION_ERROR); } /* determine which room is absent (in the envelope) */ murderLocation=-1; for (i=12; i < CARDS; i++) { if (!used[i]) { murderLocation=i; } } if (murderLocation < 0) { fprintf(stderr,"No room card in the envelope.\n"); Exit(EXIT_CONFIGURATION_ERROR); } #ifdef DEBUG fputs("(",stdout); DisplayCard(stdout,murderer); fputs(",",stdout); DisplayCard(stdout,murderWeapon); fputs(",",stdout); DisplayCard(stdout,murderLocation); fputs(")\n",stdout); #endif /* read in predetermined room sequence */ roomCount=0; while (fgets(s,MAXSLEN,f) != NULL) { int r; n=sscanf(s,"%d",&r); if (n != 1) { fprintf(stderr,"Unable to convert room number: %s",s); Exit(EXIT_CONFIGURATION_ERROR); } if (roomCount < MAXROOMS) { room[roomCount]=r; roomCount++; } } /* perform the server function, supplying rooms and handling client requests */ #define ConditionallyExit(exitCode) \ if (interactivePrompt) { \ errorSuppressed=TRUE; \ goto ErrorSuppression; \ } \ else { \ Exit(exitCode); \ } currentRoomIndex=0; state=0; queryCount=0; while (state >= 0) { char *cptr; int currentRoom; int errorSuppressed; int room; int suspect; int weapon; errorSuppressed=FALSE; if (interactivePrompt) { fputs("\t\t",stdout); if (queryCount > 0) { fprintf(stdout,"%02d",queryCount); } fputs("? ",stdout); fflush(stdout); if (fgets(s,MAXSLEN,stdin) == NULL) { fprintf(stderr,"PRESENTATION ERROR. Unexpected EOF.\n"); Exit(EXIT_PRESENTATION_ERROR); } } else { /* fgets(s,MAXSLEN,stdin); */ int rc=ubgets(s,MAXSLEN,MAX_DELAY); if (rc == 0) { fprintf(stderr,"TIME LIMIT EXCEEDED. Input wait exceeded %d ms.\n",MAX_DELAY); Exit(EXIT_TIME_LIMIT_EXCEEDED); } else if (rc < 0) { fprintf(stderr,"PRESENTATION ERROR. Unexpected EOF.\n"); Exit(EXIT_PRESENTATION_ERROR); } } fputs("\t\t",transcriptFile); fputs(s,transcriptFile); fflush(transcriptFile); cptr=strchr(s,'\n'); if (cptr == NULL) { fprintf(stderr,"PRESENTATION ERROR. Unable to find eol from client.\n"); Exit(EXIT_PRESENTATION_ERROR); } *cptr='\0'; /* remove eol */ switch (state) { /* state 0 - expect 'C' command */ case 0: if (s[0] != 'C') { fprintf(stderr,"PRESENTATION ERROR. Upper case C command expected; is there leading whitespace?\n"); ConditionallyExit(EXIT_PRESENTATION_ERROR); } if (strlen(s) != 1) { fprintf(stderr,"PRESENTATION ERROR. C command should be only 1 character long; is there trailing whitspace?\n"); ConditionallyExit(EXIT_PRESENTATION_ERROR); } state=1; fprintf(transcriptFile,"%d\n",P); fflush(transcriptFile); fprintf(stdout,"%d\n",P); fflush(stdout); DisplayHand(transcriptFile,hand[1]); fputc('\n',transcriptFile); fflush(transcriptFile); DisplayHand(stdout,hand[1]); fputc('\n',stdout); fflush(stdout); break; /* case 1 - expect 'Q' or 'A' command */ case 1: if (strlen(s) != 4) { fprintf(stderr,"PRESENTATION ERROR. Q or A command should be 4 characters long; is there trailing whitspace?\n"); ConditionallyExit(EXIT_PRESENTATION_ERROR); } /* check ranges for suspect,weapon,room */ if ((s[1] < '1') || ('6' < s[1])) { fprintf(stderr,"PRESENTATION ERROR. Suspect number should be 1..6.\n"); ConditionallyExit(EXIT_PRESENTATION_ERROR); } if ((s[2] < '1') || ('6' < s[2])) { fprintf(stderr,"PRESENTATION ERROR. Weapon number should be 1..6.\n"); ConditionallyExit(EXIT_PRESENTATION_ERROR); } if ((s[3] < '1') || ('9' < s[3])) { fprintf(stderr,"PRESENTATION ERROR. Room number should be 1..9.\n"); ConditionallyExit(EXIT_PRESENTATION_ERROR); } suspect=s[1] - '1'; /* suspects are cards 0..5 */ weapon=s[2] - '1' + 6; /* weapons are cards 6..11 */ room=s[3] - '1' + 6 + 6; /* rooms are cards 12..20 */ switch (s[0]) { case 'A': if ((suspect==murderer) && (weapon==murderWeapon) && (room==murderLocation)) { fprintf(transcriptFile,"Victory\n"); fprintf(stdout,"Victory\n"); /* * Send nothing to stderr. The zero-length output indicates correct functioning. The presence * of stderr indicates that something went wrong. Sending "CORRECT. CONGRATULATIONS." would be * even worse because the contestant might assume that the problem was submitted. */ } else { fprintf(transcriptFile,"Disqualified"); fprintf(stdout,"Disqualified"); #ifdef DEBUG if (interactivePrompt) { fprintf(transcriptFile,"; envelope contents = S%d W%d R%d.",murderer + 1,murderWeapon - 5,murderLocation - 11); fprintf(stdout,"; envelope contents = S%d W%d R%d.",murderer + 1,murderWeapon - 5,murderLocation - 11); } #endif fputs("\n",transcriptFile); fputs("\n",stdout); fprintf(stderr,"WRONG ANSWER. %s(wrong) != S%d W%d R%d\n",s,murderer + 1,murderWeapon - 5,murderLocation - 11); } fflush(transcriptFile); fflush(stdout); Exit(0); break; case 'Q': if (queryCount > QUERY_LIMIT) { fprintf(stderr,"TIME LIMIT EXCEEDED. Query limit of %d exceeded.\n",QUERY_LIMIT); Exit(EXIT_TIME_LIMIT_EXCEEDED); } if ((room - 11) != currentRoom) { fprintf(stderr,"PRESENTATION ERROR. Queried room number does not match current room number.\n"); ConditionallyExit(EXIT_PRESENTATION_ERROR); } for (p=2; p <= P; p++) { int c; c=CheckAlibi(suspect,weapon,room,p); if (c >= 0) { /* if an alibi card found by player p, play advances */ fprintf(transcriptFile,"%d",p); DisplayCard(transcriptFile,c); fputs("\n",transcriptFile); fflush(transcriptFile); fprintf(stdout,"%d",p); DisplayCard(stdout,c); fputs("\n",stdout); fflush(stdout); break; /* for (p=2... */ } else { fprintf(transcriptFile,"%d--\n",p); fflush(transcriptFile); fprintf(stdout,"%d--\n",p); fflush(stdout); } } break; default: fprintf(stderr,"PRESENTATION ERROR. Upper case Q or A command expected; is there leading whitespace?\n"); ConditionallyExit(EXIT_PRESENTATION_ERROR); } break; default: assert("Programming Error; unknown state" == 0); } ErrorSuppression: if (errorSuppressed) { fprintf(stdout,"The error has been suppressed because the server is running in test mode.\n"); fflush(stdout); fprintf(stdout,"During judging, this run would return a response of PRESENTATION ERROR.\n"); fflush(stdout); fprintf(stdout,"Try again.\n"); fflush(stdout); } else { currentRoom=NextRoom(¤tRoomIndex); queryCount++; fprintf(transcriptFile,"%d\n",currentRoom); fflush(transcriptFile); fprintf(stdout,"%d\n",currentRoom); fflush(stdout); } } }