/* * Copyright (C) 2010 Markus Echterhoff <tam@edu.uni-klu.ac.at> * * This file is part of EvoPaint. * * EvoPaint 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. * * 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 EvoPaint. If not, see <http://www.gnu.org/licenses/>. */ package evopaint.pixel.rulebased; import evopaint.Configuration; import evopaint.interfaces.IRandomNumberGenerator; import evopaint.pixel.rulebased.actions.ChangeEnergyAction; import evopaint.pixel.rulebased.actions.CopyAction; import evopaint.pixel.rulebased.actions.MoveAction; import evopaint.pixel.rulebased.actions.SetColorAction; import evopaint.pixel.rulebased.conditions.ExistenceCondition; import evopaint.pixel.rulebased.interfaces.IHTML; import evopaint.pixel.rulebased.targeting.ActionMetaTarget; import evopaint.pixel.rulebased.targeting.Qualifier; import evopaint.pixel.rulebased.targeting.ITarget; import evopaint.pixel.rulebased.targeting.MetaTarget; import evopaint.pixel.rulebased.targeting.QualifiedMetaTarget; import evopaint.pixel.rulebased.targeting.SingleTarget; import evopaint.pixel.rulebased.targeting.qualifiers.ColorLikenessColorQualifier; import evopaint.pixel.rulebased.targeting.qualifiers.ColorLikenessMyColorQualifier; import evopaint.pixel.rulebased.targeting.qualifiers.EnergyQualifier; import evopaint.pixel.rulebased.targeting.qualifiers.ExistenceQualifier; import evopaint.pixel.rulebased.util.ObjectComparisonOperator; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * * @author Markus Echterhoff <tam@edu.uni-klu.ac.at> */ public class Rule implements IHTML, Serializable { private List<Condition> conditions; private Action action; public List<Condition> getConditions() { return conditions; } public void setConditions(List<Condition> conditions) { this.conditions = conditions; } public Action getAction() { return action; } public void setAction(Action action) { this.action = action; } @Override public String toString() { String ret = "IF "; for (Iterator<Condition> ii = conditions.iterator(); ii.hasNext();) { Condition condition = ii.next(); ret += condition.toString(); if (ii.hasNext()) { ret += " AND "; } } ret += " THEN "; ret += action.toString(); return ret; } public String toHTML() { String ret = "<span style='color: #0000E6; font-weight: bold;'>if</span> "; for (Iterator<Condition> ii = conditions.iterator(); ii.hasNext();) { Condition condition = ii.next(); ret += condition.toHTML(); if (ii.hasNext()) { ret += " <span style='color: #0000E6; font-weight: bold;'>and</span> "; } } ret += " <span style='color: #0000E6; font-weight: bold;'>then</span> "; ret += action.toHTML(); ret += " "; ITarget target = action.getTarget(); if (action instanceof MoveAction || action instanceof CopyAction) { ret += " to "; } else if (action instanceof ChangeEnergyAction || action instanceof SetColorAction) { ret += " of "; } ret += action.getTarget().toHTML(); if (target instanceof ActionMetaTarget) { ret += " <span style='color: #0000E6; font-weight: bold;'>which</span> "; List<Qualifier> qualifiers = ((ActionMetaTarget)target).getQualifiers(); for (Iterator<Qualifier> ii = qualifiers.iterator(); ii.hasNext();) { ret += ii.next().toHTML(); if (ii.hasNext()) { ret += " <span style='color: #0000E6; font-weight: bold;'>and</span> "; } } } return ret; } public boolean apply(RuleBasedPixel actor, Configuration configuration) { for (Condition condition : conditions) { if (condition.isMet(actor, configuration) == false) { return false; } } actor.changeEnergy(action.execute(actor, configuration)); return true; } public Rule(List<Condition> conditions, Action action) { this.conditions = conditions; this.action = action; } public Rule() { this.conditions = new ArrayList<Condition>(); this.conditions.add(new ExistenceCondition()); this.action = new ChangeEnergyAction(); } public Rule(Rule rule) { this.conditions = new ArrayList(rule.conditions); this.action = rule.action; } public Rule(List<Action>usableActions, IRandomNumberGenerator rng) { this.conditions = new ArrayList<Condition>(); this.conditions.add(Condition.createRandom(rng)); this.action = usableActions.get(rng.nextPositiveInt(usableActions.size())); } public String validate() { String msg = null; if ((msg = validateTargetsNotEmpty()) != null) { return msg; } if ((msg = validateQualifiers()) != null) { return msg; } return null; } private String validateTargetsNotEmpty() { for (Condition c : conditions) { if (c.getTarget() instanceof SingleTarget) { if (((SingleTarget)c.getTarget()).getDirection() == null) { return "A condition has no target, please review your rule!"; } } else if (((MetaTarget)c.getTarget()).getDirections().size() == 0) { return "A condition has no target, please review your rule!"; } } if (action.getTarget() instanceof SingleTarget) { if (((SingleTarget)action.getTarget()).getDirection() == null) { return "The action has no target, please review your rule!"; } } else if (((MetaTarget)action.getTarget()).getDirections().size() == 0) { return "The action has no target, please review your rule!"; } return null; } private String validateQualifiers() { if (false == action.getTarget() instanceof QualifiedMetaTarget) { return null; } List<Qualifier> qualifiers = ((QualifiedMetaTarget)action.getTarget()).getQualifiers(); boolean foundIsPixel = false; boolean foundIsFreeSpot = false; boolean foundHasLeastEnergy = false; boolean foundHasMostEnergy = false; boolean foundHasColorLeastLikeColor = false; boolean foundHasColorMostLikeColor = false; boolean foundHasColorLeastLikeMe = false; boolean foundHasColorMostLikeMe = false; ArrayList<Qualifier> seen = new ArrayList<Qualifier>(); for (Qualifier q : qualifiers) { // check for doublicates for (Qualifier seenQ : seen) { if (seenQ.equals(q)) { return "You have doublicate action target qualifiers.\nThis makes no sense, but will influence performance, so please review your rule!"; } } seen.add(q); // gather information about existence of qualifiers if (q instanceof ExistenceQualifier) { if (((ExistenceQualifier)q).getObjectComparisonOperator() == ObjectComparisonOperator.EQUAL) { foundIsPixel = true; } else { foundIsFreeSpot = true; } } else if (q instanceof EnergyQualifier) { if (((EnergyQualifier)q).isLeast()) { foundHasLeastEnergy = true; } else { foundHasMostEnergy = true; } } else if (q instanceof ColorLikenessColorQualifier) { if (((ColorLikenessColorQualifier)q).isLeast()) { foundHasColorLeastLikeColor = true; } else { foundHasColorMostLikeColor = true; } } else if (q instanceof ColorLikenessMyColorQualifier) { if (((ColorLikenessMyColorQualifier)q).isLeast()) { foundHasColorLeastLikeMe = true; } else { foundHasColorMostLikeMe = true; } } } if (foundIsPixel) { // check for most obvious conflict if (foundIsFreeSpot) { return "Are you female? Just asking, because you want your target to be existent and non existent at the same time.\nHow about we fix that before we continue, shall we?"; } // check for redundancy if (foundHasLeastEnergy || foundHasMostEnergy || foundHasColorLeastLikeColor || foundHasColorMostLikeColor || foundHasColorLeastLikeMe || foundHasColorMostLikeMe) { return "You have redundant action target qualifiers.\nAll qualifiers except for the Non-Existence qualifier will check if their target is existent,\nso you can safely remove the Existence qualifier, which will improve performance."; } } // check for other conflicts if (foundIsFreeSpot) { if (foundHasLeastEnergy || foundHasMostEnergy || foundHasColorLeastLikeColor || foundHasColorMostLikeColor || foundHasColorLeastLikeMe || foundHasColorMostLikeMe) { return "How can a non-existing pixel have any other attributes to check for? Sense much?\nGo fix that before I download gay porn onto your hard disc and screw up your OS\nso the guys at the computer store can have a good laugh at your expense!"; } } if (foundHasLeastEnergy && foundHasMostEnergy) { return "The one with the least energy which has the most energy, hu?\nFix that before I get really mad at you for even trying!"; } // least like green, most like red will favor blue over green // the only case where we would want to catch this is when // the colors are the same. but this means creating a second equals() // which would suck // if (foundHasColorLeastLikeColor && foundHasColorMostLikeColor) { // } if (foundHasColorLeastLikeMe && foundHasColorMostLikeMe) { return "The one whose color is least and most like me at the same time, hu?\nI hate you!"; } return null; } public int countGenes() { int ret = 0; for (Condition condition : conditions) { ret += condition.countGenes(); } ret += 1; // a gene to remove a condition ret += 1; // a gene to add a condition; ret += action.countGenes(); return ret; } public void mutate(int mutatedGene, IRandomNumberGenerator rng) { for (int i = 0; i < conditions.size(); i++) { int conditionGeneCount = conditions.get(i).countGenes(); if (mutatedGene < conditionGeneCount) { Condition newCondition = Condition.copy(conditions.get(i)); newCondition.mutate(mutatedGene, rng); conditions.set(i, newCondition); return; } mutatedGene -= conditionGeneCount; } if (mutatedGene == 0) { if (conditions.size() == 0) { return; } conditions.remove(rng.nextPositiveInt(conditions.size())); return; } mutatedGene -= 1; if (mutatedGene == 0) { conditions.add(Condition.createRandom(rng)); return; } mutatedGene -= 1; int actionGenes = action.countGenes(); if (mutatedGene < actionGenes) { action = Action.copy(action); action.mutate(mutatedGene, rng); return; } mutatedGene -= actionGenes; assert false; // we have an error in the mutatedGene calculation } public void mixWith(Rule theirRule, float theirShare, IRandomNumberGenerator rng) { // conditions // cache size() calls for maximum performance int ourSize = conditions.size(); int theirSize = theirRule.conditions.size(); // now mix as many conditions as we have in common and add the rest depending // on share percentage // we have more conditions if (ourSize > theirSize) { int i = 0; while (i < theirSize) { Condition ourCondition = conditions.get(i); Condition theirCondition = theirRule.conditions.get(i); if (ourCondition.getType() == theirCondition.getType()) { Condition newCondition = Condition.copy(ourCondition); newCondition.mixWith(theirCondition, theirShare, rng); conditions.set(i, newCondition); } else { if (rng.nextFloat() < theirShare) { conditions.set(i, theirCondition); } } i++; } int removed = 0; while (i < ourSize - removed) { if (rng.nextFloat() < theirShare) { conditions.remove(i); removed ++; } else { i++; } } } else { // they have more conditions or we have an equal number of conditions int i = 0; while (i < ourSize) { Condition ourCondition = conditions.get(i); Condition theirCondition = theirRule.conditions.get(i); if (ourCondition.getType() == theirCondition.getType()) { Condition newCondition = Condition.copy(ourCondition); newCondition.mixWith(theirCondition, theirShare, rng); conditions.set(i, newCondition); } else { if (rng.nextFloat() < theirShare) { conditions.set(i, theirCondition); } } i++; } while (i < theirSize) { if (rng.nextFloat() < theirShare) { conditions.add(theirRule.conditions.get(i)); } i++; } } if (action.getType() == theirRule.action.getType()) { action = Action.copy(action); action.mixWith(theirRule.action, theirShare, rng); } else { if (rng.nextFloat() < theirShare) { action = theirRule.action; } } } }