diff --git a/src/engine/posMoveDB.nim b/src/engine/posMoveDB.nim new file mode 100644 index 0000000..69658f8 --- /dev/null +++ b/src/engine/posMoveDB.nim @@ -0,0 +1,224 @@ +import db_sqlite +import sequtils +import strutils +import sugar +import tables +import os + +include chess + +let dbConn = "openings.db" +let dbUser = "" +let dbPasswd = "" +let dbName = "" + +let tableName = "posmoves" + +proc initDB*(): void = + let db = open(dbConn, dbUser, dbPasswd, dbName) + db.exec(sql"""CREATE TABLE IF NOT EXISTS ? ( + fen VARCHAR(100) NOT NULL, + move VARCHAR(6) NOT NULL, + white INTEGER NOT NULL, + black INTEGER NOT NULL, + draw INTEGER NOT NULL, + rating INTEGER NOT NULL, + PRIMARY KEY (fen, move) + )""", tableName) + db.close() + echo("Database initialization done.") + +proc storeMove*(fen: string, move: string, white: bool, black: bool, draw: bool, + rating: int): void = + let db = open(dbConn, dbUser, dbPasswd, dbName) + var query = """ + INSERT INTO ? (fen, move, white, black, draw, rating) + VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT(fen, move) DO UPDATE SET + """ + if not rating == 0: + query &= "rating = ((rating * (black + white + draw)) + ?) / (black + white + draw + 1)," + if white: + query &= "white = white + 1" + elif black: + query &= "black = black + 1" + else: + query &= "draw = draw + 1" + db.exec(sql(query), tableName, fen, move, int(white), int(black), int(draw), + rating, rating) + db.close() + echo("inserted (", join([fen, move, $white, $black, $draw, $rating], ", "), + ") into ", tableName) + +proc loadMove*(fen: string): seq[Row] = + let db = open(dbConn, dbUser, dbPasswd, dbName) + let res = db.getAllRows(sql """SELECT move, white, black, draw, rating + FROM ? + WHERE fen == ? + ORDER BY rating DESC + """, tableName, fen) + db.close() + return res + +proc sanToPcn(sanMoves: string): string = + ## Convert a list of `sanMoves` to pure coordinate notation (assuming the game + ## starts in the standard initial position) + var sanMoveArr = sanMoves.replace("+").replace("x").replace("#").replace( + "!").replace("?").split(" ") + sanMoveArr.del(sanMoveArr.high) + var fSanMoves: string + var inComment: bool + for word in sanMoveArr: + if inComment: + if word.endsWith("}"): + inComment = false + else: + if word.startsWith("{"): + inComment = true + continue + if not word.endsWith("."): + fSanMoves &= word & " " + var revPieceChar = toSeq(PieceChar.pairs).map(y => (y[1], y[0])).toTable + var chess = initChess() + var lastChess = chess + let sanArr = fsanMoves.split(" ") + var pcnMoves: string + for sanMove in sanArr: + var start: string + var dest: string + if sanMove.isEmptyOrWhitespace: + continue + dest = sanMove[sanMove.high-1..sanMove.high] + if sanMove.contains("="): + let promotion = sanMove[sanMove.high] + if sanMove.len == 5: + let file = sanMove[0] + dest = sanMove[1..2] & promotion + if chess.toMove == Color.White: + start = file & "7" + else: + start = file & "2" + elif sanMove.len == 4: + dest = sanMove[0..1] & promotion + start = indToField(fieldToInd(dest) + ord(chess.toMove) * S) + chess.checkedMove(notationToMove(start & dest, chess.toMove)) + elif "abcdefgh".contains(sanMove[0]): + let file = sanMove[0] + for rank in 1..8: + start = file & $rank + if fieldToInd(dest) in chess.genPawnDests(fieldToInd(start), chess.toMove): + if chess.checkedMove(notationToMove(start & dest, chess.toMove)): + break + elif sanMove.startsWith("O-O"): + if chess.toMove == Color.White: + start = "e1" + if sanMove.len == 3: + dest = "g1" + else: + dest = "c1" + else: + start = "e8" + if sanMove.len == 3: + dest = "g8" + else: + dest = "c8" + chess.checkedMove(notationToMove(start & dest, chess.toMove)) + else: + var piece = revPieceChar[$sanMove[0]] * ord(chess.toMove) + if sanMove.len == 3: + var possibleStarts: seq[string] + for i, field in chess.board: + if field == piece: + possibleStarts.add(indToField(i)) + for possibleStart in possibleStarts: + if chess.checkedMove(notationToMove(possibleStart & dest, + chess.toMove)): + start = possibleStart + break + elif sanMove.len == 4: + if sanMove[1].isDigit(): + let rank = $sanMove[1] + for file in "abcdefg": + if chess.board[fieldToInd($file & rank)] == piece: + start = $file & rank + chess.checkedMove(notationToMove(start & dest, chess.toMove)) + break + continue + else: + let file = sanMove[1] + for rank in 1..8: + if chess.board[fieldToInd(file & $rank)] == piece: + start = file & $rank + chess.checkedMove(notationToMove(start & dest, chess.toMove)) + break + elif sanMove.len == 5: + start = sanMove[1..2] + dest = sanMove[3..4] + chess.checkedMove(notationToMove(start & dest, chess.toMove)) + if lastChess == chess: + chess.echoBoard(Color.White) + echo("ERROR OCCURED") + return "" + pcnMoves.add(start & dest & " ") + lastChess = chess + return pcnMoves + +proc iterMultiPGN(fileP: string): void = + ## Iterate through a (multi) PGN file at `fileP` and store the games in the + ## opening database. This function is designed to work with multi PGN files + ## from lichess, but should also work for other formats (elo may be not working) + var sanMoves: string + var secondSpace: bool + var white: bool + var black: bool + var wElo: int + var bElo: int + var i: int + var chess: Chess + for line in lines(fileP): + if line.isEmptyOrWhitespace: + if not secondSpace: + secondSpace = true + else: + secondSpace = false + chess = initChess() + for move in sanToPcn(sanMoves).split(" "): + var rating = 0 + if chess.toMove == Color.White: + rating = wElo + else: + rating = bElo + storeMove(chess.convertToFen(), move, white, black, not white and + not black, rating) + chess.checkedMove(notationToMove(move, chess.toMove)) + i += 1 + sanMoves = "" + white = false + black = false + wElo = 0 + bElo = 0 + if i == 1000: + return + if line.startsWith("["): + if line.contains("Result "): + if line.contains("1-0"): + white = true + elif line.contains("0-1"): + black = true + elif line.contains("WhiteElo"): + var eloStr = line.replace("[WhiteElo \"").replace("\"]") + if eloStr != "?": + wElo = parseInt(eloStr) + else: + wElo = 0 + elif line.contains("BlackElo"): + var eloStr = line.replace("[BlackElo \"").replace("\"]") + if eloStr != "?": + bElo = parseInt(eloStr) + else: + bElo = 0 + else: + sanMoves &= line + + +initDB()