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