refactoring: absolute path for db, general refactoring

Due to the path for the db being relative it came
to problems when importing the openingBook.nim file
in other modules (especially in ).
To change this i added a variable, that needs to point to the
root directory of the project.
Additionally i set some coding guidelines and enforced them
into the current codebase.
master
TiynGER 4 years ago
parent 80772462da
commit e306de0573

@ -52,3 +52,32 @@ The engine uses a simple implementation of the
For the evaluation function each piece has a corresponding value. For the evaluation function each piece has a corresponding value.
Additionally [piece-square tables](https://www.chessprogramming.org/Piece-Square_Tables) Additionally [piece-square tables](https://www.chessprogramming.org/Piece-Square_Tables)
are used. are used.
## Contributing
### Setup
To setup the project for development you need to create the file
`src/secret.nim`.
It should contain values for the following variables:
```nim
let projectdir* = "<absolute path to root dir of ychess>"
let api_token* = "<lichess api token for bot>"
```
### Code Style Guide
Basic arithmetic operations should be surrounded by spaces for example: `1 + 3`.
This however is not true for negation of a single value (`-1`) or if the
arithmetic operation is done inside array brackets or in iterators (`a+1..3`,
`a[c+3]`).
Determining the length of a string, array, etc should not be done via a function
(`len(array)`) but by appending it like `array.len`.
If statements should not contain outer brackets.
In some cases (especially concatenations of `and` and `or`) inner brackets are
useful to increase readability in complexer logic formulas.
When assigning booleans with logical formulas outer brackets are expected.

@ -153,7 +153,7 @@ proc fieldToInd(field: string): int =
proc indToField(ind: int): string = proc indToField(ind: int): string =
## Calculate and returns field name from board index `ind`. ## Calculate and returns field name from board index `ind`.
let line = int(ind / 10 - 1) let line = int(ind / 10 - 1)
let file_ind = 7 - ((ind) %% 10 - 1) let file_ind = 7 - (ind %% 10 - 1)
for file, i in FileChar: for file, i in FileChar:
if FileChar[file] == file_ind: if FileChar[file] == file_ind:
return $file & $line return $file & $line
@ -162,7 +162,7 @@ proc getMove(start: int, dest: int, prom: int, color: Color): Move =
## Get a move object of the `color` player from `start` to `dest` with an ## Get a move object of the `color` player from `start` to `dest` with an
## eventual promition to `prom`. ## eventual promition to `prom`.
var move = Move(start: start, dest: dest, prom: prom * ord(color), color: color) var move = Move(start: start, dest: dest, prom: prom * ord(color), color: color)
if (prom < WKnight or prom > WQueen): if prom < WKnight or prom > WQueen:
move.prom = WQueen move.prom = WQueen
return move return move
@ -181,7 +181,7 @@ proc notationToMove*(notation: string, color: Color): Move =
var start = fieldToInd(notation[0..1]) var start = fieldToInd(notation[0..1])
var dest = fieldToInd(notation[2..3]) var dest = fieldToInd(notation[2..3])
move = getMove(start, dest, color) move = getMove(start, dest, color)
if (len(notation) > 4): if notation.len > 4:
var promStr = $notation[4] var promStr = $notation[4]
let prom = case promStr: let prom = case promStr:
of "R": of "R":
@ -351,22 +351,22 @@ proc echoBoard*(chess: Chess, color: Color) =
## Prints out the given `board` with its pieces as characters and line ## Prints out the given `board` with its pieces as characters and line
## indices from perspecive of `color`. ## indices from perspecive of `color`.
var line_str: string var line_str: string
if (color == Color.Black): if color == Color.Black:
for i in 0..len(chess.board)-1: for i in 0..chess.board.len-1:
if (chess.board[i] == Block): if chess.board[i] == Block:
continue continue
line_str &= PieceChar[chess.board[i]] & " " line_str &= PieceChar[chess.board[i]] & " "
if ((i + 2) %% 10 == 0): if (i + 2) %% 10 == 0:
line_str &= $(int((i / 10) - 1)) & "\n" line_str &= $int((i / 10) - 1) & "\n"
echo line_str echo line_str
echo "h g f e d c b a" echo "h g f e d c b a"
else: else:
for i in countdown(len(chess.board) - 1, 0): for i in countdown(len(chess.board) - 1, 0):
if (chess.board[i] == Block): if chess.board[i] == Block:
continue continue
line_str &= PieceChar[chess.board[i]] & " " line_str &= PieceChar[chess.board[i]] & " "
if ((i - 1) %% 10 == 0): if (i - 1) %% 10 == 0:
line_str &= $(int((i / 10) - 1)) & "\n" line_str &= $int((i / 10) - 1) & "\n"
echo line_str echo line_str
echo "a b c d e f g h" echo "a b c d e f g h"
@ -374,7 +374,7 @@ proc genPawnAttackDests(chess: Chess, field: int, color: Color): seq[int] =
## Generate possible attack destinations for a pawn with specific `color` ## Generate possible attack destinations for a pawn with specific `color`
## located at index `field` of `chess`. ## located at index `field` of `chess`.
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
if (not field in chess.board.low..chess.board.high): if field < chess.board.low or field > chess.board.high:
return @[] return @[]
var res = newSeq[int]() var res = newSeq[int]()
var dest: int var dest: int
@ -395,7 +395,7 @@ proc genPawnDoubleDests(chess: Chess, field: int, color: Color): seq[int] =
## Generate possible double destinations for a pawn with specific `color` ## Generate possible double destinations for a pawn with specific `color`
## located at index `field` of `chess`. ## located at index `field` of `chess`.
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
if (not field in chess.board.low..chess.board.high): if field < chess.board.low or field > chess.board.high:
return @[] return @[]
var res = newSeq[int]() var res = newSeq[int]()
var dest: int var dest: int
@ -403,12 +403,12 @@ proc genPawnDoubleDests(chess: Chess, field: int, color: Color): seq[int] =
for doubles in Pawn_Moves_White_Double: for doubles in Pawn_Moves_White_Double:
dest = field + doubles * ord(color) dest = field + doubles * ord(color)
target = chess.board[dest] target = chess.board[dest]
if ((target != 0) or ( if target != 0 or
chess.board[dest + (S * ord(color))] != 0)): chess.board[dest + (S * ord(color))] != 0:
continue continue
if (color == Color.White and not (field in fieldToInd("h2")..fieldToInd("a2"))): if color == Color.White and not (field in fieldToInd("h2")..fieldToInd("a2")):
continue continue
if (color == Color.Black and not (field in fieldToInd("h7")..fieldToInd("a7"))): if color == Color.Black and not (field in fieldToInd("h7")..fieldToInd("a7")):
continue continue
res.add(dest) res.add(dest)
return res return res
@ -417,17 +417,17 @@ proc genPawnDests(chess: Chess, field: int, color: Color): seq[int] =
## Generate possible destinations for a pawn with specific `color` located at ## Generate possible destinations for a pawn with specific `color` located at
## index `field` of `chess`. ## index `field` of `chess`.
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
if (not field in chess.board.low..chess.board.high): if field < chess.board.low or field > chess.board.high:
return @[] return @[]
var res = newSeq[int]() var res = newSeq[int]()
var dest: int var dest: int
var target: int var target: int
for move in Pawn_Moves_White: for move in Pawn_Moves_White:
dest = field + move * ord(color) dest = field + move * ord(color)
if (not dest in chess.board.low..chess.board.high): if not dest in chess.board.low..chess.board.high:
continue continue
target = chess.board[dest] target = chess.board[dest]
if (target != 0 and dest != chess.enPassantSquare): if target != 0 and dest != chess.enPassantSquare:
continue continue
res.add(dest) res.add(dest)
res.add(chess.genPawnAttackDests(field, color)) res.add(chess.genPawnAttackDests(field, color))
@ -438,17 +438,17 @@ proc genKnightDests(chess: Chess, field: int, color: Color): seq[int] =
## Generate possible destinations for a knight with specific `color` located ## Generate possible destinations for a knight with specific `color` located
## at index `field` of `chess`. ## at index `field` of `chess`.
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
if (not field in chess.board.low..chess.board.high): if field < chess.board.low or field > chess.board.high:
return @[] return @[]
var res = newSeq[int]() var res = newSeq[int]()
var dest: int var dest: int
var target: int var target: int
for move in Knight_Moves: for move in Knight_Moves:
dest = field + move dest = field + move
if (not dest in chess.board.low..chess.board.high): if not dest in chess.board.low..chess.board.high:
continue continue
target = chess.board[dest] target = chess.board[dest]
if (target == Block or (ord(color) * target > 0)): if target == Block or ord(color) * target > 0:
continue continue
res.add(dest) res.add(dest)
return res return res
@ -458,19 +458,19 @@ proc genSlidePieceDests(chess: Chess, field: int, color: Color, moves: seq[
## Generate possible destinations for a piece with `moves` and specific `color` ## Generate possible destinations for a piece with `moves` and specific `color`
## located at index `field` of `chess`. ## located at index `field` of `chess`.
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
if (not field in chess.board.low..chess.board.high): if field < chess.board.low or field > chess.board.high:
return @[] return @[]
var res = newSeq[int]() var res = newSeq[int]()
var dest: int var dest: int
var target: int var target: int
for move in moves: for move in moves:
dest = field + move dest = field + move
if (not dest in chess.board.low..chess.board.high): if not dest in chess.board.low..chess.board.high:
continue continue
target = chess.board[dest] target = chess.board[dest]
while (target != Block and (ord(color) * target <= 0)): while target != Block and ord(color) * target <= 0:
res.add(dest) res.add(dest)
if (ord(color) * target < 0): if ord(color) * target < 0:
break break
dest = dest + move dest = dest + move
target = chess.board[dest] target = chess.board[dest]
@ -498,7 +498,7 @@ proc genKingCastleDest(chess: Chess, field: int, color: Color): seq[int] =
## Generate possible castle destinations for a king with specific `color` ## Generate possible castle destinations for a king with specific `color`
## located at index `field` of `chess` ## located at index `field` of `chess`
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
if (not field in chess.board.low..chess.board.high): if field < chess.board.low or field > chess.board.high:
return @[] return @[]
var res = newSeq[int]() var res = newSeq[int]()
var dest: int var dest: int
@ -507,14 +507,14 @@ proc genKingCastleDest(chess: Chess, field: int, color: Color): seq[int] =
var half_target: int var half_target: int
for castle in King_Moves_White_Castle: for castle in King_Moves_White_Castle:
dest = field + castle dest = field + castle
if (not dest in chess.board.low..chess.board.high): if not dest in chess.board.low..chess.board.high:
continue continue
target = chess.board[dest] target = chess.board[dest]
half_dest = field + int(castle / 2) half_dest = field + int(castle / 2)
half_target = chess.board[half_dest] half_target = chess.board[half_dest]
if (target == Block or (target != 0)): if target == Block or target != 0:
continue continue
if (half_target == Block or (half_target != 0)): if half_target == Block or half_target != 0:
continue continue
res.add(dest) res.add(dest)
return res return res
@ -523,17 +523,17 @@ proc genKingDests(chess: Chess, field: int, color: Color): seq[int] =
## Generate possible destinations for a king with specific `color` ## Generate possible destinations for a king with specific `color`
## located at index `field` of `chess`. ## located at index `field` of `chess`.
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
if (not field in chess.board.low..chess.board.high): if field < chess.board.low or field > chess.board.high:
return @[] return @[]
var res = newSeq[int]() var res = newSeq[int]()
var dest: int var dest: int
var target: int var target: int
for move in King_Moves: for move in King_Moves:
dest = field + move dest = field + move
if (not dest in chess.board.low..chess.board.high): if not dest in chess.board.low..chess.board.high:
continue continue
target = chess.board[dest] target = chess.board[dest]
if (target == Block or (ord(color) * target > 0)): if target == Block or ord(color) * target > 0:
continue continue
res.add(dest) res.add(dest)
res.add(chess.genKingCastleDest(field, color)) res.add(chess.genKingCastleDest(field, color))
@ -717,11 +717,11 @@ proc castling(chess: var Chess, kstart: int, dest_kingside: bool,
var rstart: int var rstart: int
var rdest: int var rdest: int
var rights: bool var rights: bool
if (dest_kingside): if dest_kingside:
kdest = kstart + E + E kdest = kstart + E + E
rstart = kstart + E + E + E rstart = kstart + E + E + E
rdest = rstart + W + W rdest = rstart + W + W
if (color == Color.White): if color == Color.White:
rights = chess.castleRights.wk rights = chess.castleRights.wk
else: else:
rights = chess.castleRights.bk rights = chess.castleRights.bk
@ -729,12 +729,12 @@ proc castling(chess: var Chess, kstart: int, dest_kingside: bool,
kdest = kstart + W + W kdest = kstart + W + W
rstart = kstart + W + W + W + W rstart = kstart + W + W + W + W
rdest = rstart + E + E + E rdest = rstart + E + E + E
if (color == Color.White): if color == Color.White:
rights = chess.castleRights.wq rights = chess.castleRights.wq
else: else:
rights = chess.castleRights.bq rights = chess.castleRights.bq
if (rights): if rights:
if (dest_kingside): if dest_kingside:
if chess.isAttacked(kstart, color) or chess.isAttacked(kstart+E, color) or if chess.isAttacked(kstart, color) or chess.isAttacked(kstart+E, color) or
chess.isAttacked(kstart+E+E, color) or chess.board[kstart+E] != 0 or chess.isAttacked(kstart+E+E, color) or chess.board[kstart+E] != 0 or
chess.board[kstart+E+E] != 0: chess.board[kstart+E+E] != 0:
@ -760,7 +760,7 @@ proc checkedMove*(chess: var Chess, move: Move): bool {.discardable.} =
let dest = move.dest let dest = move.dest
let color = move.color let color = move.color
let prom = move.prom let prom = move.prom
if (chess.toMove != color or start == -1 or dest == -1): if chess.toMove != color or start == -1 or dest == -1:
return false return false
let piece = chess.board[start] let piece = chess.board[start]
var createEnPassant: bool var createEnPassant: bool
@ -768,17 +768,17 @@ proc checkedMove*(chess: var Chess, move: Move): bool {.discardable.} =
var fiftyMoveRuleReset: bool var fiftyMoveRuleReset: bool
var move: Move var move: Move
move = getMove(start, dest, color) move = getMove(start, dest, color)
if (piece == WKing * ord(color) and (start - dest == (W+W))): if piece == WKing * ord(color) and start - dest == (W+W):
return chess.castling(start, true, color) return chess.castling(start, true, color)
elif (piece == WKing * ord(color) and (start - dest == (E+E))): elif piece == WKing * ord(color) and start - dest == (E+E):
return chess.castling(start, false, color) return chess.castling(start, false, color)
if (piece == WPawn * ord(color)): if piece == WPawn * ord(color):
createEnPassant = dest in chess.genPawnDoubleDests(start, color) createEnPassant = dest in chess.genPawnDoubleDests(start, color)
capturedEnPassant = (dest == chess.enPassantSquare) capturedEnPassant = (dest == chess.enPassantSquare)
fiftyMoveRuleReset = true fiftyMoveRuleReset = true
if (chess.board[move.dest] != 0): if chess.board[move.dest] != 0:
fiftyMoveRuleReset = true fiftyMoveRuleReset = true
if (move in chess.genLegalMoves(start, color)): if move in chess.genLegalMoves(start, color):
chess.enPassantSquare = -1 chess.enPassantSquare = -1
chess.uncheckedMove(start, dest) chess.uncheckedMove(start, dest)
chess.toMove = Color(ord(chess.toMove)*(-1)) chess.toMove = Color(ord(chess.toMove)*(-1))
@ -812,8 +812,8 @@ proc threeMoveRep(chess: Chess): bool =
return false return false
var lastState = chess.previousBoard[chess.previousBoard.high] var lastState = chess.previousBoard[chess.previousBoard.high]
var reps: int var reps: int
for stateInd in (chess.previousBoard.low)..(chess.previousBoard.high): for stateInd in chess.previousBoard.low..chess.previousBoard.high:
if (chess.previousBoard[stateInd] == lastState): if chess.previousBoard[stateInd] == lastState:
reps = reps + 1 reps = reps + 1
return reps >= 3 return reps >= 3
@ -839,7 +839,7 @@ proc checkInsufficientMaterial(board: Board): bool =
pieces[4]) pieces[4])
let bpieces: PieceAmount = (pieces[5], pieces[6], pieces[7], pieces[8], let bpieces: PieceAmount = (pieces[5], pieces[6], pieces[7], pieces[8],
pieces[9]) 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 =
## Returns true if the `color` player is stalemate in a `chess`. ## Returns true if the `color` player is stalemate in a `chess`.

@ -2,7 +2,7 @@ import nimpy
import asyncnet, asyncdispatch import asyncnet, asyncdispatch
import chess import chess
import engine/secret import secret
import engine/engine import engine/engine
let berserk = pyImport("berserk") let berserk = pyImport("berserk")

@ -3,18 +3,31 @@ import sequtils
import strutils import strutils
import sugar import sugar
import tables import tables
import os
include chess include chess
let dbConn = "openings.db" import secret
type
BookMove* = object
## `PossibleMove` capsulates a possible moves in a position with additional
## statistics.
fen*: string # `fen` is the fen string of a position.
move*: string # `move` describes a move in pure coordinate notation.
white*: int # `white` is the number of game white won from this position.
black*: int # `black` is the number of game black won from this position.
draw*: int # `draw` is the number of game drawn from this position.
rating*: int # `rating` is the average rating of the player to move.
let dbConn = projectdir & "src/engine/openings.db"
let dbUser = "" let dbUser = ""
let dbPasswd = "" let dbPasswd = ""
let dbName = "" let dbName = ""
let tableName = "posmoves" let tableName = "posmoves"
proc initDB*(): void = proc initDB(): void =
## Initialize the database with a table if it doesnt currently exist. ## Initialize the database with a table if it doesnt currently exist.
let db = open(dbConn, dbUser, dbPasswd, dbName) let db = open(dbConn, dbUser, dbPasswd, dbName)
db.exec(sql"""CREATE TABLE IF NOT EXISTS ? ( db.exec(sql"""CREATE TABLE IF NOT EXISTS ? (
@ -29,7 +42,7 @@ proc initDB*(): void =
db.close() db.close()
echo("Database initialization done.") echo("Database initialization done.")
proc storeMove*(fen: string, move: string, white: bool, black: bool, draw: bool, proc storeMove(fen: string, move: string, white: bool, black: bool, draw: bool,
rating: int): void = rating: int): void =
## Store a possible `move` done by a player with `rating` (0 for unknown) ## Store a possible `move` done by a player with `rating` (0 for unknown)
## in a position described by `fen`. ## in a position described by `fen`.
@ -54,7 +67,9 @@ proc storeMove*(fen: string, move: string, white: bool, black: bool, draw: bool,
echo("inserted (", join([fen, move, $white, $black, $draw, $rating], ", "), echo("inserted (", join([fen, move, $white, $black, $draw, $rating], ", "),
") into ", tableName) ") into ", tableName)
proc loadMove*(fen: string): seq[Row] = proc loadMove*(fen: string): seq[BookMove] =
## Load all possible moves possible in a given position described by `fen`
## from the database. Format moves as a BookMove object.
let db = open(dbConn, dbUser, dbPasswd, dbName) let db = open(dbConn, dbUser, dbPasswd, dbName)
let res = db.getAllRows(sql """SELECT move, white, black, draw, rating let res = db.getAllRows(sql """SELECT move, white, black, draw, rating
FROM ? FROM ?
@ -62,7 +77,17 @@ proc loadMove*(fen: string): seq[Row] =
ORDER BY rating DESC ORDER BY rating DESC
""", tableName, fen) """, tableName, fen)
db.close() db.close()
return res var fRes: seq[BookMove]
for entry in res:
var bookMv: BookMove
bookMv.fen = fen
bookMv.move = entry[0]
bookMv.white = parseInt(entry[1])
bookMv.black = parseInt(entry[2])
bookMv.draw = parseInt(entry[3])
bookMv.rating = parseInt(entry[4])
fRes.add(bookMv)
return fRes
proc sanToPcn(sanMoves: string): string = proc sanToPcn(sanMoves: string): string =
## Convert a list of `sanMoves` to pure coordinate notation (assuming the game ## Convert a list of `sanMoves` to pure coordinate notation (assuming the game
@ -224,5 +249,6 @@ proc iterMultiPGN(fileP: string): void =
else: else:
sanMoves &= line sanMoves &= line
when isMainModule:
initDB() initDB()
#iterMultiPGN("file.pgn")

@ -15,10 +15,10 @@ proc runGameHotseat*(): void =
while not chess.checkedMove(notationToMove(move, chess.toMove)): while not chess.checkedMove(notationToMove(move, chess.toMove)):
move = readLine(stdin) move = readLine(stdin)
chess.echoBoard(chess.toMove) chess.echoBoard(chess.toMove)
if (chess.isDrawClaimable()): if chess.isDrawClaimable():
echo "Do you want to claim a draw? (y/N)" echo "Do you want to claim a draw? (y/N)"
draw = readLine(stdin) draw = readLine(stdin)
if (draw == "y"): if draw == "y":
echo "Draw claimed" echo "Draw claimed"
break break
if chess.isCheckmate(chess.toMove): if chess.isCheckmate(chess.toMove):
@ -33,17 +33,17 @@ proc runGameSolo*(color: Color, difficulty: int): void =
var chess = initChess() var chess = initChess()
var draw: string var draw: string
while not chess.isCheckmate(chess.toMove) and not chess.isStalemate(chess.toMove): while not chess.isCheckmate(chess.toMove) and not chess.isStalemate(chess.toMove):
if (chess.toMove == color): if chess.toMove == color:
chess.echoBoard(color) chess.echoBoard(color)
echo "Make a move" echo "Make a move"
var hMove = readLine(stdin) var hMove = readLine(stdin)
while not chess.checkedMove(notationToMove(hMove, chess.toMove)): while not chess.checkedMove(notationToMove(hMove, chess.toMove)):
hMove = readLine(stdin) hMove = readLine(stdin)
chess.echoBoard(color) chess.echoBoard(color)
if (chess.isDrawClaimable): if chess.isDrawClaimable():
echo "Do you want to claim a draw? (y/N)" echo "Do you want to claim a draw? (y/N)"
draw = readLine(stdin) draw = readLine(stdin)
if (draw == "y"): if draw == "y":
echo "Draw claimed" echo "Draw claimed"
break break
else: else:
@ -63,7 +63,7 @@ proc menu(): void =
echo("How many players? (1/2)") echo("How many players? (1/2)")
input = readLine(stdin) input = readLine(stdin)
discard parseInt(input, playerCount, 0) discard parseInt(input, playerCount, 0)
if (playerCount == 1 or playerCount == 2): if playerCount == 1 or playerCount == 2:
break break
if playerCount == 1: if playerCount == 1:
var color: string var color: string
@ -72,16 +72,16 @@ proc menu(): void =
echo("Choose the difficulty for the engine (1-10)") echo("Choose the difficulty for the engine (1-10)")
input = readLine(stdin) input = readLine(stdin)
discard parseInt(input, difficulty, 0) discard parseInt(input, difficulty, 0)
if (difficulty >= 1 and difficulty <= 10): if difficulty >= 1 and difficulty <= 10:
break break
while true: while true:
echo("Do you want to play Black or White? (B/W)") echo("Do you want to play Black or White? (B/W)")
color = readLine(stdin) color = readLine(stdin)
if (color == "B"): if color == "B":
echo("\n\n\n") echo("\n\n\n")
runGameSolo(Color.Black, difficulty) runGameSolo(Color.Black, difficulty)
break break
elif (color == "W"): elif color == "W":
echo("\n\n\n") echo("\n\n\n")
runGameSolo(Color.White, difficulty) runGameSolo(Color.White, difficulty)
break break

Loading…
Cancel
Save