From e306de0573df7f5be6fef7c61c87a4b521fe84d7 Mon Sep 17 00:00:00 2001 From: TiynGER Date: Sun, 16 May 2021 03:15:40 +0200 Subject: [PATCH] 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. --- README.md | 29 ++++++ src/chess.nim | 96 +++++++++---------- src/engine/lichessBridge.nim | 2 +- src/engine/{posMoveDB.nim => openingBook.nim} | 42 ++++++-- src/game.nim | 18 ++-- 5 files changed, 121 insertions(+), 66 deletions(-) rename src/engine/{posMoveDB.nim => openingBook.nim} (84%) diff --git a/README.md b/README.md index a090765..145d1b6 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,32 @@ The engine uses a simple implementation of the For the evaluation function each piece has a corresponding value. Additionally [piece-square tables](https://www.chessprogramming.org/Piece-Square_Tables) 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* = "" +let api_token* = "" +``` + +### 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. diff --git a/src/chess.nim b/src/chess.nim index 7b68795..893036f 100644 --- a/src/chess.nim +++ b/src/chess.nim @@ -153,7 +153,7 @@ proc fieldToInd(field: string): int = proc indToField(ind: int): string = ## Calculate and returns field name from board index `ind`. 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: if FileChar[file] == file_ind: 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 ## eventual promition to `prom`. 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 return move @@ -181,7 +181,7 @@ proc notationToMove*(notation: string, color: Color): Move = var start = fieldToInd(notation[0..1]) var dest = fieldToInd(notation[2..3]) move = getMove(start, dest, color) - if (len(notation) > 4): + if notation.len > 4: var promStr = $notation[4] let prom = case promStr: of "R": @@ -351,22 +351,22 @@ proc echoBoard*(chess: Chess, color: Color) = ## Prints out the given `board` with its pieces as characters and line ## indices from perspecive of `color`. var line_str: string - if (color == Color.Black): - for i in 0..len(chess.board)-1: - if (chess.board[i] == Block): + if color == Color.Black: + for i in 0..chess.board.len-1: + if chess.board[i] == Block: continue line_str &= PieceChar[chess.board[i]] & " " - if ((i + 2) %% 10 == 0): - line_str &= $(int((i / 10) - 1)) & "\n" + if (i + 2) %% 10 == 0: + line_str &= $int((i / 10) - 1) & "\n" echo line_str echo "h g f e d c b a" else: for i in countdown(len(chess.board) - 1, 0): - if (chess.board[i] == Block): + if chess.board[i] == Block: continue line_str &= PieceChar[chess.board[i]] & " " - if ((i - 1) %% 10 == 0): - line_str &= $(int((i / 10) - 1)) & "\n" + if (i - 1) %% 10 == 0: + line_str &= $int((i / 10) - 1) & "\n" echo line_str 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` ## located at index `field` of `chess`. ## 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 @[] var res = newSeq[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` ## located at index `field` of `chess`. ## 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 @[] var res = newSeq[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: dest = field + doubles * ord(color) target = chess.board[dest] - if ((target != 0) or ( - chess.board[dest + (S * ord(color))] != 0)): + if target != 0 or + chess.board[dest + (S * ord(color))] != 0: 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 - 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 res.add(dest) 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 ## index `field` of `chess`. ## 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 @[] var res = newSeq[int]() var dest: int var target: int for move in Pawn_Moves_White: 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 target = chess.board[dest] - if (target != 0 and dest != chess.enPassantSquare): + if target != 0 and dest != chess.enPassantSquare: continue res.add(dest) 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 ## at index `field` of `chess`. ## 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 @[] var res = newSeq[int]() var dest: int var target: int for move in Knight_Moves: dest = field + move - if (not dest in chess.board.low..chess.board.high): + if not dest in chess.board.low..chess.board.high: continue target = chess.board[dest] - if (target == Block or (ord(color) * target > 0)): + if target == Block or ord(color) * target > 0: continue res.add(dest) 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` ## located at index `field` of `chess`. ## 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 @[] var res = newSeq[int]() var dest: int var target: int for move in moves: dest = field + move - if (not dest in chess.board.low..chess.board.high): + if not dest in chess.board.low..chess.board.high: continue target = chess.board[dest] - while (target != Block and (ord(color) * target <= 0)): + while target != Block and ord(color) * target <= 0: res.add(dest) - if (ord(color) * target < 0): + if ord(color) * target < 0: break dest = dest + move 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` ## located at index `field` of `chess` ## 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 @[] var res = newSeq[int]() var dest: int @@ -507,14 +507,14 @@ proc genKingCastleDest(chess: Chess, field: int, color: Color): seq[int] = var half_target: int for castle in King_Moves_White_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 target = chess.board[dest] half_dest = field + int(castle / 2) half_target = chess.board[half_dest] - if (target == Block or (target != 0)): + if target == Block or target != 0: continue - if (half_target == Block or (half_target != 0)): + if half_target == Block or half_target != 0: continue res.add(dest) 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` ## located at index `field` of `chess`. ## 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 @[] var res = newSeq[int]() var dest: int var target: int for move in King_Moves: dest = field + move - if (not dest in chess.board.low..chess.board.high): + if not dest in chess.board.low..chess.board.high: continue target = chess.board[dest] - if (target == Block or (ord(color) * target > 0)): + if target == Block or ord(color) * target > 0: continue res.add(dest) res.add(chess.genKingCastleDest(field, color)) @@ -717,11 +717,11 @@ proc castling(chess: var Chess, kstart: int, dest_kingside: bool, var rstart: int var rdest: int var rights: bool - if (dest_kingside): + if dest_kingside: kdest = kstart + E + E rstart = kstart + E + E + E rdest = rstart + W + W - if (color == Color.White): + if color == Color.White: rights = chess.castleRights.wk else: rights = chess.castleRights.bk @@ -729,12 +729,12 @@ proc castling(chess: var Chess, kstart: int, dest_kingside: bool, kdest = kstart + W + W rstart = kstart + W + W + W + W rdest = rstart + E + E + E - if (color == Color.White): + if color == Color.White: rights = chess.castleRights.wq else: rights = chess.castleRights.bq - if (rights): - if (dest_kingside): + if rights: + if dest_kingside: 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.board[kstart+E+E] != 0: @@ -760,7 +760,7 @@ proc checkedMove*(chess: var Chess, move: Move): bool {.discardable.} = let dest = move.dest let color = move.color 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 let piece = chess.board[start] var createEnPassant: bool @@ -768,17 +768,17 @@ proc checkedMove*(chess: var Chess, move: Move): bool {.discardable.} = var fiftyMoveRuleReset: bool var move: Move 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) - 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) - if (piece == WPawn * ord(color)): + if piece == WPawn * ord(color): createEnPassant = dest in chess.genPawnDoubleDests(start, color) capturedEnPassant = (dest == chess.enPassantSquare) fiftyMoveRuleReset = true - if (chess.board[move.dest] != 0): + if chess.board[move.dest] != 0: fiftyMoveRuleReset = true - if (move in chess.genLegalMoves(start, color)): + if move in chess.genLegalMoves(start, color): chess.enPassantSquare = -1 chess.uncheckedMove(start, dest) chess.toMove = Color(ord(chess.toMove)*(-1)) @@ -812,8 +812,8 @@ proc threeMoveRep(chess: Chess): bool = return false var lastState = chess.previousBoard[chess.previousBoard.high] var reps: int - for stateInd in (chess.previousBoard.low)..(chess.previousBoard.high): - if (chess.previousBoard[stateInd] == lastState): + for stateInd in chess.previousBoard.low..chess.previousBoard.high: + if chess.previousBoard[stateInd] == lastState: reps = reps + 1 return reps >= 3 @@ -839,7 +839,7 @@ proc checkInsufficientMaterial(board: Board): bool = 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 = ## Returns true if the `color` player is stalemate in a `chess`. diff --git a/src/engine/lichessBridge.nim b/src/engine/lichessBridge.nim index acec904..689ad05 100644 --- a/src/engine/lichessBridge.nim +++ b/src/engine/lichessBridge.nim @@ -2,7 +2,7 @@ import nimpy import asyncnet, asyncdispatch import chess -import engine/secret +import secret import engine/engine let berserk = pyImport("berserk") diff --git a/src/engine/posMoveDB.nim b/src/engine/openingBook.nim similarity index 84% rename from src/engine/posMoveDB.nim rename to src/engine/openingBook.nim index 5d85b00..5a39d91 100644 --- a/src/engine/posMoveDB.nim +++ b/src/engine/openingBook.nim @@ -3,18 +3,31 @@ import sequtils import strutils import sugar import tables -import os 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 dbPasswd = "" let dbName = "" let tableName = "posmoves" -proc initDB*(): void = +proc initDB(): void = ## Initialize the database with a table if it doesnt currently exist. let db = open(dbConn, dbUser, dbPasswd, dbName) db.exec(sql"""CREATE TABLE IF NOT EXISTS ? ( @@ -29,7 +42,7 @@ proc initDB*(): void = db.close() 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 = ## Store a possible `move` done by a player with `rating` (0 for unknown) ## 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], ", "), ") 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 res = db.getAllRows(sql """SELECT move, white, black, draw, rating FROM ? @@ -62,7 +77,17 @@ proc loadMove*(fen: string): seq[Row] = ORDER BY rating DESC """, tableName, fen) 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 = ## Convert a list of `sanMoves` to pure coordinate notation (assuming the game @@ -224,5 +249,6 @@ proc iterMultiPGN(fileP: string): void = else: sanMoves &= line - -initDB() +when isMainModule: + initDB() + #iterMultiPGN("file.pgn") diff --git a/src/game.nim b/src/game.nim index 127a5c3..b23da33 100644 --- a/src/game.nim +++ b/src/game.nim @@ -15,10 +15,10 @@ proc runGameHotseat*(): void = while not chess.checkedMove(notationToMove(move, chess.toMove)): move = readLine(stdin) chess.echoBoard(chess.toMove) - if (chess.isDrawClaimable()): + if chess.isDrawClaimable(): echo "Do you want to claim a draw? (y/N)" draw = readLine(stdin) - if (draw == "y"): + if draw == "y": echo "Draw claimed" break if chess.isCheckmate(chess.toMove): @@ -33,17 +33,17 @@ proc runGameSolo*(color: Color, difficulty: int): void = var chess = initChess() var draw: string while not chess.isCheckmate(chess.toMove) and not chess.isStalemate(chess.toMove): - if (chess.toMove == color): + if chess.toMove == color: chess.echoBoard(color) echo "Make a move" var hMove = readLine(stdin) while not chess.checkedMove(notationToMove(hMove, chess.toMove)): hMove = readLine(stdin) chess.echoBoard(color) - if (chess.isDrawClaimable): + if chess.isDrawClaimable(): echo "Do you want to claim a draw? (y/N)" draw = readLine(stdin) - if (draw == "y"): + if draw == "y": echo "Draw claimed" break else: @@ -63,7 +63,7 @@ proc menu(): void = echo("How many players? (1/2)") input = readLine(stdin) discard parseInt(input, playerCount, 0) - if (playerCount == 1 or playerCount == 2): + if playerCount == 1 or playerCount == 2: break if playerCount == 1: var color: string @@ -72,16 +72,16 @@ proc menu(): void = echo("Choose the difficulty for the engine (1-10)") input = readLine(stdin) discard parseInt(input, difficulty, 0) - if (difficulty >= 1 and difficulty <= 10): + if difficulty >= 1 and difficulty <= 10: break while true: echo("Do you want to play Black or White? (B/W)") color = readLine(stdin) - if (color == "B"): + if color == "B": echo("\n\n\n") runGameSolo(Color.Black, difficulty) break - elif (color == "W"): + elif color == "W": echo("\n\n\n") runGameSolo(Color.White, difficulty) break