mirror of
https://github.com/tiyn/yeschess.git
synced 2025-04-01 14:47:44 +02:00
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.
This commit is contained in:
parent
9ea20b6c1f
commit
9095cf8eee
@ -38,4 +38,4 @@ Due to easier off the board checking a
|
||||
### Engine
|
||||
|
||||
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.
|
||||
RookVal = 5 ## `RookVal` is the engines value for a rook.
|
||||
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.
|
||||
HiVal = 1000000 ## `HiVal` is the highest possible value (used in minimax).
|
||||
LoVal = -HiVal ## `LoVal` is the lowest possible value (used in minimax).
|
||||
LoVal = -1000000 ## `LoVal` is a value always lower than any evaluation.
|
||||
|
||||
proc pieceEval*(game: Game): int =
|
||||
## Returns the evaluation of existing pieces on the `board`
|
||||
@ -50,14 +49,25 @@ proc pieceEval*(game: Game): int =
|
||||
proc evaluate(game: Game): int =
|
||||
## Returns a complete evaluation of a `game` with player `toMove` about to make
|
||||
## 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
|
||||
|
||||
proc spanMoveTree*(game: Game, depth: int): MoveTree =
|
||||
## Create and return a Movetree of a given `game` with a given maximum `depth`.
|
||||
var mTree: MoveTree
|
||||
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)
|
||||
for move in possibleMoves:
|
||||
var tmpGame = game
|
||||
@ -65,46 +75,30 @@ proc spanMoveTree*(game: Game, depth: int): MoveTree =
|
||||
mTree.children.add(spanMoveTree(tmpGame, depth-1))
|
||||
return mTree
|
||||
|
||||
proc minimax*(mTree: MoveTree): int =
|
||||
proc negaMax*(mTree: MoveTree): int =
|
||||
## Return the value of the root node of a given `MoveTree`
|
||||
if mTree.children == []:
|
||||
return evaluate(mTree.game)
|
||||
var bestVal: int
|
||||
var tmpVal: int
|
||||
if mTree.game.toMove == Color.White:
|
||||
bestVal = LoVal
|
||||
for child in mTree.children:
|
||||
tmpVal = minimax(child)
|
||||
bestVal = max(bestVal, tmpVal)
|
||||
else:
|
||||
bestVal = HiVal
|
||||
for child in mTree.children:
|
||||
tmpVal = minimax(child)
|
||||
bestVal = min(bestVal, tmpVal)
|
||||
return mTree.game.evaluate()
|
||||
var bestVal = LoVal
|
||||
for child in mTree.children:
|
||||
var tmpVal = -negaMax(child)
|
||||
bestVal = max(bestVal, tmpVal)
|
||||
return bestVal
|
||||
|
||||
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.
|
||||
var moves = game.genLegalMoves(game.toMove)
|
||||
var bestMove: Move
|
||||
var bestEval: int
|
||||
if game.toMove == Color.White:
|
||||
bestEval = LoVal
|
||||
else:
|
||||
bestEval = HiVal
|
||||
bestEval = LoVal
|
||||
for move in moves:
|
||||
var tmpGame = game
|
||||
tmpGame.checkedMove(move)
|
||||
var tmpEval = tmpGame.evaluate()
|
||||
if game.toMove == Color.White:
|
||||
if tmpEval > bestEval:
|
||||
bestEval = tmpEval
|
||||
bestMove = move
|
||||
else:
|
||||
if tmpEval < bestEval:
|
||||
bestEval = tmpEval
|
||||
bestMove = move
|
||||
var tmpMTree = tmpGame.spanMoveTree(depth)
|
||||
var tmpEval = -tmpMTree.negaMax()
|
||||
echo("move:", moveToNotation(move),"; eval:", tmpEval)
|
||||
if tmpEval > bestEval:
|
||||
bestEval = tmpEval
|
||||
bestMove = move
|
||||
return bestMove
|
||||
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import einheit
|
||||
import algorithm
|
||||
|
||||
import ./chess
|
||||
import ./engine
|
||||
@ -12,7 +11,7 @@ testSuite GameTest of TestSuite:
|
||||
method setup() =
|
||||
self.game = initGame()
|
||||
|
||||
method testPieceEval() =
|
||||
method testPieceEvalStalemate() =
|
||||
self.game = initGame([
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, WKing, 0, 0, 0, 0,
|
||||
@ -37,10 +36,28 @@ testSuite GameTest of TestSuite:
|
||||
0, 0, 0, 0, 0, BKing, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
], Color.Black)
|
||||
var mTree = self.game.spanMoveTree(2)
|
||||
var mTree = self.game.spanMoveTree(1)
|
||||
self.check(mTree.children == [])
|
||||
|
||||
method testManualMiniMaxEval() =
|
||||
method testBestMoveProm() =
|
||||
self.game = initGame([
|
||||
0, 0, 0, 0, 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,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, WPawn, 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) == "e7")
|
||||
self.check(indToField(testBestMove.dest) == "e8")
|
||||
|
||||
|
||||
|
||||
method testBestMoveStopProm() =
|
||||
self.game = initGame([
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, WKing, 0, 0, 0, 0,
|
||||
@ -51,25 +68,41 @@ testSuite GameTest of TestSuite:
|
||||
0, 0, 0, 0, WPawn, BKing, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
], Color.Black)
|
||||
var mTree = self.game.spanMoveTree(2)
|
||||
var evaluation = mTree.minimax()
|
||||
self.check(evaluation == 0)
|
||||
|
||||
method testBestMove() =
|
||||
var testGame = initGame([
|
||||
0, 0, 0, 0, 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,
|
||||
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, 0, 0, 0, 0, 0
|
||||
], 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.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:
|
||||
einheit.runTests()
|
||||
|
Loading…
x
Reference in New Issue
Block a user