// gpjpp example program
// Copyright (c) 1997, Kim Kokkonen
//
// This program is free software; you can redistribute it and/or 
// modify it under the terms of version 2 of the GNU General Public 
// License as published by the Free Software Foundation.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// Send comments, suggestions, problems to kimk@turbopower.com

import java.io.*;
import gpjpp.*;

//Othello Result
class GPOthello extends GPRun {

    //must override GPRun.createNodeSet to return 
    //  initialized set of functions & terminals
    protected GPAdfNodeSet createNodeSet(GPVariables cfg) {
        GPAdfNodeSet adfNs = new GPAdfNodeSet();

        if (cfg.UseADFs) {
	  /*            adfNs.reserveSpace(2);
            GPNodeSet ns0 = new GPNodeSet(6);
            GPNodeSet ns1 = new GPNodeSet(4);
            adfNs.put(0, ns0);
            adfNs.put(1, ns1);

            //RPB has 4 functions plus ADF
            ns0.putNode(new GPNode('+', " + ", 2));
            ns0.putNode(new GPNode('-', " - ", 2));
            ns0.putNode(new GPNode('*', " * ", 2));
            ns0.putNode(new GPNode('%', " / ", 2));
            ns0.putNode(new GPNode('A', " ADF0 ", 2));
            ns0.putNode(new GPNode('X', "x"));

            //ADF0 does only + and * functions
            ns1.putNode(new GPNode('+', "+", 2));
            ns1.putNode(new GPNode('*', "*", 2));
            ns1.putNode(new GPNode(1, "x1"));
            ns1.putNode(new GPNode(2, "x2"));
	    */
        } else {
            adfNs.reserveSpace(1);
            GPNodeSet ns0 = new GPNodeSet(9);
            adfNs.put(0, ns0);

            ns0.putNode(new GPNode('+', " + ", 2));
            ns0.putNode(new GPNode('-', " - ", 2));
            ns0.putNode(new GPNode('*', " * ", 2));
            ns0.putNode(new GPNode('%', " / ", 2));
            ns0.putNode(new GPNode('B', " black_edges "));
            ns0.putNode(new GPNode('C', " black_corners "));
	    ns0.putNode(new GPNode('N', " black_near_corners "));
	    ns0.putNode(new GPNode(8, " 8 "));
	    ns0.putNode(new GPNode(3, " 3 "));
        }

        return adfNs;
    }

    //must override GPRun.createPopulation to return 
    //  SymbReg-specific population
    protected GPPopulation createPopulation(GPVariables cfg, 
        GPAdfNodeSet adfNs) {
        return new GPOthelloPopulation(cfg, adfNs);
    }

    //construct this test case
    GPOthello(String baseName) { super(baseName, true); }

    //main application function
    public static void main(String[] args) {

        //compute base file name from command line parameter
        String baseName;
        if (args.length == 1)
            baseName = args[0];
        else
            baseName = "GPOthello";


	//	GPOthelloGP.evaluatetest();

        //construct the test case
        GPOthello test = new GPOthello(baseName);

        //run the test
        test.run();

        //make sure all threads are killed
        System.exit(0);
    }
}

//=====================================================================

//extend GPGene to evaluate equations

class GPOthelloGene extends GPGene {



  // Overwritting to get our own testing data.
  //    public void drawOn(GPDrawing ods,
  //                  String fname,
  //                 String title,
  //	       GPVariables cfg) throws IOException {
  //		 super.drawOn(ods,fname,title,cfg);
  //
  //		 System.out.println("We are HERE!");
  //}




    //public null constructor required during stream loading only

    //this constructor called when new genes are created
    GPOthelloGene(GPNode gpo) { super(gpo); }

    //this constructor called when genes are cloned during reproduction
    GPOthelloGene(GPOthelloGene gpo) { super(gpo); }

    //called when genes are cloned during reproduction
    protected Object clone() { return new GPOthelloGene(this); }

    //ID routine isA() required only for streams

    //must override GPGene.createChild to create AntGene instances
    public GPGene createChild(GPNode gpo) { return new GPOthelloGene(gpo); }

    //protected divide, here protected by IEEE floating point standards
    double divide(double x, double y) {
        //return NaN or INF if y = 0
      if (y==0) {
	return x;
      }
      else return x/y;
    }

    //called by OthelloGP.evaluate() for each branch of each GP
    //evaluate works with or without ADFs 
    double evaluate(double black_edges,
		    double black_corners, 
		    double black_near_corners, GPOthelloGP gp, 
		    double arg0, double arg1) { 
      double ret = 0.0; //avoid compiler error

        if (isFunction()) {
            switch (node.value()) {
                case '*':
                    ret = ((GPOthelloGene)get(0)).evaluate(black_edges, black_corners, black_near_corners, gp, arg0, arg1) *
                          ((GPOthelloGene)get(1)).evaluate(black_edges, black_corners, black_near_corners, gp, arg0, arg1);
                    break;

                case '+':
                    ret = ((GPOthelloGene)get(0)).evaluate(black_edges, black_corners, black_near_corners, gp, arg0, arg1) +
                          ((GPOthelloGene)get(1)).evaluate(black_edges, black_corners, black_near_corners, gp, arg0, arg1);
                    break;

                case '-':
                    ret = ((GPOthelloGene)get(0)).evaluate(black_edges, black_corners, black_near_corners, gp, arg0, arg1) -
                          ((GPOthelloGene)get(1)).evaluate(black_edges, black_corners, black_near_corners, gp, arg0, arg1);
                    break;

                case '%':
                    //use protected divide function
                    ret = divide(((GPOthelloGene)get(0)).evaluate(black_edges, black_corners, black_near_corners, gp, arg0, arg1), 
                          ((GPOthelloGene)get(1)).evaluate(black_edges, black_corners, black_near_corners, gp, arg0, arg1));
                    break;

                case 'A':
                    //ADF0 function call.  We have access to that
                    //  subtree, as the GP gave us a reference to itself as
                    //  parameter.  We first evaluate the subtrees, and then
                    //  call the adf with the parameters
                    double a0 = ((GPOthelloGene)get(0)).evaluate(black_edges, black_corners, black_near_corners, gp, arg0, arg1);
                    double a1 = ((GPOthelloGene)get(1)).evaluate(black_edges, black_corners, black_near_corners, gp, arg0, arg1);
                    ret = ((GPOthelloGene)gp.get(1)).evaluate(black_edges, black_corners, black_near_corners, gp, a0, a1);
                    break;

                default:
                    throw new RuntimeException("Undefined function type "+node.value());
            }

        } else { //is terminal
            switch (node.value()) {
                case 'B':
                    ret = black_edges;
                    break;
                case 'C':
                    ret = black_corners;
                    break;
                case 'N':
                    ret = black_near_corners;
                    break;
                case 8:
                    ret = 8;
                    break;
                case 3:
                    ret = 3;
                    break;
	        case 1:
                    ret = arg0;
                    break;

                case 2:
                    ret = arg1;
                    break;

                default:
                    throw new RuntimeException("Undefined terminal type "+node.value());
            }
        }

        //keep results in reasonable range (needed only when NaN not used)
        //if (ret > 1.0e30)
        //    ret = 1.0e30;
        //else if (ret < -1.0e30)
        //    ret = -1.0e30;

        return ret;
    }
}

//=====================================================================

class GPOthelloGP extends GP {

  // define relevant parameters.

  static double[][] dataMoves = new double[200][8]; // The training data
  static int numMoves = 0;

  static double lowRank = - 1000000;


    //define fitness cases


    //initialize test case values  This is for doing stuff before the
  // system starts training.  Put code here if you want to do it.
  /*    static {
       try {
	 	 FileInputStream istream = new FileInputStream("traindata");
	 StreamTokenizer myTok = new StreamTokenizer(istream);
	 myTok.parseNumbers();
	 int counter = 0;

	 do {
	   myTok.nextToken();
	   if (myTok.ttype == myTok.TT_NUMBER) 
	     {
	       double thisNum = myTok.nval;
	       dataMoves[counter / 8][counter % 8] = thisNum;
	       counter++;
	     }
	   else 
	     {
	       System.out.println("Read a string: " + myTok.sval +
				  "at counter " + counter);
	     }
	 }
	 while (myTok.ttype != myTok.TT_EOF);
	 numMoves = counter / 8;
	 System.out.println("Read data file.  Number of moves = " + numMoves+ ". ");
       } catch (Exception e)
	 {System.out.println("An Error Occured Reading the data file.");}

    }*/

    //public null constructor required during stream loading only

    //this constructor called when new GPs are created
    GPOthelloGP(int genes) { super(genes); }

    //this constructor called when GPs are cloned during reproduction
    GPOthelloGP(GPOthelloGP gpo) { super(gpo); }

    //called when GPs are cloned during reproduction
    protected Object clone() { return new GPOthelloGP(this); }

    //must override GP.createGene to create GPOthelloGene instances
    public GPGene createGene(GPNode gpo) { return new GPOthelloGene(gpo); }

    //must override GP.evaluate to return standard fitness

  // Code is from evaluating the fitness measure.  put your code here if you want.


  /* static void evaluatetest() {
        double rawFitness = 0.0;

	double lastValue = lowRank;
	double lastEdgarValue = lowRank;

        for (int i = 0; i < numMoves; i++) {
	  //double rating = ((GPOthelloGene)get(0)).evaluate(dataMoves[i][2], 
	  //						     dataMoves[i][3],
	  //						     dataMoves[i][4],
	  //						     dataMoves[i][5],
	  //						     dataMoves[i][6],
	  //						     dataMoves[i][7],
	  //						     this, 0, 0);
	  double rating = dataMoves[i][2] - dataMoves[i][3] + 5*(dataMoves[i][4]*dataMoves[i][4])
	    - 5*(dataMoves[i][5]*dataMoves[i][5]) + 10*(dataMoves[i][6]*dataMoves[i][6]*dataMoves[i][6])
	    - 10*(dataMoves[i][7]*dataMoves[i][7]*dataMoves[i][7]);

	    String output = "" + dataMoves[i][0] + ": "
              + dataMoves[i][2] + " " + dataMoves[i][3] + " " + dataMoves[i][4] +
	      " " + dataMoves[i][5] + " " + dataMoves[i][6] + " " + dataMoves[i][7] + " Rating = "
	      + rating + " ";


	    //            if (Double.isNaN(rating) || Double.isInfinite(rating)) {
                //set maximum error
	    //  return 1.0E30;
            //} 

	    if (dataMoves[i][0]==1) {
	      lastValue = rating;
	    }
	    else if ((lastValue > rating) && (dataMoves[i][1] != lastEdgarValue)){
                //add absolute error to rawFitness
                rawFitness += 1;
		output = output + "Fitness = " + rawFitness;
	    }
	    System.out.println(output);
	    lastEdgarValue = dataMoves[i][1];

        }
        double stdF = rawFitness;
        System.out.println("The test rating is " +  stdF);
    }*/


  // Fitness is actually evaluede here.evaluated here.
    public double evaluate(GPVariables cfg) {
        double rawFitness = 0.0;
	double stdF = 0.0;

	OthelloBoard board;

	OthelloPlayer whitePlayer = new OthelloRandomPlayer(OthelloBoard.WHITE);
	OthelloPlayer blackPlayer = new OthelloGPTester(OthelloBoard.BLACK,this);
	// OthelloPlayer blackPlayer = new OthelloRandomPlayer(OthelloBoard.BLACK);
	// OthelloPlayer whitePlayer = new OthelloGPTester(OthelloBoard.WHITE,this);

    Move currentMove;
    
    // Play five games and evaluate the board.
    for (int i=0; i<5; i++) {

      board = new OthelloBoard();
      while(!board.gameOver()) {
	
	if (board.gameTurn==OthelloBoard.BLACK) {
	  
	  currentMove = blackPlayer.bestMove(board);
	  board.makeMove(OthelloBoard.BLACK, currentMove.col(), currentMove.row());
	} else {
	  currentMove = whitePlayer.bestMove(board);
	  board.makeMove(OthelloBoard.WHITE, currentMove.col(), currentMove.row());
	}
      }
      rawFitness = board.score[OthelloBoard.BLACK];
      stdF += rawFitness;

    }

    // This boolean is set in the ini file.
    if (cfg.ComplexityAffectsFitness)
      //add 1 per unit length
      stdF += (double)length();

    System.out.println("Standard Fitness for individual after 5 games = "+ stdF);

    return stdF;
    }

}

//=====================================================================

//extend GPPopulation to create GPOthello GP trees

class GPOthelloPopulation extends GPPopulation {

    //this constructor called when new populations are created
    GPOthelloPopulation(GPVariables gpVar, GPAdfNodeSet adfNs) {
        super(gpVar, adfNs);
    }

    //populations are not cloned in standard runs
    //GPOthelloPopulation(GPOthelloPopulation gpo) { super(gpo); }
    //protected Object clone() { return new GPOthelloPopulation(this); }

    //must override GPPopulation.createGP to create GPOthelloGP instances
    public GP createGP(int numOfGenes) { return new GPOthelloGP(numOfGenes); }
}
