/*
* Copyright 2007-2010 Lawrence Beadle & Tom Castle
* Licensed under GNU General Public License
*
* This file is part of Epoch X - (The Genetic Programming Analysis Software)
*
* Epoch X is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Epoch X 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 Epoch X. If not, see <http://www.gnu.org/licenses/>.
*/
package org.epochx.semantics;
import java.util.ArrayList;
import java.util.List;
import org.epochx.core.Model;
import org.epochx.epox.*;
import org.epochx.epox.ant.*;
import org.epochx.gp.model.GPModel;
import org.epochx.gp.representation.GPCandidateProgram;
import org.epochx.representation.CandidateProgram;
import org.epochx.tools.ant.*;
/**
* The ant semantic module controls the translation of code
* to the ant behaviour and back again
*/
public class AntSemanticModule implements SemanticModule {
//private List<TerminalNode<Action>> terminals;
private Model model;
private ArrayList<String> antModel;
private String orientation;
private Ant ant;
private String environment;
@SuppressWarnings("unused")
private AntLandscape landscape;
/**
* Constructor for Ant Semantic Module
* @param list List of terminal nodes
* @param model The GPModel object
* @param ant The Ant object
* @param antLandscape The AntLanscape object
*/
public AntSemanticModule(List<VoidNode> list, Model model, Ant ant, AntLandscape landscape, String environment) {
//this.terminals = list;
this.model = model;
this.ant = ant;
this.landscape = landscape;
this.environment = environment;
}
/* (non-Javadoc)
* @see com.epochx.semantics.SemanticModule#behaviourToCode(com.epochx.semantics.Representation)
*/
@Override
public CandidateProgram behaviourToCode(Representation representation) {
Node rootNode = this.repToCode1(representation, "E");
CandidateProgram result = null;
if(environment.equalsIgnoreCase("GP")) {
result = new GPCandidateProgram(rootNode, (GPModel) model);
} else if(environment.equalsIgnoreCase("GE")) {
// TODO GE Constructor
} else if(environment.equalsIgnoreCase("GR")) {
// TODO GR Constructor
}
return result;
}
/* (non-Javadoc)
* @see com.epochx.semantics.SemanticModule#codeToBehaviour(com.epochx.core.representation.CandidateProgram)
*/
@Override
public Representation codeToBehaviour(CandidateProgram program) {
// develop ant monitoring model
antModel = new ArrayList<String>();
// initialise a new ant
orientation = "E";
SemanticCandidateProgram prog = new SemanticCandidateProgram(program, ant);
Node rootNode = prog.getRootNode();
this.runAnt(rootNode);
// work out depth of if statements
int depth = 0;
int maxDepth = 0;
for(String s: antModel) {
if(s.equalsIgnoreCase("{")) {
depth++;
}
if(s.equalsIgnoreCase("}")) {
depth--;
}
if(depth>maxDepth) {
maxDepth = depth;
}
}
for (int i = 0; i < (maxDepth + 2); i++) {
if (antModel.size() > 0) {
antModel = this.condenseAntRep(antModel);
}
}
return new AntRepresentation(antModel);
}
private void runAnt(Node rootNode) {
if(rootNode instanceof VoidNode) {
// terminals
Object value = ((VoidNode) rootNode).evaluate();
if(value instanceof AntMoveAction) {
antModel.add("M");
} else if(value instanceof AntTurnLeftAction) {
if(orientation.equalsIgnoreCase("E")) {
antModel.add("N");
orientation = "N";
} else if(orientation.equalsIgnoreCase("N")) {
antModel.add("W");
orientation = "W";
} else if(orientation.equalsIgnoreCase("W")) {
antModel.add("S");
orientation = "S";
} else if(orientation.equalsIgnoreCase("S")) {
antModel.add("E");
orientation = "E";
}
} else if(value instanceof AntTurnRightAction) {
if(orientation.equalsIgnoreCase("E")) {
antModel.add("S");
orientation = "S";
} else if(orientation.equalsIgnoreCase("S")) {
antModel.add("W");
orientation = "W";
} else if(orientation.equalsIgnoreCase("W")) {
antModel.add("N");
orientation = "N";
} else if(orientation.equalsIgnoreCase("N")) {
antModel.add("E");
orientation = "E";
}
}
} else if(rootNode instanceof Seq2Function) {
// PROGN2
int arity = rootNode.getArity();
for(int i = 0; i<arity; i++) {
this.runAnt(rootNode.getChild(i));
}
} else if(rootNode instanceof Seq3Function) {
// PROGN3
int arity = rootNode.getArity();
for(int i = 0; i<arity; i++) {
this.runAnt(rootNode.getChild(i));
}
} else if(rootNode instanceof IfFoodAheadFunction) {
// IF-FOOD-AHEAD
int arity = rootNode.getArity();
String oBeforeIf = orientation;
for(int i = 0; i<arity; i++) {
orientation = oBeforeIf;
antModel.add("{");
this.runAnt(rootNode.getChild(i));
antModel.add("}");
}
// reset orientation for after if statement
orientation = oBeforeIf;
}
}
/**
* The condense ant representation applies reduction rules to representation of
* behaviour in order to return a canonical representation of behaviour
* @param result the representation of behaviour to condense
* @return the canonical behavioural representation
*/
public ArrayList<String> condenseAntRep(ArrayList<String> result) {
// cycle through removing duplicate subsets
// work out total depth
int maxDepth = 0;
int depth = 0;
// ---------------------------------------------------------------------
for (String s : result) {
if (s.equals("{")) {
depth++;
}
if (s.equals("}")) {
depth--;
}
if (depth > maxDepth) {
maxDepth = depth;
}
}
//condense brackets only if there are brackets i.e. maxDepth>0
if (maxDepth > 0) {
boolean reduce = true;
while (reduce) {
reduce = false;
// cycle through and condense
int masterDepth = 0;
depth = 0;
int[] tracker = new int[maxDepth + 1];
// set all to zero
for (int i = 0; i < tracker.length; i++) {
tracker[i] = 0;
}
ArrayList<String> subset1, subset2;
for (int i = 0; i < result.size() - 1; i++) {
if (result.get(i).equalsIgnoreCase("{")) {
tracker[masterDepth]++;
}
if (tracker[masterDepth] % 2 == 1 && result.get(i).equalsIgnoreCase("{")) {
subset1 = new ArrayList<String>();
subset2 = new ArrayList<String>();
depth = 0;
int endPoint1 = 0;
int endPoint2 = 0;
for (int y = i; y < result.size(); y++) {
if (result.get(y).equalsIgnoreCase("{")) {
depth++;
}
if (result.get(y).equalsIgnoreCase("}")) {
depth--;
}
subset1.add(result.get(y));
if (depth == 0) {
endPoint1 = y;
break;
}
}
for (int y = endPoint1 + 1; y < result.size(); y++) {
if (result.get(y).equalsIgnoreCase("{")) {
depth++;
}
if (result.get(y).equalsIgnoreCase("}")) {
depth--;
}
subset2.add(result.get(y));
if (depth == 0) {
endPoint2 = y;
break;
}
}
// check if subsets equivalent
if (subset1.equals(subset2)) {
// pull up pre if code
ArrayList<String> preif = new ArrayList<String>();
// work out expected orientation before IF
String expectedO = "E";
if (i > 0) {
for (int k = 0; k < i; k++) {
preif.add(result.get(k));
if (result.get(k).equalsIgnoreCase("N") || result.get(k).equalsIgnoreCase("S") || result.get(k).equalsIgnoreCase("E") || result.get(k).equalsIgnoreCase("W")) {
expectedO = result.get(k);
}
}
}
// add subset1 to pre if
subset1.remove(0);
subset1.remove(subset1.size()-1);
for(String s: subset1) {
preif.add(s);
}
// get post if code if necessary
if(result.size()>endPoint2) {
// get post if code
ArrayList<String> postif = new ArrayList<String>();
for(int k = (endPoint2+1); k<result.size(); k++) {
postif.add(result.get(k));
}
// add post if code to preif+subset1 - care with orientation
result = joinPaths(preif, postif, expectedO);
} else {
result = preif;
}
reduce = true;
break;
}
}
// fix depth afterwards
if (result.get(i).equalsIgnoreCase("{")) {
masterDepth++;
}
if (result.get(i).equalsIgnoreCase("}")) {
masterDepth--;
}
}
}
}
// ---------------------------------------------------------------------
// pull out orientation letters in sequence
for (int i = 0; i < result.size() - 1; i++) {
if (result.get(i).equalsIgnoreCase("N") || result.get(i).equalsIgnoreCase("W") || result.get(i).equalsIgnoreCase("S") || result.get(i).equalsIgnoreCase("E")) {
if (result.get(i + 1).equalsIgnoreCase("N") || result.get(i + 1).equalsIgnoreCase("W") || result.get(i + 1).equalsIgnoreCase("S") || result.get(i + 1).equalsIgnoreCase("E")) {
result.remove(i);
i--;
}
}
}
ArrayList<String> controlStack = new ArrayList<String>();
controlStack.add("E");
depth = 0;
for(int i = 0; i<result.size(); i++) {
if(result.get(i).equalsIgnoreCase("{")) {
// amend depth
depth++;
// move up previous depths orientation
controlStack.add(controlStack.get(depth-1));
} else if(result.get(i).equalsIgnoreCase("}")){
// remove orientation from top of control stack
controlStack.remove(depth);
// amend depth
depth--;
} else if(result.get(i).equalsIgnoreCase("M")){
// do nothing
} else {
if(result.get(i).equalsIgnoreCase(controlStack.get(depth))) {
// remove duplicate orientation
result.remove(i);
i--;
} else {
controlStack.set(depth, result.get(i));
}
}
}
return result;
}
private Node repToCode1(Representation thisRep, String lastO) {
ArrayList<String> representation = ((AntRepresentation) thisRep).getAntRepresentation();
ArrayList<VoidNode> sequence = new ArrayList<VoidNode>();
// create a linear move list
String oBeforeIf = "E";
String lastOrientation = lastO;
String instruction;
for (int i = 0; i < representation.size(); i++) {
instruction = representation.get(i);
// SCENARIOS
// interpret instruction
if (instruction.equals("M")) {
sequence.add(new AntMoveAction(ant));
} else if (instruction.equals("E")) {
if (lastOrientation.equalsIgnoreCase("N")) {
sequence.add(new AntTurnRightAction(ant));
}
if (lastOrientation.equalsIgnoreCase("W")) {
if (Math.random() < 0.5) {
sequence.add(new AntTurnRightAction(ant));
sequence.add(new AntTurnRightAction(ant));
} else {
sequence.add(new AntTurnLeftAction(ant));
sequence.add(new AntTurnLeftAction(ant));
}
}
if (lastOrientation.equalsIgnoreCase("S")) {
sequence.add(new AntTurnLeftAction(ant));
}
lastOrientation = new String(instruction);
} else if (instruction.equals("S")) {
if (lastOrientation.equalsIgnoreCase("E")) {
sequence.add(new AntTurnRightAction(ant));
}
if (lastOrientation.equalsIgnoreCase("N")) {
if (Math.random() < 0.5) {
sequence.add(new AntTurnRightAction(ant));
sequence.add(new AntTurnRightAction(ant));
} else {
sequence.add(new AntTurnLeftAction(ant));
sequence.add(new AntTurnLeftAction(ant));
}
}
if (lastOrientation.equalsIgnoreCase("W")) {
sequence.add(new AntTurnLeftAction(ant));
}
lastOrientation = new String(instruction);
} else if (instruction.equals("W")) {
if (lastOrientation.equalsIgnoreCase("S")) {
sequence.add(new AntTurnRightAction(ant));
}
if (lastOrientation.equalsIgnoreCase("E")) {
if (Math.random() < 0.5) {
sequence.add(new AntTurnRightAction(ant));
sequence.add(new AntTurnRightAction(ant));
} else {
sequence.add(new AntTurnLeftAction(ant));
sequence.add(new AntTurnLeftAction(ant));
}
}
if (lastOrientation.equalsIgnoreCase("N")) {
sequence.add(new AntTurnLeftAction(ant));
}
lastOrientation = new String(instruction);
} else if (instruction.equals("N")) {
if (lastOrientation.equalsIgnoreCase("W")) {
sequence.add(new AntTurnRightAction(ant));
}
if (lastOrientation.equalsIgnoreCase("S")) {
if (Math.random() < 0.5) {
sequence.add(new AntTurnRightAction(ant));
sequence.add(new AntTurnRightAction(ant));
} else {
sequence.add(new AntTurnLeftAction(ant));
sequence.add(new AntTurnLeftAction(ant));
}
}
if (lastOrientation.equalsIgnoreCase("E")) {
sequence.add(new AntTurnLeftAction(ant));
}
lastOrientation = new String(instruction);
} else if (instruction.equalsIgnoreCase("{")) {
// save entry position
oBeforeIf = lastOrientation;
// IF-FOOD-AHEAD recursive call
int depth = 1;
ArrayList<String> part = new ArrayList<String>();
// pull out first section of if and submit to recursive call
while (i < representation.size()) {
i++;
if (representation.get(i).equalsIgnoreCase("{")) {
depth++;
}
if (representation.get(i).equalsIgnoreCase("}")) {
depth--;
}
if (depth == 0) {
break;
}
part.add(representation.get(i));
}
// pull part back from recursive call
VoidNode child1 = (VoidNode) this.repToCode1(new AntRepresentation(part), oBeforeIf);
// add part to sequence if no part then add skip
if (child1 == null) {
child1 = new AntSkipAction(ant);
}
// reset lastX and last Y to before if branch
lastOrientation = oBeforeIf;
// do second part of if
i = i + 2;
depth = 1;
part = new ArrayList<String>();
while (i < representation.size()) {
if (representation.get(i).equalsIgnoreCase("{")) {
depth++;
}
if (representation.get(i).equalsIgnoreCase("}")) {
depth--;
}
if (depth == 0) {
break;
}
part.add(representation.get(i));
i++;
}
// pull part back from recursive call
VoidNode child2 = (VoidNode) this.repToCode1(new AntRepresentation(part), oBeforeIf);
// add part to sequence if no part then add skip
if (child2 == null) {
child2 = new AntSkipAction(ant);
}
// end close bracket
VoidNode iFANode = new IfFoodAheadFunction(ant, child1, child2);
sequence.add(iFANode);
// move i along one to get out of final if bracket
lastOrientation = oBeforeIf;
} else if (instruction.equalsIgnoreCase("}")) {
// do nothing
} else {
System.out.println("REP TO CODE ERROR - GPEQUIVALENCE AA");
}
}
// run reduce sequence once to add skip node if needed
sequence = this.reduceSequence(sequence);
// then run repeatedly if need be
while(sequence.size()>1) {
sequence = this.reduceSequence(sequence);
}
// System.out.println(sequence);
return sequence.get(0);
}
private ArrayList<VoidNode> reduceSequence(ArrayList<VoidNode> sequence) {
int count = sequence.size();
if (count == 0) {
sequence.add(new AntSkipAction(ant));
} else if (count == 1) {
// do nothing is resolved ready to return
} else if (count == 2) {
// function up PROGN2
VoidNode p1 = sequence.get(0);
VoidNode p2 = sequence.get(1);
sequence.set(0, new Seq2Function(p1, p2));
sequence.remove(1);
} else if (count == 3) {
// function up PROGN3
VoidNode p1 = sequence.get(0);
VoidNode p2 = sequence.get(1);
VoidNode p3 = sequence.get(2);
sequence.set(0, new Seq3Function(p1, p2, p3));
sequence.remove(2);
sequence.remove(1);
} else if (count > 3) {
//crunch 1st three into progn3
VoidNode p1 = sequence.get(0);
VoidNode p2 = sequence.get(1);
VoidNode p3 = sequence.get(2);
sequence.set(0, new Seq3Function(p1, p2, p3));
sequence.remove(2);
sequence.remove(1);
}
// pass through and remove nulls
return sequence;
}
/**
* Helper method for Semantic Artificial Ant Initialisation
* @param path1 The first path
* @param path2 The second path
* @param p2SO The initial direction of the second path if not E
* @return The combined path with with the second path positions updated relative to the 1st path
*/
public static ArrayList<String> joinPaths(ArrayList<String> path1, ArrayList<String> path2, String p2SO) {
ArrayList<String> result = new ArrayList<String>();
ArrayList<String> part1 = new ArrayList<String>();
ArrayList<String> part2 = new ArrayList<String>();
for (String p : path1) {
part1.add(p);
}
for (String p : path2) {
part2.add(p);
}
// pull off last direction
String lastOrientation = "E";
for(int i = (part1.size()-1); i>=0; i--) {
if(part1.get(i).equalsIgnoreCase("N") || part1.get(i).equalsIgnoreCase("S") || part1.get(i).equalsIgnoreCase("E") || part1.get(i).equalsIgnoreCase("W")) {
lastOrientation = part1.get(i);
}
}
// update all orientations
// work out turning
if(lastOrientation.equalsIgnoreCase("N")) {
if(p2SO.equalsIgnoreCase("S")) {
part2 = turnL(part2);
part2 = turnL(part2);
} else if(p2SO.equalsIgnoreCase("E")) {
part2 = turnL(part2);
part2 = turnL(part2);
part2 = turnL(part2);
} else if(p2SO.equalsIgnoreCase("W")) {
part2 = turnL(part2);
}
} else if(lastOrientation.equalsIgnoreCase("S")) {
if(p2SO.equalsIgnoreCase("N")) {
part2 = turnL(part2);
part2 = turnL(part2);
} else if(p2SO.equalsIgnoreCase("E")) {
part2 = turnL(part2);
} else if(p2SO.equalsIgnoreCase("W")) {
part2 = turnL(part2);
part2 = turnL(part2);
part2 = turnL(part2);
}
} else if(lastOrientation.equalsIgnoreCase("E")) {
if(p2SO.equalsIgnoreCase("S")) {
part2 = turnL(part2);
part2 = turnL(part2);
part2 = turnL(part2);
} else if(p2SO.equalsIgnoreCase("N")) {
part2 = turnL(part2);
} else if(p2SO.equalsIgnoreCase("W")) {
part2 = turnL(part2);
part2 = turnL(part2);
}
} else if(lastOrientation.equalsIgnoreCase("W")) {
if(p2SO.equalsIgnoreCase("S")) {
part2 = turnL(part2);
} else if(p2SO.equalsIgnoreCase("E")) {
part2 = turnL(part2);
part2 = turnL(part2);
} else if(p2SO.equalsIgnoreCase("N")) {
part2 = turnL(part2);
part2 = turnL(part2);
part2 = turnL(part2);
}
}
// add all together
for (String p : part1) {
result.add(p);
}
for (String p : part2) {
result.add(p);
}
part1 = null;
part2 = null;
return result;
}
private static ArrayList<String> turnL(ArrayList<String> toTurn) {
for(int i = 0; i<toTurn.size(); i++) {
if(toTurn.get(i).equalsIgnoreCase("N")) {
toTurn.set(i, "W");
} else if(toTurn.get(i).equalsIgnoreCase("W")) {
toTurn.set(i, "S");
} else if(toTurn.get(i).equalsIgnoreCase("S")) {
toTurn.set(i, "E");
} else if(toTurn.get(i).equalsIgnoreCase("E")) {
toTurn.set(i, "N");
}
}
return toTurn;
}
}