engine: added piece-square tables

Piece square tables are a way to encurage the engine to
put pieces on active squares.
Each piece got a corresponding table.
master
TiynGER 4 years ago
parent b3c76fd2c1
commit b7480a0cd4

@ -39,3 +39,6 @@ Due to easier off the board checking a
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.
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.

@ -36,7 +36,7 @@ type
b: int # `b` describes the amount of bishops. b: int # `b` describes the amount of bishops.
r: int # `r` describes the amount of rooks. r: int # `r` describes the amount of rooks.
q: int # `q` describes the amount of queens. q: int # `q` describes the amount of queens.
Pieces = array[10,int] Pieces = array[10, int]
const const
Block = 999 ## \ Block = 999 ## \
@ -54,7 +54,7 @@ const
WQueen* = 5 ## \ WQueen* = 5 ## \
## `WQueen` is the value assigned to a square in a board with a white ## `WQueen` is the value assigned to a square in a board with a white
## queen. ## queen.
WKing = 6 ## \ WKing* = 6 ## \
## `WKing` is the value assigned to a square in a board with a white king. ## `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 ## `WEnPassant` is assigned to a square in a board with an invisible white
@ -71,7 +71,7 @@ const
## `BRook` is the value assigned to a square in a board with a black rook. ## `BRook` is the value assigned to a square in a board with a black rook.
BQueen* = -WQueen ## \ BQueen* = -WQueen ## \
## `BQueen` is the value assigned to a square in a board with a black queen. ## `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. ## `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 ## `BEnPassant` is assigned to a square in a board with an invisible black
@ -205,7 +205,8 @@ proc moveToNotation*(move: Move): string =
res.add(dest) res.add(dest)
var color = move.color var color = move.color
var prom = PieceChar[move.prom] 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) res.add(prom)
return res return res
@ -812,8 +813,8 @@ proc threeMoveRep(chess: Chess): bool =
var lastCastleRights = chess.previousCastleRights[chess.previousBoard.high] var lastCastleRights = chess.previousCastleRights[chess.previousBoard.high]
var reps = 0 var reps = 0
for stateInd in (chess.previousBoard.low)..(chess.previousBoard.high): for stateInd in (chess.previousBoard.low)..(chess.previousBoard.high):
if (chess.previousBoard[stateInd] == lastState and chess.previousCastleRights[ if (chess.previousBoard[stateInd] == lastState and
stateInd] == lastCastleRights): chess.previousCastleRights[stateInd] == lastCastleRights):
reps = reps + 1 reps = reps + 1
return reps >= 3 return reps >= 3
@ -839,8 +840,10 @@ proc checkInsufficientMaterial(board: Board): bool =
elif piece <= BPawn and piece >= BRook: 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 pieces[index] += 1
let wpieces: PieceAmount = (pieces[0], pieces[1], pieces[2], pieces[3], pieces[4]) let wpieces: PieceAmount = (pieces[0], pieces[1], pieces[2], pieces[3],
let bpieces: PieceAmount = (pieces[5], pieces[6], pieces[7], pieces[8], pieces[9]) pieces[4])
let bpieces: PieceAmount = (pieces[5], pieces[6], pieces[7], pieces[8],
pieces[9])
return (wpieces in InsufficientMaterial) and (bpieces in InsufficientMaterial) return (wpieces in InsufficientMaterial) and (bpieces in InsufficientMaterial)
proc isStalemate*(chess: Chess, color: Color): bool = proc isStalemate*(chess: Chess, color: Color): bool =

@ -1,47 +1,147 @@
import algorithm
import ./chess.nim import ./chess.nim
type type
MoveTree* = object MoveTree = object
## `Movetree` is a visualization for possible moves. ## `Movetree` is a visualization for possible moves.
chess*: Chess chess: Chess
evaluation: float evaluation: float
children*: seq[Movetree] children: seq[Movetree]
const const
PawnVal = 1 ## `PawnVal` is the engines value for a pawn. PawnVal = 10 ## `PawnVal` is the engines value for a pawn.
KnightVal = 3 ## `KnightVal` is the engines value for a knight. KnightVal = 31 ## `KnightVal` is the engines value for a knight.
BishopVal = 3 ## `BishopVal` is the engines value for a bishop. BishopVal = 33 ## `BishopVal` is the engines value for a bishop.
RookVal = 5 ## `RookVal` is the engines value for a rook. RookVal = 50 ## `RookVal` is the engines value for a rook.
QueenVal = 9 ## `QueenVal` is the engines value for a queen. QueenVal = 90 ## `QueenVal` is the engines value for a queen.
CheckmateVal = -1000 ## `CheckmateVal` is the engines value for a checkmate. CheckmateVal = -10000 ## `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. 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 = 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 = DrawVal
for square in chess.board: for ind, square in chess.board:
case square: case square:
of WPawn: of WPawn:
evaluation += ord(Color.White) * PawnVal evaluation += ord(Color.White) * PawnVal
evaluation += ord(Color.White) * pawnTable[ind]
of WKnight: of WKnight:
evaluation += ord(Color.White) * KnightVal evaluation += ord(Color.White) * KnightVal
evaluation += ord(Color.White) * knightTable[ind]
of WBishop: of WBishop:
evaluation += ord(Color.White) * BishopVal evaluation += ord(Color.White) * BishopVal
evaluation += ord(Color.White) * bishopTable[ind]
of WRook: of WRook:
evaluation += ord(Color.White) * RookVal evaluation += ord(Color.White) * RookVal
evaluation += ord(Color.White) * rookTable[ind]
of WQueen: of WQueen:
evaluation += ord(Color.White) * QueenVal evaluation += ord(Color.White) * QueenVal
evaluation += ord(Color.White) * queenTable[ind]
of WKing:
evaluation += ord(Color.White) * kingTable[ind]
of BPawn: of BPawn:
evaluation += ord(Color.Black) * PawnVal evaluation += ord(Color.Black) * PawnVal
evaluation += ord(Color.Black) * pawnTable.reversed[ind]
of BKnight: of BKnight:
evaluation += ord(Color.Black) * KnightVal evaluation += ord(Color.Black) * KnightVal
evaluation += ord(Color.Black) * knightTable.reversed[ind]
of BBishop: of BBishop:
evaluation += ord(Color.Black) * BishopVal evaluation += ord(Color.Black) * BishopVal
evaluation += ord(Color.Black) * bishopTable.reversed[ind]
of BRook: of BRook:
evaluation += ord(Color.Black) * RookVal evaluation += ord(Color.Black) * RookVal
evaluation += ord(Color.Black) * rookTable.reversed[ind]
of BQueen: of BQueen:
evaluation += ord(Color.Black) * QueenVal evaluation += ord(Color.Black) * QueenVal
evaluation += ord(Color.Black) * queenTable.reversed[ind]
of BKing:
evaluation += ord(Color.White) * kingTable.reversed[ind]
else: else:
continue continue
return evaluation 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`. ## Create and return a Movetree of a given `chess` with a given maximum `depth`.
var mTree: MoveTree var mTree: MoveTree
mTree.chess = chess 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) let possibleMoves = chess.genLegalMoves(chess.toMove)
for move in possibleMoves: for move in possibleMoves:
var tmpChess = chess var tmpChess = chess
@ -97,7 +198,7 @@ proc bestMove*(chess: Chess, depth: int): Move =
tmpChess.checkedMove(move) tmpChess.checkedMove(move)
var tmpMTree = tmpChess.spanMoveTree(depth) var tmpMTree = tmpChess.spanMoveTree(depth)
var tmpEval = -tmpMTree.negaMax() 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
bestMove = move bestMove = move

@ -86,7 +86,8 @@ testSuite ChessTest of TestSuite:
], Color.Black) ], Color.Black)
var testBestMove = self.chess.bestMove(2) var testBestMove = self.chess.bestMove(2)
self.check(testBestMove.start != 0) 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() = method testBestMoveTacticWhite() =
self.chess = initChess([ self.chess = initChess([
@ -101,7 +102,8 @@ testSuite ChessTest of TestSuite:
], Color.White) ], Color.White)
var testBestMove = self.chess.bestMove(2) var testBestMove = self.chess.bestMove(2)
self.check(testBestMove.start != 0) 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: when isMainModule:

@ -8,10 +8,10 @@ import ./engine.nim
let berserk = pyImport("berserk") let berserk = pyImport("berserk")
var session = berserk.TokenSession(secret.api_token) var session = berserk.TokenSession(secret.api_token)
var client = berserk.Client(session=session) var client = berserk.Client(session = session)
let engineID = "tiyn-ychess" let engineID = "tiyn-ychess"
let engineDifficulty = 2 let engineDifficulty = 3
let toAccept = ["tiynger"] let toAccept = ["tiynger"]

Loading…
Cancel
Save