chess_db.pl -- PGN and chess game databases.

This library produces chess games databases from PGN files and provides some
predicates for manipulating these databases.

Once connected to a number of chess_db databases, information about the games
can be interrogated. (See chess_db_opening/2 for an example.)

Ideally we want to hook this pack to a web-based interface for playing the games as we select them.
Currently selected games can be saved to a PGN file and be displayed with any PGN displaying program.

Installation:

?- pack_install( chess_db ).

The pack includes code to:

There are three example pgns in pack(chess_db/data/pgn) and an example program, examples/short.pl

Dependencies

Packs:

Basics

By default, chess database dirs contain 3 sqlite databases:

A number of databases can be connected at the same time. Operations are implicit to all open databases. Connecting is via chess_db_connect/2.

Example

?- [pack('chess_db/examples/short.pl')].
?- short.      % creates a chess_db in subdirectory short/ from data/4ncl_short.pgn
               % and displays the game ids for games that start with [e4,e6] (French defence)
...

?- french.     % creates a new pgn file from the base for the 2 games in short/ that start with e4,e6
% Using existing chess_db directory: /home/nicos/short
% (short) Following games start with 1.e4,e6
gid(chdbs(<#4078544a8f7f0000>,<#4078544a8f7f0000>,<#4078544a8f7f0000>):1)
gid(chdbs(<#4078544a8f7f0000>,<#4078544a8f7f0000>,<#4078544a8f7f0000>):31)
% (short) Writing 1.e4,e6 starting games to: 'short/french.pgn'
% Closing chess db: '/home/nicos/short'
true.

% open file short/french.pgn on program that can play the games eg
linux> chessx short/french.pgn

Mini tutorial

The above example in detail.

Turn debugging of writing out the original games as they are added to the database.

?- debug( chess_db(original) ).

Create a database in fresh directory short/. The DB will be populated with games from pgn file chess_db/data/4ncl_short.pgn
The database is closed after it is populated.

?- chess_db( pgn('4ncl_short'), short, create(true) ).
.....
1. e4 e6 2. d4 d5 3. Nd2 Be7 4. Bd3 c5 5. dxc5 Nf6 6. Qe2 O-O 7. Ngf3 a5 8. O-O
Na6 9. e5 Nd7 10. Nb3 Ndxc5 11. Bb5 Bd7 12. Bxd7 Qxd7 13. Nbd4 Ne4 14. Be3 f5
15. Qb5 Qxb5 16. Nxb5 Rfc8 17. c3 Nac5 18. Nfd4 Ra6 19. f3 Ng5 20. Rad1 Nf7 21.
f4 Ne4 1/2-1/2

true.

Connect to the new database. The connections are managed internally.

?- chess_db_connect( short, profile(false) ).
true.

Interrogate all connected databases for games starting with the sequence [e4,e6] (French defence).

?- findall( Gid, (chess_db_opening([e4,e6],Gid),write(Gid),nl), Gids ).
chdbs(<sqlite>(0x276c930),<sqlite>(0x278f320),<sqlite>(0x2792450)):1
chdbs(<sqlite>(0x276c930),<sqlite>(0x278f320),<sqlite>(0x2792450)):31
Gids = [chdbs(<sqlite>(0x276c930), <sqlite>(0x278f320), <sqlite>(0x2792450)):1, chdbs(<sqlite>(0x276c930), <sqlite>(0x278f320), <sqlite>(0x2792450)):31].

Turn general library debugging on.

?- debug(chess_db).

Re-connecting is handled fine.

?- chess_db_connect( short, profile(false) ).
% Handles already exist, for chess_db directory: '/home/nicos/short'

Create a new PGN file from the original scripts of the two French defence 2 games in their original script. Moves are matched to game_move/4 and are pulled from the game_orig/2 sub-database.

?- PgnF = 'short/french.pgn', chess_db_opening_pgn( [e4,e6], PgnF ).

View the two games in a PGN interface program such as:

?- shell( 'chessx short/french.pgn' ).

Debug terms

Listens to:

Pack predicates

parsing of and saving to pgn files, and storing/retriving on/from chess_dbs

manage database connections

access db games and info

info interactions

miscellaneous

Pack info

author
- nicos angelopoulos
version
- 0.1 2018/3/18
- 0.2 2018/3/20
- 0.3 2018/9/14
See also
- http://stoics.org.uk/~nicos/sware/chess_db
- https://github.com/nicos-angelopoulos/chess_db
- [pack('chess_db/examples/short.pl')]
- pack(prosqlite), pack(db_facts), pack(stoics_lib), pack(options)
 pgn(+PgnF, -Pgn)
pgn(+PgnF, +Pgn)
pgn(+PgnIn, -PgnOut)
Reads-from and writes-to between PGN files and Pgn term representations.
It can also be used to ensure second argument is a Pgn term when either a file or Pgn term is given as ground first argument.

A Pgn term is of the form: pgn(Info,Moves,Res,Orig). Where, <br>

?- pgn( 4nclall1718.pgn, Pgn ).  % in data/
?- pgn( '18.03.10-Hampstead.pgn', Pgn ).

fixme:

  1. if we have result from both Info and end of moves, make sure they are the same <br>
  2. if the info missing and the result from end of moves exists, then add it to info
author
- nicos angelopoulos
version
- 0.1 2018/03/14
- 0.2 2018/08/01, changed from move(Num,Mvs,Cms), add $ starting NAGs, allow opening comment on game with moves
To be done
- complement the 2 ways of getting the result.
- currently valuation marks and variations are thrown away
 chess_db(+PgnOrF)
 chess_db(+PngOrF, +Db)
chess_db(+PngOrF, +Opts)
 chess_db(+PngOrF, +Db, +Opts)
Add games from a PGN file or a PGN term, PgnOrF, to the chess database pointed to by Db and/or Opts. If Db is given both as argument and option, the argument overrides. If argument Db or option Db is a variable, then the full location of the Db used is returned. To distinguish between the two arity 2 versions, Opts in that case need be a list.

Opts

create(Create=false)
if true create dir and/or db files if they do not exist
db(Db)
database location
dir(Dir)
directory where database is located, (many allowed, see chess_db_connect/2)
position(Pos=false)
if true, use position table

Options can also be picked up from ~/.pl/chess_db.pl (see options_append/3).

?- pgn( pgn('18.03-candidates'), Pgn ),
   chess_db( Pgn, chess_db('18.03-candidates'), [db(Which),create(true)] ).
Pgn = ...
Which = '.../swipl-7.7.18/pack/chess_db_data/dbs/18.03-candidates'.

?- chess_db( pgn('4ncl_short.pgn'), fourNCL, [dir('/tmp'),create(true),db(Db)] ).
Db = '/tmp/fourNCL'.
author
- nicos angelopoulos
version
- 0.1 2018/3/14
- 0.2 2018/8/17,
See also
- options_append/3
 chess_db_list(+Dir)
A simple, on-screen dump of all data in a chess games database.
Likely to be only useful for debugging.
?- chess_db_list( short ).
author
- nicos angelopoulos
version
- 0.1 2018/3/14
 chess_db_max_id(+HandleST, -Max)
For a handle or term of handles (in which case the first argument is used), return the Max id of the first column of the game_info on the Handle.
?- chess_db_current( CdbHs ), chess_db_max_id( CdbHs, Max ).
Max = 31.
author
- nicos angelopoulos
version
- 0.1 2018/3/15
 chess_db_game(-GameID)
Return all the unique game ids, constructed as Handles:Gno.
Handles are the handles to access an open chess id and Gno is the
unique game id for a game in that database.
?- chess_db_connect( [dir('/usr/local/users/chess/chess_db/18.07-Biel'),profile(false),position(true)] ).

?- chess_db_game(Gid).
Gid = chdbs(<#40380f90857f0000>, <#40380f90857f0000>, <#40380f90857f0000>, <#40380f90857f0000>):1 ;
Gid = chdbs(<#40380f90857f0000>, <#40380f90857f0000>, <#40380f90857f0000>, <#40380f90857f0000>):2 ;
author
- nicos angelopoulos
version
- 0.1 2018/8/15
 chess_db_ids_pgn(+GidS, +PgnF)
Save a number of games corresponding to list of, or single, id structures from the current dbs to a pgn file (PgnF).
?- chess_db_ids_pgn( ..., ... ).
author
- nicos angelopoulos
version
- 0.1 2018/8/19
 chess_db_connect(+DbS)
 chess_db_connect(+DbS, +Opts)
Connect a number of chess_dbs (DbS) as open db handles that will be available
to a number of predicates that access the information: eg chess_db_opening/3.
The library provides two conveniencies for locating chess dbs. First, via aliases:
chess_db (by default expands to pack(chess_db_data/dbs)). Second, via dir(Dir) option.
In this case Dbs are looked for relative to all Dir locations provided.
Note that commonly used database directories can be defined long term in ~/.pl/chess_db_connect.pl
(see options_append/4).

Opts

% connect with alias
?- Db = chess_db('18.03-candidates'),
   ( chess_db_connect(Db, db(AbsDb) ) -> true
      ; chess_db( pgn('18.03-candidates'), Db, create(true) ),
        chess_db_connect( Db, db(AbsDb) )
   ).
Db = chess_db('18.03-candidates'),
AbsDb = ['/usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/chess_db_data/dbs/18.03-candidates'].

?- chess_db_disconnect( Db ).
Db = '/usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/chess_db_data/dbs/18.03-candidates'.

% connect with dir option in profile options...

?- shell( 'cat ~/.pl/chess_db_connect.pl' ).
dir( '/usr/local/users/chess/chess_db' ).
dir( '/usr/local/users/nicos/local/git/lib/swipl/pack/chess_db_data/dbs' ).
true.

?- read_link( '/usr/local/users/nicos/local/git/lib/swipl', A, B ).
A = 'swipl-7.7.18/',
B = '/usr/local/users/nicos/local/git/lib/swipl-7.7.18/'.

?- chess_db_connect('18.03-candidates', db(Db) ).
Db = ['/usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/chess_db_data/dbs/18.03-candidates'].
author
- nicos angelopoulos
version
- 0.1 2018/3/15
- 0.2 2018/8/17, added aliases, better locator (and order), fixes, docs/examples
See also
- chess_db_opening/3
 chess_db_current(-CdbHs)
 chess_db_current(-CdbHs, -Db)
Returns the handles structure and Db location, CdbHs, for each open database.<br>
?- debug( chess_db(original) ).
?- chess_db( pack('chess_db/data/pgn/4ncl_short.pgn'), fourNCL.pgn, [dir('/tmp'),create(true)] ).
.....
?- chess_db_connect( '/tmp/fourNCL', profile(false) ).
true.
?- chess_db_current( Handles ).
Handles = chdbs(<#40f8e51c617f0000>, <#40f8e51c617f0000>, <#40f8e51c617f0000>).

?- chess_db_current( Handles, Db ).
Handles = chdbs(<#40f8e51c617f0000>, <#40f8e51c617f0000>, <#40f8e51c617f0000>),
Db = '/tmp/fourNCL'.

?- chess_db_current( CdbHs ), chess_db_max_id( CdbHs, Max ).
CdbHs = chdbs(<#40f8e51c617f0000>, <#40f8e51c617f0000>, <#40f8e51c617f0000>),
Max = 31.
author
- nicos angelopoulos
version
- 0.1 2018/3/15
- 0.2 2018/8/18, simplified to just an interface to chess_db_handles/2
See also
- chess_db_connect/1
 chess_db_game_info(+Gid, -Key, -Value)
Get Key and Value for info db corresponding to the game id GID.
Game id is of the form DBHandles:GameNo.
?- chess_db_connect( [dir('/usr/local/users/chess/chess_db/18.07-Biel'),profile(false),position(true)] ).
?- chess_db_game(Gid), chess_db_game_info(Gid,'Result',Result).
Gid = chdbs(<#40380f90857f0000>, <#40380f90857f0000>, <#40380f90857f0000>, <#40380f90857f0000>):1,
Result = '1-0' ;
Gid = chdbs(<#40380f90857f0000>, <#40380f90857f0000>, <#40380f90857f0000>, <#40380f90857f0000>):2,
Result = '1-0' ;
Gid = chdbs(<#40380f90857f0000>, <#40380f90857f0000>, <#40380f90857f0000>, <#40380f90857f0000>):3,
Result = '1/2-1/2' ;
Gid = chdbs(<#40380f90857f0000>, <#40380f90857f0000>, <#40380f90857f0000>, <#40380f90857f0000>):4,
Result = '0-1'
...

?- % give me all Sicilian defence results with opponent names
?- chess_db_opening( [e4,c5], Gid ), chess_db_game_info( Gid, 'Result', Result ),
   chess_db_game_info( Gid, 'White', White ),
   chess_db_game_info( Gid, 'Black', Black ),
   write( Result:White/Black ), nl, fail.

1-0:Svidler, Peter/Georgiadis, Nico
1/2-1/2:Carlsen, Magnus/Svidler, Peter
0-1:Georgiadis, Nico/Mamedyarov, Shakhriyar
1/2-1/2:Carlsen, Magnus/Vachier-Lagrave, Maxime
0-1:Georgiadis, Nico/Svidler, Peter
1-0:Carlsen, Magnus/Georgiadis, Nico
(*):Carlsen, Magnus/Georgiadis, Nico
author
- nicos angelopoulos
version
- 0.1 2018/8/15
 chess_db_match(+Match, ?Games)
Select a number of games from the open databases according to their info labels. If Games is a variable each matching Gid is returned on backtracking. Else, is taken to be a pgn file (via chess_db_connect/2), to which the originals from all matching games are dumped. Match is a list of the following type of terms. The list items are considered as a disjunction.
Match-terms atom (exact match to any info label) + terms (conjuction of the plused terms) Key(InfoMatch) where only Key keys are (no-cased) matches with InfoMatch ( -Key means do not match Key's case)
?- chess_db_connect( chess_db(gms) ).
?- chess_db_match( 'So, Wesley', count(Count) ).
Count = 40
?- chess_db_match( [-white(sub('So'))], Gid ), Gid = _:Gno, chess_db_id_info(Gid,'White',White), write( Gno:White ), nl, fail.
...
196:So,W
...
305:So, Wesley

?- chess_db_match( [-white(sub('So'))], count(Count) ).
Count = 27.

?- chess_db_match( [-black(sub('So'))], count(Count) ).
Count = 27.

chess_db_match( [-black(sub('So'))], Gid ), Gid = _:Gno, chess_db_id_info(Gid,'White',White), write( Gno:White ), nl, fail.
6:Caruana, Fabiano
16:Topalov, Veselin
...

?- chess_db_match( [white(sub('So'))], count(Count) ).
Count = 0.

?- chess_db_match( ['White'(sub('So'))], count(Count) ).
Count = 27.

?- chess_db_match( sub('So,'), count(Count) ).
?- chess_db_match( sub('So,'), 'SoWesley' ).
author
- nicos angelopoulos
version
- 0.1 2018/8/19
 chess_db_opening(+Moves, ?GameS)
GameS is either a variable or a PGN filename. The predicate identifies all games starting with sequence of Moves. It either returns all game ids one at a time or saves them all the original PGN fragments for all matching ids to the output file. Moves is a list of atoms in standard chess notation.
GidStr is of the form GdbHs:Gid. Games are taken from data based identified by chess_db_current/1.
?- chess_db_opening( [e4,e6], Gid ). % find a French defence game.
author
- nicos angelopoulos
version
- 0.1 2018/3/15
 chess_db_disconnect
 chess_db_disconnect(?DbS)
Disconnect from a number of chess dbs. Can disconnect from all Dbs either one at a time when -Db using backtracking, or in a single deterministic call with the /0 version.

Db can be

?- debug( chess_db(original) ).
?- chess_db( pack('chess_db/data/pgn/4ncl_short.pgn'), fourNCL, [dir('/tmp'),create(true),db(Db)] ).
...
Db = '/tmp/fourNCL'.
?- chess_db_connect( fourNCL, [dir('/tmp'),profile(false)] ).
true.
?- chess_db_current( CdbHs ).
CdbHs = chdbs(<#40e867375c7f0000>, <#40e867375c7f0000>, <#40e867375c7f0000>).
?- chess_db_current( CdbHs ), chess_db_disconnect( CdbHs ).
author
- nicos angelopoulos
version
- 0.1 2018/3/18
 chess_db_opening_pgn(+Moves, +PgnF)
Dump all the original PGN fragments that correspond to Moves opening, to file PgnF.
?- chess_db( pack('chess_db/examples/4ncl_short.pgn'), short, [create(true)] ).
?- chess_db_connect( short, profile(false) ).
?- PgnF = 'short/french.pgn', chess_db_opening_pgn( [e4,e6], PgnF ).
author
- nicos angelopoulos
version
- 0.1 2018/3/17
See also
- pack('examples/short.pl')
 chess_db_version(-Version, -Date)
The current version. Version is a Mj:Mn:Fx term, and date is a date(Y,M,D) term.
?- chess_db_version( 0:3:0, date(2018,3,21) ).
true.