Skip to content

Commit

Permalink
Add checking for terminal node in the alpha-beta search
Browse files Browse the repository at this point in the history
  • Loading branch information
melvic-ybanez committed Mar 11, 2019
1 parent e0c3bcc commit d27461c
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 25 deletions.
5 changes: 3 additions & 2 deletions src/controllers/GameController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ case class DefaultGameController(
val movedBoard = task.getValue
val accessor = boardAccessor.updatedBoard(movedBoard)
val move = movedBoard.lastMove.get
val piece = movedBoard(move.destination).get
val piece = boardAccessor.board(move.source).get
handleMoveResult(accessor.accessorMove(move),
piece, accessor, movedBoard.isCheckmate(sideToMove))
}
Expand Down Expand Up @@ -155,12 +155,13 @@ case class DefaultGameController(
historyView.addMove(accessorMove, boardAccessor.board, piece)
boardAccessor = accessor
boardView.resetBoard(false)

boardView.animateMove {
if (checkmate) gameOver(sideToMove, "checkmate")
undoneBoards.clear()
sideToMove = sideToMove.opposite
if (computerToMove) computerMove()
}
sideToMove = sideToMove.opposite
}

override def undo() = _undo(historyBoards, undoneBoards)
Expand Down
19 changes: 18 additions & 1 deletion src/engine/board/bitboards/Bitboard.scala
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ case class Bitboard(bitsets: Vector[U64],
val loosingSide = winningSide.opposite
val piecesToMove = Pawn :: Knight :: Bishop :: Rook :: Queen :: King :: Nil

!piecesToMove.exists { pieceType =>
isChecked(loosingSide) && !piecesToMove.exists { pieceType =>
val piece = Piece(pieceType, loosingSide)
val pieceIndexes = toSquareIndexes(pieceBitset(piece))
pieceIndexes.exists { squareIndex =>
Expand Down Expand Up @@ -301,4 +301,21 @@ case class Bitboard(bitsets: Vector[U64],

override def pieceLocations(piece: Piece) =
toSquareIndexes(pieceBitset(piece)).map(intToLocation)

override def toString = {
def sideString: Side => String = List("W", "B")(_)
def pieceTypeString: PieceType => String = "PNBRQK"(_).toString
val space = " " * 2

val squareStrings = (0 until 64).foldLeft[List[String]](Nil) { (acc, i) =>
val squareString = this(i).map { case Piece(pieceType, side) =>
sideString(side) + pieceTypeString(pieceType)
} getOrElse ("_" * space.length)

if (i % Board.Size == 0) squareString :: acc
else acc.head + space + squareString :: acc.tail
}

squareStrings.mkString("\n")
}
}
2 changes: 1 addition & 1 deletion src/engine/movegen/bitboards/PawnMoveGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ object PawnMoveGenerator extends BitboardMoveGenerator with PostShiftOneStep {
moves flatMap { case move@(pawnMove, _) =>
val moveBitset: PawnMove => U64 = _(pawnBitset, getTargets(bitboard, sideToMove), sideToMove)

val promotionMasks = 0xff000000000000L :: 0xff00L :: Nil
val promotionMasks = 0x00ff000000000000L :: 0xff00L :: Nil
val promote = isNonEmptySet(promotionMasks(sideToMove) & pawnBitset)

if (promote)
Expand Down
52 changes: 31 additions & 21 deletions src/engine/search/AlphaBeta.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package engine.search

import engine.board.{Board, Piece, Side}
import engine.board._
import engine.eval.Evaluator
import engine.movegen.Move.LocationMove

import scala.annotation.tailrec

Expand All @@ -24,34 +23,45 @@ sealed trait AlphaBeta {
* @param depth remaining depth in the search tree
* @return A pair consisting of the evaluation score and the chosen next move.
*/
def search(board: Board, side: Side, currentScore: Double, bound: Double, depth: Int): (Double, Board) =
if (depth == 0) (evaluateBoard(board, side), board)
else {
@tailrec
def recurse(bestScore: Double,
nextBoard: Board, updatedBoards: Stream[Board]): (Double, Board) = updatedBoards match {
case Stream() => (bestScore, nextBoard)
case updatedBoard +: nextMoves =>
val (score, _) = opponent.search(updatedBoard,
side.opposite,
bound, bestScore, // params positions switched
depth - 1)

if (cutOffBound(score, bound)) (bound, updatedBoard)
else if (isBetterScore(score, bestScore)) recurse(score, updatedBoard, nextMoves)
else recurse(bestScore, nextBoard, nextMoves)
}
def search(board: Board, side: Side, currentScore: Double, bound: Double, depth: Int): (Double, Board) = {
lazy val result = (evaluateBoard(board, side), board)

recurse(currentScore, board, {
if (depth == 0) result
else {
val validMoves = {
// Apply all the moves
val moves = board.generateMoves(side).map { case (move, piece) =>
board.updateByMove(move, piece)
}

// Only include moves that do not leave the king checked
moves.filter(!_.isChecked(side))
})
}

validMoves match {
// This is a terminal node, return immediately
case Stream() => result

case _ =>
@tailrec
def recurse(bestScore: Double,
nextBoard: Board, updatedBoards: Stream[Board]): (Double, Board) = updatedBoards match {
case Stream() => (bestScore, nextBoard)
case updatedBoard +: nextMoves =>
val (score, _) = opponent.search(updatedBoard,
side.opposite,
bound, bestScore, // params positions switched
depth - 1)

if (cutOffBound(score, bound)) (bound, updatedBoard)
else if (isBetterScore(score, bestScore)) recurse(score, updatedBoard, nextMoves)
else recurse(bestScore, nextBoard, nextMoves)
}

recurse(currentScore, board, validMoves)
}
}
}
}

object AlphaBeta {
Expand Down

0 comments on commit d27461c

Please sign in to comment.