1
0
mirror of https://github.com/tiyn/yeschess.git synced 2025-04-02 23:17:47 +02:00
yeschess/src/engine/openingBook.nim
TiynGER e306de0573 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.
2021-05-16 03:15:40 +02:00

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")