/* $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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>

#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(&currentRoomIndex);
         queryCount++;
         fprintf(transcriptFile,"%d\n",currentRoom); fflush(transcriptFile);
         fprintf(stdout,"%d\n",currentRoom); fflush(stdout);
      }
   }
}
