/* * This file is part of JGAP. * * JGAP offers a dual license model containing the LGPL as well as the MPL. * * For licensing information please see the file license.txt included with JGAP * or have a look at the top of class org.jgap.Chromosome which representatively * includes the JGAP license policy applicable for any file delivered with JGAP. */ package examples.gp.tictactoe; import org.jgap.*; import org.jgap.event.*; import org.jgap.gp.*; import org.jgap.gp.impl.*; import org.jgap.gp.function.*; import org.jgap.gp.terminal.*; import org.jgap.util.*; import org.jgap.impl.*; /** * Example demonstrating Genetic Programming (GP) capabilities of JGAP.<p> * Here, a strategy for playing Noughts and Crosses (Tic Tac Toe) is evolved.<p> * THIS PROGRAM IS STILL UNDER DEVELOPMENT AND IS NOT FINISHED YET! ANY COMMENTS * AND EXTENSIONS ARE VERY WELCOME! * * @author Klaus Meffert * @since 3.2 */ public class TicTacToeMain extends GPProblem { /** String containing the CVS revision. Read out via reflection!*/ private final static String CVS_REVISION = "$Revision: 1.6 $"; private static Variable vb; private Board m_board; public TicTacToeMain(GPConfiguration a_conf) throws InvalidConfigurationException { super(a_conf); m_board = new Board(); } public Board getBoard() { return m_board; } /** * Sets up the functions to use and other parameters. Then creates the * initial genotype. * * @param a_conf the configuration to use * @param a_color the color to create a program for * @param a_other an optional opponent, may be null * @param a_otherColor color of the opponent * * @return the genotype created * * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.2 */ public GPGenotype create(GPConfiguration a_conf, int a_color, GPGenotype a_other, int a_otherColor) throws InvalidConfigurationException { Class[] types = {CommandGene.VoidClass, CommandGene.VoidClass, CommandGene.VoidClass, CommandGene.VoidClass}; Class[][] argTypes = { {}, {}, {}, {} }; int[] minDepths = new int[] {0, 2, 2, 1}; int[] maxDepths = new int[] {0, 2, 6, 2}; // GPConfiguration conf = getGPConfiguration(); int color = a_color; ForLoop forLoop1 = new ForLoop(a_conf, SubProgram.VoidClass, 1, Board.WIDTH, 1, "x", 0, 0); ForLoop forLoop2 = new ForLoop(a_conf, SubProgram.VoidClass, 1, Board.HEIGHT, 1, "y", 0, 0); Variable vx = new Variable(a_conf, "move", CommandGene.IntegerClass); Variable vb = new Variable(a_conf, "firstmove", CommandGene.BooleanClass); // final String MATRIX1 = "MATRIX1"; a_conf.createMatrix(MATRIX1, 3, 3); // CommandGene[][] nodeSets = { { // Transfer board to evolution memory. // ----------------------------------- new TransferBoardToMemory(a_conf, m_board, 0, 0), }, { // Create strategy data. // --------------------- new Loop(a_conf, CommandGene.IntegerClass, Board.WIDTH * Board.HEIGHT), new EvaluateBoard(a_conf, m_board, CommandGene.IntegerClass), new IncrementMemory(a_conf, CommandGene.IntegerClass, "counter", 10), /**@todo evaluate board to matrix*/ }, { // Evaluate. // --------- vx, vb, new SubProgram(a_conf, new Class[] {CommandGene.VoidClass, CommandGene.VoidClass}), new SubProgram(a_conf, new Class[] {CommandGene.VoidClass, CommandGene.VoidClass, CommandGene.VoidClass}), new SubProgram(a_conf, new Class[] {TransferBoardToMemory.VoidClass, CommandGene.VoidClass}), new SubProgram(a_conf, new Class[] {CommandGene.VoidClass, CommandGene.VoidClass, CommandGene.VoidClass, CommandGene.VoidClass}), // forLoop1, // forLoop2, new ReadTerminalIndexed(a_conf, CommandGene.IntegerClass, 0), new ReadTerminalIndexed(a_conf, CommandGene.IntegerClass, 1), new ReadTerminalIndexed(a_conf, CommandGene.IntegerClass, 2), new ReadTerminalIndexed(a_conf, CommandGene.IntegerClass, 3), new ReadTerminalIndexed(a_conf, CommandGene.IntegerClass, 10, 22), new ReadTerminalIndexed(a_conf, CommandGene.IntegerClass, 11, 22), new ReadTerminalIndexed(a_conf, CommandGene.IntegerClass, 12, 22), new ReadTerminalIndexed(a_conf, CommandGene.IntegerClass, 13, 22), new ReadTerminalIndexed(a_conf, CommandGene.IntegerClass, 14, 23), new EvaluateBoard(a_conf, m_board, 14), new Loop(a_conf, SubProgram.class, Board.WIDTH), new Loop(a_conf, SubProgram.class, Board.HEIGHT), new Loop(a_conf, SubProgram.class, Board.WIDTH * Board.HEIGHT), new Constant(a_conf, CommandGene.IntegerClass, new Integer(0)), new Constant(a_conf, CommandGene.IntegerClass, new Integer(1)), new Constant(a_conf, CommandGene.IntegerClass, new Integer(2)), new Constant(a_conf, CommandGene.IntegerClass, new Integer(3)), new Terminal(a_conf, CommandGene.IntegerClass, 1.0d, Board.WIDTH, true, 4), new Terminal(a_conf, CommandGene.IntegerClass, 1.0d, Board.HEIGHT, true, 4), new Equals(a_conf, CommandGene.IntegerClass, 0, new int[] {22, 23}), new Equals(a_conf, CommandGene.IntegerClass, 0, new int[] {0, 8}), new IfElse(a_conf, CommandGene.BooleanClass), new ReadBoard(a_conf, m_board, 0, new int[] {4, 4}), new ReadBoard(a_conf, m_board), new Not(a_conf), new Push(a_conf, CommandGene.IntegerClass), new Pop(a_conf, CommandGene.IntegerClass), new IfIsOccupied(a_conf, m_board, CommandGene.IntegerClass, 0, new int[] {4, 4, 0}), new IfIsFree(a_conf, m_board, CommandGene.IntegerClass, 0, new int[] {4, 4, 0}), // new CountStones(conf, m_board, color, "count", 2), new IfColor(a_conf, CommandGene.IntegerClass, color), new IsOwnColor(a_conf, color), new Increment(a_conf, CommandGene.IntegerClass, 1), new Increment(a_conf, CommandGene.IntegerClass, -1), new StoreTerminalIndexed(a_conf, 0, CommandGene.IntegerClass), new StoreTerminalIndexed(a_conf, 1, CommandGene.IntegerClass), new StoreTerminalIndexed(a_conf, 2, CommandGene.IntegerClass), new StoreTerminalIndexed(a_conf, 3, CommandGene.IntegerClass), new StoreTerminalIndexed(a_conf, 10, CommandGene.IntegerClass), new StoreTerminalIndexed(a_conf, 11, CommandGene.IntegerClass), new StoreTerminalIndexed(a_conf, 12, CommandGene.IntegerClass), new StoreTerminalIndexed(a_conf, 13, CommandGene.IntegerClass), new StoreTerminalIndexed(a_conf, 14, CommandGene.IntegerClass), new StoreTerminal(a_conf, "mem0", CommandGene.IntegerClass), // new StoreTerminal(conf, "mem1", CommandGene.IntegerClass), new AddAndStoreTerminal(a_conf, "memA", CommandGene.IntegerClass), // new AddAndStoreTerminal(conf, "memB", CommandGene.IntegerClass), new ReadTerminal(a_conf, CommandGene.IntegerClass, "mem0"), // new ReadTerminal(conf, CommandGene.IntegerClass, "mem1"), new ReadTerminal(a_conf, CommandGene.IntegerClass, "memA"), // new ReadTerminal(conf, CommandGene.IntegerClass, "memB"), // new ReadTerminal(conf, CommandGene.IntegerClass, "countr0", 1), // new ReadTerminal(conf, CommandGene.IntegerClass, "countr1", 1), // new ReadTerminal(conf, CommandGene.IntegerClass, "countc0", 8), // new ReadTerminal(conf, CommandGene.IntegerClass, "countc1", 8), // new ReadTerminal(conf, CommandGene.IntegerClass, "countd0"), // new ReadTerminal(conf, CommandGene.IntegerClass, "countd1"), // new ReadTerminal(conf, CommandGene.IntegerClass, // forLoop1.getCounterMemoryName(), 5), // new ReadTerminal(conf, CommandGene.IntegerClass, // forLoop2.getCounterMemoryName(), 6), new ReadFromMatrix(a_conf, MATRIX1), new WriteToMatrix(a_conf, MATRIX1), new ResetMatrix(a_conf, MATRIX1, ' '), new CountMatrix(a_conf, MATRIX1, CountMatrix.CountType.COLUMN, CountMatrix.CountMode.EMPTY, ' ', ' '), new ReplaceInMatrix(a_conf, MATRIX1, ReplaceInMatrix.ReplacementMode.COLUMN, "ABC", 'R'), }, { // Make a move. // ------------ vb, // vx, new Constant(a_conf, CommandGene.IntegerClass, new Integer(1)), new Constant(a_conf, CommandGene.IntegerClass, new Integer(2)), new Equals(a_conf, CommandGene.IntegerClass), new PutStone(a_conf, m_board, color), new PutStone1(a_conf, m_board, color, 0, 33), new IfIsFree(a_conf, m_board, CommandGene.IntegerClass), new IfElse(a_conf, CommandGene.BooleanClass), new Increment(a_conf, CommandGene.IntegerClass, 1), new Increment(a_conf, CommandGene.IntegerClass, -1), new ReadTerminalIndexed(a_conf, CommandGene.IntegerClass, 15, 33), new ReadTerminal(a_conf, CommandGene.IntegerClass, "mem0"), new ReadTerminal(a_conf, CommandGene.IntegerClass, "mem1"), new ReadTerminal(a_conf, CommandGene.IntegerClass, "memA"), // new SubProgram(conf, new Class[] {CommandGene.VoidClass, // CommandGene.VoidClass}), // new Terminal(conf, CommandGene.IntegerClass, 1.0d, Board.WIDTH, true), // new Terminal(conf, CommandGene.IntegerClass, 1.0d, Board.HEIGHT, true), // new IfIsOccupied(conf, m_board, CommandGene.IntegerClass), } }; a_conf.setFitnessFunction(new TicTacToeMain. GameFitnessFunction(getBoard(), a_color, a_other, a_otherColor)); // } // Create genotype with initial population. // ---------------------------------------- GPGenotype result = GPGenotype.randomInitialGenotype(a_conf, types, argTypes, nodeSets, minDepths, maxDepths, 600, new boolean[] {!true, !true, !true, !true}, true); // Register variables to later have access to them. // ------------------------------------------------ result.putVariable(vb); result.putVariable(vx); return result; } public GPGenotype create() throws InvalidConfigurationException { throw new InvalidConfigurationException( "Please use create(Board a_board, int a_color)"); } /** * Starts the example. * * @param args ignored * @throws Exception * * @author Klaus Meffert * @since 3.2 */ public static void main(String[] args) { try { System.out.println("Problem: Find a strategy for playing Tic Tac Toe"); GPConfiguration config = new GPConfiguration(); config.setRandomGenerator(new GaussianRandomGenerator()); config.setGPFitnessEvaluator(new DeltaGPFitnessEvaluator()); int popSize; popSize = 50; System.out.println("Using population size of " + popSize); // Setup for player 1. // ------------------- config.setMaxInitDepth(6); config.setMinInitDepth(2); config.setNewChromsPercent(0.1d); config.setPopulationSize(popSize); config.setStrictProgramCreation(false); config.setProgramCreationMaxTries(3); config.setMaxCrossoverDepth(10); INodeValidator validator = new GameNodeValidator(); config.setNodeValidator(validator); final TicTacToeMain problem = new TicTacToeMain(config); config.getEventManager().addEventListener(GeneticEvent. GPGENOTYPE_EVOLVED_EVENT, new MyGeneticEventListener()); // Setup for player 2. // ------------------- GPConfiguration config2 = new GPConfiguration(config.getId() + "_2", config.getName() + "_2"); config2.setGPFitnessEvaluator(new DeltaGPFitnessEvaluator()); config2.setMaxInitDepth(6); config.setMinInitDepth(2); config.setNewChromsPercent(0.1d); config2.setPopulationSize(popSize); config2.setStrictProgramCreation(false); config2.setProgramCreationMaxTries(3); config2.setMaxCrossoverDepth(7); config2.setNodeValidator(validator); final TicTacToeMain problem2 = new TicTacToeMain(config); GPGenotype gp2 = problem2.create(config2, 2, null, 1); gp2.setVerboseOutput(true); config.getEventManager().addEventListener(GeneticEvent. GPGENOTYPE_NEW_BEST_SOLUTION, new BestGeneticEventListener(gp2)); // config2.getEventManager().addEventListener(GeneticEvent. // GPGENOTYPE_EVOLVED_EVENT, new MyGeneticEventListener()); // GPGenotype gp1 = problem.create(config, 1, gp2, 2); ( (GameFitnessFunction) gp1.getGPConfiguration().getGPFitnessFunction()). setPlayer(gp1); gp1.setVerboseOutput(true); // config2.getEventManager().addEventListener(GeneticEvent. GPGENOTYPE_NEW_BEST_SOLUTION, new BestGeneticEventListener(gp1)); // ( (GameFitnessFunction) gp2.getGPConfiguration().getGPFitnessFunction()). setOpponent(gp1); ( (GameFitnessFunction) gp2.getGPConfiguration().getGPFitnessFunction()). setPlayer(gp2); // Coevolution executer = new Coevolution(config, gp1, gp2); executer.start(); } catch (Exception ex) { ex.printStackTrace(); System.exit(1); } } public static class GameFitnessFunction extends GPFitnessFunction { private Board m_board; private int m_color; private GPGenotype m_other; private boolean firstTime; private static int maxMoves; private static int maxreads; private GPGenotype m_player; private int nullfound; static final double GAME_LOST = 5000; static final double MY_WORST_FITNESS_VALUE = 9999999; static final double READ_VALUE = 10000; static final double UNKNOWN_BUT_SOMETHING = MY_WORST_FITNESS_VALUE - 5000; static final double ONE_MOVE = MY_WORST_FITNESS_VALUE / (Board.HEIGHT * Board.WIDTH); static final double ONE_MOVE2 = ONE_MOVE * 0.9; public GameFitnessFunction(Board a_board, int a_color, GPGenotype a_other, int a_otherColor) { m_board = a_board; m_color = a_color; m_other = a_other; firstTime = true; } public void setPlayer(GPGenotype a_player) { m_player = a_player; } public void setOpponent(GPGenotype a_other) { m_other = a_other; } protected double evaluate(final IGPProgram a_subject) { return computeRawFitness(a_subject); } public double computeRawFitness(final IGPProgram a_program) { double error = MY_WORST_FITNESS_VALUE; double errorOpponent = MY_WORST_FITNESS_VALUE; Object[] noargs = new Object[0]; // Determine opponent's program. // ----------------------------- IGPProgram opponent; if (firstTime) { // First call. // ----------- firstTime = false; opponent = m_other.getGPPopulation().getGPProgram(0); if (opponent == null) { System.err.println("First time: opponent is null!"); } } else { opponent = m_other.getFittestProgramComputed(); if (opponent == null) { nullfound++; if (nullfound == 100) { System.err.println( "---------- Consecutive calls: opponent is null!"); } opponent = m_other.getGPPopulation().getGPProgram(0); } } // Compute fitness for each program. // --------------------------------- int moves = 0; // Set opponent's fitness value to a value to have it set at least here. // --------------------------------------------------------------------- opponent.setFitnessValue(UNKNOWN_BUT_SOMETHING); try { while (moves < Board.WIDTH * Board.HEIGHT) { m_board.startNewRound(); Boolean var; if (moves == 0) { var = new Boolean(true); } else { var = new Boolean(false); } Integer var2 = new Integer(moves); Variable vb1 = m_player.getVariable("firstmove"); vb1.set(var); Variable vb2 = m_other.getVariable("firstmove"); vb2.set(var); Variable vx1 = m_player.getVariable("move"); vx1.set(var2); Variable vx2 = m_other.getVariable("move"); vx2.set(var2); // Initialize local stores. // ------------------------ a_program.getGPConfiguration().clearStack(); a_program.getGPConfiguration().clearMemory(); try { // First player. // ------------- m_board.beginTurn(); for (int j = 0; j < a_program.size(); j++) { if (j == a_program.size() - 1) { a_program.execute_void(j, noargs); } else { a_program.execute_void(j, noargs); } } // Value the number of distinct read outs of the board by the // player. // ----------------------------------------------------------- int readCount = m_board.getReadPositionCount(); if (readCount > maxreads) { maxreads = readCount; if (maxreads > 1) { System.out.println("**** Number of board reads reached: " + maxreads); } } error -= readCount * READ_VALUE; m_board.endTurn(); moves++; error -= ONE_MOVE2; // Initialize local stores. // ------------------------ a_program.getGPConfiguration().clearStack(); a_program.getGPConfiguration().clearMemory(); // Second player. // -------------- m_board.beginTurn(); for (int j = 0; j < opponent.size(); j++) { if (j == opponent.size() - 1) { opponent.execute_void(j, noargs); } else { opponent.execute_void(j, noargs); } } // Value the number of distincts read outs of the board by the // player. // ----------------------------------------------------------- readCount = m_board.getReadPositionCount(); if (readCount > maxreads) { maxreads = readCount; if (maxreads > 1) { System.out.println("**** Number of board reads reached: " + maxreads); } } errorOpponent -= readCount * READ_VALUE; m_board.endTurn(); moves++; errorOpponent -= ONE_MOVE2; } catch (GameWonException gex) { if (gex.getColor() == m_color) { // Best case. // ---------- error -= 0; errorOpponent = GAME_LOST; break; } else { // Worse case: Player lost, but finished game correctly! // ----------------------------------------------------- error -= GAME_LOST; errorOpponent = 0.0d; break; } } m_board.endRound(); } System.out.println("******************* SUPERB: WE MADE IT"); } catch (IllegalArgumentException iax) { // Already cared about by not reducing error rate. // ----------------------------------------------- ; } catch (IllegalStateException iex) { // Already cared about by not reducing error rate. // ----------------------------------------------- } if (maxMoves < moves) { maxMoves = moves; System.out.println("**** Number of valid moves reached: " + maxMoves); } if (error < 0.000001) { error = 0.0d; } else if (error < MY_WORST_FITNESS_VALUE * 0.8d) { /**@todo add penalty for longer solutions*/ } if (errorOpponent < 0.000001) { errorOpponent = 0.0d; } else if (errorOpponent < MY_WORST_FITNESS_VALUE * 0.8d) { /**@todo add penalty for longer solutions*/ } opponent.setFitnessValue(errorOpponent); return error; } } } class BestGeneticEventListener implements GeneticEventListener { private GPGenotype m_other; public BestGeneticEventListener(GPGenotype a_other) { m_other = a_other; } /** * New best solution found. * * @param a_firedEvent GeneticEvent */ public void geneticEventFired(GeneticEvent a_firedEvent) { GPGenotype genotype = (GPGenotype) a_firedEvent.getSource(); int evno = genotype.getGPConfiguration().getGenerationNr(); String indexString = "" + evno; while (indexString.length() < 5) { indexString = "0" + indexString; } IGPProgram best = genotype.getAllTimeBest(); IGPProgram fittest = genotype.getFittestProgram(); if (fittest != null) { // Inject fittest program into opponent's population. // -------------------------------------------------- m_other.addFittestProgram(fittest); double bestFitness = fittest.getFitnessValue(); if (bestFitness < 0.5) { genotype.outputSolution(best); System.exit(0); } } } }; class MyGeneticEventListener implements GeneticEventListener { public MyGeneticEventListener() { } public void geneticEventFired(GeneticEvent a_firedEvent) { GPGenotype genotype = (GPGenotype) a_firedEvent.getSource(); int evno = genotype.getGPConfiguration().getGenerationNr(); double freeMem = SystemKit.getFreeMemoryMB(); if (evno % 100 == 0) { IGPProgram best = genotype.getAllTimeBest(); double allBestFitness = FitnessFunction.NO_FITNESS_VALUE; if (best != null) { allBestFitness = best. getFitnessValue(); } System.out.println("Evolving generation " + evno + ", all-time-best fitness: " + allBestFitness + ", memory free: " + freeMem + " MB"); IGPProgram best1 = genotype.getFittestProgram(); System.out.println(" Best in current generation: " + best1.getFitnessValue()); System.out.println(" " + best1.toStringNorm(0)); } if (evno > 30000) { System.exit(0); } else { // Collect garbage if memory low. // ------------------------------ if (freeMem < 50) { System.gc(); } } } } class Coevolution { private GPConfiguration m_conf; private GPGenotype m_gp1; private GPGenotype m_gp2; public Coevolution(GPConfiguration a_conf, GPGenotype a_gp1, GPGenotype a_gp2) { m_conf = a_conf; m_gp1 = a_gp1; m_gp2 = a_gp2; } public void start() { try { do { m_gp1.evolve(); Thread.currentThread().sleep(5); m_gp2.evolve(); Thread.currentThread().sleep(5); m_gp1.calcFitness(); Thread.currentThread().sleep(5); m_gp2.calcFitness(); Thread.currentThread().sleep(20); } while (true); } catch (InterruptedException iex) { ; //this is OK and means: end of evolution iex.printStackTrace(); } } }