import java.util.Random;

/*
 * The Game class constructs and allows access to internally processed information stored 
 * in game objects which represent finitely and variably repeated games of perfect 
 * information. Each game object holds the utilities of all possible outcomes of all 
 * possible stage games as well as how the outcomes affect the course of the whole game.
 
 * The Game class is meant to be coordinated with player objects by allowing them knowledge
 * of rewards and changes in state through the course of the game. The getRewards method and
 * changeState method should be used pairwise by another method in this way in order to: 
 * obtain the outcome, and progress to the next stage of the game, respectively.
 */

public class Game {
  
  private final int NUM_STATES;
  private final int NUM_PLAYERS;
  private final int NUM_ACTIONS;
  private final int NUM_TRIALS;
  private final int[][][] PAYOUTS;
  private final double[][][] STF;
  private int currentState;
  private int currentTrial;
  
  /*
   * The Game object holds the parameters of the game as final variables and keeps track
   * of the current state, the starting one being state zero, and the current trial
   * number, which begins at one.
   */
  
  public Game(int states, int players, int actions, int trials, int[][][] rewards, double[][][] probs){
    currentState = 0;
    currentTrial = 1;
    NUM_STATES = states;
    NUM_PLAYERS = players;
    NUM_ACTIONS = actions;
    NUM_TRIALS = trials;
    PAYOUTS = rewards;
    STF = probs;
  }
  
  /*
   * The getRewards method takes as input an array of integers representing the sum of
   * actions of all the players from which the payout to each player is accessed from
   * the PAYOUTS array and outputted.
   */
  
  public int[] getRewards(int[] actions){
    return PAYOUTS[currentState][this.getOutcome(actions)];
  }
  
  /*
   * The changeState method takes as input an array of integers representing the sum of
   * actions of all the players from which the next state is obtained stochastically from
   * the STF array of probabilities. The integer of the new state is outputted.
   */
  
  public int changeState(int[] actions){
    if(currentTrial >= NUM_TRIALS){
      return -1;
    }
    int index = this.getOutcome(actions);
    Random r = new Random();
    double path = r.nextDouble();
    double probSum = 0.0;
    for(int i = 0; i < NUM_STATES; i++){
      probSum += STF[currentState][index][i];
      if(probSum > path){
        currentState = i;
        currentTrial++;
        return i;
      }
    }
    currentState = (NUM_STATES - 1);
    currentTrial++;
    return (NUM_STATES - 1);
  }
  
  /*
   * Converts a total list of actions to its outcome number.
   */
  
  private int getOutcome(int[] actions){
    int index = 0;
    for(int i = 0; i < NUM_ACTIONS; i++){
      index += actions[i]*(Math.pow(NUM_PLAYERS, NUM_ACTIONS - (i + 1)));
  
    }
    return index;
  }
  
  /*
   * Returns the parameters and state of the game as six integers in an array. (The 
   * number of states, the number of players, the number of actions, the number of trials
   * the current game-state, and the current trial number)
   */
  
  public int[] getSpecs(){
    int[] specs = new int[6];
    specs[0= NUM_STATES;
    specs[1= NUM_PLAYERS;
    specs[2= NUM_ACTIONS;
    specs[3= NUM_TRIALS;
    specs[4= currentState;
    specs[5= currentTrial;
    return specs;
  }
  
  /*
   * Returns true iff the game has not incremented currentTrial, ie no round has yet been
   * played.
   */
  
  public boolean isNew(){
    if(currentTrial == 1){
      return true;
    }
    return false;
  }
  
  /*
   * Hands the value of the NUM_PLAYERS field.
   */
  
  public int getNumPlayers(){
    return NUM_PLAYERS;
  }
  
  /*
   * Hands a pointer to the PAYOUTS array. 
   */
  
  public int[][][] getPayouts(){
    return PAYOUTS;
  }
}