All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fathzer.games.ai.Negamax Maven / Gradle / Ivy

The newest version!
package com.fathzer.games.ai;

import java.util.List;

import com.fathzer.games.MoveGenerator;
import com.fathzer.games.MoveGenerator.MoveConfidence;
import com.fathzer.games.Status;
import com.fathzer.games.HashProvider;
import com.fathzer.games.ai.evaluation.EvaluatedMove;
import com.fathzer.games.ai.evaluation.Evaluator;
import com.fathzer.games.ai.evaluation.QuiesceEvaluator;
import com.fathzer.games.ai.transposition.AlphaBetaState;
import com.fathzer.games.ai.transposition.EntryType;
import com.fathzer.games.ai.transposition.TTAi;
import com.fathzer.games.ai.transposition.TranspositionTable;
import com.fathzer.games.ai.transposition.TranspositionTableEntry;
import com.fathzer.games.util.exec.ExecutionContext;

/**
 * A Negamax with alpha beta pruning implementation and transposition table usage.
 * @param  The type of the moves
 * @param  The type of the {@link MoveGenerator} to use
 */
public class Negamax> extends AbstractAI implements TTAi {
    private TranspositionTable transpositionTable;
    private QuiesceEvaluator quiesceEvaluator;
    
	/** Constructor
	 * @param exec The execution context
	 */
	public Negamax(ExecutionContext> exec) {
		super(exec);
		quiesceEvaluator = (ctx, depth, alpha, beta) -> {
			final SearchContext context = getContext();
			context.getStatistics().evaluationDone();
			return context.getEvaluator().evaluate(context.getGamePosition());
		};
	}

	@Override
    public SearchResult getBestMoves(DepthFirstSearchParameters params) {
		final SearchResult result = super.getBestMoves(params);
		// Warning, result can be empty if searching position with no possible moves
		if ((getContext().getGamePosition() instanceof HashProvider hp) && transpositionTable!=null && !isInterrupted() && !result.getList().isEmpty()) {
			// Store best move info in table
			final EvaluatedMove best = result.getList().get(0);
			transpositionTable.store(hp.getHashKey(), EntryType.EXACT, params.getDepth(), best.getScore(), best.getMove(), p->true);
		}
		return result;
    }

	@Override
	protected int getRootScore(final int depth, int lowestInterestingScore) {
		return -negamax(depth-1, depth, -Integer.MAX_VALUE, -lowestInterestingScore);
	}
	
	/** Gets the evaluation of the position after quiescence search.
	 * 
The default implementation returns the quiesce policy result. * @param depth The depth (number of half moves) at which the method is called (it is useful to return correct mate scores) * @param alpha Alpha value after normal search performed by {@link #negamax(int, int, int, int)} method. * @param beta Beta value after normal search performed by {@link #negamax(int, int, int, int)} method. * @return the node evaluation * @see #setQuiesceEvaluator(QuiesceEvaluator) */ protected int quiesce(int depth, int alpha, int beta) { return quiesceEvaluator.evaluate(getContext(), depth, alpha, beta); } /** * Performs a recursive search. * @param depth The depth (number of half moves) at which the method is called (it is useful to return correct mate scores) * @param maxDepth The maximum depth of the search * @param alpha The alpha value * @param beta The beta value * @return the position evaluation */ protected int negamax(final int depth, int maxDepth, int alpha, int beta) { final SearchContext context = getContext(); final B position = context.getGamePosition(); final Evaluator evaluator = context.getEvaluator(); final Status fastAnalysisStatus = position.getContextualStatus(); if (fastAnalysisStatus!=Status.PLAYING) { return getScore(evaluator, fastAnalysisStatus, depth, maxDepth); } final boolean keyProvider = (position instanceof HashProvider) && transpositionTable!=null; final long key; final AlphaBetaState state; if (keyProvider) { key = ((HashProvider)position).getHashKey(); TranspositionTableEntry entry = transpositionTable.get(key); state = transpositionTable.getPolicy().accept(entry, depth, alpha, beta, v -> ttToScore(v, depth, maxDepth, evaluator)); if (state.isValueSet()) { return state.getValue(); } else if (state.isAlphaBetaUpdated()) { alpha = state.getAlphaUpdated(); beta = state.getBetaUpdated(); } } else { key = 0; state = null; } if (depth == 0 || isInterrupted()) { return quiesce(maxDepth, alpha, beta); } int value = Integer.MIN_VALUE; M bestMove = null; boolean noValidMove = true; final M moveFromTT = state!=null ? state.getBestMove() : null; boolean moveFromTTBreaks = false; if (moveFromTT!=null && getContext().makeMove(moveFromTT, MoveConfidence.UNSAFE)) { // Try move from TT noValidMove = false; getStatistics().moveFromTTPlayed(); final int score = -negamax(depth-1, maxDepth, -beta, -alpha); getContext().unmakeMove(); if (score > value) { value = score; bestMove = moveFromTT; if (score > alpha) { alpha = score; if (score >= beta) { moveFromTTBreaks = true; } } } } if (!moveFromTTBreaks) { final List moves = position.getMoves(); getStatistics().movesGenerated(moves.size()); for (M move : moves) { if (!move.equals(moveFromTT) && getContext().makeMove(move, MoveConfidence.PSEUDO_LEGAL)) { noValidMove = false; getStatistics().movePlayed(); final int score = -negamax(depth-1, maxDepth, -beta, -alpha); getContext().unmakeMove(); if (score > value) { value = score; bestMove = move; if (score > alpha) { alpha = score; if (score >= beta) { break; } } } } } if (noValidMove) { // Player can't move it's a draw or a loose value = getScore(evaluator, position.getEndGameStatus(), depth, maxDepth); if (value>alpha) { alpha = value; } } } if (keyProvider && !isInterrupted()) { // If a transposition table is available state.setValue(value); state.updateAlphaBeta(alpha, beta); state.setBestMove(bestMove); transpositionTable.getPolicy().store(transpositionTable, key, state, v -> scoreToTT(v, depth, maxDepth, evaluator)); } return value; } @Override public final TranspositionTable getTranspositionTable() { return transpositionTable; } @Override public void setTranspositonTable(TranspositionTable table) { this.transpositionTable = table; } /** Gets the quiesce evaluator used to evaluate positions (see quiescence search). *
The default implementation simply returns the current position evaluation without performing any quiescence search. * @return The quiesce evaluator. */ public QuiesceEvaluator getQuiesceEvaluator() { return quiesceEvaluator; } /** Sets the quiesce evaluator used to evaluate positions (see quiescence search). *
The default implementation simply returns the current position evaluation without performing any quiescence search. * @param quiesceEvaluator The new quiesce evaluator. */ public void setQuiesceEvaluator(QuiesceEvaluator quiesceEvaluator) { this.quiesceEvaluator = quiesceEvaluator; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy