// A program to solve Jim Propp's Self-Referential Quiz using a genetic
// algorithm.
//
// The quiz has 20 multiple-choice questions, answered A through E.  A
// proposed 'solution' to the quiz, then, will be an array of these characters.
//
// The initial generation of 'n_members' solutions will be generated at random.
// Then each quiz will be score.  Whichever solution generates the highest
// score will be used as a 'parent' for the next generation.  If multiple
// scores tie for the top spot, the first one found with that score is used.
//
// 'Children' are simple copies of the parent, with each answer risking a
// PMUTAION chance of 'mutating' to  different value.
//
// Each time the top score is improved upon, we'll print out the generation
// number, the score, and the answer string.
//
// The program stops (other than by a user break) when the top score of 20
// is achieved.
//
// Note that this sort of strategy only works, in this case, because of the
// novel nature of Jim Propp's puzzle.  Since it is self-referential, the
// answers can be checked against themselves, rather than a key provided by
// the instructor.
//
// mark van dine, 9/19/02
//

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>


#define MAX_MEMBERS      10000

#define MAX_GENERATIONS   5000

#define LOWER_LIMIT         10

/* PMUTATION is the default probability of a mutation when copying a     */
/* character from parent to child.  Defined as the number of times per   */
/* 1000 (one thousand) that a typo occurs. (i.e., 30 = 3% mutation rate. */

#define PMUTATION           30
#define NQUESTIONS          20

#define TRUE                 1
#define FALSE                0

#define MAX_TRIALS          10


char     *population[MAX_MEMBERS];
int       score;
int       n_members;
char     *answers = "ABCDE";

char *errors[] = {
/*  0 */ "No error.  Condition 0 indicates success."
/*  1 */,"Incorrect arguments"
/*  2 */,"Argument out of range"
};

void      main(void);
void      mutate(char *,char *,int);
void      print_results(int,int,char *,int,int,int);
void      print_status(int,int,char *);
char      random_answer(void);   
int       score_quiz(char *);
void      show_error(int,char *);
char     *stblank(int);

// ------------------------------------------------------------------------
void print_status(int generation,int score,char *answers)
{
   printf("status:\t%d\t%d\t%s\n",generation,score,answers);
}                                                       
// ------------------------------------------------------------------------
void print_results(int generation,int score,char *answers,int success,int pop_size,int m_rate)
{
   printf(">>%sSUCCESSFUL",(success == TRUE ? "" : "UN"));

   printf("\t%d\t%d\t%s\t%d\t%d\n",
      generation,score,answers,pop_size,m_rate);
}
// ------------------------------------------------------------------------
void show_error(int errnum,char *extra)
{
   fprintf(stderr,"\n\n");
   fprintf(stderr,"FATAL ERROR: %s\n\n",errors[errnum]);
   if (extra != NULL)
      fprintf(stderr,"\t%s\n\n",extra);

   exit(errnum);
}
// ------------------------------------------------------------------------
char *stblank(int n)
{
   char *t;

   if ((t = (char *)calloc(n + 1, sizeof(char))) == NULL)
      show_error(2,"stblank()");

   return(t);
}
// ------------------------------------------------------------------------
char random_answer(void)
{
   return(answers[random(5)]);
}
// ------------------------------------------------------------------------
void mutate(char *str1,char *str2,int rate)
{
   int   i;
   int   j;
   int   len;

   len = strlen(str1);

   strcpy(str1,str2);

   if ((rate > 0) && (rate <= 1000))
      for (i = 0;i < len;i++)
         for (j = 0;j < rate;j++)
            if (random(1000) == 1)
               str1[i] = random_answer();


}
// ------------------------------------------------------------------------
void main(void)
{
   int   i;
   int   j;
   int   best_score;
   int   best_candidate;
   char *winner;
   int   last_best;
   int   generation;
   int   m_rate;
   int   trial;


   for (i = 0;i < MAX_MEMBERS;i++)
      population[i] = stblank(NQUESTIONS);   

   winner = stblank(NQUESTIONS);

   randomize();

   n_members = 100;

   while (n_members <= 1000)
   {
      m_rate    =   10;
      while (m_rate <= 100)
      {
         for (trial = 0;trial < MAX_TRIALS;trial++)
         {
            fprintf(stderr,"%2d Starting population %d, mutation rate %d per 1000\n",
               trial,n_members, m_rate);

            for (i = 0;i < n_members;i++)
               for (j = 0;j < NQUESTIONS;j++)
                  population[i][j] = answers[rand() % 5];

            best_score = 0;
            last_best  = 0;

            generation = 0;

            while ((best_score < NQUESTIONS) && (generation < MAX_GENERATIONS))
            {
               generation++;

               best_score     = 0;
               best_candidate = 0;

               for (i = 0;i < n_members;i++)
               {
                  score = score_quiz(population[i]);
                  if (score > best_score)
                  {
                     best_score     = score;
                     best_candidate = i;
                  }
               }

               strcpy(winner,population[best_candidate]);

               if (best_score > last_best)
               {
                  /* print_status(generation,best_score,winner); */
                  last_best = best_score;
               }

               if (best_score == NQUESTIONS)
                  continue;

               for (i = 0;i < n_members;i++)
                  mutate(population[i],winner,m_rate);
            }

            if (best_score == NQUESTIONS)
               print_results(generation,best_score,winner,TRUE ,n_members,m_rate);
            else
               print_results(generation,best_score,winner,FALSE,n_members,m_rate);
         }

         m_rate += 10;
      }


      n_members += 100;

   }


   exit(0);
}
// ------------------------------------------------------------------------
int score_quiz(char *answer_str)
{
   int   score;
   int   i;
   int   counters[5];
   char  ch;
   int   pairs;
   int   count;
   int   gap;

   score = 0;

   /*  Question 1:

       1. The first question whose answer is B is question
         (A) 1
         (B) 2
         (C) 3
         (D) 4
         (E) 5

       MCV: Okay, we have to find out if there is an answer 'B' among the first
            five questions.  If so, is it where this particular answer says it is?
   */

   i = 0;

   while ((answer_str[i] != 'B') && (i < 5))
      i++;
   if (i < 5)
   {
      ch = answer_str[0];

      switch (ch) {
         case 'A' : if (i == 0)
                       score++;
                    break;
         case 'B' : if (i == 1)
                       score++;
                    break;
         case 'C' : if (i == 2)
                       score++;
                    break;
         case 'D' : if (i == 3)
                       score++;
                    break;
         case 'E' : if (i == 4)
                       score++;
                    break;
      }
   }

   /*  Question 2:

       2. The only two consecutive questions with identical answers are
          questions
         (A) 6 and 7
         (B) 7 and 8
         (C) 8 and 9
         (D) 9 and 10
         (E) 10 and 11  

       MCV: Two parts to scoring this ... first sorting out that 'only'
            business ... if there are more than one consecutive pair of
            matching answers, I guess any answer is wrong.  If there aren't any
            such pairs it is wrong.  If we clear those two hurdles, then we just
            have to check the answer.
   */

   pairs = 0;
   for (i = 1;i < NQUESTIONS;i++)
      if (answer_str[i] == answer_str[i - 1])
         pairs++;

   if (pairs == 1)
   {
      ch = answer_str[1];

      switch (ch) {
         case 'A' : if (answer_str[5] == answer_str[6])
                       score++;
                    break;
         case 'B' : if (answer_str[6] == answer_str[7])
                       score++;
                    break;
         case 'C' : if (answer_str[7] == answer_str[8])
                       score++;
                    break;
         case 'D' : if (answer_str[8] == answer_str[9])
                       score++;
                    break;
         case 'E' : if (answer_str[9] == answer_str[10])
                       score++;
                    break;
      }
   }

   /*  Question 3:

       3. The number of questions with the answer E is
         (A) 0
         (B) 1
         (C) 2
         (D) 3
         (E) 4

       MCV: Just count 'em up and check the answer.
   */

   count = 0;
   for (i = 0;i < NQUESTIONS;i++)
      if (answer_str[i] == 'E')
         count++;

   ch = answer_str[2];

   switch (ch) {
      case 'A' : if (count == 0)
                    score++;
                 break;
      case 'B' : if (count == 1)
                    score++;
                 break;
      case 'C' : if (count == 2)
                    score++;
                 break;
      case 'D' : if (count == 3)
                    score++;
                 break;
      case 'E' : if (count == 4)
                    score++;
                 break;
   }


   /*  Question 4:

       4. The number of questions with the answer A is
         (A) 4
         (B) 5
         (C) 6
         (D) 7
         (E) 8

       MCV: Just like the last one.  Just count 'em up and check the answer.
   */

   count = 0;
   for (i = 0;i < NQUESTIONS;i++)
      if (answer_str[i] == 'A')
         count++;

   ch = answer_str[3];

   switch (ch) {
      case 'A' : if (count == 4)
                    score++;
                 break;
      case 'B' : if (count == 5)
                    score++;
                 break;
      case 'C' : if (count == 6)
                    score++;
                 break;
      case 'D' : if (count == 7)
                    score++;
                 break;
      case 'E' : if (count == 8)
                    score++;
                 break;
   }


   /*  Question 5:

       5. The answer to this question is the same as the answer to question
         (A) 1
         (B) 2
         (C) 3
         (D) 4
         (E) 5
   */

   ch = answer_str[4];

   switch (ch) {
      case 'A' : if (ch == answer_str[0])
                    score++;
                 break;
      case 'B' : if (ch == answer_str[1])
                    score++;
                 break;
      case 'C' : if (ch == answer_str[2])
                    score++;
                 break;
      case 'D' : if (ch == answer_str[3])
                    score++;
                 break;
      case 'E' : if (ch == answer_str[4])
                    score++;
                 break;
   }


   /*  Question 6:

       6. The answer to question 17 is
         (A) C
         (B) D
         (C) E
         (D) none of the above
         (E) all of the above

       MCV: Okay, answer 'E' is never right (can only have a one-letter answer!)
            unless 'all' means 'any', and I say it don't.
   */

   ch = answer_str[5];

   switch (ch) {
      case 'A' : if (answer_str[16] == 'C')
                    score++;
                 break;
      case 'B' : if (answer_str[16] == 'D')
                    score++;
                 break;
      case 'C' : if (answer_str[16] == 'E')
                    score++;
                 break;
      case 'D' : if ((answer_str[16] == 'A') || (answer_str[16] == 'B'))
                    score++;
                 break;
   }


   /*  Question 7:

       7. Alphabetically, the answer to this question and the answer to the
          following question are
         (A) 4 apart
         (B) 3 apart
         (C) 2 apart
         (D) 1 apart
         (E) the same
   */

   ch = answer_str[6];
   gap = abs(answer_str[6] - answer_str[7]);

   switch (ch) {
      case 'A' : if (gap == 4)
                    score++;
                 break;
      case 'B' : if (gap == 3)
                    score++;
                 break;
      case 'C' : if (gap == 2)
                    score++;
                 break;
      case 'D' : if (gap == 1)
                    score++;
                 break;       
      case 'E' : if (gap == 0)
                    score++;
                 break;
   }


   /*  Question 8:

       8. The number of questions whose answers are vowels is
         (A) 4
         (B) 5
         (C) 6
         (D) 7
         (E) 8
   */

   count = 0;
   for (i = 0;i < NQUESTIONS;i++)
      if ((answer_str[i] == 'A') || (answer_str[i] == 'E'))
         count++;

   ch = answer_str[7];

   switch (ch) {
      case 'A' : if (count == 4)
                    score++;
                 break;
      case 'B' : if (count == 5)
                    score++;
                 break;
      case 'C' : if (count == 6)
                    score++;
                 break;
      case 'D' : if (count == 7)
                    score++;
                 break;
      case 'E' : if (count == 8)
                    score++;
                 break;
   }


   /*  Question 9:

       9. The next question with the same answer as this one is question
         (A) 10
         (B) 11
         (C) 12
         (D) 13
         (E) 14
   */

   ch = answer_str[8];

   switch (ch) {
      case 'A' : if (ch == answer_str[9])
                    score++;
                 break;
      case 'B' : if (ch == answer_str[10])
                    score++;
                 break;
      case 'C' : if (ch == answer_str[11])
                    score++;
                 break;
      case 'D' : if (ch == answer_str[12])
                    score++;
                 break;
      case 'E' : if (ch == answer_str[13])
                    score++;
                 break;
   }

   /*  Question 10:

       10. The answer to question 16 is
         (A) D
         (B) A
         (C) E
         (D) B
         (E) C
   */

   ch = answer_str[9];     

   switch (ch) {
      case 'A' : if (answer_str[15] == 'D')
                    score++;
                 break;
      case 'B' : if (answer_str[15] == 'A')
                    score++;
                 break;
      case 'C' : if (answer_str[15] == 'E')
                    score++;
                 break;
      case 'D' : if (answer_str[15] == 'B')
                    score++;
                 break;
      case 'E' : if (answer_str[15] == 'C')
                    score++;
                 break;
   }


   /*  Question 11:

       11. The number of questions preceding this one with the answer B is
          (A) 0
          (B) 1
          (C) 2
          (D) 3
          (E) 4
   */

   count = 0;
   for (i = 0;i < 10;i++)
      if (answer_str[i] == 'B')
         count++;

   ch = answer_str[10];

   switch (ch) {
      case 'A' : if (count == 0)
                    score++;
                 break;
      case 'B' : if (count == 1)
                    score++;
                 break;
      case 'C' : if (count == 2)
                    score++;
                 break;
      case 'D' : if (count == 3)
                    score++;
                 break;
      case 'E' : if (count == 4)
                    score++;
                 break;
   }


   /*  Question 12:

       12. The number of questions whose answer is a consonant is
          (A) an even number
          (B) an odd number
          (C) a perfect square
          (D) a prime
          (E) divisible by 5          

       MCV: Just about everybody agrees about 'even' and 'odd'.  For the
            purposes of the exercise, I also assume the following:
            0 (zero) is neither Even nor Odd          
            1, 4, 9, and 16 are perfect squares we may find.
            1, 2, 3, 5, 7, 11, 13, 17 and 19 are Prime numbers we need to be
               concerned about.
            Any number is divisible by 5, so we'll assume the author of the quiz
            means 'divisible with no remainder'.  O (zero) counts!
   */

   count = 0;
   for (i = 0;i < NQUESTIONS;i++)
      if ((answer_str[i] > 'A') && (answer_str[i] < 'E'))
         count++;

   ch = answer_str[11];

   switch (ch) {
      case 'A' : if (count != 0)
                    if ((count % 2) == 0)
                       score++;
                 break;
      case 'B' : if ((count % 2) == 1)
                    score++;
                 break;
      case 'C' : if ((count == 1) || (count == 4) || (count == 9) || (count == 16))
                    score++;
                 break;
      case 'D' : if ((count ==  1) || (count ==  2) || (count ==  3) ||
                     (count ==  5) || (count ==  7) || (count == 11) ||
                     (count == 13) || (count == 17) || (count == 19))
                    score++;
                 break;
      case 'E' : if ((count % 5) == 0)
                    score++;
                 break;
   }


   /*  Question 13:

       13 The only odd-numbered problem with answer A is
         (A) 9
         (B) 11
         (C) 13
         (D) 15
         (E) 17

       MCV: Two parts to scoring this ... again, the use of 'only' means that
            if we find more than one, then any answer is wrong (I assume ... you
            could easily make the alternate decision!)  Otherwise, we
            just have to check the answer.
   */

   count = 0;
   for (i = 0;i < NQUESTIONS;i++)
      if (((i + 1) % 2) != 0)
         if (answer_str[i] == 'A')
            count++;

   if (count == 1)
   {
      ch = answer_str[12];

      switch (ch) {
         case 'A' : if (answer_str[8]  == 'A')
                       score++;
                     break;
         case 'B' : if (answer_str[10] == 'A')
                       score++;
                     break;
         case 'C' : if (answer_str[12] == 'A')
                       score++;
                     break;
         case 'D' : if (answer_str[14] == 'A')
                       score++;
                     break;
         case 'E' : if (answer_str[16] == 'A')
                       score++;
                     break;
      }
   }


   /*  Question 14:

       14. The number of questions with answer D is
          (A) 6
          (B) 7
          (C) 8
          (D) 9
          (E) 10
   */
    
   count = 0;
   for (i = 0;i < NQUESTIONS;i++)
      if (answer_str[i] == 'D')
         count++;

   ch = answer_str[13];

   switch (ch) {
      case 'A' : if (count == 6)
                    score++;
                 break;
      case 'B' : if (count == 7)
                    score++;
                 break;
      case 'C' : if (count == 8)
                    score++;
                 break;
      case 'D' : if (count == 9)
                    score++;
                 break;
      case 'E' : if (count == 10)
                    score++;
                 break;
   }


   /*  Question 15:

       15. The answer to question 12 is
          (A) A
          (B) B
          (C) C
          (D) D
          (E) E
   */

   ch = answer_str[14];

   switch (ch) {
      case 'A' : if (answer_str[11] == 'A')
                    score++;
                 break;
      case 'B' : if (answer_str[11] == 'B')
                    score++;
                 break;
      case 'C' : if (answer_str[11] == 'C')
                    score++;
                 break;
      case 'D' : if (answer_str[11] == 'D')
                    score++;
                 break;
      case 'E' : if (answer_str[11] == 'E')
                    score++;
                 break;
   }

   /*  Question 16:

       16. The answer to question 10 is
          (A) D
          (B) C
          (C) B
          (D) A
          (E) E
   */       

   ch = answer_str[15];

   switch (ch) {
      case 'A' : if (answer_str[9] == 'D')
                    score++;
                 break;
      case 'B' : if (answer_str[9] == 'C')
                    score++;
                 break;
      case 'C' : if (answer_str[9] == 'B')
                    score++;
                 break;
      case 'D' : if (answer_str[9] == 'A')
                    score++;
                 break;
      case 'E' : if (answer_str[9] == 'E')
                    score++;
                 break;
   }


   /*  Question 17:

       17. The answer to question 6 is
          (A) C
          (B) D
          (C) E
          (D) none of the above
          (E) all of the above

       MCV: Okay, answer 'E' is never right (can only have a one-letter answer!)
            unless 'all' means 'any', and I say it don't.
   */

   ch = answer_str[16];

   switch (ch) {
      case 'A' : if (answer_str[5] == 'C')
                    score++;
                 break;
      case 'B' : if (answer_str[5] == 'D')
                    score++;
                 break;
      case 'C' : if (answer_str[5] == 'E')
                    score++;
                 break;
      case 'D' : if ((answer_str[5] == 'A') || (answer_str[16] == 'B'))
                    score++;
                 break;
   }


   /* Question 18:

       18. The number of questions with answer A equals the number of
           questions with answer
          (A) B
          (B) C
          (C) D
          (D) E
          (E) none of the above
   */

   for (i = 0;i < 5;i++)
      counters[i] = 0;
   for (i = 0;i < NQUESTIONS;i++)
      counters[answer_str[i] - 'A'] += 1;

   ch = answer_str[17];

   switch (ch) {
      case 'A' : if (counters[0] == counters[1])
                    score++;
                 break;
      case 'B' : if (counters[0] == counters[2])
                    score++;
                 break;
      case 'C' : if (counters[0] == counters[3])
                    score++;
                 break;
      case 'D' : if (counters[0] == counters[4])
                    score++;
                 break;         
      case 'E' : if ((counters[0] != counters[1]) &&
                     (counters[0] != counters[2]) &&
                     (counters[0] != counters[3]) &&
                     (counters[0] != counters[4]))
                    score++;
                 break;
   }


    /* Question 19:

       19. The answer to this question is:
         (A) A
         (B) B
         (C) C
         (D) D
         (E) E
                  
       MCV: Constructed such that any answer is correct.
    */

    ch = answer_str[18];
    if (ch == answer_str[18])
       score++;


    /*  Question 20:

       20. Standardized test : intelligence :: barometer :
          (A) temperature (only)
          (B) wind-velocity (only)
          (C) latitude (only)
          (D) longitude (only)
          (E) temperature, wind-velocity, latitude, and longitude

       MCV: A non self-referential question!  Since, as we know, the score of
            a standardized test only rates the ability to take standardized tests,
            then any of the answers will work.  (This by the mathematical
            principle of "A woman needs a man like a fish needs a bicycle.").
            BUT, E is effectively "all of the above", so that is what we'll
            call the "right" answer.
   */

   ch = answer_str[19];
   if (ch == 'E')
      score++;


   return(score);
}
// ------------------------------------------------------------------------




