
com.fathzer.games.ai.iterativedeepening.IterativeDeepeningEngine 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.iterativedeepening;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import com.fathzer.games.MoveGenerator;
import com.fathzer.games.ai.Negamax;
import com.fathzer.games.ai.SearchContext;
import com.fathzer.games.ai.SearchResult;
import com.fathzer.games.ai.SearchStatistics;
import com.fathzer.games.ai.evaluation.EvaluatedMove;
import com.fathzer.games.ai.evaluation.Evaluator;
import com.fathzer.games.ai.transposition.TTAi;
import com.fathzer.games.ai.transposition.TranspositionTable;
import com.fathzer.games.movelibrary.MoveLibrary;
import com.fathzer.games.util.exec.ExecutionContext;
/** An engine that iteratively deepens the search.
* @param The class that represents a move
* @param The class that represents the move generator
*/
public class IterativeDeepeningEngine> {
/** A class that logs events during search at a specific level.
*
By default, a logger does nothing.
* @param The class that represents a move
*/
public interface SearchEventLogger {
/** Called when the search ends at a specified depth.
*
Does nothing by default.
* @param depth The depth at which the search was done
* @param stats The search statistics
* @param results The search results
*/
default void logSearchAtDepth(int depth, SearchStatistics stats, SearchResult results) {
// Does nothing by default
}
/** Called when the search is interrupted by timeout.
*
Does nothing by default.
* @param depth The depth at which the search was interrupted
*/
default void logTimeOut(int depth) {
// Does nothing by default
}
/** Called when the search is interrupted by the {@link DeepeningPolicy}.
*
Does nothing by default.
* @param depth The depth at which the search was interrupted
*/
default void logEndedByPolicy(int depth) {
// Does nothing by default
}
}
/** A class that logs events during the search.
*
By default, a logger does nothing.
* @param The class that represents a move
* @param The class that represents the move generator
*/
public interface EngineEventLogger> extends SearchEventLogger {
/** Called when the move is choosen from the library.
* @param board The board
* @param move The move
*/
default void logLibraryMove(B board, EvaluatedMove move) {
// Does nothing by default
}
/** Called when the search starts (at the beginning of {@link #doSearch(MoveGenerator, List)}).
* @param board The board
* @param engine The engine
*/
default void logSearchStart(B board, IterativeDeepeningEngine engine) {
// Does nothing by default
}
/** Called when the search ends (at the end of {@link #doSearch(MoveGenerator, List)}).
* @param board The board
* @param result The result
*/
default void logSearchEnd(B board, SearchHistory result) {
// Does nothing by default
}
}
/** A logger that logs nothing.
* @param The class that represents a move
* @param The class that represents the move generator
*/
public static final class Mute> implements EngineEventLogger {}
private MoveLibrary movesLibrary;
private Supplier> evaluatorSupplier;
private DeepeningPolicy deepeningPolicy;
private TranspositionTable transpositionTable;
private int parallelism;
private EngineEventLogger logger;
private IterativeDeepeningSearch rs;
private AtomicBoolean running;
/** Constructor
*
By default, the parallelism of the search is 1, the event logger logs nothing and the engine select randomly a move in the best move list.
* @param deepeningPolicy The policy to decide what move to deepen, how to merge results at different depth, etc...
* @param tt A transposition table used across different depth
* @param evaluatorSupplier An evaluation function supplier
* @see #setParallelism(int)
* @see #setLogger(EngineEventLogger)
*/
public IterativeDeepeningEngine(DeepeningPolicy deepeningPolicy, TranspositionTable tt, Supplier> evaluatorSupplier) {
this.parallelism = 1;
this.transpositionTable = tt;
this.evaluatorSupplier = evaluatorSupplier;
this.running = new AtomicBoolean();
this.logger = new Mute<>();
this.deepeningPolicy = deepeningPolicy;
}
/** Interrupts the current search if any.
*
Does nothing if there's no search running.
*/
public void interrupt() {
if (running.get()) {
rs.interrupt();
}
}
/** Gets the number of threads used to perform the searches.
* @return the number of threads used to perform the search (default is 1)
*/
public int getParallelism() {
return parallelism;
}
/** Sets how many threads are used to perform the searches.
*
Calling this method while performing a search may have unpredictable results
* @param parallelism The number of threads used to perform the search (default is 1)
*/
public void setParallelism(int parallelism) {
this.parallelism = parallelism;
}
/** Sets a move library (typically an openings library) of this engine.
* @param library The opening library or null, the default value, to play without such library.
*
An openings library is a function that should return null if the library does not known what to play here.
*/
public void setOpenings(MoveLibrary library) {
this.movesLibrary = library;
}
/** Gets the supplier of the evaluation function.
* @return the supplier of the evaluation function
*/
public Supplier> getEvaluationSupplier() {
return this.evaluatorSupplier;
}
/** Sets the supplier of the evaluation function.
* @param evaluatorSupplier The supplier of the evaluation function
*/
public void setEvaluatorSupplier(Supplier> evaluatorSupplier) {
this.evaluatorSupplier = evaluatorSupplier;
}
/** Gets the logger.
* @return the logger (The default one does nothing - see {@link Mute})
*/
public EngineEventLogger getLogger() {
return logger;
}
/** Sets the logger.
* @param logger The new logger (The default one does nothing)
*/
public void setLogger(EngineEventLogger logger) {
this.logger = logger;
}
/** Gets the transposition table.
* @return the transposition table or null, the default value, if no transposition table is set
*/
public TranspositionTable getTranspositionTable() {
return transpositionTable;
}
/** Sets the transposition table.
* @param transpositionTable The transposition table or null to use no transposition table (not recommended)
*/
public void setTranspositionTable(TranspositionTable transpositionTable) {
this.transpositionTable = transpositionTable;
}
/** Gets the deepening policy.
* @return the deepening policy
*/
public DeepeningPolicy getDeepeningPolicy() {
return deepeningPolicy;
}
/** Sets the deepening policy.
* @param policy The new deepening policy
*/
public void setDeepeningPolicy(DeepeningPolicy policy) {
this.deepeningPolicy = policy;
}
/** Should be called when a new game is started.
*
The default implementation call the {@link MoveLibrary#newGame} and {@link TranspositionTable#newGame} methods
*/
public void newGame() {
if (movesLibrary!=null) {
movesLibrary.newGame();
}
if (transpositionTable!=null) {
transpositionTable.newGame();
}
}
/** Searches for the best moves.
*
By default, this method will return the moves from the library if any, otherwise the result of the {@link #doSearch(MoveGenerator, List)} method.
* @param board The board
* @param searchedMoves A restricted list of moves to search, null to search all possible moves
* @return A search history
*/
public SearchHistory getBestMoves(B board, List searchedMoves) {
final SearchHistory result = new SearchHistory<>(deepeningPolicy);
//TODO Filter library with candidates + return more than one move if search params requires more than one move
EvaluatedMove move = movesLibrary==null ? null : movesLibrary.apply(board).orElse(null);
if (move!=null) {
logger.logLibraryMove(board, move);
result.add(Collections.singletonList(move), 0);
return result;
}
final IterativeDeepeningSearch search = doSearch(board, searchedMoves);
return search.getSearchHistory();
}
/** Searches for the best moves.
*
Calls {@link #getBestMoves(MoveGenerator, List)} with null as second parameter.
* @param board The board
* @return A search history
*/
public SearchHistory getBestMoves(B board) {
return getBestMoves(board, null);
}
/** Performs the iterative deepening search.
* @param board The board
* @param searchedMoves A restricted list of moves to search, null to search all possible moves
* @return A search history
* @throws IllegalStateException If a search is already running
*/
protected IterativeDeepeningSearch doSearch(B board, List searchedMoves) {
if (!running.compareAndSet(false, true)) {
throw new IllegalStateException();
}
try {
logger.logSearchStart(board, this);
if (transpositionTable!=null) {
transpositionTable.newPosition(board);
}
try (ExecutionContext> context = buildExecutionContext(board)) {
final TTAi internal = buildAI(context);
internal.setTranspositonTable(transpositionTable);
rs = new IterativeDeepeningSearch<>(internal, deepeningPolicy);
rs.setEventLogger(logger);
rs.setSearchedMoves(searchedMoves);
logger.logSearchEnd(board, rs.getSearchHistory());
return rs;
}
} finally {
running.set(false);
}
}
/** Builds the execution context used for a search.
*
The default implementation builds a new execution context with the given board and evaluator supplier using {@link #getParallelism()} threads.
* @param board The board to search
* @return The execution context
*/
protected ExecutionContext> buildExecutionContext(B board) {
final SearchContext context = SearchContext.get(board, evaluatorSupplier);
return ExecutionContext.get(getParallelism(), context);
}
/** Builds the AI used to search best moves at different depth.
* @param context An execution context that can be used by the AI.
* @return An AI that supports transposition tables. THe default implementation returns a Negamax instance.
*/
protected TTAi buildAI(ExecutionContext> context) {
return new Negamax<>(context);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy