/* **************************************************************************
 * Tricky-Eights Server Program -- te_eights.c
 * 
 *      This program, through standard input/output, issues the messages
 * needed to control four computerized tricky-eights players.
 *
 *      As much effort as possible was put into making this program
 * machine and compiler independent, memory-leak proof, idiot
 * proof, and o.k. to read.  All modifications should follow those
 * guidelines.
 *
 * Written by:  PAJ -- University of Utah   January 12, 1998.
 * Modified by: no one yet!  :)
 *
 * Makeline for SGI's:  gcc -Wall -o te_server -mips2 -O32 te_server.c
 ************************************************************************* */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MATCH_POINTS 300


/* **************************************************************************
 * Global variables
 ************************************************************************* */

/* Character definitions for players, suits, and card values. */

char players[] = "nesw";
char suits[] = "cdhs";
char values[] = "23456789tjqka";

/* This is an array of the players score totals, and their scores for
   the current game. */

int total_score [4];
int score [4];

/* This is the name of the file where the transactions for this match are
   logged. *//* This is the 'deck'.  Each card has a suit and value, and can be
   marked as owned by one of the players 'nsew', assigned as not dealt '-',
   not picked '+', or already played 'p'. */

char card_suit[52];
char card_value[52];
char card_owner[52];

/* This variable keeps track of who the lead player is -- the lead player
   gets to pick first and gets to play first. */

int lead, control;

/* Variables related to I/O and logging. */

char log_filename[] = "te.log";
FILE * log_file = NULL;
char message[500];
char word[15][500];
int wordcount;

/* Global state variable to terminate the game. */

int fail;


/* **************************************************************************
 * Helper functions  (Some of these may or may not exist in some libraries.)
 *
 * randomize -- This function seeds the random number generator and then
 *              asks for 'N' random numbers to provide repeatable randomness.
 *
 * force_lower -- This function changes all upper case characters in a
 *                text string to lower case.
 *
 * player_to_int -- Converts a player designation 'nsew' to an integer 0-3.
 *
 * suit_to_int -- Converts a suit designation to an integer 0-3.
 *
 * value_to_int -- Converts a value designation to an integer 0-12.
 ************************************************************************* */

void randomize (int n)
{
  /* Special note -- this function will be changed drastically for
     the competition.  Random numbers will NOT be predictable. */
  
  /* Reseed random number generator. */

  srand (1);

  /* Use up N random numbers so that we can choose from different random
     number sets, but still retain repeatability. */

  while (n-- > 0)
    rand ();
}


void force_lower (char * text)
{
  if (text == NULL) return;

  /* Loop, advancing string pointer, changing characters to lower case. */

  do
  {
    if (*text >= 'A' && *text <= 'Z')
      *text += 'a' - 'A';
  } while (*(text++) != 0); 
}


int player_to_int (char p)
{
  int c;

  /* Find the integer mapping to the character. */

  for (c = 0; c < 4; c++)
    if (players[c] == p)
      return c;

  /* Not found, indicate error by returning -1. */

  return -1;
}


int suit_to_int (char s)
{
  int c;

  /* Find the integer mapping to the character. */

  for (c = 0; c < 4; c++)
    if (suits[c] == s)
      return c;

  /* Not found, indicate error by returning -1. */

  return -1;
}


int value_to_int (char v)
{
  int c;

  /* Find the integer mapping to the character. */

  for (c = 0; c < 13; c++)
    if (values[c] == v)
      return c;

  /* Not found, indicate error by returning -1. */

  return -1;
}


/* **************************************************************************
 * I/O functions
 *
 * message_to_words -- This function breaks down the message array into a
 *                     set of up to 15 different words.
 *
 * echo_out -- This function outputs a string to stdout as well as to the
 *             log file.  The string to be output is the 'message' array.
 *
 * echo_in -- This function reads one line from stdin, and echos it to the
 *            log file.  The input line is placed in the 'message' array,
 *            forced to lower case, and processed into 'words'.
 *
 ************************************************************************* */

void message_to_words ()
{
  /* Break down any message into a given set of words. */
  
  wordcount = sscanf (message, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
		      word[0], word[1], word[2], word[3], word[4],
		      word[5], word[6], word[7], word[8], word[9],
		      word[10], word[11], word[12], word[13], word[14]);
}


void echo_out (int c)
{
  /* Output the message as is. */

  fputs (message, stdout);

  if (log_file != NULL)
    fputs (message, log_file);

  fflush (stdout);
}


void echo_in ()
{
  int length;
  
  /* Input the message, insuring a null terminated string. */
  
  message[0] = 0;
  if (fgets (message, sizeof (message), stdin) == NULL)
    message[0] = 0;

  /* Strip the trailing return, if any. */
  
  length = strlen(message);
  
  if (length > 0 && message[length - 1] == '\n')
    message[length - 1] = 0;

  /* Log the message. */

  if (log_file != NULL)
  {
    fputs (message, log_file);
    fputs ("\n", log_file);
  }

  /* Process the message. */

  force_lower (message);
  message_to_words ();
}


void fail_player (char p)
{
  echo_out (sprintf (message, "Illegal response from player %c!\n", p));
  fail++;
}


/* **************************************************************************
 * Card helper functions
 *
 * compare_cards -- This function returns 0 if two cards are the same,
 *                  -1 if the first is less than the second, 1 if the
 *                  first is greater than the second.  Less than /
 *                  greater than is determined the same as it is when
 *                  deciding which card takes a trick, given that the
 *                  first card was the first played.  Input = two pair
 *                  of suit/value characters.
 *
 * find_card -- This function returns the position in the 'deck' of this
 *              card.  Input = one pair of suit/value characters.
 ************************************************************************* */

int compare_cards (char suit1, char value1, char suit2, char value2)
{
  /* Suit is higher precedence, cards must follow suit. */

  if (suit1 != suit2)
    return 1;

  if (value1 == value2)
    return 0;

  /* Do comparison on integer equivalents for each card's ordinal value. */

  return value_to_int(value1) < value_to_int (value2) ? -1 : 1;
}


int find_card (char suit, char value)
{
  int c;

  /* Just look for it the hard way, one card at a time. */

  for (c = 0; c < 52; c++)
    if (suit == card_suit[c] && value == card_value[c])
      return c;

  /* Not in the deck?  Uh oh...  Return -1 to indicate error. */

  return -1;
}


/* **************************************************************************
 * Deck management functions
 *
 * build_deck -- This function initializes the deck of cards, and marks all
 *               cards as unowned.
 *
 * shuffle_deck -- This function randomizes the deck by moving every card
 *                 at least once to a random location and marks all cards
 *                 as unowned.
 *
 * validate_deck -- This function was used during debugging to make sure
 *                  all the cards remained in the deck.  Left there for
 *                  your perusal.
 ************************************************************************* */

void build_deck ()
{
  int s, v, c;

  /* Place a different card at each location in the deck. */

  c = 0;

  for (s = 0; s < 4; s++)
    for (v = 0; v < 13; v++)
    {
      card_suit[c] = suits[s];
      card_value[c] = values[v];
      card_owner[c] = '-';
      c++;
    }
}

void shuffle_deck ()
{
  int c1, c2;
  char temp;

  /* Swap every card with one other random card, reset ownerships. */ 

  for (c1 = 0; c1 < 52; c1++)
  {
    c2 = rand () % 52;

    temp = card_suit[c1];
    card_suit[c1] = card_suit[c2];
    card_suit[c2] = temp;

    temp = card_value[c1];
    card_value[c1] = card_value[c2];
    card_value[c2] = temp;

    card_owner[c1] = '-';
  }
}


int validate_deck ()
{
  int s, v;

  /* Find each and every possible card. */

  for (s = 0; s < 4; s++)
    for (v = 0; v < 13; v++)
      if (find_card (suits[s], values[v]) < 0)
	return 0;

  return 1;
}


/* **************************************************************************
 * Game playing functions
 *
 * deal_cards -- This function logically gives ownership of all the cards
 *               in the deck to the players and the pick pool, then
 *               it reports to each player their hand and the pick pool.
 ************************************************************************* */

void deal_cards ()
{
  int c, p;
  
  /* Logically give cards to the players and draw pool. */
  /*   10 cards are given round robin fashion to the players, 40 total. */
  /*   The remaining 12 go in the draw pool. */
  
  for (c = 0; c < 52; c++)
    card_owner[c] = c < 40 ? players[c % 4] : '+';

  /* Report to each player that the round is beginning, which cards they
     got, and which cards remain in the pool. */

  for (p = 0; p < 4; p++)
  {
    echo_out (sprintf (message, "# %c\n", players[p]));
    echo_out (sprintf (message, "* game start\n"));

    echo_out (sprintf (message, "* first 10 cards are your hand  last 12 cards are the pool\n"));
    echo_out (sprintf (message, "starthand %c", players[p]));
    for (c = 0; c < 52; c++)
      if (card_owner[c] == players[p])
        echo_out (sprintf (message, " %c%c", card_suit[c], card_value[c]));

    for (c = 0; c < 52; c++)
      if (card_owner[c] == '+')
        echo_out (sprintf (message, " %c%c", card_suit[c], card_value[c]));
    echo_out (sprintf (message, "\n"));
  }
}


void do_picking ()
{
  int p, c, r, card, slot;
  char suit[4], value[4], player[4];
 
  /* Starting with the lead player, allow cards to be picked, and report
     choices to other players.  (12 cards to be picked.) */
  
  p = lead;
  slot = 0;

  for (c = 0; c < 12; c++)
  {
    /* Ask player to pick a card. */
    
    echo_out (sprintf (message, "# %c\n", players[p]));
    echo_out (sprintf (message, "take"));

    for (r = 0; r < slot; r++)
      echo_out (sprintf (message, " %c %c%c", player[r], suit[r], value[r]));

    echo_out (sprintf (message, " %c", players[p]));
  
    echo_out (sprintf (message, "\n"));

    /* Get and validate a response. */

    echo_in ();
    
    card = find_card (word[0][0], word[0][1]);

    if (wordcount < 1 ||
	card < 0      || card_owner[card] != '+')
    {
      fail_player (players[p]);
      return;
    }

    card_owner[card] = players[p];
    
    /* Send the choice to the other players. */

    suit[slot] = card_suit[card];
    value[slot] = card_value[card];
    player[slot] = players[p];
    
    slot++;
    
    if (slot == 4)
    {
      for (r = 0; r < 4; r++)
      {
	echo_out (sprintf (message, "# %c\n", players[r]));
	echo_out (sprintf (message, "aftertake"));

	for (slot = 0; slot < 4; slot++)
	  echo_out (sprintf (message, " %c %c%c", player[slot],
			     suit[slot], value[slot]));

	echo_out (sprintf (message, "\n"));
      }

      slot = 0;
    }
    
    /* Let next player pick. */
	
    p = (p + 1) % 4;
  }
}


void do_tricks ()
{
  int t, p, c, r, best, card;
  char suit[4], value[4];
  
  /* Starting with the player in control, play out thirteen tricks, assigning
     points as the round progresses. */

  control = lead;
  
  for (t = 0; t < 13; t++)
  {
    /* This is the top of the trick-play loop.  The loop below is iterated
       once for each card played. */

    p = control;
    
    for (c = 0; c < 4; c++)
    {
      /* Report cards played so far to the player to play next. */
      
      echo_out (sprintf (message, "# %c\n", players[p%4]));
      echo_out (sprintf (message, "play"));

      for (r = 0; r < c; r++)
        echo_out (sprintf (message, " %c %c%c", players[(r+control)%4],
			   suit[r], value[r]));

      echo_out (sprintf (message, " %c\n", players[p%4]));
      
      /* Get and validate a played card from the player. */
      
      echo_in ();
    
      card = find_card (word[0][0], word[0][1]);

      if (wordcount < 1 ||
	  card < 0      || card_owner[card] != players[p])
      {
	fail_player (players[p]);
	return;
      }

      /* Make sure the player followed suit if possible, inefficiently. */

      if (c > 0)
	for (r = 0 ; r < 52; r++)
	  if (card_owner[r] == players[p] && card_suit[r] == suit[0] &&
	      card_suit[card] != suit[0]) 
	    {
	      echo_out (sprintf (message, "Player did not follow suit.\n"));
	      fail_player (players[p]);
	      return;
	    }
      
      /* Keep track of this played card. */
      
      card_owner[card] = 'p';
      suit[c] = card_suit[card];
      value[c] = card_value[card];

      /* Next player. */

      p = (p + 1) % 4;
    }

    /* Determine the trick winner. */

    best = 0;

    for (c = 1; c < 4; c++)
      if (compare_cards (suit[best], value[best], suit[c], value[c]) < 0)
	best = c;


    /* Report the winner and the trick to all players. */

    for (p = 0; p < 4; p++)
    {
      echo_out (sprintf (message, "# %c\n", players[p]));
      echo_out (sprintf (message, "result"));

      for (r = 0; r < 4; r++)
        echo_out (sprintf (message, " %c %c%c", players[(control+r)%4],
			   suit[r], value[r]));

      echo_out (sprintf (message, "\n"));
    }  

    /* Give control to trick winner. */
    
    control = (best + control) % 4;

    /* Increase the score of the trick winner. */

    score[control] += 1;   /* For the trick. */

    if (t == 0 || t == 12)
      score[control] += 2;  /* Bonus for the first and last trick. */

    for (c = 0; c < 4; c++)
      if (value[c] == '8')
	score[control] += 2;  /* Bonus for taking an eight. */
  }  
}


void tally_scores ()
{
  int c, p, r;

  /* Report results of this round to each player. */

  for (p = 0; p < 4; p++)
  {
    echo_out (sprintf (message, "# %c\n", players[p]));
    echo_out (sprintf (message, "end"));

    for (r = 0; r < 4; r++)
      echo_out (sprintf (message, " %i", score[r]));

    echo_out (sprintf (message, "\n"));
  }
  
  /* Add up scores. */

  for (c = 0; c < 4; c++)
    total_score[c] += score[c];

  /* Report total scores to each player. */

  for (p = 0; p < 4; p++)
  {
    echo_out (sprintf (message, "# %c\n", players[p]));
    echo_out (sprintf (message, "* totals "));

    for (r = 0; r < 4; r++)
      echo_out (sprintf (message, " %i", total_score[r]));

    echo_out (sprintf (message, "\n"));
  }
}


/* **************************************************************************
 * Main function
 *
 *      This main function runs one match of tricky-eights games.  The
 * match is ended when one player's score is about 250 points and is above
 * the score of all other players.  All data for the match is logged
 * to a file.
 ************************************************************************* */

int main (int argc, char ** argv)
{
  int c, winner, done;
  
  /* Use the first parameter on the command line to set up a known
     random number order. */

  randomize (argc > 1 ? atoi (argv[1]) : 0);

  /* Set up the deck, reset the scores, and other misc. */
  
  build_deck ();

  for (c = 0; c < 4; c++)
    total_score[c] = 0;

  fail = 0;
  lead = 0;

  /* Open the log file. */

  log_file = fopen (log_filename, "w");
  
  /* Round loop -- play until someone wins. */

  for (done = 0; !done && !fail; lead = (lead + 1) % 4)
  {
    for (c = 0; c < 4; c++)
      score[c] = 0;

    /* Play one game. */
    
    if (!fail) shuffle_deck ();
    if (!fail) deal_cards ();
    if (!fail) do_picking ();
    if (!fail) do_tricks ();
    if (!fail) tally_scores ();

    /* See if we have a clear winner. */

    /* Find the highest score (would be winner). */
    
    for (c = winner = 0; c < 4; c++)
      if (total_score[c] > total_score[winner])
	winner = c;

    /* Make sure they have enough points. */
    
    if (total_score[winner] >= MATCH_POINTS)
      done++;

    /* Make sure it's not a tie. */
    
    for (c = 0; c < 4; c++)
      if (c != winner && total_score[c] == total_score[winner])
	done = 0;
  }
  
  /* Close the log file. */

  if (log_file != NULL)
    fclose (log_file);
  
  /* All done -- end the program without error. */

  return 0;
}

