diff --git a/README.md b/README.md index 8b88bb7..16b4be1 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,6 @@ Due to easier off the board checking a The engine uses a simple implementation of the [NegaMax](https://www.chessprogramming.org/NegaMax)-algorithm. +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 31b923e..9ed76c9 100644 --- a/src/chess.nim +++ b/src/chess.nim @@ -36,10 +36,10 @@ type b: int # `b` describes the amount of bishops. r: int # `r` describes the amount of rooks. q: int # `q` describes the amount of queens. - Pieces = array[10,int] + Pieces = array[10, int] const - Block = 999 ## \ + Block = 999 ## \ ## `Block` is the value assigned to empty blocked fields in a board. WPawn* = 1 ## `WPawn` is the value assigned to a square in a board with a white pawn. @@ -54,9 +54,9 @@ const WQueen* = 5 ## \ ## `WQueen` is the value assigned to a square in a board with a white ## queen. - WKing = 6 ## \ + WKing* = 6 ## \ ## `WKing` is the value assigned to a square in a board with a white king. - WEnPassant = 7 ## \ + WEnPassant = 7 ## \ ## `WEnPassant` is assigned to a square in a board with an invisible white ## en passant pawn. BPawn* = -WPawn ## \ @@ -71,9 +71,9 @@ const ## `BRook` is the value assigned to a square in a board with a black rook. BQueen* = -WQueen ## \ ## `BQueen` is the value assigned to a square in a board with a black queen. - BKing = -WKing ## \ + BKing* = -WKing ## \ ## `BKing` is the value assigned to a square in a board with a black king. - BEnPassant = -WEnPassant ## \ + BEnPassant = -WEnPassant ## \ ## `BEnPassant` is assigned to a square in a board with an invisible black ## en passant pawn. N = 10 ## `N` describes a move a field up the board from whites perspective. @@ -205,7 +205,8 @@ proc moveToNotation*(move: Move): string = res.add(dest) var color = move.color var prom = PieceChar[move.prom] - if (color == Color.White and dest[1] == '8') or (color == Color.Black and dest[1] == '1'): + if (color == Color.White and dest[1] == '8') or (color == Color.Black and + dest[1] == '1'): res.add(prom) return res @@ -812,8 +813,8 @@ proc threeMoveRep(chess: Chess): bool = var lastCastleRights = chess.previousCastleRights[chess.previousBoard.high] var reps = 0 for stateInd in (chess.previousBoard.low)..(chess.previousBoard.high): - if (chess.previousBoard[stateInd] == lastState and chess.previousCastleRights[ - stateInd] == lastCastleRights): + if (chess.previousBoard[stateInd] == lastState and + chess.previousCastleRights[stateInd] == lastCastleRights): reps = reps + 1 return reps >= 3 @@ -837,10 +838,12 @@ proc checkInsufficientMaterial(board: Board): bool = index = piece - WPawn # map lowest value to 0 pieces[index] += 1 elif piece <= BPawn and piece >= BRook: - index = WRook - piece # map black pieces after whites + index = WRook - piece # map black pieces after whites pieces[index] += 1 - let wpieces: PieceAmount = (pieces[0], pieces[1], pieces[2], pieces[3], pieces[4]) - let bpieces: PieceAmount = (pieces[5], pieces[6], pieces[7], pieces[8], pieces[9]) + let wpieces: PieceAmount = (pieces[0], pieces[1], pieces[2], pieces[3], + pieces[4]) + let bpieces: PieceAmount = (pieces[5], pieces[6], pieces[7], pieces[8], + pieces[9]) return (wpieces in InsufficientMaterial) and (bpieces in InsufficientMaterial) proc isStalemate*(chess: Chess, color: Color): bool = diff --git a/src/engine.nim b/src/engine.nim index bb07dd0..9cbe43f 100644 --- a/src/engine.nim +++ b/src/engine.nim @@ -1,47 +1,147 @@ +import algorithm + import ./chess.nim type - MoveTree* = object + MoveTree = object ## `Movetree` is a visualization for possible moves. - chess*: Chess + chess: Chess evaluation: float - children*: seq[Movetree] + children: seq[Movetree] const - PawnVal = 1 ## `PawnVal` is the engines value for a pawn. - KnightVal = 3 ## `KnightVal` is the engines value for a knight. - 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. + 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. + 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, 5, 10, 10, -20, -20, 10, 10, 5, 0, + 0, 5, -5, -10, 0, 0, -10, -5, 5, 0, + 0, 0, 0, 0, 20, 20, 0, 0, 0, 0, + 0, 5, 5, 10, 25, 25, 10, 5, 5, 0, + 0, 10, 10, 20, 30, 30, 20, 10, 10, 0, + 0, 50, 50, 50, 50, 50, 50, 50, 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 + ] ## `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, + 0, -50, -40, -30, -30, -30, -30, -40, -50, 0, + 0, -40, -20, 0, 5, 5, 0, -20, -40, 0, + 0, -30, 0, 10, 15, 15, 10, 0, -30, 0, + 0, -30, 5, 15, 20, 20, 15, 5, -30, 0, + 0, -30, 0, 15, 20, 20, 15, 0, -30, 0, + 0, -30, 5, 10, 15, 15, 10, 5, -30, 0, + 0, -40, -20, 0, 0, 0, 0, -20, -40, 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 + ] ## `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, + 0, -20, -10, -10, -10, -10, -10, -10, -20, 0, + 0, -10, 5, 0, 0, 0, 0, 5, -10, 0, + 0, -10, 10, 10, 10, 10, 10, 10, -10, 0, + 0, -10, 0, 10, 10, 10, 10, 0, -10, 0, + 0, -10, 5, 5, 10, 10, 5, 5, -10, 0, + 0, -10, 0, 5, 10, 10, 5, 0, -10, 0, + 0, -10, 0, 0, 0, 0, 0, 0, -10, 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 + ] ## `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, + 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, + 0, -5, 0, 0, 0, 0, 0, 0, -5, 0, + 0, -5, 0, 0, 0, 0, 0, 0, -5, 0, + 0, -5, 0, 0, 0, 0, 0, 0, -5, 0, + 0, -5, 0, 0, 0, 0, 0, 0, -5, 0, + 0, -5, 0, 0, 0, 0, 0, 0, -5, 0, + 0, 5, 10, 10, 10, 10, 10, 10, 5, 0, + 0, 0, 0, 0, 0, 0, 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. + queenTable = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, -20, -10, -10, -5, -5, -10, -10, -20, 0, + 0, -10, 0, 0, 0, 0, 5, 0, -10, 0, + 0, -10, 0, 5, 5, 5, 5, 0, -10, 0, + 0, -10, 0, 5, 5, 5, 5, 0, -10, 0, + 0, -10, 0, 5, 5, 5, 5, 0, -10, 0, + 0, -10, 0, 5, 5, 5, 5, 0, -10, 0, + 0, -10, 0, 0, 0, 0, 0, 0, -10, 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 + ] ## `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, + 0, 20, 30, 10, 0, 0, 10, 30, 20, 0, + 0, 20, 20, 0, 0, 0, 0, 20, 20, 0, + 0, -10, -20, -20, -20, -20, -20, -20, -10, 0, + 0, -20, -30, -30, -40, -40, -30, -30, -20, 0, + 0, -30, -40, -40, -50, -50, -40, -40, -30, 0, + 0, -30, -40, -40, -50, -50, -40, -40, -30, 0, + 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 + ] ## `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 square in chess.board: + for ind, square in chess.board: case square: of WPawn: evaluation += ord(Color.White) * PawnVal + evaluation += ord(Color.White) * pawnTable[ind] of WKnight: evaluation += ord(Color.White) * KnightVal + evaluation += ord(Color.White) * knightTable[ind] of WBishop: evaluation += ord(Color.White) * BishopVal + evaluation += ord(Color.White) * bishopTable[ind] of WRook: evaluation += ord(Color.White) * RookVal + evaluation += ord(Color.White) * rookTable[ind] of WQueen: evaluation += ord(Color.White) * QueenVal + evaluation += ord(Color.White) * queenTable[ind] + of WKing: + evaluation += ord(Color.White) * kingTable[ind] of BPawn: evaluation += ord(Color.Black) * PawnVal + evaluation += ord(Color.Black) * pawnTable.reversed[ind] of BKnight: evaluation += ord(Color.Black) * KnightVal + evaluation += ord(Color.Black) * knightTable.reversed[ind] of BBishop: evaluation += ord(Color.Black) * BishopVal + evaluation += ord(Color.Black) * bishopTable.reversed[ind] of BRook: evaluation += ord(Color.Black) * RookVal + evaluation += ord(Color.Black) * rookTable.reversed[ind] of BQueen: evaluation += ord(Color.Black) * QueenVal + evaluation += ord(Color.Black) * queenTable.reversed[ind] + of BKing: + evaluation += ord(Color.White) * kingTable.reversed[ind] else: continue return evaluation @@ -67,7 +167,8 @@ 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): + 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 @@ -97,7 +198,7 @@ proc bestMove*(chess: Chess, depth: int): Move = tmpChess.checkedMove(move) var tmpMTree = tmpChess.spanMoveTree(depth) var tmpEval = -tmpMTree.negaMax() - echo("move:", moveToNotation(move),"; eval:", tmpEval) + echo("move:", moveToNotation(move), "; eval:", tmpEval) if tmpEval > bestEval: bestEval = tmpEval bestMove = move diff --git a/src/engineTest.nim b/src/engineTest.nim index 9bf3479..dce35a6 100644 --- a/src/engineTest.nim +++ b/src/engineTest.nim @@ -86,7 +86,8 @@ testSuite ChessTest of TestSuite: ], Color.Black) var testBestMove = self.chess.bestMove(2) self.check(testBestMove.start != 0) - self.check(indToField(testBestMove.start) != "g5" or indToField(testBestMove.dest) != "f4") + self.check(indToField(testBestMove.start) != "g5" or indToField( + testBestMove.dest) != "f4") method testBestMoveTacticWhite() = self.chess = initChess([ @@ -101,7 +102,8 @@ testSuite ChessTest of TestSuite: ], Color.White) var testBestMove = self.chess.bestMove(2) self.check(testBestMove.start != 0) - self.check(indToField(testBestMove.start) != "g4" or indToField(testBestMove.dest) != "f5") + self.check(indToField(testBestMove.start) != "g4" or indToField( + testBestMove.dest) != "f5") when isMainModule: diff --git a/src/lichessBridge.nim b/src/lichessBridge.nim index 70ca38f..d108889 100644 --- a/src/lichessBridge.nim +++ b/src/lichessBridge.nim @@ -8,10 +8,10 @@ import ./engine.nim let berserk = pyImport("berserk") var session = berserk.TokenSession(secret.api_token) -var client = berserk.Client(session=session) +var client = berserk.Client(session = session) let engineID = "tiyn-ychess" -let engineDifficulty = 2 +let engineDifficulty = 3 let toAccept = ["tiynger"]