コード例 #1
0
int AspirationSearch::alphaBeta(GameState& gameState, int depth, int alpha, int beta)
{
#ifdef GATHER_STATISTICS
	++nodesVisited;
#endif // GATHER_STATISTICS

	int originalAlpha = alpha;
	uint64_t zobrist = gameState.getZobrist();
	const TableData& tableData = transpositionTable.retrieve(zobrist);
	// true iff relevant data was retrieved from the Transposition Table
	bool tableDataValid = tableData.isValid();

#ifdef VERIFY_MOVE_LEGALITY
	if(tableDataValid && !gameState.isMoveLegal(tableData.bestMove))
	{
		LOG_ERROR("ERROR: table data contains invalid move in AspirationSearch::alphaBetaTT")
		tableDataValid = false;
	}
#endif

	if(tableDataValid)
	{
		if(tableData.depth >= depth)	// ensure table stored in data resulted from a deep enough search
		{
			if(tableData.valueType == EValue::Type::REAL)
			{
				return tableData.value;
			}
			else if(tableData.valueType == EValue::Type::LOWER_BOUND)
			{
				alpha = std::max(alpha, tableData.value);
			}
			else if(tableData.valueType == EValue::Type::UPPER_BOUND)
			{
				beta = std::min(beta, tableData.value);
			}

			if(alpha >= beta)
			{
				return tableData.value;
			}
		}
	}

	EPlayerColors::Type winner = gameState.getWinner();

	// stop search if we reached max depth or have found a winner
	if(depth == 0 || winner != EPlayerColors::Type::NOTHING)
	{
		return evaluate(gameState, winner);
	}

	EPlayerColors::Type currentPlayer = gameState.getCurrentPlayer();
	Move transpositionMove = (tableDataValid) ? tableData.bestMove : INVALID_MOVE;

	Move killerMove1 = INVALID_MOVE;
	Move killerMove2 = INVALID_MOVE;

	if(killerMoves.size() > depth)	// may have killer moves stored
	{
		std::vector<Move>& currentDepthKillerMoves = killerMoves[depth];

		if(currentDepthKillerMoves.size() > 0)
		{
			killerMove1 = currentDepthKillerMoves[0];

			if(currentDepthKillerMoves.size() > 1)
			{
				killerMove2 = currentDepthKillerMoves[1];
			}
		}
	}

	MoveGenerator moveGenerator(currentPlayer,
								gameState.getBitboard(currentPlayer),
								gameState.getBitboard(gameState.getOpponentColor(currentPlayer)),
								transpositionMove, killerMove1, killerMove2);

	int score = MathConstants::LOW_ENOUGH_INT;
	Move m = moveGenerator.nextMove();
	Move bestMove = m;

	while(!(m == INVALID_MOVE))
	{
		gameState.applyMove(m);												// apply move
		int value = -alphaBeta(gameState, depth - 1, -beta, -alpha);		// continue searching
		gameState.undoMove(m);												// finished searching this subtree, so undo the move

		if(clock.getElapsedTimeInMilliSec() >= MIN_SEARCH_TIME_MS + MAX_EXTRA_SEARCH_TIME_MS)		// exceeding time limit
		{
			return 0;
		}

		if(value > score)		// new best move found
		{
			score = value;
			bestMove = m;
		}
		if(score > alpha)
		{
			alpha = score;
		}
		if(score >= beta)
		{
			// pruning, store Killer Move
			while(depth >= killerMoves.size()) // don't have a vector of killer moves yet for this depth
			{
				std::vector<Move> currentDepthKillerMoves;
				currentDepthKillerMoves.reserve(2);		// 2 slots of Killer Moves
				killerMoves.push_back(currentDepthKillerMoves);
			}

			std::vector<Move>& currentDepthKillerMoves = killerMoves[depth];	// killer moves for this depth
			if(currentDepthKillerMoves.size() > 0)		// already have at least 1 killer move
			{
				if(currentDepthKillerMoves[0] == m)		// this killer move already stored in first slot
				{
					break;
				}

				if(currentDepthKillerMoves.size() > 1)	// already have 2 killer moves stored
				{
					if(currentDepthKillerMoves[1] == m)	// this killer move already stored in second slot
					{
						break;
					}

					currentDepthKillerMoves[0] = currentDepthKillerMoves[1];	// move second kill move to first slot
					currentDepthKillerMoves.pop_back();							// and then remove second (which is now also in first slot)
				}
			}

			currentDepthKillerMoves.push_back(m);							// and put the new kill move in second slot
			
			break;
		}

		m = moveGenerator.nextMove();
	}

	// Store data in Transposition Table
	if(score <= originalAlpha)		// found upper bound
	{
		transpositionTable.storeData(bestMove, zobrist, score, EValue::Type::UPPER_BOUND, depth);
	}
	else if(score >= beta)			// found lower bound
	{
		transpositionTable.storeData(bestMove, zobrist, score, EValue::Type::LOWER_BOUND, depth);
	}
	else							// found exact value
	{
		transpositionTable.storeData(bestMove, zobrist, score, EValue::Type::REAL, depth);
	}

	return score;
}
コード例 #2
0
Move AspirationSearch::startAspirationSearch(GameState& gameState)
{
	clock.start();
	EPlayerColors::Type winner = gameState.getWinner();

	// stop search if we reached max depth or have found a winner
	if(winner != EPlayerColors::Type::NOTHING)
	{
		return INVALID_MOVE;		// can't return any normal move if game already ended
	}

	std::vector<Move> moves;				// will store all the moves in the root node, necessary for move ordering based on scores found in previous searches
	moves.reserve(16 * 4);

	EPlayerColors::Type currentPlayer = gameState.getCurrentPlayer();
	MoveGenerator moveGenerator(currentPlayer,
								gameState.getBitboard(currentPlayer),
								gameState.getBitboard(gameState.getOpponentColor(currentPlayer)));

	Move rootMove = moveGenerator.nextMove();

	while(!(rootMove == INVALID_MOVE))
	{
		moves.push_back(rootMove);
		rootMove = moveGenerator.nextMove();
	}

	std::vector<int> moveScores;			// will store the scores of the moves here, to use for sorting
	moveScores.reserve(moves.size());

	// best move found from a complete search (so not considering searches that were terminated early)
	Move bestMoveCompleteSearch = moves[0];

	int guess = lastRootEvaluation;		// start guess with the final root evaluation of our previous search
	int deltaGuess = 100;

	// compensate guess for odd-even effect
	if(searchDepth % 2 == 0)			// last search ended at even depth
	{
		guess += 141;
	}
	// no need to compensate if previous search ended at odd depth, since we're also starting at odd depth now

	searchDepth = 0;
	while(true)
	{
		++searchDepth;			// increment search depth for the new search
		killerMoves.clear();	// clear table of killer moves

		// ================= ALPHA BETA ALGORITHM STARTS HERE =================
		int score = MathConstants::LOW_ENOUGH_INT;
		int alpha = guess - deltaGuess;
		int beta = guess + deltaGuess;

		// best move for only this particular search
		Move bestMove = moves[0];

		for(int i = 0; i < moves.size(); ++i)
		{
			const Move& m = moves[i];											// select move
			gameState.applyMove(m);												// apply move
			int value = -alphaBeta(gameState, searchDepth - 1, -beta, -alpha);	// continue searching
			gameState.undoMove(m);												// finished searching this subtree, so undo the move

			if(clock.getElapsedTimeInMilliSec() >= MIN_SEARCH_TIME_MS + MAX_EXTRA_SEARCH_TIME_MS)		// exceeding time limit
			{
				bestMove = INVALID_MOVE;
				break;
			}

			moveScores[i] = value;

			if(value > score)		// new best move found
			{
				score = value;
				bestMove = m;
			}
			if(score > alpha)
			{
				alpha = score;
			}
			if(score >= beta)
			{
				break;
			}
		}
		// =================  ALPHA BETA ALGORITHM RESTARTS IF ASPIRATION SEARCH GAVE INCORRECT RESULT  =================
		bool newSearchNeeded = false;

		if(score >= (guess + deltaGuess) && !(bestMove == INVALID_MOVE))
		{
			newSearchNeeded = true;
			alpha = score;
			beta = MathConstants::LARGE_ENOUGH_INT;
		}
		else if(score <= (guess - deltaGuess) && !(bestMove == INVALID_MOVE))
		{
			newSearchNeeded = true;
			alpha = MathConstants::LOW_ENOUGH_INT;
			beta = score;
		}

		if(newSearchNeeded)		// Aspiration Search failed us, re-start the entire thing
		{
			LOG_MESSAGE(StringBuilder() << ">>>>>>>>>>>>>>>> Aspiration Search required a new Search at depth = " << searchDepth << "! <<<<<<<<<<<<<<<<<<<")
			LOG_MESSAGE(StringBuilder() << "Window = [" << (guess - deltaGuess) << ", " << (guess + deltaGuess) << "]")
			score = MathConstants::LOW_ENOUGH_INT;

			// best move for only this particular search
			bestMove = moves[0];

			for(int i = 0; i < moves.size(); ++i)
			{
				const Move& m = moves[i];											// select move
				gameState.applyMove(m);												// apply move
				int value = -alphaBeta(gameState, searchDepth - 1, -beta, -alpha);	// continue searching
				gameState.undoMove(m);												// finished searching this subtree, so undo the move

				if(clock.getElapsedTimeInMilliSec() >= MIN_SEARCH_TIME_MS + MAX_EXTRA_SEARCH_TIME_MS)		// exceeding time limit
				{
					bestMove = INVALID_MOVE;
					break;
				}

				moveScores[i] = value;

				if(value > score)		// new best move found
				{
					score = value;
					bestMove = m;
				}
				if(score > alpha)
				{
					alpha = score;
				}
				if(score >= beta)
				{
					break;
				}
			}

			LOG_MESSAGE(StringBuilder() << "True score = " << score)
		}
		// =================  ALPHA BETA ALGORITHM ENDS HERE  =================

		if(!(bestMove == INVALID_MOVE))	// managed to complete the search within time
		{
			lastRootEvaluation = score;

			if(score == WIN_EVALUATION)	// the search was enough to prove a win for us, so return best move of this latest search
			{
				return bestMove;
			}
			else if(score == -WIN_EVALUATION)	// the search proved a win for opponent, so return best move of the previous search
			{
				return bestMoveCompleteSearch;
			}

			// finished search, and didn't prove a win for either team, so save the new best result
			bestMoveCompleteSearch = bestMove;
		}
		else
		{
			--searchDepth;	// since last search was unsuccessful, decrement this so GUI doesn't lie to us
		}

		if(clock.getElapsedTimeInMilliSec() >= MIN_SEARCH_TIME_MS)		// exceeding time limit
		{
			clock.stop();
			return bestMoveCompleteSearch;
		}

		MoveOrdering::orderMoves(moves, moveScores);	// order moves for the next search

		// reset all scores to 0 before starting new search
		for(int i = 0; i < moveScores.size(); ++i)
		{
			moveScores[i] = 0;
		}

		// set our new guess for the next depth
		guess = score;

		if(searchDepth % 2 == 0)	// we've just done an even depth search, gonna do odd now, so compensate by increasing guess
		{
			guess += 141;
		}
		else						// we've just done an odd depth search, gonna do even now, so compensate by decreasing guess
		{
			guess -= 141;
		}
	}
}