//////////////////////////////////////////////////////////////////////////////
//
// File: ttt.cc
//
// Purpose: Trav's Tic-Tac-Toe program.  This main routine parses the
//          command line input and then starts a game or match.
//
// Authors:
//   txe  Travis Emmitt
//
// Modifications:
//   14-APR-1998  txe  Initial creation
//   15-APR-1998  txe  Added command line options, comments, DEBUG()
//   16-APR-1998  txe  Added input files
//   19-APR-1998  txe  Added multiple matches and sessions
//   21-APR-1998  txe  Added school logic, random seed
//   22-APR-1998  txe  Added random seed file
//   23-APR-1998  txe  Using static debug, added loss_rate_goal
//   24-APR-1998  txe  Added interactive (-i) mode
//
//////////////////////////////////////////////////////////////////////////////

#define MAIN_CC

#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "debug.h"
#include "game.h"
#include "school.h"
#include "tplayer.h"  // for ability levels

#define DEFAULT_BEST_ROOT	"z/best"
#define DEFAULT_PLAYERS_FILE    "players/np.pla"
#define DEFAULT_RULES_FILE	"rules/standard.rul"
#define RANDOM_SEED_FILE        "random.seed"
#define TEMP_PLAYERS_FILE       "players.tmp"

#define scanf         fflush (stdin); scanf

typedef struct {            // used for creating players interactively //
  char type;
  int  ability;
  char arch_file[MAX_LEN+1];
  char wts_file[MAX_LEN+1];
} PlayerSpecs;

#ifdef STATIC_WORKS
static int   debug;
#endif

static char  best_root[MAX_LEN+1]    = DEFAULT_BEST_ROOT;
static int   interactive             = 0;
static float loss_rate_goal 	     = DEFAULT_LOSS_RATE_GOAL;
static float mutation_rate           = DEFAULT_MUTATION_RATE;
static char  name[NAME_LEN+1]        = "main routine";
static int   num_games               = 1;
static int   num_matches             = 1;
static int   num_sessions            = 0;
static char  players_file[MAX_LEN+1] = DEFAULT_PLAYERS_FILE;
static int   random_seed             = -1;
static char  rules_file[MAX_LEN+1]   = DEFAULT_RULES_FILE;

#define OPTIONS "\n\
    GENERAL OPTIONS:\n\
\n\
\t -d<debug_level>    - debugging level (higher is more verbose)\n\
\t -h                 - prints brief command line help (this)\n\
\t -i                 - interactive mode (recommended for beginners)\n\
\n\
\n\
    GAME PLAY OPTIONS:\n\
\n\
\t -s<num_sessions>   - number of school sessions to run\n\
\t -m<num_matches>    - number of matches per session\n\
\t -g<num_games>      - number of games per match\n\
\t -R<seed>           - random number seed\n\
\n\
\n\
    CONFIGURATION FILE OPTIONS:\n\
\n\
\t -p<players_file>   - player configuration file\n\
\t -r<rules_file>     - rules  configuration file\n\
\n\
\n\
    SCHOOLING OPTIONS: (only if sessions > 0 and using NeuralPlayer)\n\
\n\
\t -b<best_root>      - root file name for saving best NN architecture\n\
\t -l<loss_rate_goal> - target session loss rate (a percentage)\n\
\t -M<rate>           - NN architecture mutation rate (0 - 1)\n\
\n"

void CreatePlayersFile (char *filename);
int  FileExists        (char *filename);
void GetOptions        ();
void GetPlayers        ();
int  LoadRandomSeed    (char *filename);
int  RunProgram        ();

///////////////////////////////////////////////////////////////////////////////

int main (int argc, char **argv) {
  debug = 0;

  // Get program options //

  random_seed = LoadRandomSeed (RANDOM_SEED_FILE);

  for (int i = 1; i < argc; i++) {
    if (argv[i][0] != '-') {
      cout << "Invalid option: '" << argv[i] << "'\nUse 'ttt -h' for help\n\n";
      return -1;
    }

    switch (argv[i][1]) {
    case 'b' : sscanf (&argv[i][2], "%s", best_root);         break;
    case 'd' : sscanf (&argv[i][2], "%d", &debug);            break;
    case 'g' : sscanf (&argv[i][2], "%d", &num_games);        break;
    case 'h' : cout << "\nusage: ttt [options]\n" << OPTIONS; return 0;
    case 'i' : interactive = 1;                               break;
    case 'l' : sscanf (&argv[i][2], "%f", &loss_rate_goal);   break;
    case 'm' : sscanf (&argv[i][2], "%d", &num_matches);      break;
    case 'p' : sscanf (&argv[i][2], "%s", players_file);      break;
    case 'r' : sscanf (&argv[i][2], "%s", rules_file);        break;
    case 's' : sscanf (&argv[i][2], "%d", &num_sessions);     break;
    case 'M' : sscanf (&argv[i][2], "%f", &mutation_rate);    break;
    case 'R' : sscanf (&argv[i][2], "%d", &random_seed);      break;
      
    default:
      cout << "Invalid option: '" << argv[i] << "'\nUse 'ttt -h' for help\n\n";
      return -1;
    }
  }

  if (interactive) {
    GetOptions ();
  }

#ifdef STATIC_WORKS
  Debug::debug = debug;
#endif

  num_matches = MAX (0, num_matches);
  num_games   = MAX (0, num_games);

  randomize (random_seed);
  
  if (!RunProgram ()) {
    cout << "\nProgram terminated prematurely.\n";
    return -1;
  }

  cout << "\nDone.\n";
  return 0;
}

////////////////////////////////////////////////////////////////////////////

void CreatePlayersFile (char *filename) {
  ASSERT (filename != NULL);

  PlayerSpecs p[MAX_PLAYERS];
  int i, num_players = 0;
  char choice;

  // Get number of players //

#ifdef ANY_NUMBER_PLAYERS
  while (num_players < 1 || num_players > MAX_PLAYERS) {
    cout << "\nEnter number of players (1 - " << MAX_PLAYERS << ") : ";
    scanf ("%d", &num_players);
  }
#else
  num_players = 2;
#endif

  // Have to specify the characteristics of each player //

  for (i = 0; i < num_players; i++) {
    cout << "\nCreating Player " << i + 1 << ":\n";

    p[i].type         = 0;
    p[i].ability      = 0;
    p[i].arch_file[0] = 0;
    p[i].wts_file[0]  = 0;
     
    // Get player type //

    while (!p[i].type || !strchr ("htn", p[i].type)) {
      cout << "\nEnter player type ('h'uman, 't'raditional AI, 'n'eural AI) :";
      scanf ("%c", &p[i].type);
    }

    if (p[i].type == 'h') {       // no more input needed for a human player //
      continue;         
    }

    // If Traditional player, get ability level //

    if (p[i].type == 't') {
      p[i].ability = IDIOT - 1;
      while (p[i].ability < IDIOT || p[i].ability > PERFECT) {
        cout << "\nEnter ability level ("
	     << IDIOT << " - " << PERFECT << ") : ";
        scanf ("%d", &p[i].ability);
      }
      continue;
    }

    // Otherwise, must be Neural player...  Get NeuNet architecture file //

    p[i].ability = AVERAGE;

    while (!p[i].arch_file[0]) {
      cout << "\nEnter name of NeuNet architecture file : ";
      scanf ("%s", p[i].arch_file);
      if (!FileExists (p[i].arch_file)) {
	cout << "\nThe file '" << p[i].arch_file
	     << "' does not exist; try again.\n";
	p[i].arch_file[0] = 0;
      }
    }

    // Get NeuNet weights file //

    choice = 0;

    while (!choice && !strchr ("sr", choice)) {
      cout << "\nDo you want to:"
	   << "\n  's'pecify a NeuNet weights file"
	   << "\n  'r'andomize weights"
	   << "\n\nEnter choice: ";
      scanf ("%c", &choice);
    }

    if (choice == 's') {
      while (!p[i].wts_file[0]) {
	cout << "\nEnter name of NeuNet weights file : ";
	scanf ("%s", p[i].wts_file);
	if (!FileExists (p[i].wts_file)) {
	  cout << "\nThe file '" << p[i].wts_file
	       << "' does not exist; try again.\n";
	  p[i].wts_file[0] = 0;
	}
      }
    }
  }

  // Done characterizing players, now have to create temp players file //

  FILE *fp;
  if ((fp = fopen (filename, "wt")) == NULL) {
    ERR << "Could not write to '" << filename << "', exiting\n";
    exit (-1);
  }

  fprintf (fp, "%d\n", num_players);

  for (i = 0; i < num_players; i++) {
    fprintf (fp, "%c %d %s %s\n", p[i].type,      p[i].ability,
	                          p[i].arch_file, p[i].wts_file);
  }

  fclose (fp);

  cout << "\nDone.\nSaved new players to '" << filename
       << ", returning to main menu\n";
}


///////////////////////////////////////////////////////////////////////////

int FileExists (char *filename) {
  ASSERT (filename != NULL);

  FILE *fp;

  if ((fp = fopen (filename, "rt")) == NULL) {
    return 0;
  }
  fclose (fp);
  return 1;
}

///////////////////////////////////////////////////////////////////////////

void GetOptions () {
  char choice, temp[MAX_LEN+1];

  cout << "\n=============================================================================="
       << "\n                  Welcome to TTT (Trav's Tic-Tac-Toe Test)\n";

  while (1) {
    cout   << "\nCurrent settings:\n"
	   << "\n  'p'layers file                   : " << players_file
	   << "\n  'r'ules file                     : " << rules_file;

    if (num_sessions) {
      cout << "\n  Mode                             : " << "School"
	   << "\n  'M'utation rate for NN arch      : " << mutation_rate
	   << "\n  'l'oss rate goal (percentage)    : " << loss_rate_goal <<"%"
	   << "\n  rootname for saving 'b'est state : " << best_root
	   << "\n  Number of 's'essions             : " << num_sessions
	   << "  (use 0 to switch to Game mode)"
	   << "\n  Number of 'm'atches per session  : " << num_matches;
    }
    else {
      cout << "\n  Mode                             : " << "Game"
           << "  (use 's' to switch to School mode)"
	   << "\n  Number of 'm'atches to run       : " << num_matches;
    }
    
    cout   << "\n  Number of 'g'ames per match      : " << num_games
	   << "\n  'd'ebugging level                : " << debug
	   << "\n  'R'andom number seed             : " << random_seed
	   << "\n"
	   << "\nEnter letter of option to change (or '.' done, 'q'uit) : ";

    scanf ("%c", &choice);

    switch (choice) {
      case 'p' :
	GetPlayers ();
	break;
      
      case 'r' :
	cout << "\nEnter name of rules file : ";
	scanf ("%s", temp);
	if (!FileExists (temp)) {
	  cout << "The file '" << temp << "' does not exist\n";
	  break;
	}
	sprintf (rules_file, "%s", temp);
	break;

      case 'b' :
	cout << "\nEnter root name of NN best state files : ";
	scanf ("%s", best_root);
	break;

      case '.' :
	cout << "Done editing options\n\n";
	fflush (stdin);
	return;

      case 'q' :
	cout << "Quitting\n";
	exit (0);
     
      case 'M' :
	cout << "\nEnter mutation rate : ";
	scanf ("%f", &mutation_rate);
	break;

      case 'l' :
	cout << "\nEnter loss rate goal (percentage) : ";
	scanf ("%f", &loss_rate_goal);
	break;

      case 's' :
	cout << "\nEnter number of school sessions "
	     << "('0' if you want Game mode) : ";
	scanf ("%d", &num_sessions);
	break;

      case 'm' :
	cout << "\nEnter number of matches : ";
	scanf ("%d", &num_matches);
	break;

      case 'g' :
	cout << "\nEnter number of games per match : ";
	scanf ("%d", &num_games);
	break;

      case 'd' :
	cout << "\nEnter debug level : ";
	scanf ("%d", &debug);
	break;

      case 'R' :
	cout << "\nEnter random_seed : ";
	scanf ("%d", &random_seed);
	break;

      default :
        cout << "\nInvalid choice: '" << choice << "', try again\n";
    }
  }
}

///////////////////////////////////////////////////////////////////////

void GetPlayers () {
  char choice = 0, temp[MAX_LEN+1] = "";

  while (1) {
    cout << "\nDo you want to:"
	 << "\n  's'pecify an existing players file"
	 << "\n  'c'reate a new (temporary) players file"
	 << "\n  'r'eturn to the main menu"
	 << "\n  'q'uit the game"
	 << "\n\nEnter choice: ";

    scanf ("%c", &choice);

    switch (choice) {
    case 's' : 
      cout << "\nEnter name of players file : ";
      scanf ("%s", temp);

      if (!FileExists (temp)) {
        cout << "\nThe file '" << temp << "' does not exist; try again.\n";
	continue;
      }
      sprintf (players_file, "%s", temp);
      return;

    case 'c' :
      sprintf (players_file, "%s", TEMP_PLAYERS_FILE);
      CreatePlayersFile (players_file);
      return;

    case 'r' :
      return;

    case 'q' :
      cout << "Quitting\n";
      exit (0);

    default:
      cout << "Invalid choice '" << choice << "'\n";
    }
  }
}

///////////////////////////////////////////////////////////////////////////

int LoadRandomSeed (char *filename) {
  ASSERT (filename != NULL);
  FILE *fp;
  int seed = 0;

  // Read random seed from SEED_FILE //

  if ((fp = fopen (filename, "rt")) == NULL) {
    WARN << "Couldn't read from '" << filename << "', using seed 0\n";
  }
  else {
    if (!fscanf (fp, "%d", &seed)) {
      WARN << "Error reading seed from '" << filename << "', using seed 0\n";
    }
    fclose (fp);
  }

  // Write seed+1 back to the file (so that next time will be different) //

  if ((fp = fopen (filename, "wt")) == NULL) {
    WARN << "Couldn't write to '" << filename << "'\n";
  }
  else {
    fprintf (fp, "%d", seed + 1);
    fclose (fp);
  }

  // Return the seed we read in (or 0 if we couldn't read it in) //

  return seed;
}

///////////////////////////////////////////////////////////////////////////

int RunProgram () {
  if (!num_sessions) {
    Game game ("Game", players_file, rules_file);
    return game.Run (num_matches, num_games);
  }

  // If running in school mode, find (and save) best arch and weights //

  School school ("School", players_file, rules_file, best_root);

  school.SetMutationRate (mutation_rate);
  school.SetLossRateGoal (loss_rate_goal);
    
  if (!school.FindBestArch (num_sessions, num_matches, num_games)) {
    ERR << "Couldn't find best architecture\n";
    return 0;
  }
  
  if (!school.FindBestWeights (num_matches, num_games)) {
    ERR << "Couldn't find best weights\n";
    return 0;
  }
  
  // Create player file so that we can test our best arch & weights //

  char play_file[MAX_LEN+1];
  sprintf (play_file, "%s.pla", best_root);
  
  DEBUG(0) << "\nWriting test-ready player file...\n";
  
  FILE *fp;

  if ((fp = fopen (play_file, "wt")) == NULL) {
    ERR << "Couldn't write to '" << play_file << "'\n";
    return 0;
  }

  fprintf (fp, "2\nt p\nn 0 %s.cfg %s.wts\n", best_root, best_root);
  fclose (fp);

  DEBUG(0) << "\nTest by typing:\n\tttt -s0"
	   << " -p" << play_file   << " -r" << rules_file
	   << " -m" << num_matches << " -g" << num_games
	   << "\n\n";

  return 1;
}

///////////////////////////////////////////////////////////////////////////




