#include <stdio.h>
#include <string.h>

#include "mix.h"

// global variables
FILE *log_file, *values_file;
char GAME_TABLE[ROWS_NUM][COLS_NUM];
int current_table_state;
double sarsa_values[PLAYERS_NUMBER][MDP_STATES_NUMBER];
double alpha, epsilon;

int player_round = XPLAYER;
int human = EMPTY;
int winner;

int NUMBER_OF_RUNS = 500;
int run_ix;


//////////////////////////////////////
//////////// Init procedure //////////
//////////////////////////////////////
void InitGame(void) {
  int r_ix, c_ix;

  // init table
  for (r_ix=0 ; r_ix<ROWS_NUM ; r_ix++) {
    for (c_ix=0 ; c_ix<COLS_NUM ; c_ix++) {
      GAME_TABLE[r_ix][c_ix] = 0;
    }
  }

  current_table_state = 0;

  // init winner
  winner = FALSE;

}       // end of InitGame


///////////////////////////////////////////////////////////////////////////
/////////// function IsMateExist return TRUE if found sequence _PPP_  /////
/////////// where P is PLAYER's symbol and _ is available slot        /////
///////////////////////////////////////////////////////////////////////////

int IsMateExist(int player) {
  int r_ix, c_ix;

  for (r_ix=0 ; r_ix<ROWS_NUM ; r_ix++) {
    for (c_ix=0 ; c_ix<COLS_NUM-WINNING_LENGTH ; c_ix++) {
      if (GAME_TABLE[r_ix][c_ix+1] == player && 
          GAME_TABLE[r_ix][c_ix+2] == player && 
          GAME_TABLE[r_ix][c_ix+3] == player) {
        if ( GAME_TABLE[r_ix][c_ix] == EMPTY && 
              (r_ix == ROWS_NUM-1 || GAME_TABLE[r_ix+1][c_ix] != EMPTY) 
            &&
             GAME_TABLE[r_ix][c_ix+WINNING_LENGTH] == EMPTY && 
              (r_ix == ROWS_NUM-1 || GAME_TABLE[r_ix+1][c_ix+WINNING_LENGTH] != EMPTY) )

             return TRUE;
      }
    }
  }

  return FALSE;
}       // end of IsMateExist


/////////////////////////////////////////////////////////////////////////////////
/////// function GetEmptySlotOfCol return index of the available slot of COL ////
/////////////////////////////////////////////////////////////////////////////////

int GetEmptySlotOfCol(int col) {
  int r_ix;
  
  for (r_ix=ROWS_NUM-1 ; r_ix >=0 ; r_ix--) {
    if ( GAME_TABLE[r_ix][col] == EMPTY) {
      return r_ix;
    }
  }

  return NOT_AVAILABLE_COL;

}       // end of GetEmptySlotOfCol


///////////////////////////////////////////////////////////////////////////
/////////// function IsChessExist return TRUE if found sequence of    /////
/////////// WINNING_LENGTH-1 PLAYER's symbol where the missing one is /////
/////////// empty. If POTENTIAL is FALSE then check also if the empty /////
/////////// slot is available.                                        /////
///////////////////////////////////////////////////////////////////////////

int IsChessExist(int player, int potential) {
  int r_ix, c_ix, w_ix;
  int counter, empty_slot, seq;

  // check horizontal sequence
  for (r_ix=0 ; r_ix<ROWS_NUM ; r_ix++) {
    for (c_ix=0 ; c_ix<COLS_NUM-WINNING_LENGTH+1 ; c_ix++) {
      counter = 0;
      empty_slot = -1;
      for (w_ix=0 ; w_ix<WINNING_LENGTH ; w_ix++) {
        if (GAME_TABLE[r_ix][c_ix+w_ix] == player) counter++;
          else if (GAME_TABLE[r_ix][c_ix+w_ix] == EMPTY) empty_slot = c_ix+w_ix;
      }
      // check if all but one empty slot are of PLAYER's symbol
      if (counter == WINNING_LENGTH-1 && empty_slot != -1) {   

        // check if not Mate already
        if ( c_ix == 0 || 
             c_ix+WINNING_LENGTH-1 == COLS_NUM || 
             (GAME_TABLE[r_ix][c_ix-1] != EMPTY || GAME_TABLE[r_ix][c_ix+WINNING_LENGTH-1] != EMPTY) ) {

            // check if potential or real
            if (potential || 
                (r_ix == ROWS_NUM-1 || GAME_TABLE[r_ix+1][empty_slot] != EMPTY))

              return TRUE;
           }
      }
    }
  }

  // check vertical sequence
  if (! potential) {    
    for (c_ix=0 ; c_ix<COLS_NUM ; c_ix++) {
      r_ix = GetEmptySlotOfCol(c_ix);
      if (r_ix != NOT_AVAILABLE_COL) {
        if (r_ix < ROWS_NUM-WINNING_LENGTH+1) {
          seq = TRUE;
          for (w_ix=1 ; w_ix<WINNING_LENGTH ; w_ix++) {
            if (GAME_TABLE[r_ix+w_ix][c_ix] != player) {
              seq = FALSE;
              break;
            }
          }
          if (seq) return TRUE;
        }
      }
    }
  }

  return FALSE;

}       // end of IsChessExist


///////////////////////////////////////////////////////////////////////////
/////////// function IsWinningSequenceExist return TRUE if PLAYER has /////
/////////// a winning sequence, FALSE otherwise.                      /////
///////////////////////////////////////////////////////////////////////////

int IsWinningSequenceExist(int player) {
  int r_ix, c_ix, w_ix;
  int seq;

  // check vertical sequence
  for (c_ix=0 ; c_ix<COLS_NUM ; c_ix++) {
    for (r_ix=0 ; r_ix<ROWS_NUM-WINNING_LENGTH+1 ; r_ix++) {
      seq = TRUE;
      for (w_ix=0 ; w_ix<WINNING_LENGTH ; w_ix++) {
        if (GAME_TABLE[r_ix+w_ix][c_ix] != player) {
          seq = FALSE;
          break;
        }
      }
      if (seq) return TRUE;
    }
  }

  // check horizontal sequence
  for (r_ix=0 ; r_ix<ROWS_NUM ; r_ix++) {
    for (c_ix=0 ; c_ix<COLS_NUM-WINNING_LENGTH+1 ; c_ix++) {
      seq = TRUE;
      for (w_ix=0 ; w_ix<WINNING_LENGTH ; w_ix++) {
        if (GAME_TABLE[r_ix][c_ix+w_ix] != player) {
          seq = FALSE;
          break;
        }
      }
      if (seq) return TRUE;
    }
  }

  return FALSE;

}       // end of IsWinningSequenceExist


//////////////////////////////////////////////////////////////////////////////
/////////// procedure GetTableState return order number of the state  ////////
/////////// in which the game is according to GAME_TABLE              ////////
//////////////////////////////////////////////////////////////////////////////

int GetTableState() {
  int state;

  if (IsWinningSequenceExist(XPLAYER)) return X_WINNING_STATE;
  if (IsWinningSequenceExist(OPLAYER)) return O_WINNING_STATE;

  //  calculate first bit
  state = (player_round == OPLAYER); 

  //  calculate second bit
  state = state + 2*(IsMateExist(XPLAYER));
  
  //  calculate third bit
  state = state + 4*(IsMateExist(OPLAYER));
  
  //  calculate fourth bit
  state = state + 8*(IsChessExist(XPLAYER, FALSE));

  //  calculate fifth bit
  state = state + 16*(IsChessExist(OPLAYER, FALSE));
    
  //  calculate sixth bit
  state = state + 32*(IsChessExist(XPLAYER, TRUE));

  //  calculate seventh bit 
  state = state + 64*(IsChessExist(OPLAYER, TRUE));

  return state;

}       // end of GetTableState


//////////////////////////////////////////////////////////////////////////////////
///////// procedure PrintSarsaValuesToLog print the sarsa values to FILE /////////
//////////////////////////////////////////////////////////////////////////////////

void PrintSarsaValuesToLog(FILE *file) {
  int plr_ix, val_ix;

  for (plr_ix=0 ; plr_ix<PLAYERS_NUMBER ; plr_ix++) {
    for (val_ix=0 ; val_ix<MDP_STATES_NUMBER ; val_ix++) {
      fprintf(file, "%2.3f ", sarsa_values[plr_ix][val_ix]);
      if (val_ix%10 == 9) fprintf(file, "\n");
    }   
  }
  fprintf(file, "\n");
}


/////////////////////////////////////////////////////////////////////////////////////////
///////// procedure ReadSarsaValuesFromFile init sarsa values according to FILE /////////
/////////////////////////////////////////////////////////////////////////////////////////

void ReadSarsaValuesFromFile(FILE *file) {
  int plr_ix, val_ix;
  float val;

  for (plr_ix=0 ; plr_ix<PLAYERS_NUMBER ; plr_ix++) {
    for (val_ix=0 ; val_ix<MDP_STATES_NUMBER ; val_ix++) {
      fscanf(values_file, "%f ", &val);
      sarsa_values[plr_ix][val_ix] = val;
    }
  }
}


////////////////////////////////////////////////////////////////////////////////
///////// procedure PrintTable print GAME_TABLE to log file or screen  /////////
////////////////////////////////////////////////////////////////////////////////

void PrintTable(int TO_LOG) {
  int r_ix, c_ix;
  char ch;

  for (r_ix=0 ; r_ix<ROWS_NUM ; r_ix++) {
    if (TO_LOG) fprintf(log_file, "|");
    else printf("|");
    for (c_ix=0 ; c_ix<COLS_NUM ; c_ix++) {
      if (GAME_TABLE[r_ix][c_ix] == EMPTY) ch = ' ';
      else if (GAME_TABLE[r_ix][c_ix] == XPLAYER) ch = 'X';
      else ch = 'O';

      if (TO_LOG) fprintf(log_file, " %c ", ch);
      else printf(" %c ", ch);
    }
    if (TO_LOG) fprintf(log_file, "|\n");
    else printf("|\n");
  }

  if (TO_LOG) fprintf(log_file, "|");
  else printf("|");
  for (c_ix=0 ; c_ix<COLS_NUM ; c_ix++) {
    if (TO_LOG) fprintf(log_file, "---");
    else printf("---");
  }
  if (TO_LOG) fprintf(log_file, "|\n");
  else printf("|\n");
  if (TO_LOG) fprintf(log_file, "|");
  else printf("|");
  for (c_ix=0 ; c_ix<COLS_NUM ; c_ix++) {
    if (TO_LOG) fprintf(log_file, "-%d-", (c_ix+1)%10);
    else printf("-%d-", (c_ix+1)%10);
  }
  if (TO_LOG) fprintf(log_file, "|\n");
  else printf("|\n");

//  if (TO_LOG) fprintf(log_file, "STATE = %d, TURN = %d \n", GetTableState(GAME_TABLE), player_round);
  printf("STATE = %d, TURN = %d \n", current_table_state, player_round);

  if (TO_LOG) PrintSarsaValuesToLog(log_file);

}       // end of PrintTable


//////////////////////////////////////////////////////////////////////////
/////////// procedure UpdateSarsaValues update PREV_STATE value  /////////
/////////// according to current state                           /////////
//////////////////////////////////////////////////////////////////////////

void UpdateSarsaValues(int prev_state) {

  sarsa_values[XPLAYER-1][prev_state] = (double) ((1-alpha) * sarsa_values[XPLAYER-1][prev_state] +
                                                  alpha * sarsa_values[XPLAYER-1][current_table_state]);
  sarsa_values[OPLAYER-1][prev_state] = (double) ((1-alpha) * sarsa_values[OPLAYER-1][prev_state] +
                                                  alpha * sarsa_values[OPLAYER-1][current_table_state]);
}       // end of UpdateSarsaValues


/////////////////////////////////////////////////////////////////////////
/////////// Procedure ChangeTurn set round to be of other player ////////
/////////////////////////////////////////////////////////////////////////

void ChangeTurn(void) {

  if (player_round == XPLAYER) player_round = OPLAYER;
  else player_round = XPLAYER;

}


//////////////////////////////////////////////////////////////////////////////////
/////////// procedure InsertToTable put PLAYERS's symbol in COL,       ///////////
/////////// change turn and update sarsa values                        ///////////
//////////////////////////////////////////////////////////////////////////////////

void InsertToTable(int col) {
  int row = GetEmptySlotOfCol(col);
  int prev_state = current_table_state;

  if (row==NOT_AVAILABLE_COL) {
     printf("NOT LEGAL PLAY ! (%d,%d)\n", row, col);
     exit();
  }

  GAME_TABLE[row][col] = player_round;
  ChangeTurn();
  current_table_state = GetTableState();

  UpdateSarsaValues(prev_state);

}       // end of InsertToTable


//////////////////////////////////////////////////////////////////////
/////////// procedure RemoveFromTable remove one player's  ///////////
/////////// symbol from COL.                               ///////////
//////////////////////////////////////////////////////////////////////

void RemoveFromTable(int col) {
  int r_ix;

  ChangeTurn();

  for (r_ix=0 ; r_ix<ROWS_NUM ; r_ix++) {
    if (GAME_TABLE[r_ix][col] != EMPTY) {
      GAME_TABLE[r_ix][col] = EMPTY;
      break;
    }
  }

  current_table_state = GetTableState();

}       // end of RemoveFromTable


///////////////////////////////////////////////////////////////////////////
/////////// Procedure CalculateMax return the index of the biggest ////////
/////////// value in VAL_ARRAY.                                    ////////
///////////////////////////////////////////////////////////////////////////

int CalculateMax(double val_array[COLS_NUM]) {
  int ix;
  int max_ix;
  int multiple_max = 0;
  double max_val;

  max_val = val_array[0];
  max_ix = 0;

  for (ix=1 ; ix <COLS_NUM ; ix++) {
    if (max_val < val_array[ix]) {
      max_val = val_array[ix];
      max_ix  = ix;
      multiple_max = 0;
    }
    else if (max_val == val_array[ix]) {
      multiple_max = -1;  // indicate more than one maximum
    }
  }

  if (multiple_max == -1) {       // get one random max val
    do
      max_ix = rand()%COLS_NUM;
    while (val_array[max_ix] != max_val);
  }

  if (human != EMPTY) printf ("I put mine in col %d. \n", max_ix+1);

  return max_ix;

}       // end of CalculateMax


////////////////////////////////////////////////////////////////////////////
/////////// Procedure CalculateMin return the index of the smallest ////////
/////////// value in VAL_ARRAY.                                     ////////
////////////////////////////////////////////////////////////////////////////

int CalculateMin(double val_array[COLS_NUM]) {
  int ix;
  int min_ix;
  int multiple_min = 0;
  double min_val;

  min_val = val_array[0];
  min_ix = 0;

  for (ix=1 ; ix <COLS_NUM ; ix++) {
    if (min_val > val_array[ix]) {
      min_val = val_array[ix];
      min_ix  = ix;
      multiple_min = 0;
    }
    else if (min_val == val_array[ix]) {
      multiple_min = -1;  // indicate more than one maximum
    }
  }

  if (multiple_min == -1) {       // get one random min val
    do
      min_ix = rand()%COLS_NUM;
    while (val_array[min_ix] != min_val);
  }

//if (human != EMPTY)  printf ("--min-  %d(%2.3f)  --- \n", min_ix+1, val_array[min_ix]);
//fprintf (log_file, "--min-  %d(%2.3f)  --- \n", min_ix+1, val_array[min_ix]);

  return min_ix;

}       // end of CalculateMin


/////////////////////////////////////////////////////////////////////
/////////// function EndOfGame return FALSE if game is not over, ////
/////////// winner code otherwise                                ////
/////////////////////////////////////////////////////////////////////

int EndOfGame(void) {
  int c_ix;
  int full_table = TRUE;

  // check existence of unoccupied place
  for (c_ix=0 ; c_ix<COLS_NUM ; c_ix++) {
    if (GAME_TABLE[0][c_ix] == EMPTY) {
      full_table = FALSE;
      break;
    }
  }
  if (full_table) return DRAW;

  if (IsWinningSequenceExist(XPLAYER)) return XPLAYER;
  if (IsWinningSequenceExist(OPLAYER)) return OPLAYER;

  return FALSE;

}       // end of EndOfGame


////////////////////////////////////////////////////////////////////////////////////
/////////// procedure CalculateFutureValues calculates future sarsa values /////////
/////////// for any avaliable column for the next two plays                /////////
////////////////////////////////////////////////////////////////////////////////////

void CalculateFutureValues(double future_values[COLS_NUM][COLS_NUM]) {
  int col_ix1, col_ix2;
  int acting_player = player_round-1;
  int is_draw;

  for (col_ix1=0 ; col_ix1<COLS_NUM ; col_ix1++) {
    if (GAME_TABLE[0][col_ix1] != EMPTY)          
      // not available col
      for (col_ix2=0 ; col_ix2<COLS_NUM ; col_ix2++) 
        future_values[col_ix1][col_ix2] = (double) 2;
    else {                                              
      // available col
                                                        // simulate player play                            
      InsertToTable(col_ix1);  
                                                        // simulate other play
      is_draw = (EndOfGame() == DRAW);

      for (col_ix2=0 ; col_ix2<COLS_NUM ; col_ix2++) {  
        if (current_table_state == X_WINNING_STATE || current_table_state == O_WINNING_STATE) {
          future_values[col_ix1][col_ix2] = sarsa_values[acting_player][current_table_state]; 
        }
        else {
          if (is_draw) future_values[col_ix1][col_ix2] = 1.5;
          else {
            if (GAME_TABLE[0][col_ix2] != EMPTY)          
              // not available col
                future_values[col_ix1][col_ix2] = (double) 2;
            else {
              // available col
              InsertToTable(col_ix2);
              future_values[col_ix1][col_ix2] = sarsa_values[acting_player][current_table_state];
              RemoveFromTable(col_ix2);
            }
          }
        }
      }
      RemoveFromTable(col_ix1);
    }
  }
}       // end of CalculateFutureValues


/////////////////////////////////////////////////////////////////////////////////
/////////// Procedure GetPlay choose action for player according to epsilon /////
/////////// if exploration then randomiclly if exploitation then accordng   /////
/////////// to sarsa values.                                                /////
/////////////////////////////////////////////////////////////////////////////////

void GetPlay() {
  int col;
  int rnd;
  int col_ix, col_ix1;
  double future_future_values[COLS_NUM][COLS_NUM];
  double future_values[COLS_NUM];

  rnd = rand()%100;

  if ( (100 * epsilon) < rnd) {
    if (human != EMPTY) printf("explore...\n");
    // exploration -  get random free col
    do  
      col = rand()%COLS_NUM;
    while (GAME_TABLE[0][col] != EMPTY);
  }
  else {

    // exploitation - compute future values for any avaliable column
    CalculateFutureValues(future_future_values);
    
    for (col_ix=0 ; col_ix<COLS_NUM ; col_ix++) {
      future_values[col_ix] = future_future_values[col_ix][CalculateMin(future_future_values[col_ix])];
      if (future_values[col_ix] == 2) future_values[col_ix] = -2;       // anavilable col
    }

/* fprintf(log_file, "+++++++++++++++++++++++++++++++ \n"); */
/* for (col_ix=0 ; col_ix<COLS_NUM ; col_ix++) { */
/*   for (col_ix1=0 ; col_ix1<COLS_NUM ; col_ix1++) {  */
/*     fprintf(log_file, " %2.3f", future_future_values[col_ix][col_ix1]); */
/*   } */
/*   fprintf(log_file, " \n"); */
/* } */
/* fprintf(log_file, "------------------------------- \n"); */
/* for (col_ix=0 ; col_ix<COLS_NUM ; col_ix++) { */
/*   fprintf(log_file, " %2.3f", future_values[col_ix]); */
/* } */
/* fprintf(log_file, " \n"); */
/* fprintf(log_file, "+++++++++++++++++++++++++++++++ \n"); */

    col = CalculateMax(future_values);

  }

  InsertToTable(col);

}       // end of GetPlay


///////////////////////////////////////////////////
/////////////// main procedure ////////////////////
///////////////////////////////////////////////////

void main(int argc, char *argv[]) {

  time_t dummy; // for random
  char learning_need = TRUE;
  char selection;
  char str_selection[10];
  int col_selection;
  int plr_ix, val_ix;
  int col;

  // init game
  InitGame();

  // for random
  srand(time(&dummy)); 

  // sarsa value initialization
  if ((values_file = fopen("mix.init", "r")) == NULL) {
    printf("can't open file %s - will perform %d learning games. \n", "mix.init", NUMBER_OF_RUNS);
    for (plr_ix=0 ; plr_ix<PLAYERS_NUMBER ; plr_ix++) {
      for (val_ix=0 ; val_ix<MDP_STATES_NUMBER ; val_ix++) {
        sarsa_values[plr_ix][val_ix] = (double) 0;
      }
    }
    sarsa_values[XPLAYER-1][X_WINNING_STATE] = (double) 1;
    sarsa_values[XPLAYER-1][O_WINNING_STATE] = (double) -1;
    sarsa_values[OPLAYER-1][X_WINNING_STATE] = (double) -1;
    sarsa_values[OPLAYER-1][O_WINNING_STATE] = (double) 1;
  }
  else {
    printf("Init sarsa values from file %s. \n", "mix.init");
    ReadSarsaValuesFromFile(values_file);
    fclose(values_file);
    learning_need = FALSE;
    epsilon = 1;        // exploit always
  }

  if (learning_need) {

  // open log file 
  if ((log_file = fopen(LOG_FILE_NAME, "w")) == NULL) { 
    printf("can't open file %s! \n", LOG_FILE_NAME); 
    return; 
  } 

  // computer vs computer 
  for (run_ix=1 ; run_ix<=NUMBER_OF_RUNS ; run_ix++) {

    alpha = (double) 1/(run_ix/(NUMBER_OF_RUNS/10)+1);
    epsilon = (double) run_ix/NUMBER_OF_RUNS;

    fprintf(log_file, "--------- RUN NUMBER %d ---------\n", run_ix);
    printf("--------- RUN NUMBER %d (alpha %2.3f epsilon %2.3f) \n", run_ix, alpha, epsilon);

    while (! winner) {
      GetPlay();
      winner = EndOfGame();
    }
    fprintf(log_file, "   winner is player %d (state %d) \n", winner, current_table_state);
    PrintSarsaValuesToLog(log_file);
    InitGame();
  }

  }     // learning need

  // human vs computer 
  while (selection != 'q') {
    InitGame();

    printf("You may choose your symbol [x|o] (or [q]uit) : ");  
    while (selection != 'q' && selection != 'x' && selection != 'o') {
      selection = getchar();
    }
    switch(selection)  {
      case 'q' :
        break;
      case 'x'  :
        human = XPLAYER;
        break;
      case 'o'  :
        human = OPLAYER;
        break;
      default   :
        printf("  > %c Unknown command. \n", selection);
    }
  
    if (selection != 'q') {
      selection = ' ';
      player_round = XPLAYER; 
      while (! winner) {
        if (player_round != human) {
          GetPlay();
          PrintTable(FALSE);
        }
        else {
          printf("You put your's in col : ");
          col_selection = 0;
          while (col_selection < 1 || col_selection > COLS_NUM) {
            gets(str_selection);
            col_selection = atoi(str_selection);
          }
          InsertToTable(col_selection-1);
        }
        winner = EndOfGame();
      }
      printf("   winner is player %d. \n\n", winner);
    }
  }     // end of while

  // close log file 
  fclose(log_file); 

  // save sarsa values
  if ((values_file = fopen("sarsa_values.out", "w")) == NULL) {
    printf("can't open file %s! \n", "sarsa_values.out");
  }
  else PrintSarsaValuesToLog(values_file);
  fclose(values_file);

}










