engine: engine now correctly uses the negamax algorithm

NegaMax is a simplified version of the MiniMax algorithm that
doesn't need different subroutines for the different colors.
The testcases were selected and extended.
Evaluation now has values for draws and wins.
master
TiynGER 4 years ago
parent 9ea20b6c1f
commit 9095cf8eee

@ -38,4 +38,4 @@ Due to easier off the board checking a
### Engine ### Engine
The engine uses a simple implementation of the The engine uses a simple implementation of the
[Minimax](https://www.chessprogramming.org/Minimax)-algorithm. [NegaMax](https://www.chessprogramming.org/NegaMax)-algorithm.

@ -13,10 +13,9 @@ const
BishopVal = 3 ## `BishopVal` is the engines value for a bishop. BishopVal = 3 ## `BishopVal` is the engines value for a bishop.
RookVal = 5 ## `RookVal` is the engines value for a rook. RookVal = 5 ## `RookVal` is the engines value for a rook.
QueenVal = 9 ## `QueenVal` is the engines value for a queen. QueenVal = 9 ## `QueenVal` is the engines value for a queen.
CheckmateVal = 1000 ## `CheckmateVal` is the engines value for a checkmate. CheckmateVal = -1000 ## `CheckmateVal` is the engines value for a checkmate.
DrawVal = 0 ## `DrawVal` is the engines value for a draw. DrawVal = 0 ## `DrawVal` is the engines value for a draw.
HiVal = 1000000 ## `HiVal` is the highest possible value (used in minimax). LoVal = -1000000 ## `LoVal` is a value always lower than any evaluation.
LoVal = -HiVal ## `LoVal` is the lowest possible value (used in minimax).
proc pieceEval*(game: Game): int = proc pieceEval*(game: Game): int =
## Returns the evaluation of existing pieces on the `board` ## Returns the evaluation of existing pieces on the `board`
@ -50,14 +49,25 @@ proc pieceEval*(game: Game): int =
proc evaluate(game: Game): int = proc evaluate(game: Game): int =
## Returns a complete evaluation of a `game` with player `toMove` about to make ## Returns a complete evaluation of a `game` with player `toMove` about to make
## a move. ## a move.
var evaluation = game.pieceEval() var evaluation: int
if game.isCheckmate(game.toMove):
evaluation = ord(game.toMove) * CheckmateVal
if game.isStalemate(game.toMove):
evaluation = DrawVal
else:
evaluation = game.pieceEval()
if game.isDrawClaimable():
if game.toMove == Color.White:
evaluation = max(DrawVal, evaluation)
else:
evaluation = min(DrawVal, evaluation)
return evaluation return evaluation
proc spanMoveTree*(game: Game, depth: int): MoveTree = proc spanMoveTree*(game: Game, depth: int): MoveTree =
## Create and return a Movetree of a given `game` with a given maximum `depth`. ## Create and return a Movetree of a given `game` with a given maximum `depth`.
var mTree: MoveTree var mTree: MoveTree
mTree.game = game mTree.game = game
if depth != 1 and not game.isCheckmate(game.toMove) and not game.isStalemate(game.toMove): if depth != 0 and not game.isCheckmate(game.toMove) and not game.isStalemate(game.toMove):
let possibleMoves = game.genLegalMoves(game.toMove) let possibleMoves = game.genLegalMoves(game.toMove)
for move in possibleMoves: for move in possibleMoves:
var tmpGame = game var tmpGame = game
@ -65,46 +75,30 @@ proc spanMoveTree*(game: Game, depth: int): MoveTree =
mTree.children.add(spanMoveTree(tmpGame, depth-1)) mTree.children.add(spanMoveTree(tmpGame, depth-1))
return mTree return mTree
proc minimax*(mTree: MoveTree): int = proc negaMax*(mTree: MoveTree): int =
## Return the value of the root node of a given `MoveTree` ## Return the value of the root node of a given `MoveTree`
if mTree.children == []: if mTree.children == []:
return evaluate(mTree.game) return mTree.game.evaluate()
var bestVal: int var bestVal = LoVal
var tmpVal: int
if mTree.game.toMove == Color.White:
bestVal = LoVal
for child in mTree.children: for child in mTree.children:
tmpVal = minimax(child) var tmpVal = -negaMax(child)
bestVal = max(bestVal, tmpVal) bestVal = max(bestVal, tmpVal)
else:
bestVal = HiVal
for child in mTree.children:
tmpVal = minimax(child)
bestVal = min(bestVal, tmpVal)
return bestVal return bestVal
proc bestMove*(game: Game, depth: int): Move = proc bestMove*(game: Game, depth: int): Move =
## Generate a MoveTree of a `game` with a given `depth`, run Minimax and return ## Generate a MoveTree of a `game` with a given `depth`, run negaMax and return
## the best evaluated move. ## the best evaluated move.
var moves = game.genLegalMoves(game.toMove) var moves = game.genLegalMoves(game.toMove)
var bestMove: Move var bestMove: Move
var bestEval: int var bestEval: int
if game.toMove == Color.White:
bestEval = LoVal bestEval = LoVal
else:
bestEval = HiVal
for move in moves: for move in moves:
var tmpGame = game var tmpGame = game
tmpGame.checkedMove(move) tmpGame.checkedMove(move)
var tmpEval = tmpGame.evaluate() var tmpMTree = tmpGame.spanMoveTree(depth)
if game.toMove == Color.White: var tmpEval = -tmpMTree.negaMax()
echo("move:", moveToNotation(move),"; eval:", tmpEval)
if tmpEval > bestEval: if tmpEval > bestEval:
bestEval = tmpEval bestEval = tmpEval
bestMove = move bestMove = move
else:
if tmpEval < bestEval:
bestEval = tmpEval
bestMove = move
return bestMove return bestMove

@ -1,5 +1,4 @@
import einheit import einheit
import algorithm
import ./chess import ./chess
import ./engine import ./engine
@ -12,7 +11,7 @@ testSuite GameTest of TestSuite:
method setup() = method setup() =
self.game = initGame() self.game = initGame()
method testPieceEval() = method testPieceEvalStalemate() =
self.game = initGame([ self.game = initGame([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, WKing, 0, 0, 0, 0, 0, 0, 0, WKing, 0, 0, 0, 0,
@ -37,10 +36,10 @@ testSuite GameTest of TestSuite:
0, 0, 0, 0, 0, BKing, 0, 0, 0, 0, 0, 0, 0, BKing, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0
], Color.Black) ], Color.Black)
var mTree = self.game.spanMoveTree(2) var mTree = self.game.spanMoveTree(1)
self.check(mTree.children == []) self.check(mTree.children == [])
method testManualMiniMaxEval() = method testBestMoveProm() =
self.game = initGame([ self.game = initGame([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, WKing, 0, 0, 0, 0, 0, 0, 0, WKing, 0, 0, 0, 0,
@ -48,15 +47,18 @@ testSuite GameTest of TestSuite:
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, WPawn, BKing, 0, 0, 0, 0, 0, WPawn, 0, BKing, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0
], Color.Black) ], Color.White)
var mTree = self.game.spanMoveTree(2) var testBestMove = self.game.bestMove(2)
var evaluation = mTree.minimax() self.check(testBestMove.start != 0)
self.check(evaluation == 0) self.check(indToField(testBestMove.start) == "e7")
self.check(indToField(testBestMove.dest) == "e8")
method testBestMove() =
var testGame = initGame([ method testBestMoveStopProm() =
self.game = initGame([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, WKing, 0, 0, 0, 0, 0, 0, 0, WKing, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@ -66,10 +68,41 @@ testSuite GameTest of TestSuite:
0, 0, 0, 0, WPawn, BKing, 0, 0, 0, 0, 0, 0, WPawn, BKing, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0
], Color.Black) ], Color.Black)
var testBestMove = testGame.bestMove(2) var testBestMove = self.game.bestMove(2)
self.check(testBestMove.start != 0)
self.check(indToField(testBestMove.start) == "c7") self.check(indToField(testBestMove.start) == "c7")
self.check(indToField(testBestMove.dest) == "d7") self.check(indToField(testBestMove.dest) == "d7")
method testBestMoveTacticBlack() =
self.game = initGame([
0, 0, 0, 0, 0, 0, 0, 0,
0, WRook, 0, WKing, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, WPawn, 0, 0, 0, 0, 0,
0, BPawn, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, BRook, 0, 0, 0, BKing, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
], Color.Black)
var testBestMove = self.game.bestMove(2)
self.check(testBestMove.start != 0)
self.check(indToField(testBestMove.start) != "g5" or indToField(testBestMove.dest) != "f4")
method testBestMoveTacticWhite() =
self.game = initGame([
0, 0, 0, 0, 0, 0, 0, 0,
0, WRook, 0, WKing, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, WPawn, 0, 0, 0, 0, 0, 0,
0, 0, BPawn, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, BRook, 0, 0, 0, BKing, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
], Color.White)
var testBestMove = self.game.bestMove(2)
self.check(testBestMove.start != 0)
self.check(indToField(testBestMove.start) != "g4" or indToField(testBestMove.dest) != "f5")
when isMainModule: when isMainModule:
einheit.runTests() einheit.runTests()

Loading…
Cancel
Save