aima.core.search.adversarial.IterativeDeepeningAlphaBetaSearch Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aima-core Show documentation
Show all versions of aima-core Show documentation
AIMA-Java Core Algorithms from the book Artificial Intelligence a Modern Approach 3rd Ed.
package aima.core.search.adversarial;
import java.util.ArrayList;
import java.util.List;
import aima.core.search.framework.Metrics;
/**
* Implements an iterative deepening Minimax search with alpha-beta pruning and
* action ordering. Maximal computation time is specified in seconds. The
* algorithm is implemented as template method and can be configured and tuned
* by subclassing.
*
* @author Ruediger Lunde
*
* @param
* Type which is used for states in the game.
* @param
* Type which is used for actions in the game.
* @param
* Type which is used for players in the game.
*/
public class IterativeDeepeningAlphaBetaSearch
implements AdversarialSearch {
protected Game game;
protected double utilMax;
protected double utilMin;
protected int currDepthLimit;
private boolean maxDepthReached;
private long maxTime;
private boolean logEnabled;
private int expandedNodes;
private int maxDepth;
/** Creates a new search object for a given game. */
public static IterativeDeepeningAlphaBetaSearch createFor(
Game game, double utilMin, double utilMax,
int time) {
return new IterativeDeepeningAlphaBetaSearch(
game, utilMin, utilMax, time);
}
public IterativeDeepeningAlphaBetaSearch(Game game,
double utilMin, double utilMax, int time) {
this.game = game;
this.utilMin = utilMin;
this.utilMax = utilMax;
this.maxTime = time * 1000; // internal: ms instead of s
}
public void setLogEnabled(boolean b) {
logEnabled = b;
}
/**
* Template method controlling the search.
*/
@Override
public ACTION makeDecision(STATE state) {
List results = null;
double resultValue = Double.NEGATIVE_INFINITY;
PLAYER player = game.getPlayer(state);
StringBuffer logText = null;
expandedNodes = 0;
maxDepth = 0;
currDepthLimit = 0;
long startTime = System.currentTimeMillis();
boolean exit = false;
do {
incrementDepthLimit();
maxDepthReached = false;
List newResults = new ArrayList();
double newResultValue = Double.NEGATIVE_INFINITY;
double secondBestValue = Double.NEGATIVE_INFINITY;
if (logEnabled)
logText = new StringBuffer("depth " + currDepthLimit + ": ");
for (ACTION action : orderActions(state, game.getActions(state),
player, 0)) {
if (results != null
&& System.currentTimeMillis() > startTime + maxTime) {
exit = true;
break;
}
double value = minValue(game.getResult(state, action), player,
Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 1);
if (logEnabled)
logText.append(action + "->" + value + " ");
if (value >= newResultValue) {
if (value > newResultValue) {
secondBestValue = newResultValue;
newResultValue = value;
newResults.clear();
}
newResults.add(action);
} else if (value > secondBestValue) {
secondBestValue = value;
}
}
if (logEnabled)
System.out.println(logText);
if (!exit || isSignificantlyBetter(newResultValue, resultValue)) {
results = newResults;
resultValue = newResultValue;
}
if (!exit && results.size() == 1
&& this.isSignificantlyBetter(resultValue, secondBestValue))
break;
} while (!exit && maxDepthReached && !hasSafeWinner(resultValue));
return results.get(0);
}
public double maxValue(STATE state, PLAYER player, double alpha,
double beta, int depth) { // returns an utility value
expandedNodes++;
maxDepth = Math.max(maxDepth, depth);
if (game.isTerminal(state) || depth >= currDepthLimit) {
return eval(state, player);
} else {
double value = Double.NEGATIVE_INFINITY;
for (ACTION action : orderActions(state, game.getActions(state),
player, depth)) {
value = Math.max(value, minValue(game.getResult(state, action), //
player, alpha, beta, depth + 1));
if (value >= beta)
return value;
alpha = Math.max(alpha, value);
}
return value;
}
}
public double minValue(STATE state, PLAYER player, double alpha,
double beta, int depth) { // returns an utility
expandedNodes++;
maxDepth = Math.max(maxDepth, depth);
if (game.isTerminal(state) || depth >= currDepthLimit) {
return eval(state, player);
} else {
double value = Double.POSITIVE_INFINITY;
for (ACTION action : orderActions(state, game.getActions(state),
player, depth)) {
value = Math.min(value, maxValue(game.getResult(state, action), //
player, alpha, beta, depth + 1));
if (value <= alpha)
return value;
beta = Math.min(beta, value);
}
return value;
}
}
/** Returns some statistic data from the last search. */
@Override
public Metrics getMetrics() {
Metrics result = new Metrics();
result.set("expandedNodes", expandedNodes);
result.set("maxDepth", maxDepth);
return result;
}
/**
* Primitive operation which is called at the beginning of one depth limited
* search step. This implementation increments the current depth limit by
* one.
*/
protected void incrementDepthLimit() {
currDepthLimit++;
}
/**
* Primitive operation which is used to stop iterative deepening search in
* situations where a clear best action exists. This implementation returns
* always false.
*/
protected boolean isSignificantlyBetter(double newUtility, double utility) {
return false;
}
/**
* Primitive operation which is used to stop iterative deepening search in
* situations where a safe winner has been identified. This implementation
* returns true if the given value (for the currently preferred action
* result) is the highest or lowest utility value possible.
*/
protected boolean hasSafeWinner(double resultUtility) {
return resultUtility <= utilMin || resultUtility >= utilMax;
}
/**
* Primitive operation, which estimates the value for (not necessarily
* terminal) states. This implementation returns the utility value for
* terminal states and (utilMin + utilMax) / 2
for non-terminal
* states.
*/
protected double eval(STATE state, PLAYER player) {
if (game.isTerminal(state)) {
return game.getUtility(state, player);
} else {
maxDepthReached = true;
return (utilMin + utilMax) / 2;
}
}
/**
* Primitive operation for action ordering. This implementation preserves
* the original order (provided by the game).
*/
public List orderActions(STATE state, List actions,
PLAYER player, int depth) {
return actions;
}
}