
com.fathzer.games.ai.AbstractAI Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of games-core Show documentation
Show all versions of games-core Show documentation
A core library to help implement two players games.
The newest version!
package com.fathzer.games.ai;
import java.util.List;
import java.util.function.BiFunction;
import com.fathzer.games.MoveGenerator;
import com.fathzer.games.MoveGenerator.MoveConfidence;
import com.fathzer.games.Status;
import com.fathzer.games.ai.evaluation.Evaluator;
import com.fathzer.games.util.exec.ExecutionContext;
import com.fathzer.games.util.exec.Interruptible;
/** An abstract {@link DepthFirstAI} implementation.
* @param Implementation of the Move interface to use
* @param Implementation of the MoveGenerator interface to use
*/
public abstract class AbstractAI> implements DepthFirstAI, Interruptible {
private final ExecutionContext> context;
private boolean interrupted;
/** Constructor
* @param context The context to use for the search
*/
protected AbstractAI(ExecutionContext> context) {
this.context = context;
this.interrupted = false;
}
@Override
public SearchStatistics getStatistics() {
return context.getContext().getStatistics();
}
/**
* Gets the context used for the search.
* @return the context
*/
public SearchContext getContext() {
return context.getContext();
}
@Override
public SearchResult getBestMoves(DepthFirstSearchParameters params) {
getContext().getStatistics().clear();
List moves = getContext().getGamePosition().getMoves();
getStatistics().movesGenerated(moves.size());
return this.getBestMoves(moves, params);
}
@Override
public SearchResult getBestMoves(List moves, DepthFirstSearchParameters params) {
return getBestMoves(moves, params, (m,lowestInterestingScore)->rootEvaluation(m,params.getDepth(), lowestInterestingScore));
}
/**
* Evaluates a root move of the search tree.
* @param move The move to evaluate
* @param depth The depth of the search
* @param lowestInterestingScore The lowest interesting score under which the evaluation is not interesting (typically this can be used to cut the tree when this evaluation can't be reached)
* @return The score of the move (the score is computed by the {@link #getRootScore(int, int)} method), or null if the move is not valid
*/
protected Integer rootEvaluation(M move, final int depth, int lowestInterestingScore) {
if (lowestInterestingScore==Integer.MIN_VALUE) {
// WARNING: -Integer.MIN_VALUE is equals to ... Integer.MIN_VALUE
// So using it as alpha value makes negamax fail
lowestInterestingScore += 1;
}
if (context.getContext().makeMove(move, MoveConfidence.UNSAFE)) {
getStatistics().movePlayed();
final int score = getRootScore(depth, lowestInterestingScore);
context.getContext().unmakeMove();
return score;
} else {
return null;
}
}
/**
* Gets the score of a root move.
* @param depth The depth of the search
* @param lowestInterestingScore The lowest interesting score under which the evaluation is not interesting (typically this can be used to cut the tree when this evaluation can't be reached)
* @return The score of the move
*/
protected abstract int getRootScore(final int depth, int lowestInterestingScore);
/**
* Performs a search on a list of moves.
*
It is called by the {@link #getBestMoves(List, DepthFirstSearchParameters)} method and uses the execution context to process the moves (see {@link ExecutionContext#execute(Collection)}).
* @param moves The moves to evaluate
* @param params The parameters of the search
* @param rootEvaluator A function that evaluates the root moves
* @return The search result
*/
protected SearchResult getBestMoves(List moves, DepthFirstSearchParameters params, BiFunction rootEvaluator) {
final SearchResult search = new SearchResult<>(params);
context.execute(moves.stream().map(m -> getRootEvaluationTask(rootEvaluator, search, m)).toList());
return search;
}
private Runnable getRootEvaluationTask(BiFunction rootEvaluator, final SearchResult search, M m) {
return () -> {
final Integer score = rootEvaluator.apply(m, search.getLow());
if (!isInterrupted() && score!=null) {
// Do not return interrupted evaluations, they are false
search.add(m, getContext().getEvaluator().toEvaluation(score));
}
};
}
@Override
public boolean isInterrupted() {
return interrupted;
}
@Override
public void interrupt() {
interrupted = true;
}
/**
* Gets the score when the game ended during the search (for instance, for chess, when the last move played during the search is a mate).
* @param evaluator The evaluator used (returned by {@link SearchContext#getEvaluator()})
* @param status The status of the game
* @param depth The current search depth
* @param maxDepth The maximum depth of the search
* @return The score. The default implementation returns 0 for a draw, otherwise it considers the player loose and returns -{@link Evaluator#getWinScore(int)}.
*/
protected int getScore(final Evaluator evaluator, final Status status, final int depth, int maxDepth) {
if (Status.DRAW==status) {
return 0;
} else {
return -evaluator.getWinScore(maxDepth-depth);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy