mirror of
https://github.com/tiyn/yeschess.git
synced 2025-04-02 23:17:47 +02:00
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.
255 lines
8.3 KiB
Nim
255 lines
8.3 KiB
Nim
import db_sqlite
|
|
import sequtils
|
|
import strutils
|
|
import sugar
|
|
import tables
|
|
|
|
include chess
|
|
|
|
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 =
|
|
## 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 ? (
|
|
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 =
|
|
## Store a possible `move` done by a player with `rating` (0 for unknown)
|
|
## in a position described by `fen`.
|
|
## The result of the game is described by `white`, `black` and `draw`.
|
|
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[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 ?
|
|
WHERE fen == ?
|
|
ORDER BY rating DESC
|
|
""", tableName, fen)
|
|
db.close()
|
|
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
|
|
## 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
|
|
|
|
when isMainModule:
|
|
initDB()
|
|
#iterMultiPGN("file.pgn")
|