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

com.fathzer.games.ai.experimental.Negamax3 Maven / Gradle / Ivy

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

import java.util.List;

import static com.fathzer.games.ai.experimental.Spy.Event.*;

import com.fathzer.games.Status;
import com.fathzer.games.MoveGenerator.MoveConfidence;
import com.fathzer.games.HashProvider;
import com.fathzer.games.MoveGenerator;
import com.fathzer.games.ai.Negamax;
import com.fathzer.games.ai.SearchContext;
import com.fathzer.games.ai.DepthFirstSearchParameters;
import com.fathzer.games.ai.SearchResult;
import com.fathzer.games.ai.evaluation.EvaluatedMove;
import com.fathzer.games.ai.evaluation.Evaluator;
import com.fathzer.games.ai.transposition.AlphaBetaState;
import com.fathzer.games.ai.transposition.EntryType;
import com.fathzer.games.ai.transposition.TranspositionTableEntry;
import com.fathzer.games.util.exec.ExecutionContext;

/**
 * An experimental of a Negamax with alpha beta pruning implementation and transposition table.
 * 
It has the same functionalities than {@link Negamax} except it allows easier search debugging at the price of a (little) performance loss. * @param The type of the moves * @param The type of the {@link MoveGenerator} to use */ public class Negamax3> extends Negamax { private Spy spy = new Spy() {}; public Negamax3(ExecutionContext> exec) { super(exec); } @Override public SearchResult getBestMoves(DepthFirstSearchParameters params) { SearchResult result = super.getBestMoves(params); final B gamePosition = getContext().getGamePosition(); if ((gamePosition instanceof HashProvider hp) && getTranspositionTable()!=null && !isInterrupted()) { // Store best move info in table final EvaluatedMove best = result.getList().get(0); getTranspositionTable().store(hp.getHashKey(), EntryType.EXACT, params.getDepth(), best.getScore(), best.getMove(), p->true); } return result; } @Override 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; } final TreeSearchStateStack stack = new TreeSearchStateStack<>(getContext(), depth); stack.init(stack.getCurrent(), lowestInterestingScore, Integer.MAX_VALUE); if (stack.makeMove(move, MoveConfidence.UNSAFE)) { getStatistics().movePlayed(); final int score = -negamax(stack); stack.unmakeMove(); spy.moveUnmade(stack, null, move); return score; } else { return null; } } @Override protected int negamax(final int depth, int maxDepth, int alpha, int beta) { throw new IllegalStateException("Should not be called anymore"); } protected int negamax(TreeSearchStateStack searchStack) { try { spy.enter(searchStack); final TreeSearchState searchState = searchStack.getCurrent(); final Evaluator evaluator = searchStack.context.getEvaluator(); final Status fastAnalysisStatus = searchStack.context.getGamePosition().getContextualStatus(); if (fastAnalysisStatus!=Status.PLAYING) { spy.exit(searchStack, END_GAME); return getScore(evaluator, fastAnalysisStatus, searchState.depth, searchStack.maxDepth); } final boolean keyProvider = (searchStack.context instanceof HashProvider) && getTranspositionTable()!=null; final long key; final AlphaBetaState state; if (keyProvider) { key = ((HashProvider)searchStack.context).getHashKey(); TranspositionTableEntry entry = getTranspositionTable().get(key); state = getTranspositionTable().getPolicy().accept(entry, searchState.depth, searchState.alphaOrigin, searchState.betaOrigin, v -> ttToScore(v, searchState.depth, searchStack.maxDepth, evaluator)); if (state.isValueSet()) { searchState.value = state.getValue(); spy.exit(searchStack, TT); return state.getValue(); } else if (state.isAlphaBetaUpdated()) { spy.alphaBetaFromTT(searchStack, state); searchState.alpha = state.getAlphaUpdated(); searchState.beta = state.getBetaUpdated(); } } else { key = 0; state = null; } if (searchState.depth == 0 || isInterrupted()) { getStatistics().evaluationDone(); searchState.value = quiesce(searchStack.maxDepth, searchState.alpha, searchState.beta); spy.exit(searchStack, EVAL); return searchState.value; } boolean noValidMove = true; final M moveFromTT = state!=null ? state.getBestMove() : null; // Try move from TT boolean moveFromTTBreaks = false; if (moveFromTT!=null && searchStack.makeMove(moveFromTT, MoveConfidence.UNSAFE)) { noValidMove = false; getStatistics().moveFromTTPlayed(); if (onValidMove(searchStack, null, moveFromTT)) { moveFromTTBreaks = true; } } if (!moveFromTTBreaks) { // Try other moves final List moves = searchStack.context.getGamePosition().getMoves(); spy.movesComputed(searchStack, moves); getStatistics().movesGenerated(moves.size()); for (M move : moves) { if (searchStack.makeMove(move, MoveConfidence.PSEUDO_LEGAL)) { noValidMove = false; getStatistics().movePlayed(); if (onValidMove(searchStack, moves, move)) { break; } } } if (noValidMove) { // Player can't move it's a draw or a loose //TODO Maybe there's some games where the player wins if it can't move... searchState.value = getScore(evaluator, searchStack.context.getGamePosition().getEndGameStatus(), searchState.depth, searchStack.maxDepth); if (searchState.value > searchState.alpha) { searchState.alpha = searchState.value; } } } if (keyProvider && !isInterrupted()) { // If a transposition table is available state.setValue(searchState.value); state.updateAlphaBeta(searchState.alpha, searchState.beta); state.setBestMove(searchState.bestMove); boolean store = getTranspositionTable().getPolicy().store(getTranspositionTable(), key, state, v -> scoreToTT(v, searchState.depth, searchStack.maxDepth, evaluator)); spy.storeTT(searchStack, state, store); } if (noValidMove) { spy.exit(searchStack, END_GAME); } else { spy.exit(searchStack, EXIT); } return searchState.value; } catch (RuntimeException e) { spy.exception(searchStack, e); throw e; } } private boolean onValidMove(TreeSearchStateStack searchStack, List moves, M move) { final int score = -negamax(searchStack); searchStack.unmakeMove(); spy.moveUnmade(searchStack, moves, move); final TreeSearchState searchState = searchStack.getCurrent(); if (score > searchState.value) { searchState.value = score; searchState.bestMove = move; if (score > searchState.alpha) { searchState.alpha = score; if (score >= searchState.beta) { spy.cut(searchStack, move); return true; } } } return false; } public void setSpy(Spy spy) { this.spy = spy; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy