com.netsensia.rivalchess.engine.search.Search.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rivalchess-engine Show documentation
Show all versions of rivalchess-engine Show documentation
The engine used by Rival Chess
package com.netsensia.rivalchess.engine.search
import UCI_TIMER_INTERVAL_MILLIS
import com.netsensia.rivalchess.config.*
import com.netsensia.rivalchess.consts.*
import com.netsensia.rivalchess.consts.BITBOARD_ENEMY
import com.netsensia.rivalchess.consts.FEN_START_POS
import com.netsensia.rivalchess.engine.board.*
import com.netsensia.rivalchess.engine.eval.*
import com.netsensia.rivalchess.engine.eval.see.StaticExchangeEvaluator
import com.netsensia.rivalchess.engine.hash.isAlwaysReplaceHashTableEntryValid
import com.netsensia.rivalchess.engine.hash.isHeightHashTableEntryValid
import com.netsensia.rivalchess.engine.type.EngineMove
import com.netsensia.rivalchess.enums.*
import com.netsensia.rivalchess.model.Board
import com.netsensia.rivalchess.model.Colour
import com.netsensia.rivalchess.model.util.FenUtils.getBoardModel
import com.netsensia.rivalchess.openings.OpeningLibrary
import com.netsensia.rivalchess.util.getSimpleAlgebraicMoveFromCompactMove
import java.io.PrintStream
import java.util.*
import kotlin.math.abs
class Search @JvmOverloads constructor(printStream: PrintStream = System.out, board: Board = getBoardModel(FEN_START_POS)) : Runnable {
private val printStream: PrintStream
val staticExchangeEvaluator: StaticExchangeEvaluator = StaticExchangeEvaluator()
val moveOrderStatus = arrayOfNulls(MAX_TREE_DEPTH)
private val drawnPositionsAtRoot: MutableList>
private val drawnPositionsAtRootCount = mutableListOf(0,0)
val mateKiller = IntArray(MAX_TREE_DEPTH)
val killerMoves: Array
val historyMovesSuccess = Array(2) { Array(64) { IntArray(64) } }
val historyMovesFail = Array(2) { Array(64) { IntArray(64) } }
val orderedMoves: Array
private val searchPath: Array
private var depthZeroMoveScores = IntArray(MAX_LEGAL_MOVES)
var engineState: SearchState
private set
private var quit = false
var nodes = 0
var millisSetByEngineMonitor: Long = 0
private var millisToThink = 0
private var nodesToSearch = Int.MAX_VALUE
@JvmField
var abortingSearch = true
private var searchStartTime: Long = -1
private var searchTargetEndTime: Long = 0
private var searchEndTime: Long = 0
private var finalDepthToSearch = 1
var iterativeDeepeningDepth = 1
var currentDepthZeroMove = 0
var currentDepthZeroMoveNumber = 0
var currentPath: SearchPath
var isUciMode = false
val engineBoard = EngineBoard(getBoardModel(FEN_START_POS))
var useOpeningBook = USE_INTERNAL_OPENING_BOOK
set(useOpeningBook) {
field = USE_INTERNAL_OPENING_BOOK && useOpeningBook
}
constructor(board: Board) : this(System.out, board) {}
fun go() {
initSearchVariables()
if (isBookMoveAvailable()) return
determineDrawnPositionsAndGenerateDepthZeroMoves()
var result = AspirationSearchResult(null, -Int.MAX_VALUE, Int.MAX_VALUE)
for (depth in 1..finalDepthToSearch) {
iterativeDeepeningDepth = depth
result = aspirationSearch(depth, result.low, result.high)
reorderDepthZeroMoves()
if (abortingSearch) break
currentPath.setPath(result.path!!)
if (result.path!!.score > MATE_SCORE_START) break
}
setSearchComplete()
}
private fun aspirationSearch(depth: Int, low: Int, high: Int): AspirationSearchResult {
var path: SearchPath
var newLow = low
var newHigh = high
path = searchZero(engineBoard, depth, 0, low, high)
if (!abortingSearch && path.score <= newLow) {
newLow = -Int.MAX_VALUE
path = searchZero(engineBoard, depth, 0, newLow, high)
} else if (!abortingSearch && path.score >= high) {
newHigh = Int.MAX_VALUE
path = searchZero(engineBoard, depth, 0, low, newHigh)
}
if (!abortingSearch && (path.score <= newLow || path.score >= newHigh))
path = searchZero(engineBoard, depth, 0, -Int.MAX_VALUE, Int.MAX_VALUE)
if (!abortingSearch) {
currentPath.setPath(path)
newLow = path.score - ASPIRATION_RADIUS
newHigh = path.score + ASPIRATION_RADIUS
}
return AspirationSearchResult(path, newLow, newHigh)
}
fun searchZero(board: EngineBoard, depth: Int, ply: Int, low: Int, high: Int): SearchPath {
nodes ++
var myLow = low
var numMoves = 0
var hashEntryType = UPPER
var bestMoveForHash = 0
var numLegalMoves = 0
var useScoutSearch = false
val bestPath = searchPath[0].reset()
moveSequence(orderedMoves[0]).forEach {
val move = moveNoScore(it)
if (abortingSearch) return SearchPath()
depthZeroMoveScores[numMoves] = -Int.MAX_VALUE
if (engineBoard.makeMove(move)) {
updateCurrentDepthZeroMove(move, ++numLegalMoves)
val isCheck = board.isCheck(mover)
val extensions = getExtensions(isCheck, board.wasPawnPush())
val newPath = getPathFromSearch(move, useScoutSearch, depth, ply, myLow, high, extensions, isCheck)
if (abortingSearch) return SearchPath()
newPath.score = -newPath.score
if (newPath.score >= high) {
board.unMakeMove()
engineBoard.boardHashObject.storeHashMove(move, board, newPath.score, LOWER, depth)
depthZeroMoveScores[numMoves] = newPath.score
return bestPath.withPath(move, newPath)
}
if (newPath.score > bestPath.score) {
bestPath.setPath(move, newPath)
if (newPath.score > myLow) {
hashEntryType = EXACT
bestMoveForHash = move
myLow = newPath.score
useScoutSearch = useScoutSearch(depth, extensions)
currentPath.setPath(bestPath)
}
}
depthZeroMoveScores[numMoves] = newPath.score
engineBoard.unMakeMove()
}
numMoves++
}
if (abortingSearch) return SearchPath()
abortingSearch = onlyOneMoveAndNotOnFixedTime(numLegalMoves)
engineBoard.boardHashObject.storeHashMove(bestMoveForHash, board, bestPath.score, hashEntryType, depth)
return bestPath
}
fun search(board: EngineBoard, depth: Int, ply: Int, low: Int, high: Int, extensions: Int, recaptureSquare: Int, isCheck: Boolean): SearchPath {
nodes++
if (abortIfTimeIsUp()) return SearchPath()
val searchPathPly = searchPath[ply].reset()
if (board.previousOccurrencesOfThisPosition() == 2 || board.halfMoveCount >= 100) return searchPathPly.withScore(DRAW_CONTEMPT)
if (board.onlyKingsRemain()) return searchPathPly.withScore(0)
val depthRemaining = depth + extensions / FRACTIONAL_EXTENSION_FULL
val hashProbeResult = hashProbe(board, depthRemaining, Window(low, high), searchPathPly)
if (hashProbeResult.bestPath != null) return searchPathPly
var localLow = hashProbeResult.window.low
val localHigh = hashProbeResult.window.high
val checkExtend = checkExtension(extensions, isCheck)
var hashFlag = UPPER
if (depthRemaining <= 0) return finalPath(board, ply, localLow, localHigh, isCheck)
val highRankingMove = highRankingMove(board, hashProbeResult.move, depthRemaining, depth, ply, Window(localLow, localHigh), extensions, recaptureSquare, isCheck)
searchPathPly.reset()
var bestMoveForHash = 0
var useScoutSearch = false
var threatExtend = 0
if (performNullMove(board, depthRemaining, isCheck))
searchNullMove(board, depth, nullMoveReduceDepth(depthRemaining), ply, localLow, localHigh, extensions).also {
if (abortingSearch) return SearchPath()
adjustScoreForMateDepth(it)
if (-it.score >= localHigh) return searchPathPly.withScore(-it.score)
else threatExtend = threatExtensions(it, extensions)
}
orderedMoves[ply] = board.moveGenerator().generateLegalMoves().moves
moveOrderStatus[ply] = MoveOrder.NONE
val startNodes = nodes
for (move in highScoreMoveSequence(board, ply, highRankingMove)) {
val recaptureExtensionResponse =
recaptureExtensions(extensions,
board.getBitboardTypeOfPieceOnSquare(toSquare(move), board.mover.opponent()),
board.getBitboardTypeOfPieceOnSquare(fromSquare(move), board.mover), board, move, recaptureSquare)
if (board.makeMove(move)) {
val newExtensions = extensions + extensions(checkExtend, threatExtend, recaptureExtensionResponse.extend, pawnExtensions(extensions, board), maxExtensionsForPly(ply))
val newPath =
scoutSearch(useScoutSearch, depth, ply, localLow, localHigh, newExtensions,
recaptureExtensionResponse.captureSquare, board.isCheck(mover)).also {
it.score = -it.score
}
if (abortingSearch) return SearchPath()
if (newPath.score >= localHigh) {
updateHistoryMoves(board.mover, move, depthRemaining, true)
board.unMakeMove()
board.boardHashObject.storeHashMove(move, board, newPath.score, LOWER, depthRemaining)
updateKillerMoves(board.getBitboard(BITBOARD_ENEMY), move, ply, newPath)
return searchPathPly.withPath(move, newPath)
}
if (newPath.score > searchPathPly.score) {
searchPathPly.setPath(move, newPath)
if (newPath.score > localLow) {
hashFlag = EXACT
bestMoveForHash = move
localLow = newPath.score
useScoutSearch = useScoutSearch(depth, newExtensions)
}
}
updateHistoryMoves(board.mover, move, depthRemaining, false)
board.unMakeMove()
}
}
if (abortingSearch) return SearchPath()
if (nodes == startNodes) {
board.boardHashObject.storeHashMove(0, board, searchPathPly.score, EXACT, MAX_SEARCH_DEPTH)
return searchPathPly.withScore(if (board.isCheck(mover)) -VALUE_MATE else 0)
}
board.boardHashObject.storeHashMove(bestMoveForHash, board, searchPathPly.score, hashFlag, depthRemaining)
return searchPathPly
}
private fun onlyOneMoveAndNotOnFixedTime(numLegalMoves: Int) = numLegalMoves == 1 && millisToThink < MAX_SEARCH_MILLIS
private fun highScoreMoveSequence(board: EngineBoard, ply: Int, highRankingMove: Int) = sequence {
while (getHighScoreMove(board, ply, highRankingMove).also { if (it != 0) yield(it) } != 0);
}
private fun pawnExtensions(extensions: Int, board: EngineBoard) =
if (extensions / FRACTIONAL_EXTENSION_FULL < MAX_EXTENSION_DEPTH && FRACTIONAL_EXTENSION_PAWN > 0 && board.wasPawnPush()) 1 else 0
private fun maxExtensionsForPly(ply: Int) =
maxNewExtensionsTreePart[(ply / iterativeDeepeningDepth).coerceAtMost(LAST_EXTENSION_LAYER)]
private fun updateKillerMoves(enemyBitboard: Long, move: Int, ply: Int, newPath: SearchPath) {
if (enemyBitboard and toSquare(move).toLong() == 0L || move and PROMOTION_PIECE_TOSQUARE_MASK_FULL == 0) {
killerMoves[ply][1] = killerMoves[ply][0]
killerMoves[ply][0] = move
if (USE_MATE_HISTORY_KILLERS && newPath.score > MATE_SCORE_START) mateKiller[ply] = move
}
}
private fun recaptureExtensions(extensions: Int, targetPiece: Int, movePiece: Int, board: EngineBoard, move: Int, recaptureSquare: Int) =
if (FRACTIONAL_EXTENSION_RECAPTURE > 0 && extensions / FRACTIONAL_EXTENSION_FULL < MAX_EXTENSION_DEPTH) {
var currentSEEValue = -Int.MAX_VALUE
var recaptureExtend = 0
var newRecaptureSquare = -1
if (targetPiece != -1 && pieceValues[movePiece] == pieceValues[targetPiece]) {
currentSEEValue = staticExchangeEvaluator.staticExchangeEvaluation(board, move)
if (abs(currentSEEValue) <= RECAPTURE_EXTENSION_MARGIN) newRecaptureSquare = toSquare(move)
}
if (toSquare(move) == recaptureSquare) {
if (currentSEEValue == -Int.MAX_VALUE) currentSEEValue = staticExchangeEvaluator.staticExchangeEvaluation(board, move)
if (abs(currentSEEValue) > getPieceValue(board.getBitboardTypeOfPieceOnSquare(recaptureSquare, board.mover.opponent())) - RECAPTURE_EXTENSION_MARGIN) {
recaptureExtend = 1
}
}
RecaptureExtensionResponse(recaptureExtend, newRecaptureSquare)
} else {
RecaptureExtensionResponse(-Int.MAX_VALUE, -1)
}
private fun scoutSearch(useScoutSearch: Boolean, depth: Int, ply: Int, low: Int, high: Int, newExtensions: Int, newRecaptureSquare: Int, localIsCheck: Boolean) =
if (useScoutSearch) {
val scoutPath = search(engineBoard, (depth - 1), ply + 1, -low-1, -low, newExtensions, newRecaptureSquare, localIsCheck).also {
adjustScoreForMateDepth(it)
}
if (!abortingSearch && -scoutPath.score > low) {
search(engineBoard, (depth - 1), ply + 1, -high, -low, newExtensions, newRecaptureSquare, localIsCheck).also {
adjustScoreForMateDepth(it)
}
} else scoutPath
} else {
search(engineBoard, depth - 1, ply + 1, -high, -low, newExtensions, newRecaptureSquare, localIsCheck).also {
adjustScoreForMateDepth(it)
}
}
private fun getPathFromSearch(move: Int, scoutSearch: Boolean, depth: Int, ply: Int, low: Int, high: Int, extensions: Int, isCheck: Boolean) =
if (isDrawnAtRoot()) SearchPath().withScore(0).withPath(move) else
scoutSearch(scoutSearch, depth, ply, low, high, extensions, -1, isCheck)
private fun adjustScoreForMateDepth(newPath: SearchPath) {
newPath.score += if (newPath.score > MATE_SCORE_START) -1 else if (newPath.score < -MATE_SCORE_START) 1 else 0
}
private fun highRankingMove(board: EngineBoard, hashMove: Int, depthRemaining: Int, depth: Int, ply: Int, window: Window, extensions: Int, recaptureSquare: Int, isCheck: Boolean): Int {
if (hashMove == 0 && !board.isOnNullMove && USE_INTERNAL_ITERATIVE_DEEPENING && depthRemaining >= IID_MIN_DEPTH) {
val iidDepth = depth - IID_REDUCE_DEPTH
if (iidDepth > 0) search(board, iidDepth, ply, window.low, window.high, extensions, recaptureSquare, isCheck).also {
if (it.height > 0) return it.move[0]
}
}
return hashMove
}
private fun extensions(checkExtend: Int, threatExtend: Int, recaptureExtend: Int, pawnExtend: Int, maxNewExtensionsInThisPart: Int) =
(checkExtend * FRACTIONAL_EXTENSION_CHECK +
threatExtend * FRACTIONAL_EXTENSION_THREAT +
recaptureExtend * FRACTIONAL_EXTENSION_RECAPTURE +
pawnExtend * FRACTIONAL_EXTENSION_PAWN).coerceAtMost(maxNewExtensionsInThisPart)
private fun updateHistoryMoves(mover: Colour, move: Int, depthRemaining: Int, success: Boolean) {
val historyMovesArray = if (success) historyMovesSuccess else historyMovesFail
val moverIndex = if (mover == Colour.WHITE) 1 else 0
val fromSquare = fromSquare(move)
val toSquare = toSquare(move)
if (USE_HISTORY_HEURISTIC) {
historyMovesArray[moverIndex][fromSquare][toSquare] += depthRemaining
if (historyMovesArray[moverIndex][fromSquare][toSquare] > HISTORY_MAX_VALUE) {
for (i in 0..1) for (j in 0..63) for (k in 0..63) {
if (historyMovesSuccess[i][j][k] > 0) historyMovesSuccess[i][j][k] /= 2
if (historyMovesFail[i][j][k] > 0) historyMovesFail[i][j][k] /= 2
}
}
}
}
private fun updateCurrentDepthZeroMove(move: Int, arrayIndex: Int) {
currentDepthZeroMove = move
currentDepthZeroMoveNumber = arrayIndex
}
private fun getExtensions(isCheck: Boolean, wasPawnPush: Boolean) =
if (FRACTIONAL_EXTENSION_CHECK > 0 && isCheck) FRACTIONAL_EXTENSION_CHECK
else if (FRACTIONAL_EXTENSION_PAWN > 0 && wasPawnPush) FRACTIONAL_EXTENSION_PAWN
else 0
private fun hashProbe(board: EngineBoard, depthRemaining: Int, window: Window, bestPath: SearchPath): HashProbeResult {
val boardHash = board.boardHashObject
var hashMove = 0
val hashIndex = board.boardHashObject.getHashIndex(board)
if (USE_HEIGHT_REPLACE_HASH && isHeightHashTableEntryValid(depthRemaining, boardHash, hashIndex)) {
boardHash.setHashTableUseHeightVersion(hashIndex, boardHash.hashTableVersion)
hashMove = boardHash.useHeight(hashIndex + HASHENTRY_MOVE)
val flag = boardHash.useHeight(hashIndex + HASHENTRY_FLAG)
val score = boardHash.useHeight(hashIndex + HASHENTRY_SCORE)
if (hashProbeResult(flag, score, window)) return HashProbeResult(hashMove, window, bestPath.withScore(score).withPath(hashMove))
}
if (USE_ALWAYS_REPLACE_HASH && hashMove == 0 && isAlwaysReplaceHashTableEntryValid(depthRemaining, boardHash, hashIndex)) {
hashMove = boardHash.ignoreHeight(hashIndex + HASHENTRY_MOVE)
val flag = boardHash.ignoreHeight(hashIndex + HASHENTRY_FLAG)
val score = boardHash.ignoreHeight(hashIndex + HASHENTRY_SCORE)
if (hashProbeResult(flag, score, window)) return HashProbeResult(hashMove, window, bestPath.withScore(score).withPath(hashMove))
}
return HashProbeResult(hashMove, window, null)
}
private fun hashProbeResult(flag: Int, score: Int, window: Window): Boolean {
when (flag) {
EXACT -> return true
LOWER -> if (score > window.low) window.low = score
UPPER -> if (score < window.high) window.high = score
}
return window.low >= window.high
}
private fun finalPath(board: EngineBoard, ply: Int, low: Int, high: Int, isCheck: Boolean): SearchPath {
val bestPath = quiesce(board, MAX_QUIESCE_DEPTH - 1, ply, 0, low, high, isCheck)
val hashFlag = if (bestPath.score < low) UPPER else if (bestPath.score > high) LOWER else EXACT
board.boardHashObject.storeHashMove(0, board, bestPath.score, hashFlag,0)
return bestPath
}
private fun abortIfTimeIsUp(): Boolean {
if (millisSetByEngineMonitor > searchTargetEndTime || nodes >= nodesToSearch) abortingSearch = true
return abortingSearch
}
private fun performNullMove(board: EngineBoard, depthRemaining: Int, isCheck: Boolean) =
((USE_NULL_MOVE_PRUNING && !isCheck && !board.isOnNullMove && depthRemaining > 1) &&
((if (board.mover == Colour.WHITE) board.whitePieceValues else board.blackPieceValues) >= NULLMOVE_MINIMUM_FRIENDLY_PIECEVALUES &&
(if (board.mover == Colour.WHITE) board.whitePawnValues else board.blackPawnValues) > 0))
private fun searchNullMove(board: EngineBoard, depth: Int, nullMoveReduceDepth: Int, ply: Int, low: Int, high: Int, extensions: Int): SearchPath {
board.makeNullMove()
val newPath = search(board, (depth - nullMoveReduceDepth - 1), ply + 1, -high, -low, extensions, -1, false)
board.unMakeNullMove()
return newPath
}
private fun threatExtensions(newPath: SearchPath, extensions: Int) =
if (FRACTIONAL_EXTENSION_THREAT > 0 && -newPath.score < -MATE_SCORE_START && extensions / FRACTIONAL_EXTENSION_FULL < MAX_EXTENSION_DEPTH) 1 else 0
private fun checkExtension(extensions: Int, isCheck: Boolean) =
if (extensions / FRACTIONAL_EXTENSION_FULL < MAX_EXTENSION_DEPTH && FRACTIONAL_EXTENSION_CHECK > 0 && isCheck) 1 else 0
private fun reorderDepthZeroMoves() {
val moveCount = moveCount(orderedMoves[0])
for (pass in 1 until moveCount) {
for (i in 0 until moveCount - pass) {
if (depthZeroMoveScores[i] < depthZeroMoveScores[i + 1]) {
swapElements(depthZeroMoveScores, i, i + 1)
swapElements(orderedMoves[0], i, i + 1)
}
}
}
}
private fun determineDrawnPositionsAndGenerateDepthZeroMoves() {
orderedMoves[0] = engineBoard.moveGenerator().generateLegalMoves().moves
moveSequence(orderedMoves[0]).forEach {
if (engineBoard.makeMove(it)) {
val plyDraw = booleanArrayOf(false, false)
if (engineBoard.previousOccurrencesOfThisPosition() == 2) plyDraw[0] = true
moveSequence(engineBoard.moveGenerator().generateLegalMoves().moves).forEach {
if (engineBoard.makeMove(moveNoScore(it))) {
if (engineBoard.previousOccurrencesOfThisPosition() == 2) plyDraw[1] = true
engineBoard.unMakeMove()
}
}
for (i in 0..1)
if (plyDraw[i]) drawnPositionsAtRoot[i].add(engineBoard.boardHashObject.trackedHashValue)
engineBoard.unMakeMove()
}
}
scoreFullWidthMoves(engineBoard, 0)
}
private fun isBookMoveAvailable(): Boolean {
if (useOpeningBook) {
val libraryMove = OpeningLibrary.getMove(getFen())
if (libraryMove != null) {
currentPath = SearchPath().withPath(EngineMove(libraryMove).compact)
setSearchComplete()
return true
}
}
return false
}
fun setHashSizeMB(hashSizeMB: Int) {
engineBoard.boardHashObject.setHashSizeMB(hashSizeMB)
}
fun setBoard(board: Board) {
engineBoard.setBoard(board)
engineBoard.boardHashObject.incVersion()
engineBoard.boardHashObject.setHashTable()
}
fun clearHash() {
engineBoard.boardHashObject.clearHash()
}
fun newGame() {
engineBoard.boardHashObject.clearHash()
}
private fun scoreQuiesceMoves(board: EngineBoard, ply: Int, includeChecks: Boolean) {
var moveCount = 0
var i = 0
while (orderedMoves[ply][i] != 0) {
var move = orderedMoves[ply][i]
val isCapture = board.isCapture(move)
move = moveNoScore(move)
val score = board.getScore(move, includeChecks, isCapture, staticExchangeEvaluator)
if (score > 0) orderedMoves[ply][moveCount++] = move or (127 - score shl 24)
i++
}
orderedMoves[ply][moveCount] = 0
}
fun quiesce(board: EngineBoard, depth: Int, ply: Int, quiescePly: Int, low: Int, high: Int, isCheck: Boolean): SearchPath {
nodes ++
var newPath: SearchPath
val evalScore = evaluate(board)
searchPath[ply].height = 0
searchPath[ply].score = if (isCheck) -VALUE_MATE else evalScore
if (depth == 0 || searchPath[ply].score >= high) return searchPath[ply]
var newLow = searchPath[ply].score.coerceAtLeast(low)
setOrderedMovesArrayForQuiesce(isCheck, ply, board, quiescePly)
var move = getHighestScoringMoveFromArray(orderedMoves[ply])
val startNodes = nodes
while (move != 0) {
if (board.makeMove(move)) {
newPath = quiesce(board, depth - 1,ply + 1,quiescePly + 1, -high, -newLow,
quiescePly <= GENERATE_CHECKS_UNTIL_QUIESCE_PLY && board.isCheck(mover))
board.unMakeMove()
newPath.score = -newPath.score
if (newPath.score > searchPath[ply].score) searchPath[ply].setPath(move, newPath)
if (newPath.score >= high) return searchPath[ply]
newLow = newLow.coerceAtLeast(newPath.score)
}
move = getHighestScoringMoveFromArray(orderedMoves[ply])
}
if (isCheck && nodes == startNodes) searchPath[ply].score = -VALUE_MATE
return searchPath[ply]
}
private fun setOrderedMovesArrayForQuiesce(isCheck: Boolean, ply: Int, board: EngineBoard, quiescePly: Int) {
if (isCheck) {
orderedMoves[ply] = board.moveGenerator().generateLegalMoves().moves
scoreFullWidthMoves(board, ply)
} else {
orderedMoves[ply] = board.moveGenerator()
.generateLegalQuiesceMoves(quiescePly <= GENERATE_CHECKS_UNTIL_QUIESCE_PLY)
.moves
scoreQuiesceMoves(board, ply, quiescePly <= GENERATE_CHECKS_UNTIL_QUIESCE_PLY)
}
}
val mover: Colour
inline get() = engineBoard.mover
private fun initSearchVariables() {
engineState = SearchState.SEARCHING
abortingSearch = false
val boardHash = engineBoard.boardHashObject
boardHash.incVersion()
searchStartTime = System.currentTimeMillis()
searchEndTime = 0
searchTargetEndTime = searchStartTime + millisToThink - UCI_TIMER_INTERVAL_MILLIS
nodes = 0
setupMateAndKillerMoveTables()
setupHistoryMoveTable()
}
private fun setupMateAndKillerMoveTables() {
for (i in 0 until MAX_TREE_DEPTH) {
mateKiller[i] = -1
killerMoves[i][0] = -1
killerMoves[i][1] = -1
}
}
private fun setupHistoryMoveTable() {
for (i in 0..63) for (j in 0..63) {
historyMovesSuccess[0][i][j] = 0
historyMovesSuccess[1][i][j] = 0
historyMovesFail[0][i][j] = 0
historyMovesFail[1][i][j] = 0
}
}
fun setMillisToThink(millisToThink: Int) {
this.millisToThink = if (millisToThink < MIN_SEARCH_MILLIS) MIN_SEARCH_MILLIS else millisToThink
}
fun setNodesToSearch(nodesToSearch: Int) {
this.nodesToSearch = nodesToSearch
}
fun setSearchDepth(searchDepth: Int) {
finalDepthToSearch = if (searchDepth < 0) 1 else searchDepth
}
val isSearching: Boolean
get() = engineState === SearchState.SEARCHING || engineState === SearchState.REQUESTED
val currentScoreHuman: String
get() {
val score = currentPath.score
val abs = Math.abs(score)
if (abs > MATE_SCORE_START) {
val mateIn = (VALUE_MATE - abs + 1) / 2
return "mate " + (if (score < 0) "-" else "") + mateIn
}
return "cp $score"
}
val currentScore: Int
get() = currentPath.score
val searchDuration: Long
get() {
return when (engineState) {
SearchState.READY -> 0
SearchState.SEARCHING -> System.currentTimeMillis() - searchStartTime
SearchState.COMPLETE -> searchEndTime - searchStartTime
SearchState.REQUESTED -> 0
}
}
val nodesPerSecond: Int
get() {
val timePassed = searchDuration
return if (timePassed == 0L) 0 else (nodes.toDouble() / timePassed.toDouble() * 1000.0).toInt()
}
private fun isDrawnAtRoot(): Boolean {
val boardHash = engineBoard.boardHashObject
var i = 0
while (i < drawnPositionsAtRootCount[0]) {
if (drawnPositionsAtRoot[0][i] == boardHash.trackedHashValue) {
return true
}
i++
}
return false
}
val currentMove: Int
get() = currentPath.move[0]
fun startSearch() {
engineState = SearchState.REQUESTED
}
fun stopSearch() {
abortingSearch = true
}
fun setSearchComplete() {
searchEndTime = System.currentTimeMillis()
engineState = SearchState.COMPLETE
}
fun quit() {
quit = true
}
override fun run() {
while (!quit) {
Thread.yield()
if (engineState === SearchState.REQUESTED) {
go()
if (isUciMode) {
val s1 = "info" +
" currmove " + getSimpleAlgebraicMoveFromCompactMove(currentDepthZeroMove) +
" currmovenumber " + currentDepthZeroMoveNumber +
" depth " + iterativeDeepeningDepth +
" score " + currentScoreHuman +
" pv " + currentPath.toString() +
" time " + searchDuration +
" nodes " + nodes +
" nps " + nodesPerSecond
val s2 = "bestmove " + getSimpleAlgebraicMoveFromCompactMove(currentMove)
printStream.println(s1)
printStream.println(s2)
}
}
}
}
fun isOkToSendInfo() = engineState == SearchState.SEARCHING && iterativeDeepeningDepth > 1 && !abortingSearch
fun getFen() = engineBoard.getFen()
fun makeMove(compactMove: Int) {
engineBoard.makeMove(compactMove)
}
init {
engineBoard.setBoard(board)
drawnPositionsAtRoot = ArrayList()
drawnPositionsAtRoot.add(ArrayList())
drawnPositionsAtRoot.add(ArrayList())
this.printStream = printStream
millisSetByEngineMonitor = System.currentTimeMillis()
currentPath = SearchPath()
engineState = SearchState.READY
searchPath = Array(MAX_TREE_DEPTH) { SearchPath() }
killerMoves = Array(MAX_TREE_DEPTH) { IntArray(2) }
for (i in 0 until MAX_TREE_DEPTH) killerMoves[i] = IntArray(2)
orderedMoves = Array(MAX_TREE_DEPTH) { IntArray(MAX_LEGAL_MOVES) }
}
}