For my chess engine I use statistics to choose optimal moves. I collected them from millions of games. I'm interested in the current move, the next move and how much times the next move was played given the current move.
For using a Python dictionary and storing it with pickle the file is too large, and hard to update with new games. So I decided to use SQLite.
I created a class MovesDatabase:
class MovesDatabase:
def __init__(self, work_dir):
self.con = sqlite3.connect(os.path.join(work_dir, "moves.db"))
self.con.execute('PRAGMA temp_store = MEMORY')
self.con.execute('PRAGMA synchronous = NORMAL')
self.con.execute('PRAGMA journal_mode = WAL')
self.cur = self.con.cursor()
self.cur.execute("CREATE TABLE IF NOT EXISTS moves("
"move TEXT,"
"next TEXT,"
"count INTEGER DEFAULT 1);")
move and next represent the state of a chess board in a string format: FEN. Example:
- rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR
- r1b1k1nr/p2p1pNp/n2B4/1p1NP2P/6P1/3P1Q2/P1P1K3/q5b1
- 8/8/8/4p1K1/2k1P3/8/8/8 b
The method below is responsible for taking a games file, extracting the moves and inserting if the couple (move, next) is new, or updating if (move, next) already exist in the database:
def insert_moves_from_file(self, file: str):
print("Extracting moves to database from " + file)
count = 0
with open(file) as games_file:
game = chess.pgn.read_game(games_file)
while game is not None:
batch = []
board = game.board()
state_one = board.fen().split(' ')[0] + ' ' + board.fen().split(' ')[1]
for move in game.mainline_moves():
board.push(move)
fen = board.fen().split(' ')
state_two = fen[0] + ' ' + fen[1]
res = self.cur.execute("SELECT * FROM moves WHERE move=? AND next=?",
(state_one, state_two))
res = res.fetchall()
if len(res) != 0:
self.cur.execute("UPDATE moves SET count=count+1 WHERE move=? AND next=?",
(state_one, state_two))
else:
batch.append((state_one, state_two))
state_one = state_two
self.cur.executemany("INSERT INTO moves(move, next) VALUES"
"(?, ?)", batch)
count += 1
print('\r' "%d games was add to the database.." % (count + 1), end='')
game = chess.pgn.read_game(games_file)
self.con.commit()
print("\n Finished!")
The couple (move, next) is unique.
I tested with a file containing approximately 4 million (move, next). It started inserting/updating 3.000 rows/s, but with 50K rows it slows down to 100 rows/s and keeps going down. I designed this method to process multiple game files, that's why I choose an SQL database in the first place.