refactoring: cleanup engine code and add alpha beta to readme

master
TiynGER 4 years ago
parent b7480a0cd4
commit 3c97395ce8

@ -38,7 +38,8 @@ 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
[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. For the evaluation function each piece has a corresponding value.
Additionally there are [piece-square tables](https://www.chessprogramming.org/Piece-Square_Tables) Additionally there are [piece-square tables](https://www.chessprogramming.org/Piece-Square_Tables)
to encurage putting pieces on active squares. to encurage putting pieces on active squares.

@ -667,8 +667,8 @@ proc genLegalKingMoves(chess: Chess, field: int, color: Color): seq[Move] =
proc genLegalMoves(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`. ## Generates all legal moves in a `chess` starting from `field` for a `color`.
var legal_moves = newSeq[Move]() var legal_moves = newSeq[Move]()
var target = ord(color) * chess.board[field] var target = abs(chess.board[field])
if 0 < target and target < WEnPassant: if WPawn <= target and target <= WKing:
legal_moves = case target: legal_moves = case target:
of WPawn: of WPawn:
chess.genLegalPawnMoves(field, color) chess.genLegalPawnMoves(field, color)

@ -10,14 +10,15 @@ type
children: seq[Movetree] children: seq[Movetree]
const const
PawnVal = 10 ## `PawnVal` is the engines value for a pawn. PawnVal = 100 ## `PawnVal` is the engines value for a pawn.
KnightVal = 31 ## `KnightVal` is the engines value for a knight. KnightVal = 310 ## `KnightVal` is the engines value for a knight.
BishopVal = 33 ## `BishopVal` is the engines value for a bishop. BishopVal = 330 ## `BishopVal` is the engines value for a bishop.
RookVal = 50 ## `RookVal` is the engines value for a rook. RookVal = 500 ## `RookVal` is the engines value for a rook.
QueenVal = 90 ## `QueenVal` is the engines value for a queen. QueenVal = 900 ## `QueenVal` is the engines value for a queen.
CheckmateVal = -10000 ## `CheckmateVal` is the engines value for a checkmate. CheckmateVal = -100000 ## `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.
LoVal = -1000000 ## `LoVal` is a value always lower than any evaluation. HiVal = 10000 ## `LoVal` is a value always lower than any evaluation.
LoVal = -HiVal ## `LoVal` is a value always lower than any evaluation.
pawnTable = [ pawnTable = [
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,
@ -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, 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 = [ knightTable = [
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,
@ -45,7 +46,7 @@ const
0, -50, -40, -30, -30, -30, -30, -40, -50, 0, 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,
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 = [ bishopTable = [
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,
@ -59,7 +60,7 @@ const
0, -20, -10, -10, -10, -10, -10, -10, -20, 0, 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,
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 = [ rookTable = [
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,
@ -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, 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 = [ queenTable = [
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,
@ -87,7 +88,7 @@ const
0, -20, -10, -10, -5, -5, -10, -10, -20, 0, 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,
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 = [ kingTable = [
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,
@ -101,49 +102,40 @@ const
0, -30, -40, -40, -50, -50, -40, -40, -30, 0, 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,
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 = proc pieceEval(chess: Chess): int =
## Returns the evaluation of existing pieces on the `board` ## Returns the evaluation of existing pieces on the `board`
var evaluation = DrawVal var evaluation: int
for ind, square in chess.board: for ind, piece in chess.board:
case square: let tmpEval = case piece:
of WPawn: of WPawn:
evaluation += ord(Color.White) * PawnVal ord(Color.White) * PawnVal + ord(Color.White) * pawnTable[ind]
evaluation += ord(Color.White) * pawnTable[ind]
of WKnight: of WKnight:
evaluation += ord(Color.White) * KnightVal ord(Color.White) * KnightVal + ord(Color.White) * knightTable[ind]
evaluation += ord(Color.White) * knightTable[ind]
of WBishop: of WBishop:
evaluation += ord(Color.White) * BishopVal ord(Color.White) * BishopVal + ord(Color.White) * bishopTable[ind]
evaluation += ord(Color.White) * bishopTable[ind]
of WRook: of WRook:
evaluation += ord(Color.White) * RookVal ord(Color.White) * RookVal + ord(Color.White) * rookTable[ind]
evaluation += ord(Color.White) * rookTable[ind]
of WQueen: of WQueen:
evaluation += ord(Color.White) * QueenVal ord(Color.White) * QueenVal + ord(Color.White) * queenTable[ind]
evaluation += ord(Color.White) * queenTable[ind]
of WKing: of WKing:
evaluation += ord(Color.White) * kingTable[ind] ord(Color.White) * kingTable[ind]
of BPawn: of BPawn:
evaluation += ord(Color.Black) * PawnVal ord(Color.Black) * PawnVal + ord(Color.Black) * pawnTable.reversed[ind]
evaluation += ord(Color.Black) * pawnTable.reversed[ind]
of BKnight: of BKnight:
evaluation += ord(Color.Black) * KnightVal ord(Color.Black) * KnightVal + ord(Color.Black) * knightTable.reversed[ind]
evaluation += ord(Color.Black) * knightTable.reversed[ind]
of BBishop: of BBishop:
evaluation += ord(Color.Black) * BishopVal ord(Color.Black) * BishopVal + ord(Color.Black) * bishopTable.reversed[ind]
evaluation += ord(Color.Black) * bishopTable.reversed[ind]
of BRook: of BRook:
evaluation += ord(Color.Black) * RookVal ord(Color.Black) * RookVal + ord(Color.Black) * rookTable.reversed[ind]
evaluation += ord(Color.Black) * rookTable.reversed[ind]
of BQueen: of BQueen:
evaluation += ord(Color.Black) * QueenVal ord(Color.Black) * QueenVal + ord(Color.Black) * queenTable.reversed[ind]
evaluation += ord(Color.Black) * queenTable.reversed[ind]
of BKing: of BKing:
evaluation += ord(Color.White) * kingTable.reversed[ind] ord(Color.White) * kingTable.reversed[ind]
else: else:
continue DrawVal
evaluation += tmpEval
return evaluation return evaluation
proc evaluate(chess: Chess): int = proc evaluate(chess: Chess): int =
@ -163,28 +155,23 @@ proc evaluate(chess: Chess): int =
evaluation = min(DrawVal, evaluation) evaluation = min(DrawVal, evaluation)
return evaluation return evaluation
proc spanMoveTree(chess: Chess, depth: int): MoveTree = proc negaMax(chess: Chess, depth: int, a: int, b: int): int =
## Create and return a Movetree of a given `chess` with a given maximum `depth`. ## Return the value of a given `chess` with `depth` and `a` and `b` for alpha-beta
var mTree: MoveTree ## pruning.
mTree.chess = chess var alpha = a
if depth != 0 and not chess.isCheckmate(chess.toMove) and var beta = b
not chess.isStalemate(chess.toMove): if depth <= 0 or chess.isCheckmate(chess.toMove) or chess.isStalemate(chess.toMove):
let possibleMoves = chess.genLegalMoves(chess.toMove) return chess.evaluate()
for move in possibleMoves: let possibleMoves = chess.genLegalMoves(chess.toMove)
var tmpChess = chess for move in possibleMoves:
tmpChess.checkedMove(move) var tmpChess = chess
mTree.children.add(spanMoveTree(tmpChess, depth-1)) tmpChess.checkedMove(move)
return mTree var tmpVal = -negaMax(tmpChess, depth - 1, -beta, -alpha)
if tmpVal >= beta:
proc negaMax(mTree: MoveTree): int = return beta
## Return the value of the root node of a given `MoveTree` if tmpVal > alpha:
if mTree.children == []: alpha = tmpVal
return mTree.chess.evaluate() return alpha
var bestVal = LoVal
for child in mTree.children:
var tmpVal = -negaMax(child)
bestVal = max(bestVal, tmpVal)
return bestVal
proc bestMove*(chess: Chess, depth: int): Move = proc bestMove*(chess: Chess, depth: int): Move =
## Generate a MoveTree of a `chess` with a given `depth`, run negaMax and return ## 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: for move in moves:
var tmpChess = chess var tmpChess = chess
tmpChess.checkedMove(move) tmpChess.checkedMove(move)
var tmpMTree = tmpChess.spanMoveTree(depth) var tmpEval = -tmpChess.negaMax(depth, LoVal, HiVal)
var tmpEval = -tmpMTree.negaMax()
echo("move:", moveToNotation(move), "; eval:", tmpEval) echo("move:", moveToNotation(move), "; eval:", tmpEval)
if tmpEval > bestEval: if tmpEval > bestEval:
bestEval = tmpEval bestEval = tmpEval

@ -25,20 +25,6 @@ testSuite ChessTest of TestSuite:
var pieceEvaluation = self.chess.pieceEval() var pieceEvaluation = self.chess.pieceEval()
self.check(pieceEvaluation == 0) 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() = method testBestMoveProm() =
self.chess = initChess([ self.chess = initChess([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

Loading…
Cancel
Save