diff --git a/gdx-ai/src/com/badlogic/gdx/ai/pfa/indexed/IndexedAStarPathFinderWithTerminationCondition.java b/gdx-ai/src/com/badlogic/gdx/ai/pfa/indexed/IndexedAStarPathFinderWithTerminationCondition.java new file mode 100644 index 00000000..aca03fe0 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/pfa/indexed/IndexedAStarPathFinderWithTerminationCondition.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright 2014 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.badlogic.gdx.ai.pfa.indexed; + +import com.badlogic.gdx.ai.pfa.Connection; +import com.badlogic.gdx.ai.pfa.GraphPath; +import com.badlogic.gdx.ai.pfa.Heuristic; +import com.badlogic.gdx.ai.pfa.PathFinder; +import com.badlogic.gdx.ai.pfa.PathFinderQueue; +import com.badlogic.gdx.ai.pfa.PathFinderRequest; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.BinaryHeap; +import com.badlogic.gdx.utils.TimeUtils; + +/** + * Extends IndexedAStarPathFinder + * + * Allows search for a given Node or given termination condition + * + * Example Termination Condition: Node contains 'health' item type + * + * @param Type of node extending {@link IndexedNode} + * + * @author fvolz */ +public abstract class IndexedAStarPathFinderWithTerminationCondition> extends IndexedAStarPathFinder { + + public IndexedAStarPathFinderWithTerminationCondition(IndexedGraph graph) { + super(graph, false); + } + + public abstract boolean isTerminationConditionSatisfied(N current, N target); + + @Override + public boolean searchNodePath (N startNode, N endNode, Heuristic heuristic, GraphPath outPath) { + + // Perform AStar + search(startNode, endNode, heuristic); + + // We're here if we've either found the goal, or if we've no more nodes to search, find which + + //fv: overrode to apply 'equals' operator + if (!current.node.equals(endNode)) { + // We've run out of nodes without finding the goal, so there's no solution + return false; + } + + generateNodePath(startNode, outPath); + + return true; + } + + @Override + protected void search(N startNode, N endNode, Heuristic heuristic) { + + initSearch(startNode, endNode, heuristic); + + // Iterate through processing each node + do { + // Retrieve the node with smallest estimated total cost from the open list + current = openList.pop(); + current.category = IndexedAStarPathFinder.CLOSED; + + //Terminate if we reached the goal node/condition + if (isTerminationConditionSatisfied(current.node, endNode)) { + endNode = current.node; + + return; + } + ; + + visitChildren(endNode, heuristic); + + } while (openList.size > 0); + } +} + diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/Action.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/Action.java new file mode 100644 index 00000000..a5325420 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/Action.java @@ -0,0 +1,140 @@ +package com.badlogic.gdx.ai.utility; + +/** This is the abstract base class of all UtiltiyAI Actions tasks. The {@code Action} a status + * + * @param type of the blackboard object that tasks use to read or modify game state + * + * @author fvolz*/ +public abstract class Action { + + /** The enumeration of the values that a task's status can have. + * + * @author davebaol */ + public enum Status { + /** Means that the task has never run or has been reset. */ + FRESH, + /** Means that the task needs to run again. */ + RUNNING, + /** Means that the task returned a failure result. */ + FAILED, + /** Means that the task returned a success result. */ + SUCCEEDED, + /** Means that the task has been terminated by an ancestor. */ + CANCELLED; + } + + /** The status of this task. */ + protected Status status = Status.FRESH; + + float cooldown; + float startedTime; + String name; + + public Action(String name, float cooldown) { + this.status = Status.FRESH; + setCooldown(cooldown); + this.startedTime = 0; //start in idle mode + this.name = name; + } + + public void execute(E context) { + if(canExecute() == false) + return; + + if(doTryUpdate(context) == false) { + startedTime = System.currentTimeMillis(); + status = Status.RUNNING; + onStart(context); + } + } + + public boolean doTryUpdate(E context) { + if(status.equals(Status.RUNNING)) { + onUpdate(context); + return true; + } + return false; + } + + protected void endInSuccess(E context) { + if(!status.equals(Status.RUNNING)) + return; + + status = Status.SUCCEEDED; + finalizeAction(context); + } + + protected void onStart(E context) { + endInSuccess(context); + } + + protected abstract void onUpdate(E context) ; + + protected abstract void onStop(E context); + + public boolean canExecute() { + if(isInCooldown()) { + status = Status.FAILED; + return false; + } + + return true; + } + + void finalizeAction(E context) { + onStop(context); + } + + /// + /// Ends the action and sets its status to . + /// + /// The context. + protected void endInFailure(E context) { + if(status != Status.RUNNING) + return; + + status = Status.FAILED; + finalizeAction(context); + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public boolean isInCooldown() { + + if(status.equals(Status.RUNNING) || + status.equals(Status.FRESH)) + return false; + + return System.currentTimeMillis() - startedTime < cooldown; + } + + public float getCooldown() { + return cooldown; + } + + public void setCooldown(float cooldown) { + this.cooldown = Math.max(0, cooldown); + } + + public float getStartedTime() { + return startedTime; + } + + public void setStartedTime(float startedTime) { + this.startedTime = startedTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/Behaviour.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/Behaviour.java new file mode 100644 index 00000000..758007c9 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/Behaviour.java @@ -0,0 +1,62 @@ +package com.badlogic.gdx.ai.utility; + +import com.badlogic.gdx.ai.utility.consideration.CompositeConsideration; +import com.badlogic.gdx.ai.utility.measure.Chebyshev; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by felix on 8/6/2017. + */ +/// +/// An AI behaviour is a set of s. The behaviour itself +/// is a composite consideration and therefore, unless this is the only behaviour in a +/// , it will be selected only if its considerations +/// "win" against competing behaviours in the AI. +/// +/// +/// +public class Behaviour extends CompositeConsideration { + + List> options = new ArrayList>(); + List utilities = new ArrayList(); + Selector selector; + + public Behaviour(String name){ + setName(name); + init(); + } + + public void init(){ + setMeasure(new Chebyshev()); + selector = new MaxRankAndUtilitySelector(); + } + + + public void addOption(Option option) { + options.add(option); + utilities.add(new Utility(0.0f, 1.0f)); + } + + /// + /// Selects the action for execution, given the specified context. + /// + /// The context. + /// The action to execute. + public Action select(E context) { + for(int i = 0, count = options.size(); i < count; i++) { + options.get(i).consider(context); + utilities.set(i,options.get(i).getUtility()); + } + + return selectAction(); + } + + protected Action selectAction(){ + int idx = selector.select(utilities); + Option option = idx >= 0 ? options.get(idx) : null; + return option != null ? option.action : null; + } + +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/Context.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/Context.java new file mode 100644 index 00000000..d4818ca4 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/Context.java @@ -0,0 +1,8 @@ +package com.badlogic.gdx.ai.utility; + +/** + * Created by felix on 8/4/2017. + */ +@Deprecated +public interface Context { +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/DecisionMaker.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/DecisionMaker.java new file mode 100644 index 00000000..fd577c20 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/DecisionMaker.java @@ -0,0 +1,129 @@ +package com.badlogic.gdx.ai.utility; + +import com.badlogic.gdx.ai.GdxAI; + +/** + * Created by felix on 8/7/2017. + */ +public class DecisionMaker { + + enum State{ + STOPPED, + RUNNING, + PAUSED; + } + + E currentContext; + Action currentAction; + State currentState; + UtilityAI ai; + int recursionCounter; + public static final int MaxRecursions = 100; + + + //TODO review passing Dog into dcision maker + public DecisionMaker(UtilityAI ai, E currentContext){ + this.ai = ai; + this.currentContext = currentContext; + } + + + public void think() { + if(isActionStillRunning()) + return; + + if(couldNotUpdateContext()) + return; + + if(AiDidSelectAction()) { + while(isTransition()) + connectorSelectAction(); + + executeCurrentAction(); + } + } + + public void update() { + if(couldNotUpdateContext()) + return; + + executeCurrentAction(); + } + + public boolean isActionStillRunning() { + if(null == currentAction) return false; + + return Action.Status.RUNNING.equals(currentAction.getStatus()); + } + + public boolean couldNotUpdateContext() { + recursionCounter = 0; + //currentContext = _contextProvider.Context(); + return currentContext == null; + } + + public boolean AiDidSelectAction() { + currentAction = ai.select(currentContext); + return currentAction != null; + } + + public boolean isTransition() { + checkForRecursions(); + return false; +// _transitionAction = _currentAction as ITransition; +// return _transitionAction != null; + } + + void checkForRecursions() { + recursionCounter++; + if(recursionCounter >= MaxRecursions) + GdxAI.getLogger().error("DecisionMaker","Circular Dependency on DecisionMaker " + recursionCounter); + ; + //throw new Exception("Circular Dependency on DecisionMaker?? " + recursionCounter); + } + + public void connectorSelectAction() { + // currentAction = _transitionAction.Select(currentContext); + } + + void executeCurrentAction() { + if(currentAction == null) + return; + + currentAction.execute(currentContext); + if(!Action.Status.RUNNING.equals(currentAction.getStatus())) + currentAction = null; + } + +// public void start() { +// if(!State.STOPPED.equals(currentState)) +// return; +// +// currentState = State.RUNNING; +// //OnStart(); +// } +// +// public void stop() { +// if(State.STOPPED.equals(currentState)) +// return; +// +// currentState = State.STOPPED; +// // OnStop(); +// } +// +// public void pause() { +// if(!State.RUNNING.equals(currentState)) +// return; +// +// currentState = State.PAUSED; +// //OnPause(); +// } +// +// public void resume() { +// if(!State.PAUSED.equals(currentState)) +// return; +// +// currentState = State.RUNNING; +// // OnResume(); +// } +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/MathUtils.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/MathUtils.java new file mode 100644 index 00000000..785f0193 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/MathUtils.java @@ -0,0 +1,11 @@ +package com.badlogic.gdx.ai.utility; + +/** + * Created by felix on 8/4/2017. + */ +public class MathUtils { + + public static float Clamp01(float x){ + return Math.max(0, Math.min(x, 1)); + } +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/MaxRankAndUtilitySelector.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/MaxRankAndUtilitySelector.java new file mode 100644 index 00000000..62bde4dc --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/MaxRankAndUtilitySelector.java @@ -0,0 +1,62 @@ +package com.badlogic.gdx.ai.utility; + +import com.badlogic.gdx.ai.GdxAI; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * This selector returns the index of the Utility whose value is highest compared to any in the supllied list + */ +public class MaxRankAndUtilitySelector implements Selector{ + + public int select(List elements) { + + float maxRank = getMaxRank(elements); + + int count = elements.size(); + if(count == 0) + return -1; + if(count == 1) + return 0; + + float maxUtil = 0f; + int selIdx = -1; + for(int i = 0; i < count; i++) { + Utility el = elements.get(i); + + //GdxAI.getLogger().info("MaxRankAndUtilitySelector", el.getClass().getSimpleName() + " combined utility is : " + el.getCombined()); + + if((el.getRank() >= maxRank) && el.getCombined() > maxUtil) { + maxUtil = el.getCombined(); + selIdx = i; + + GdxAI.getLogger().info("MaxRankAndUtilitySelector", "selected with max utility: " + el.getClass().getSimpleName()); + } + } + + + + return selIdx; + } + + private float getMaxRank(List elements) { + Utility max = Collections.max(elements, new Comparator() { + @Override + public int compare(Utility o1, Utility o2) { + if(o1.getCombined() <=0) //o1 is less than because has no weight + return -1; + + if(o2.getCombined() <=0)//o1 is more than because has other no weight + return 1; + + return o1.getRank() < o2.getRank() ? -1 : o1.getRank() == o2.getRank() ? 0 : 1; + } + }); + + //GdxAI.getLogger().info("MaxRankAndUtilitySelector", "max rank: " + max.getRank() ); + + return max.getRank(); + } +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/MaxUtilitySelector.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/MaxUtilitySelector.java new file mode 100644 index 00000000..9c79867e --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/MaxUtilitySelector.java @@ -0,0 +1,29 @@ +package com.badlogic.gdx.ai.utility; + +import java.util.List; + +/** + * This selector returns the index of the Utility whose value is highest compared to any in the supllied list + */ +public class MaxUtilitySelector implements Selector{ + + public int select(List elements) { + int count = elements.size(); + if(count == 0) + return -1; + if(count == 1) + return 0; + + float maxUtil = 0f; + int selIdx = -1; + for(int i = 0; i < count; i++) { + Utility el = elements.get(i); + if(el.getCombined() > maxUtil) { + maxUtil = el.getCombined(); + selIdx = i; + } + } + + return selIdx; + } +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/Option.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/Option.java new file mode 100644 index 00000000..21780445 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/Option.java @@ -0,0 +1,37 @@ +package com.badlogic.gdx.ai.utility; + +import com.badlogic.gdx.ai.utility.consideration.CompositeConsideration; +import com.badlogic.gdx.ai.utility.measure.WeightedMetrics; + +/** + * Created by felix on 8/6/2017. + */ +public class Option extends CompositeConsideration{ + + Action action; + + public Option(){ + init(); + } + + void init() { + setWeight(1.0f); + setMeasure(new WeightedMetrics()); + } + + public Action getAction() { + return action; + } + + public void setAction(Action action) { + this.action = action; + } + + public void consider(E context) { + if(action.isInCooldown()) { + setUtility(new Utility(0.0f, getWeight(), getRank())); + } + super.consider(context); + } + +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/Selector.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/Selector.java new file mode 100644 index 00000000..6d92961f --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/Selector.java @@ -0,0 +1,11 @@ +package com.badlogic.gdx.ai.utility; + +import java.util.List; + +/** + * Created by felix on 8/7/2017. + */ +public interface Selector { + + public int select(List elements); +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/Utility.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/Utility.java new file mode 100644 index 00000000..f9052084 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/Utility.java @@ -0,0 +1,47 @@ +package com.badlogic.gdx.ai.utility; + +/** + * Created by felix on 8/6/2017. + */ +public class Utility { + float weight; + float value; + float rank = 0f; + + public Utility(float value, float weight, float rank){ + this.weight = MathUtils.Clamp01(weight); + this.value = MathUtils.Clamp01(value); + this.rank = rank; + } + + public Utility(float value, float weight){ + this(value, weight,0f); + } + + public float getWeight() { + return MathUtils.Clamp01(weight); + } + + public void setWeight(float weight) { + this.weight = weight; + } + + public float getValue() { + return MathUtils.Clamp01(value); + } + + public void setValue(float value) { + this.value = value; + } + + public float getCombined(){ + return value * weight; + } + + public float getRank() { + return rank; + } + + + +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/UtilityAI.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/UtilityAI.java new file mode 100644 index 00000000..aedf9519 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/UtilityAI.java @@ -0,0 +1,62 @@ +package com.badlogic.gdx.ai.utility; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by felix on 8/7/2017. + */ +public class UtilityAI { + List> behaviours; + List behaviourUtilities; + Selector selector; + + public UtilityAI(){ + init(); + } + + void init() { + selector = new MaxUtilitySelector(); + // _behaviourMap = new Dictionary(); + behaviours = new ArrayList>(); + behaviourUtilities = new ArrayList(); + } + + public boolean addBehaviour(Behaviour behaviour){ + behaviours.add(behaviour); //todo dont add existing + behaviourUtilities.add(new Utility(0f,0f));//todo not sure this usage + + return true; + } + + + /** + * Selects one of the contained behaviours + * Behavior in turn selected an option, which returns the action asociated with that option + * @param context + * @return + */ + public Action select(E context){ + if(behaviours == null || behaviours.isEmpty()) + return null; + + if(behaviours.size() == 1) + return behaviours.get(0).select(context); + + updateBehaviourUtilitites(context); + return selectAction(context); + } + + void updateBehaviourUtilitites(E context) { + for(int i = 0, count = behaviours.size(); i < count; i++) { + behaviours.get(i).consider(context); + behaviourUtilities.set(i,behaviours.get(i).getUtility()); + } + } + + Action selectAction(E context) { + int idx = selector.select(behaviourUtilities); + Behaviour selectedBehaviour = idx >= 0 ? behaviours.get(idx) : null; + return selectedBehaviour.select(context); + } +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/consideration/CompositeConsideration.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/consideration/CompositeConsideration.java new file mode 100644 index 00000000..7dd178ee --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/consideration/CompositeConsideration.java @@ -0,0 +1,124 @@ +package com.badlogic.gdx.ai.utility.consideration; + +import com.badlogic.gdx.ai.utility.Utility; +import com.badlogic.gdx.ai.utility.measure.Measure; +import com.badlogic.gdx.ai.utility.measure.WeightedMetrics; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Created by felix on 8/6/2017. + */ + +/// An aggregate of considerations. Although it is possible to create fully functioning +/// AIs using only s, this class allows for such +/// considerations to be combined. This is quite useful as it can reduce the complexity +/// of individual considerations allowing them to be seen as building blocks. +public class CompositeConsideration { + List> considerations = new ArrayList>(); + List considerationUtilities = new ArrayList(); + + Utility defaultUtility = new Utility(0,1); + Utility utility; + Measure measure; + float weight = 1f; + String name; + + public CompositeConsideration(){ + init(); + } + + void init() { + weight = 1.0f; + measure = new WeightedMetrics(); + utility = new Utility(0, weight, getRank()); + } + + public void addConsideration(Consideration c){ + considerations.add(c); + considerationUtilities.add(new Utility(0,0)); + } + + public void consider(E context){ + if(considerations == null || considerations.isEmpty()) return; + + updateConsiderationUtilities(context); + float mValue = measure.calculate(considerationUtilities); + //utility = new Utility(mValue, weight, getRank()); + utility.setValue(mValue); + } + + + void updateConsiderationUtilities(E context) { + for(int i = 0, count = considerations.size(); i < count; i++) { + considerations.get(i).consider(context); + considerationUtilities.set(i,considerations.get(i).utility); + } + } + + public Utility getDefaultUtility() { + return defaultUtility; + } + + public void setDefaultUtility(Utility defaultUtility) { + this.defaultUtility = defaultUtility; + } + + public Utility getUtility() { + return utility; + } + + public void setUtility(Utility utility) { + this.utility = utility; + } + + public float getWeight() { + return weight; + } + + public void setWeight(float weight) { + this.weight = weight; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public Measure getMeasure() { + return measure; + } + + public void setMeasure(Measure measure) { + this.measure = measure; + } + + //returns max rank of all non zero considerations + public float getRank() { + return getMaxRank(considerationUtilities); + } + + private float getMaxRank(List elements) { + if(elements == null || elements.isEmpty() ) return 0; + + Utility max = Collections.max(elements, new Comparator() { + @Override + public int compare(Utility o1, Utility o2) { + if(o1.getCombined() <=0) //o1 is less than because has no weight + return -1; + + if(o2.getCombined() <=0)//o1 is more than because has other no weight + return 1; + + return o1.getRank() < o2.getRank() ? -1 : o1.getRank() == o2.getRank() ? 0 : 1; + } + }); + + return max.getRank(); + } +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/consideration/Consideration.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/consideration/Consideration.java new file mode 100644 index 00000000..38cf2522 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/consideration/Consideration.java @@ -0,0 +1,56 @@ +package com.badlogic.gdx.ai.utility.consideration; + +import com.badlogic.gdx.ai.utility.MathUtils; +import com.badlogic.gdx.ai.utility.Utility; +import com.badlogic.gdx.ai.utility.evaluator.Evaluator; + +/** + * Created by felix on 8/4/2017. + */ +public abstract class Consideration { + + float rank = 0f; + float weight = 1f; + String name; + protected Evaluator evaluator; + protected Utility utility; + + + public Consideration(String name, float rank){ + this.name = name; + this.rank = rank; + } + + + public abstract void consider(E context); + + public float getWeight() { + return weight; + } + + public void setWeight(float weight) { + this.weight = MathUtils.Clamp01(weight); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Evaluator getEvaluator() { + return evaluator; + } + + public void setEvaluator(Evaluator evaluator) { + this.evaluator = evaluator; + } + + public float getRank() { + return rank; + } + + +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/evaluator/Evaluator.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/evaluator/Evaluator.java new file mode 100644 index 00000000..20050649 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/evaluator/Evaluator.java @@ -0,0 +1,38 @@ +package com.badlogic.gdx.ai.utility.evaluator; + +import com.badlogic.gdx.math.Vector2; + +/** + * Created by felix on 8/4/2017. + */ +public abstract class Evaluator { + + float xa; + float xb; + float ya; + float yb; + + protected Evaluator() { + init(0.0f, 0.0f, 1.0f, 1.0f); + } + + protected Evaluator(Vector2 ptA, Vector2 ptB) { + init(ptA.x, ptA.y, ptB.x, ptB.y); + } + + void init(float xA, float yA, float xB, float yB) { + + //todo +// if(CrMath.AeqB(xA, xB)) +// throw new EvaluatorDxZeroException(); +// if(xA > xB) +// throw new EvaluatorXaGreaterThanXbException(); + + xa = xA; + xb = xB; + ya = Math.max(0, Math.min(yA, 1)); + yb = Math.max(0, Math.min(yB, 1)); + } + + public abstract float evaluate(float x); +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/evaluator/LinearEvaluator.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/evaluator/LinearEvaluator.java new file mode 100644 index 00000000..8ad469a5 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/evaluator/LinearEvaluator.java @@ -0,0 +1,36 @@ +package com.badlogic.gdx.ai.utility.evaluator; + +import com.badlogic.gdx.ai.utility.MathUtils; +import com.badlogic.gdx.math.Vector2; + +/** + * Created by felix on 8/4/2017. + * The LinearEvaluator returns a normalized utility value based on a linear function. + /// Power for an interactive + /// plot. + */ +public class LinearEvaluator extends Evaluator { + float dyOverDx; + + public LinearEvaluator(){ + super(); + init(); + } + + + public LinearEvaluator(Vector2 ptA, Vector2 ptB) { + super(ptA, ptB); + init(); + } + + + + void init() { + dyOverDx = (this.yb - this.ya) / (this.xb - this.xa); + } + @Override + public float evaluate(float x) { + float raw = this.ya + dyOverDx * (x - xa); + return MathUtils.Clamp01(raw); + } +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/evaluator/PowerEvaluator.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/evaluator/PowerEvaluator.java new file mode 100644 index 00000000..7fa9eb04 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/evaluator/PowerEvaluator.java @@ -0,0 +1,76 @@ +package com.badlogic.gdx.ai.utility.evaluator; + +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Vector2; + +/** + * Created by felix on 8/7/2017. + * The PowerEvaluator returns a normalized utility based on a power function that has + /// effectively 1 parameter ( 0 leq p le 10000 ) bounded by the box defined by PtA and PtB with + /// PtA.x being strictly less than PtB.x! + /// Power for an interactive + /// plot. + */ +public class PowerEvaluator extends Evaluator { + public static final float MinP = 0.0f; + public static final float MaxP = 10000f; + float dy; + float p; + + /// + /// Initializes a new instance of the class. + /// Power for an interactive + /// plot. + /// +// public PowerEvaluator() { +// p = 2.0f; +// Initialize(); +// } + + /// + /// Initializes a new instance of the class. + /// Power for an interactive + /// plot. + /// + /// Point a. + /// Point b. +// public PowerEvaluator(Vector2 ptA, Vector2 ptB) { +// p = 2.0f; +// Initialize(); +// } + /// + /// Returns the utility for the specified value x. + /// + /// The x value. + public float evaluate(float x) { + float cx = MathUtils.clamp(x,xa,xb); + cx = dy * (float)Math.pow((cx - xa) / (xb - xa), p) + ya; + return cx; + } + + + + + + /// + /// Initializes a new instance of the class. + /// Power for an interactive + /// plot. + /// + /// Point a. + /// Point b. + /// Power. + public PowerEvaluator(Vector2 ptA, Vector2 ptB, float power) { + super(ptA,ptB); + p = MathUtils.clamp(power,MinP, MaxP); + + Initialize(); + } + + void Initialize() { + dy = yb - ya; + } + + + +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/evaluator/SigmoidEvaluator.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/evaluator/SigmoidEvaluator.java new file mode 100644 index 00000000..5d3901f7 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/evaluator/SigmoidEvaluator.java @@ -0,0 +1,76 @@ +package com.badlogic.gdx.ai.utility.evaluator; + +import com.badlogic.gdx.math.Vector2; + +/** + * Created by felix on 8/6/2017. + * + * The SigmoidEvaluator returns a normalized utility based on a sigmoid function that has + /// effectively 1 parameter ( -0.99999f leq k leq 0.99999f ) bounded by the box defined by PtA and PtB with + /// PtA.x being strictly less than PtB.x! + /// Parametrized Sigmoid for an interactive + /// plot. + */ +public class SigmoidEvaluator extends Evaluator { + + float dyOverTwo; + float k; + float oneMinusK; + float twoOverDx; + float xMean; + float yMean; + + + public SigmoidEvaluator(){ + k = -0.6f; + init(); + } + + public SigmoidEvaluator(Vector2 ptA, Vector2 ptB, float k){ + super(ptA, ptB); + + if(k < minK){ + k = minK; + } + if(k > maxK){ + k = maxK; + } + this.k = k; + init(); + } + + void init() { + twoOverDx = Math.abs(2.0f / (xb - xa)); + xMean = (xa + xb) / 2.0f; + yMean = (ya + yb) / 2.0f; + dyOverTwo = (yb - ya) / 2.0f; + oneMinusK = 1.0f - k; + } + + + + @Override + public float evaluate(float x) { + + //clamp to min + if(x < xa){ + x = xa; + } + + //clamp to max + if(x > xb){ + x = xb; + } + + float cxMinusXMean = x - xMean; + float num = twoOverDx * cxMinusXMean * oneMinusK; + float den = k * (1 - 2 * Math.abs(twoOverDx * cxMinusXMean)) + 1; + float val = dyOverTwo * (num / den) + yMean; + return val; + + + } + + final float minK = -0.99999f; + final float maxK = 0.99999f; +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/measure/Chebyshev.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/measure/Chebyshev.java new file mode 100644 index 00000000..b5955b40 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/measure/Chebyshev.java @@ -0,0 +1,36 @@ +package com.badlogic.gdx.ai.utility.measure; + +import com.badlogic.gdx.ai.utility.Utility; +import com.badlogic.gdx.math.MathUtils; + +import java.util.List; + +/** + * Created by felix on 8/6/2017. + */ +public class Chebyshev implements Measure { + + public float calculate(List elements){ + float wsum = 0.0f; + int count = elements.size(); + + if(count == 0) + return 0.0f; + + for(Utility el : elements) { + wsum += el.getWeight(); + } + + if(MathUtils.isEqual(0,wsum)) + return 0.0f; + + float max = 0; + for(Utility el : elements) { + float temp = el.getValue() * (el.getWeight() / wsum); + if(temp > max){ + max = temp; + } + } + return max; + } +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/measure/Measure.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/measure/Measure.java new file mode 100644 index 00000000..3af93add --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/measure/Measure.java @@ -0,0 +1,16 @@ +package com.badlogic.gdx.ai.utility.measure; + +import com.badlogic.gdx.ai.utility.Utility; + +import java.util.List; + +/** + * Created by felix on 8/6/2017. + */ +public interface Measure { + + /// + /// Calculate the measure for the given set of elements. + /// + float calculate(List elements); +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/utility/measure/WeightedMetrics.java b/gdx-ai/src/com/badlogic/gdx/ai/utility/measure/WeightedMetrics.java new file mode 100644 index 00000000..b2e18fa1 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/utility/measure/WeightedMetrics.java @@ -0,0 +1,82 @@ +package com.badlogic.gdx.ai.utility.measure; + +import com.badlogic.gdx.ai.utility.Utility; +import com.badlogic.gdx.math.MathUtils; + +import java.util.List; + +/** + * Created by felix on 8/6/2017. + */ +/// +/// Calculates the l-p weighted metrics . +/// +/// +public class WeightedMetrics implements Measure { + + float oneOverP; + float p; + + public WeightedMetrics(){ + setP(2f); + } + + /// + /// The minimum value for . + /// + public static final float PNormMin = 1.0f; + + /// + + public float getP() { + return p; + } + + public void setP(float p) { + this.p = p; + if(p> PNormMax){ + this.p = PNormMax; + } + + if( p < PNormMin){ + this.p = PNormMin; + } + + oneOverP = 1.0f / p; + } + + /// The minimum value for . + /// + public static final float PNormMax = 10000.0f; + static final double EPSILON = 0.00001; + + @Override + public float calculate(List elements) { + int count = elements.size(); + if(count == 0) + return 0.0f; + + float wsum = 0.0f; + for(Utility utility : elements) { + wsum += utility.getWeight(); + } + + + if(MathUtils.isEqual(0,wsum)) { + return 0; + } + + + //float[] vlist = new float[count]; + float sum = 0; + for(int i = 0; i < elements.size();i++) { + Utility utility = elements.get(i); + float v = utility.getWeight() / wsum * (float)Math.pow(utility.getValue(), p); + sum += v; + } + + float res = (float)Math.pow(sum, oneOverP); + + return res; + } +} diff --git a/tests/src/com/badlogic/gdx/ai/tests/UtilityTest.java b/tests/src/com/badlogic/gdx/ai/tests/UtilityTest.java new file mode 100644 index 00000000..9d247506 --- /dev/null +++ b/tests/src/com/badlogic/gdx/ai/tests/UtilityTest.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright 2014 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.badlogic.gdx.ai.tests; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.ai.GdxAI; +import com.badlogic.gdx.ai.tests.utility.Dog; +import com.badlogic.gdx.ai.tests.utility.RestAction; +import com.badlogic.gdx.ai.tests.utility.WanderAction; +import com.badlogic.gdx.ai.tests.utils.GdxAiTestUtils; +import com.badlogic.gdx.ai.utility.*; +import com.badlogic.gdx.ai.utility.consideration.Consideration; +import com.badlogic.gdx.ai.utility.evaluator.LinearEvaluator; +import com.badlogic.gdx.ai.utility.evaluator.PowerEvaluator; +import com.badlogic.gdx.math.Vector2; + +/** A simple test to demonstrate state machines combined with message handling. + * @author davebaol */ +public class UtilityTest extends ApplicationAdapter { + + public static void main (String[] argv) { + GdxAiTestUtils.launch(new UtilityTest()); + } + + float elapsedTime; + DecisionMaker decisionMaker; + Dog dog; + + @Override + public void create () { + + elapsedTime = 0; + + + Consideration tirednessConsideration = new Consideration("TirednessConsideration", 1) { + { + init(); + } + + private void init() { + Vector2 ptA = new Vector2(0, 1); + Vector2 ptB = new Vector2(30, 0); + evaluator = new PowerEvaluator(ptA, ptB, 2f); + utility = new Utility(0,getWeight(),getRank()); + } + + @Override + public void consider(Dog dog) { + float value = evaluator.evaluate(dog.energy); + utility.setValue(value); + GdxAI.getLogger().info("tirednessConsideration", "Evaluting Energy levels " + dog.energy + " with Utilty " + value); + } + + }; + + Consideration curiousityConsideration = new Consideration("PlayConsideration", 0){ + { + init(); + } + + private void init() { + Vector2 ptA = new Vector2(0f, 0.5f); + Vector2 ptB = new Vector2(100, 0.5f); + evaluator = new LinearEvaluator(ptA, ptB); + utility = new Utility(0,getWeight(),getRank()); + } + + @Override + public void consider(Dog dog) { + float value = evaluator.evaluate(dog.curiousity); + utility.setValue(value); + + GdxAI.getLogger().info("curiousityConsideration", "Evaluting Curiousity " + dog.curiousity + " with Utilty " + value); + } + }; + + //drink option triggered by thirst consideration +// Option drinkOption = new Option(); +// drinkOption.setAction(drinkAction); +// drinkOption.addConsideration(thirstConsideration); + + + // + WanderAction playAction = new WanderAction(); + RestAction restAction = new RestAction(); + + //rest optoin triggered by tiredness consideration + Option restOption = new Option(); + restOption.setAction(restAction); + restOption.addConsideration(tirednessConsideration); + + Option playOption = new Option(); + playOption.setAction(playAction); + playOption.addConsideration(curiousityConsideration); + + Behaviour behaviour = new Behaviour("Dog"); + behaviour.addOption(restOption); + behaviour.addOption(playOption); + + UtilityAI utilityAI = new UtilityAI(); + utilityAI.addBehaviour(behaviour); + + dog = new Dog("Doggy"); + decisionMaker = new DecisionMaker(utilityAI, dog); + + + } + + @Override + public void render () { + float delta = Gdx.graphics.getDeltaTime(); + elapsedTime += delta; + + // Update time + GdxAI.getTimepiece().update(delta); + + if (elapsedTime > 0.8f) { + decisionMaker.think(); + decisionMaker.update(); + + + dog.update(elapsedTime); + + elapsedTime = 0; + } + } +} diff --git a/tests/src/com/badlogic/gdx/ai/tests/utility/Dog.java b/tests/src/com/badlogic/gdx/ai/tests/utility/Dog.java new file mode 100644 index 00000000..fa2cf610 --- /dev/null +++ b/tests/src/com/badlogic/gdx/ai/tests/utility/Dog.java @@ -0,0 +1,81 @@ +package com.badlogic.gdx.ai.tests.utility; + +import com.badlogic.gdx.ai.GdxAI; +import com.badlogic.gdx.math.MathUtils; + +/** + * Created by felix on 11/6/2017. + */ +public class Dog { + + //float hunger; + float thirst = 0f; + //float bladder; + public float energy = 100f; + public float curiousity = 0f; + public String name; + public Dog (String name) { + this.name = name; + } + + public void update(float detla) { + // Do something with the context. + energy -= 0.35f; + energy = MathUtils.clamp(energy, 0, 100); + //hunger += 0.4f; + thirst += 0.5f; + thirst = MathUtils.clamp(thirst, 0, 100); + //bladder += 0.5f; + curiousity += 0.1f; + curiousity = MathUtils.clamp(curiousity, 0, 100); +// _context.Cleanliness -= 0.3f; +// _context.Fitness -= 0.5f; + + log( "Energy:" + energy + "\nThirst:" + thirst + "\ncuriousity" + curiousity); + + } + + public float getEnergy() { + return energy; + } + + public void setEnergy(float energy) { + this.energy = energy; + } + + +// public float getHunger() { +// return hunger; +// } +// +// public void setHunger(float hunger) { +// this.hunger = hunger; //Math.max(0, Math.min(hunger, 1)); +// } + + public float getThirst() { + return thirst; + } + + public void setThirst(float thirst) { + this.thirst = thirst; //Math.max(0, Math.min(thirst, 1));; + } + + + public void startWalking () { + log("Let's find a nice tree"); + } + public boolean stillWalking () { + + if (MathUtils.random(10) > 3) { + log("MUMBLE MUMBLE - Still walking about"); + return true; + } + log("finished walking"); + return false; + } + + + public void log (String msg) { + GdxAI.getLogger().info(name, msg); + } +} diff --git a/tests/src/com/badlogic/gdx/ai/tests/utility/RestAction.java b/tests/src/com/badlogic/gdx/ai/tests/utility/RestAction.java new file mode 100644 index 00000000..7feedcc4 --- /dev/null +++ b/tests/src/com/badlogic/gdx/ai/tests/utility/RestAction.java @@ -0,0 +1,53 @@ +package com.badlogic.gdx.ai.tests.utility; + +import com.badlogic.gdx.ai.GdxAI; +import com.badlogic.gdx.ai.utility.Action; + +/** + * Created by felix on 8/4/2017. + */ +public class RestAction extends Action { + public RestAction() { + super("RestAction", 0); + } + + private float energyIncrement = 4f; + private float thirstIncrement = 4f; + + + public float timeScale = 1; + public float timeStart = 0; + + @Override + protected void onStart(Dog actor) { + timeStart = GdxAI.getTimepiece().getTime(); + + takeANap(actor); + } + + @Override + protected void onUpdate(Dog dog) { + if(dog.energy > 90) { + endInSuccess(dog); + } + + takeANap(dog); + } + + @Override + protected void onStop(Dog context) { + GdxAI.getLogger().info(getName(),"RestAction ran for " + (GdxAI.getTimepiece().getTime() - timeStart) + "seconds" ); + } + + private void takeANap(Dog actor) { + + float enegery = actor.energy; + //float thirst = actor.getThirst(); + + GdxAI.getLogger().info(getName(),"Resting... Energy before " + enegery ); + + actor.energy = enegery + energyIncrement; + + GdxAI.getLogger().info(getName(),"Finish Resting... Energy after " + actor.energy ); + } +} diff --git a/tests/src/com/badlogic/gdx/ai/tests/utility/WanderAction.java b/tests/src/com/badlogic/gdx/ai/tests/utility/WanderAction.java new file mode 100644 index 00000000..4103c080 --- /dev/null +++ b/tests/src/com/badlogic/gdx/ai/tests/utility/WanderAction.java @@ -0,0 +1,52 @@ +package com.badlogic.gdx.ai.tests.utility; + +import com.badlogic.gdx.ai.GdxAI; +import com.badlogic.gdx.ai.utility.Action; + +/** + * Created by felix on 8/4/2017. + */ +public class WanderAction extends Action { + + public WanderAction() { + super("WanderAction", 0); + } + private float curiousityDelta = 1f; + private float energyDelta = 5f; + + float timeStart = 0; + + @Override + protected void onStart(Dog actor) { + timeStart = GdxAI.getTimepiece().getTime(); + actor.startWalking(); + + } + + @Override + protected void onUpdate(Dog dog) { + GdxAI.getLogger().info(getName(),"+++ WanderAction update..."); + + if (dog.stillWalking()) { + //dog.log("Walking ..."); + + float curiousity = dog.curiousity; + float energy = dog.energy; + + dog.curiousity -= curiousityDelta; + dog.energy -= energyDelta; + + GdxAI.getLogger().info(getName(),"Wander... Curoiusity before " + curiousity + " , after " + dog.curiousity); + GdxAI.getLogger().info(getName(),"Wander... Energy before " + energy + " , after " + dog.energy); + + } else { + endInSuccess(dog); + } + + } + + @Override + protected void onStop(Dog context) { + GdxAI.getLogger().info(getName(),"WanderAction ran for " + (GdxAI.getTimepiece().getTime() - timeStart) + "seconds" ); + } +}