From 3c97395ce82db9c40fef187973b9451d0a17eafb Mon Sep 17 00:00:00 2001 From: TiynGER Date: Mon, 3 May 2021 17:09:48 +0200 Subject: [PATCH] refactoring: cleanup engine code and add alpha beta to readme --- README.md | 3 +- src/chess.nim | 4 +- src/engine.nim | 114 ++++++++++++++++++++------------------------- src/engineTest.nim | 14 ------ 4 files changed, 54 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 16b4be1..250729b 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,8 @@ Due to easier off the board checking a ### Engine The engine uses a simple implementation of the -[NegaMax](https://www.chessprogramming.org/NegaMax)-algorithm. +[NegaMax](https://www.chessprogramming.org/NegaMax)-algorithm with +[Alpha-Beta-Pruning](https://www.chessprogramming.org/Alpha-Beta#Negamax_Framework). For the evaluation function each piece has a corresponding value. Additionally there are [piece-square tables](https://www.chessprogramming.org/Piece-Square_Tables) to encurage putting pieces on active squares. diff --git a/src/chess.nim b/src/chess.nim index 9ed76c9..e2d515e 100644 --- a/src/chess.nim +++ b/src/chess.nim @@ -667,8 +667,8 @@ proc genLegalKingMoves(chess: Chess, field: int, color: Color): seq[Move] = proc genLegalMoves(chess: Chess, field: int, color: Color): seq[Move] = ## Generates all legal moves in a `chess` starting from `field` for a `color`. var legal_moves = newSeq[Move]() - var target = ord(color) * chess.board[field] - if 0 < target and target < WEnPassant: + var target = abs(chess.board[field]) + if WPawn <= target and target <= WKing: legal_moves = case target: of WPawn: chess.genLegalPawnMoves(field, color) diff --git a/src/engine.nim b/src/engine.nim index 9cbe43f..11f4a12 100644 --- a/src/engine.nim +++ b/src/engine.nim @@ -10,14 +10,15 @@ type children: seq[Movetree] const - PawnVal = 10 ## `PawnVal` is the engines value for a pawn. - KnightVal = 31 ## `KnightVal` is the engines value for a knight. - BishopVal = 33 ## `BishopVal` is the engines value for a bishop. - RookVal = 50 ## `RookVal` is the engines value for a rook. - QueenVal = 90 ## `QueenVal` is the engines value for a queen. - CheckmateVal = -10000 ## `CheckmateVal` is the engines value for a checkmate. - DrawVal = 0 ## `DrawVal` is the engines value for a draw. - LoVal = -1000000 ## `LoVal` is a value always lower than any evaluation. + PawnVal = 100 ## `PawnVal` is the engines value for a pawn. + KnightVal = 310 ## `KnightVal` is the engines value for a knight. + BishopVal = 330 ## `BishopVal` is the engines value for a bishop. + RookVal = 500 ## `RookVal` is the engines value for a rook. + QueenVal = 900 ## `QueenVal` is the engines value for a queen. + CheckmateVal = -100000 ## `CheckmateVal` is the engines value for a checkmate. + DrawVal = 0 ## `DrawVal` is the engines value for a draw. + HiVal = 10000 ## `LoVal` is a value always lower than any evaluation. + LoVal = -HiVal ## `LoVal` is a value always lower than any evaluation. pawnTable = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -31,7 +32,7 @@ const 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 - ] ## `pawnTable` is the piece-square table for pawns. + ] ## `pawnTable` is the piece-square table for pawns. knightTable = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -45,7 +46,7 @@ const 0, -50, -40, -30, -30, -30, -30, -40, -50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] ## `knightTable` is the piece-square table for pawns. + ] ## `knightTable` is the piece-square table for pawns. bishopTable = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -59,7 +60,7 @@ const 0, -20, -10, -10, -10, -10, -10, -10, -20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] ## `bishopTable` is the piece-square table for pawns. + ] ## `bishopTable` is the piece-square table for pawns. rookTable = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -73,7 +74,7 @@ const 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 - ] ## `rookTable` is the piece-square table for pawns. + ] ## `rookTable` is the piece-square table for pawns. queenTable = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -87,7 +88,7 @@ const 0, -20, -10, -10, -5, -5, -10, -10, -20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] ## `queenTable` is the piece-square table for pawns. + ] ## `queenTable` is the piece-square table for pawns. kingTable = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -101,49 +102,40 @@ const 0, -30, -40, -40, -50, -50, -40, -40, -30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] ## `kingTable` is the piece-square table for pawns. + ] ## `kingTable` is the piece-square table for pawns. proc pieceEval(chess: Chess): int = ## Returns the evaluation of existing pieces on the `board` - var evaluation = DrawVal - for ind, square in chess.board: - case square: + var evaluation: int + for ind, piece in chess.board: + let tmpEval = case piece: of WPawn: - evaluation += ord(Color.White) * PawnVal - evaluation += ord(Color.White) * pawnTable[ind] + ord(Color.White) * PawnVal + ord(Color.White) * pawnTable[ind] of WKnight: - evaluation += ord(Color.White) * KnightVal - evaluation += ord(Color.White) * knightTable[ind] + ord(Color.White) * KnightVal + ord(Color.White) * knightTable[ind] of WBishop: - evaluation += ord(Color.White) * BishopVal - evaluation += ord(Color.White) * bishopTable[ind] + ord(Color.White) * BishopVal + ord(Color.White) * bishopTable[ind] of WRook: - evaluation += ord(Color.White) * RookVal - evaluation += ord(Color.White) * rookTable[ind] + ord(Color.White) * RookVal + ord(Color.White) * rookTable[ind] of WQueen: - evaluation += ord(Color.White) * QueenVal - evaluation += ord(Color.White) * queenTable[ind] + ord(Color.White) * QueenVal + ord(Color.White) * queenTable[ind] of WKing: - evaluation += ord(Color.White) * kingTable[ind] + ord(Color.White) * kingTable[ind] of BPawn: - evaluation += ord(Color.Black) * PawnVal - evaluation += ord(Color.Black) * pawnTable.reversed[ind] + ord(Color.Black) * PawnVal + ord(Color.Black) * pawnTable.reversed[ind] of BKnight: - evaluation += ord(Color.Black) * KnightVal - evaluation += ord(Color.Black) * knightTable.reversed[ind] + ord(Color.Black) * KnightVal + ord(Color.Black) * knightTable.reversed[ind] of BBishop: - evaluation += ord(Color.Black) * BishopVal - evaluation += ord(Color.Black) * bishopTable.reversed[ind] + ord(Color.Black) * BishopVal + ord(Color.Black) * bishopTable.reversed[ind] of BRook: - evaluation += ord(Color.Black) * RookVal - evaluation += ord(Color.Black) * rookTable.reversed[ind] + ord(Color.Black) * RookVal + ord(Color.Black) * rookTable.reversed[ind] of BQueen: - evaluation += ord(Color.Black) * QueenVal - evaluation += ord(Color.Black) * queenTable.reversed[ind] + ord(Color.Black) * QueenVal + ord(Color.Black) * queenTable.reversed[ind] of BKing: - evaluation += ord(Color.White) * kingTable.reversed[ind] + ord(Color.White) * kingTable.reversed[ind] else: - continue + DrawVal + evaluation += tmpEval return evaluation proc evaluate(chess: Chess): int = @@ -163,28 +155,23 @@ proc evaluate(chess: Chess): int = evaluation = min(DrawVal, evaluation) return evaluation -proc spanMoveTree(chess: Chess, depth: int): MoveTree = - ## Create and return a Movetree of a given `chess` with a given maximum `depth`. - var mTree: MoveTree - mTree.chess = chess - if depth != 0 and not chess.isCheckmate(chess.toMove) and - not chess.isStalemate(chess.toMove): - let possibleMoves = chess.genLegalMoves(chess.toMove) - for move in possibleMoves: - var tmpChess = chess - tmpChess.checkedMove(move) - mTree.children.add(spanMoveTree(tmpChess, depth-1)) - return mTree - -proc negaMax(mTree: MoveTree): int = - ## Return the value of the root node of a given `MoveTree` - if mTree.children == []: - return mTree.chess.evaluate() - var bestVal = LoVal - for child in mTree.children: - var tmpVal = -negaMax(child) - bestVal = max(bestVal, tmpVal) - return bestVal +proc negaMax(chess: Chess, depth: int, a: int, b: int): int = + ## Return the value of a given `chess` with `depth` and `a` and `b` for alpha-beta + ## pruning. + var alpha = a + var beta = b + if depth <= 0 or chess.isCheckmate(chess.toMove) or chess.isStalemate(chess.toMove): + return chess.evaluate() + let possibleMoves = chess.genLegalMoves(chess.toMove) + for move in possibleMoves: + var tmpChess = chess + tmpChess.checkedMove(move) + var tmpVal = -negaMax(tmpChess, depth - 1, -beta, -alpha) + if tmpVal >= beta: + return beta + if tmpVal > alpha: + alpha = tmpVal + return alpha proc bestMove*(chess: Chess, depth: int): Move = ## Generate a MoveTree of a `chess` with a given `depth`, run negaMax and return @@ -196,8 +183,7 @@ proc bestMove*(chess: Chess, depth: int): Move = for move in moves: var tmpChess = chess tmpChess.checkedMove(move) - var tmpMTree = tmpChess.spanMoveTree(depth) - var tmpEval = -tmpMTree.negaMax() + var tmpEval = -tmpChess.negaMax(depth, LoVal, HiVal) echo("move:", moveToNotation(move), "; eval:", tmpEval) if tmpEval > bestEval: bestEval = tmpEval diff --git a/src/engineTest.nim b/src/engineTest.nim index dce35a6..e4208d9 100644 --- a/src/engineTest.nim +++ b/src/engineTest.nim @@ -25,20 +25,6 @@ testSuite ChessTest of TestSuite: var pieceEvaluation = self.chess.pieceEval() self.check(pieceEvaluation == 0) - method testSpanMoveTree() = - self.chess = initChess([ - 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, 0, BKing, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - ], Color.Black) - var mTree = self.chess.spanMoveTree(1) - self.check(mTree.children == []) - method testBestMoveProm() = self.chess = initChess([ 0, 0, 0, 0, 0, 0, 0, 0,