3

Intro to problem

For those who are not familiar with it, Chessable contains high-quality content about "life-time repertories" of most of the main openings and defenses. However, the way they present the lines of moves is extremely inconvenient (see an example in the image below); Each line starts from move 1. forcing the user to play the first few moves over and over again. enter image description here

Solution

The best way to browse through chess lines is in a tree-like structure, such as the one I manually keyed in below, using ChessBase:enter image description here

Goal

The goal is to automatically compute the tree notation from Chessable single lines, so that it can be imported to software like ChessBase.

Attempt

I browsed to my target repertory in text-mode and copied the single lines into a text file using a text editor.

1.d4 Nf6 2.Nc3 d5 3.Bg5 g6 4.Qd2 Bg7 5.O-O-O c6 6.f3 b5 7.e4 b4 8.Nce2 dxe4 9.Bxf6 exf6
1.d4 Nf6 2.Nc3 d5 3.Bg5 g6 4.Qd2 Bg7 5.O-O-O c6 6.f3 b5 7.Bh6 Bxh6 8.Qxh6 Qa5 9.a3 Na6
1.d4 Nf6 2.Nc3 d5 3.Bg5 g6 4.Qd2 Bg7 5.h4 Ne4 6.Nxe4 dxe4 7.e3 c5 8.Ne2 Bg4
1.d4 Nf6 2.Nc3 d5 3.Bg5 g6 4.Qd2 Bg7 5.h4 Ne4 6.Nxe4 dxe4 7.O-O-O Nc6 8.e3 h6 9.Bf4 O-O
1.d4 Nf6 2.Nc3 d5 3.Bg5 g6 4.Qd2 Bg7 5.f3 h6 6.Bxf6 exf6 7.e4 dxe4 8.fxe4 O-O
1.d4 Nf6 2.Nc3 d5 3.Bg5 g6 4.Qd2 Bg7 5.f3 h6 6.Bf4 c5 7.dxc5 Nbd7
1.d4 Nf6 2.Nc3 d5 3.Bg5 g6 4.Qd2 Bg7 5.f3 h6 6.Bh4 c5 7.e3 cxd4 8.exd4 O-O
1.d4 Nf6 2.Nc3 d5 3.Bg5 g6 4.Qd2 Bg7 5.f3 h6 6.Bh4 c5 7.dxc5 O-O 8.Bxf6 Bxf6 9.Qxh6 Qa5 10.Qd2 Rd8
1.d4 Nf6 2.Nc3 d5 3.Bg5 g6 4.Qd2 Bg7 5.f3 h6 6.Bh4 c5 7.dxc5 O-O 8.O-O-O Nc6 9.Nxd5 Nxd5 10.Qxd5 Qa5
1.d4 Nf6 2.Nc3 d5 3.Bg5 g6 4.Qd2 Bg7 5.f3 h6 6.Bh4 c5 7.dxc5 O-O 8.O-O-O Nc6 9.Bxf6 Bxf6 10.Nxd5 Bg7 11.e4 e6 12.Ne3 Qg5 13.Ng4 Qxc5 14.Nxh6+ Kh7 15.Ng4 f5 16.Qg5 Kg8 17.Nh6+ Kh7 18.Ng4 Kg8
1.d4 Nf6 2.Nc3 d5 3.e4 Nxe4 4.Nxe4 dxe4 5.f3 e5 6.dxe5 Qxd1+ 7.Kxd1 Bf5
1.d4 Nf6 2.Nc3 d5 3.e4 Nxe4 4.Nxe4 dxe4 5.Bf4 Bf5 6.Qe2 e6 7.g4 Bg6 8.Qb5+ Nd7 9.Qxb7 Rb8
1.d4 Nf6 2.Nc3 d5 3.e4 Nxe4 4.Nxe4 dxe4 5.Be3 g6 6.f3 Bg7 7.fxe4 c5 8.c3 cxd4 9.cxd4 Qa5+ 10.Qd2 Qxd2+ 11.Kxd2 O-O
1.d4 Nf6 2.Nc3 d5 3.e4 Nxe4 4.Nxe4 dxe4 5.Be3 g6 6.f3 Bg7 7.fxe4 c5 8.c3 cxd4 9.Bxd4 O-O
1.d4 Nf6 2.Nc3 d5 3.e4 Nxe4 4.Nxe4 dxe4 5.Bc4 Nc6 6.c3 e5 7.Qb3 Qf6 8.d5 Bc5 9.f3 Nb8
1.d4 Nf6 2.Nc3 d5 3.e4 Nxe4 4.Nxe4 dxe4 5.Bc4 Nc6 6.c3 e5 7.d5 Ne7 8.Qa4+ Bd7 9.Qb3 Nf5 10.Qxb7 Rb8 11.Qxa7 Ra8 12.Qb7 Nd6 13.Qb3 Rb8
1.d4 Nf6 2.Nc3 d5 3.e4 Nxe4 4.Nxe4 dxe4 5.Bc4 Nc6 6.c3 e5 7.d5 Ne7 8.Qa4+ Bd7 9.Qb3 Nf5 10.Qxb7 Rb8 11.Qa6 e3
1.d4 Nf6 2.Nc3 d5 3.e4 Nxe4 4.Nxe4 dxe4 5.Bc4 Nc6 6.c3 e5 7.d5 Ne7 8.f3 exf3 9.Nxf3 f6
1.d4 Nf6 2.Nc3 d5 3.e3 g6 4.Nf3 Bg7 5.Bd3 O-O 6.O-O c5 7.dxc5 Nbd7 8.Na4 Ne4 9.Bxe4 dxe4 10.Nd4 Qc7 11.b4 Ne5
1.d4 Nf6 2.Nc3 d5 3.e3 g6 4.Nf3 Bg7 5.Bd3 O-O 6.O-O c5 7.dxc5 Nbd7 8.Na4 Ne4 9.Bxe4 dxe4 10.Ng5 Qa5 11.c3 b5 12.Qd5 Qa6 13.Nxf7 e6 14.Qxa8 Bb7
1.d4 Nf6 2.Nc3 d5 3.e3 g6 4.b3 Bg7 5.Bb2 O-O 6.Qd2 c5 7.O-O-O c4 8.bxc4 b5 9.Nxb5 Nc6
1.d4 Nf6 2.Nc3 d5 3.e3 g6 4.b3 Bg7 5.Bb2 O-O 6.Qd2 c5 7.f3 Bh6 8.f4 cxd4 9.exd4 Nc6 10.O-O-O Bf5
1.d4 Nf6 2.Nc3 d5 3.f3 c5 4.e4 Nc6 5.exd5 Nxd5 6.dxc5 Nxc3 7.Qxd8+ Nxd8 8.bxc3 Bd7
1.d4 Nf6 2.Nc3 d5 3.g3 c5 4.Bg2 cxd4 5.Qxd4 Nc6 6.Qd3 d4 7.Ne4 Nd5
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.O-O-O c6 6.f3 b5 7.Kb1 Nbd7
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.h4 c5 7.dxc5 Bxh6 8.Qxh6 d4 9.O-O-O Ng4 10.Qf4 e5 11.Qg3 Qa5
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.h4 c5 7.Bxg7 Kxg7 8.dxc5 d4 9.O-O-O Nc6 10.Nb5 Qd5 11.Kb1 Ne4 12.Qe1 Be6
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.O-O-O c5 7.dxc5 Qa5 8.Bxg7 Kxg7
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.f3 c5 7.Bxg7 Kxg7 8.dxc5 d4 9.Nb5 Nc6 10.Rd1 e5 11.e4 b6
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.f3 c5 7.Bxg7 Kxg7 8.e3 h5
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.Bxg7 Kxg7 7.e3 c5 8.dxc5 Qa5 9.Ne4 Qxd2+ 10.Nxd2 Bf5 11.Bd3 Nbd7 12.Nb3 Rfc8
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.Bxg7 Kxg7 7.Nf3 Qd6
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.Bxg7 Kxg7 7.O-O-O c5 8.dxc5 Qa5 9.Kb1 Nc6 10.Nxd5 Qxd2 11.Rxd2 Ne4
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.Bxg7 Kxg7 7.O-O-O c5 8.dxc5 Qa5 9.Nxd5 Qxa2 10.Nc3 Qa1+ 11.Nb1 Be6 12.b3 Rc8
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.Bxg7 Kxg7 7.O-O-O c5 8.dxc5 Qa5 9.Nxd5 Qxa2 10.Qc3 Be6 11.e4 Bxd5 12.exd5 Kg8 13.Qa3 Qxa3 14.bxa3 Rc8
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.Bxg7 Kxg7 7.O-O-O c5 8.dxc5 Qa5 9.Nxd5 Qxa2 10.Qc3 Be6 11.e4 Bxd5 12.exd5 Kg8 13.b3 Qa3+ 14.Kb1 a5
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.Bxg7 Kxg7 7.O-O-O c5 8.dxc5 Qa5 9.Nxd5 Qxa2 10.Qc3 Be6 11.Nxf6 exf6 12.Nf3 Nd7
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.Bxg7 Kxg7 7.O-O-O c5 8.e3 c4 9.e4 b5 10.e5 Ng8 11.Nxb5 Nc6 12.Kb1 f6 13.Na3 Qb6 14.exf6+ Nxf6 15.f3 Rb8 16.c3 e5
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Nb5 Na6 5.e3 Bg7 6.Nc3 Nh5 7.Be5 f6 8.Bg3 Nxg3 9.hxg3 O-O 10.Bxa6 bxa6
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.f3 Bg7 5.e4 O-O 6.e5 Nfd7 7.Nxd5 c5 8.c3 cxd4 9.cxd4 Qa5+ 10.Nc3 Rd8
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.f3 Bg7 5.e4 O-O 6.e5 Nfd7 7.Be3 c5 8.f4 e6
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.f3 Bg7 5.Qd2 c5 6.e4 cxd4 7.Nb5 O-O 8.Nc7 e5
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.f3 Bg7 5.Qd2 c5 6.dxc5 Nc6 7.e4 d4 8.Nb5 O-O
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Qf3 c5 7.Qg3 Nc6 8.Bc7 Qd7 9.O-O-O cxd4 10.exd4 O-O
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Be2 c5 8.Nb5 cxd4 9.Nc7 Ng4 10.Nxa8 e5
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Be2 c5 8.Nb5 cxd4 9.exd4 Nc6 10.Nc7 Rb8 11.Nb5 Bf5 12.Bxb8 Qxb8
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Be2 c5 8.dxc5 Qa5 9.O-O Qxc5 10.Nb5 a6 11.Nc7 Ra7 12.a3 Bf5 13.Nd4 Bg4 14.f3 Bd7 15.Nb3 Qc6 16.Nd4 Qb6
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Be2 c5 8.dxc5 Qa5 9.O-O Qxc5 10.Nb5 a6 11.Nc7 Ra7 12.Nb5 axb5 13.Bxb8 Ra4
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Be2 c5 8.Qd2 cxd4 9.exd4 Nc6 10.Ne5 Bf5 11.f3 Rc8 12.Nxc6 Rxc6 13.Be5 Qa5
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Be2 c5 8.Qd2 cxd4 9.exd4 Nc6 10.Ne5 Bf5 11.f3 Rc8 12.g4 hxg4 13.h5 Nxh5 14.fxg4 Nxf4 15.Qxf4 Bxc2 16.Qh2 Re8 17.Qh7+ Kf8 18.Nxf7 Kxf7 19.Rf1+ Ke6 20.Qxg7 Qa5
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Be2 c5 8.Ne5 cxd4 9.exd4 Nc6 10.O-O Bf5 11.Nxc6 bxc6
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Qd2 c5 8.dxc5 Nbd7 9.Bd3 Nxc5 10.O-O-O Bg4
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Nb5 Na6 8.Be2 c6 9.Nc3 Bf5
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Nb5 Na6 8.Ne5 c5 9.c3 Nb8
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Ne5 c5 8.dxc5 Qa5 9.Qd2 Be6
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Ne5 c5 8.dxc5 Qa5 9.Qd4 Ng4 10.Qxd5 Nc6
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Ne5 c5 8.Qd2 cxd4 9.exd4 Nc6 10.f3 Bf5 11.Nxc6 bxc6 12.O-O-O c5 13.Be5 Rc8 14.Ba6 cxd4 15.Qxd4 Rc6 16.Bb7 Re6 17.Bxd5 Rxe5 18.Qxe5 Nxd5 19.Qxd5 Qc7
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Ne5 c5 8.Qd2 cxd4 9.exd4 Nc6 10.f3 Bf5 11.Bb5 Nxe5 12.dxe5 Nd7 13.Qxd5 Qb6 14.Bxd7 Qxb2 15.O-O Qxc3
1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.e3 Bg7 5.h4 h5 6.Nf3 O-O 7.Ne5 c5 8.Qd2 cxd4 9.exd4 Nc6 10.f3 Bf5 11.O-O-O Rc8 12.g4 hxg4 13.h5 Nxe5 14.Bxe5 Nxh5 15.fxg4 Bxg4 16.Be2 Bxe2 17.Qxe2 f6

I tried to create a python program (failed attempt at the bottom) that converts the text file to tree notation (see example), but my programming skills are too poor. Essentially, if we export the tree notation (ChessBase) to a text editor, the syntax looks like this:

1. d4 Nf6 2. Nc3 d5 3. Bg5 g6 4. Qd3 Bg7 5. e4 (5. O-O-O c6 (5... Nbd7)) 5...
dxe4 6. Nxe4 Bf5 7. Bxf6 exf6

The new line, or variant, is introduced by a pair of parenthesis and the move number (e.g. 5..., for black, and 5. for white). I would appreciate help to convert single-line notation to tree notation. It could be useful to many chess fans.

with open('./text.txt') as f:
    list_of_lines = f.readlines()

list_of_lines = [line[:-1] for line in list_of_lines] # to remove /n
tree = ''
for idx, line in enumerate(list_of_lines[:2]):
    if idx == 0:
        tree += line
    else:
        prev_line = list_of_lines[idx-1]
        prev_moves = prev_line.split(' ')
        new_moves = line.split(' ')

        for prev in prev_moves:
            for i, new in enumerate(new_moves):
                if prev == new:
                    continue
                else:
                    if (i % 2) != 0:
                        variant = f' ({i}...'
                        variant += str(f'{new_moves[i:]})')
                        (' ').join(variant)
                        tree += variant


print(tree)

[Solved] Update on Kentdjb's answer

  1. Create .py file (e.g., tree.py) with the code from the accepted answer.
  2. Create a .txt (e.g. veresov.txt) file with the single-line notation such as in "Attempt" example above.
  3. To run the code, use python tree.py veresov.txt

It outputs the desired tree notation:

1. d4 Nf6 2. Nc3 d5 3. Bg5 (3. e4 Nxe4 4. Nxe4 dxe4 5. f3 (5. Bf4 Bf5 6. Qe2 e6 7. g4 Bg6 8. Qb5+ Nd7 9. Qxb7 Rb8 ) (5. Be3 g6 6. f3 Bg7 7. fxe4 c5 8. c3 cxd4 9. cxd4 (9. Bxd4 O-O ) 9... Qa5+ 10. Qd2 Qxd2+ 11. Kxd2 O-O ) (5. Bc4 Nc6 6. c3 e5 7. Qb3 (7. d5 Ne7 8. Qa4+ (8. f3 exf3 9. Nxf3 f6 ) 8... Bd7 9. Qb3 Nf5 10. Qxb7 Rb8 11. Qxa7 (11. Qa6 e3 ) 11... Ra8 12. Qb7 Nd6 13. Qb3 Rb8 ) 7... Qf6 8. d5 Bc5 9. f3 Nb8 ) 5... e5 6. dxe5 Qxd1+ 7. Kxd1 Bf5 ) (3. e3 g6 4. Nf3 (4. b3 Bg7 5. Bb2 O-O 6. Qd2 c5 7. O-O-O (7. f3 Bh6 8. f4 cxd4 9. exd4 Nc6 10. O-O-O Bf5 ) 7... c4 8. bxc4 b5 9. Nxb5 Nc6 ) 4... Bg7 5. Bd3 O-O 6. O-O c5 7. dxc5 Nbd7 8. Na4 Ne4 9. Bxe4 dxe4 10. Nd4 (10. Ng5 Qa5 11. c3 b5 12. Qd5 Qa6 13. Nxf7 e6 14. Qxa8 Bb7 ) 10... Qc7 11. b4 Ne5 ) (3. f3 c5 4. e4 Nc6 5. exd5 Nxd5 6. dxc5 Nxc3 7. Qxd8+ Nxd8 8. bxc3 Bd7 ) (3. g3 c5 4. Bg2 cxd4 5. Qxd4 Nc6 6. Qd3 d4 7. Ne4 Nd5 ) (3. Bf4 g6 4. Qd2 (4. Nb5 Na6 5. e3 Bg7 6. Nc3 Nh5 7. Be5 f6 8. Bg3 Nxg3 9. hxg3 O-O 10. Bxa6 bxa6 ) (4. f3 Bg7 5. e4 (5. Qd2 c5 6. e4 (6. dxc5 Nc6 7. e4 d4 8. Nb5 O-O ) 6... cxd4 7. Nb5 O-O 8. Nc7 e5 ) 5... O-O 6. e5 Nfd7 7. Nxd5 (7. Be3 c5 8. f4 e6 ) 7... c5 8. c3 cxd4 9. cxd4 Qa5+ 10. Nc3 Rd8 ) (4. e3 Bg7 5. h4 h5 6. Qf3 (6. Nf3 O-O 7. Be2 (7. Qd2 c5 8. dxc5 Nbd7 9. Bd3 Nxc5 10. O-O-O Bg4 ) (7. Nb5 Na6 8. Be2 (8. Ne5 c5 9. c3 Nb8 ) 8... c6 9. Nc3 Bf5 ) (7. Ne5 c5 8. dxc5 (8. Qd2 cxd4 9. exd4 Nc6 10. f3 Bf5 11. Nxc6 (11. Bb5 Nxe5 12. dxe5 Nd7 13. Qxd5 Qb6 14. Bxd7 Qxb2 15. O-O Qxc3 ) (11. O-O-O Rc8 12. g4 hxg4 13. h5 Nxe5 14. Bxe5 Nxh5 15. fxg4 Bxg4 16. Be2 Bxe2 17. Qxe2 f6 ) 11... bxc6 12. O-O-O c5 13. Be5 Rc8 14. Ba6 cxd4 15. Qxd4 Rc6 16. Bb7 Re6 17. Bxd5 Rxe5 18. Qxe5 Nxd5 19. Qxd5 Qc7 ) 8... Qa5 9. Qd2 (9. Qd4 Ng4 10. Qxd5 Nc6 ) 9... Be6 ) 7... c5 8. Nb5 (8. dxc5 Qa5 9. O-O Qxc5 10. Nb5 a6 11. Nc7 Ra7 12. a3 (12. Nb5 axb5 13. Bxb8 Ra4 ) 12... Bf5 13. Nd4 Bg4 14. f3 Bd7 15. Nb3 Qc6 16. Nd4 Qb6 ) (8. Qd2 cxd4 9. exd4 Nc6 10. Ne5 Bf5 11. f3 Rc8 12. Nxc6 (12. g4 hxg4 13. h5 Nxh5 14. fxg4 Nxf4 15. Qxf4 Bxc2 16. Qh2 Re8 17. Qh7+ Kf8 18. Nxf7 Kxf7 19. Rf1+ Ke6 20. Qxg7 Qa5 ) 12... Rxc6 13. Be5 Qa5 ) (8. Ne5 cxd4 9. exd4 Nc6 10. O-O Bf5 11. Nxc6 bxc6 ) 8... cxd4 9. Nc7 (9. exd4 Nc6 10. Nc7 Rb8 11. Nb5 Bf5 12. Bxb8 Qxb8 ) 9... Ng4 10. Nxa8 e5 ) 6... c5 7. Qg3 Nc6 8. Bc7 Qd7 9. O-O-O cxd4 10. exd4 O-O ) 4... Bg7 5. O-O-O (5. Bh6 O-O 6. h4 (6. O-O-O c5 7. dxc5 Qa5 8. Bxg7 Kxg7 ) (6. f3 c5 7. Bxg7 Kxg7 8. dxc5 (8. e3 h5 ) 8... d4 9. Nb5 Nc6 10. Rd1 e5 11. e4 b6 ) (6. Bxg7 Kxg7 7. e3 (7. Nf3 Qd6 ) (7. O-O-O c5 8. dxc5 (8. e3 c4 9. e4 b5 10. e5 Ng8 11. Nxb5 Nc6 12. Kb1 f6 13. Na3 Qb6 14. exf6+ Nxf6 15. f3 Rb8 16. c3 e5 ) 8... Qa5 9. Kb1 (9. Nxd5 Qxa2 10. Nc3 (10. Qc3 Be6 11. e4 (11. Nxf6 exf6 12. Nf3 Nd7 ) 11... Bxd5 12. exd5 Kg8 13. Qa3 (13. b3 Qa3+ 14. Kb1 a5 ) 13... Qxa3 14. bxa3 Rc8 ) 10... Qa1+ 11. Nb1 Be6 12. b3 Rc8 ) 9... Nc6 10. Nxd5 Qxd2 11. Rxd2 Ne4 ) 7... c5 8. dxc5 Qa5 9. Ne4 Qxd2+ 10. Nxd2 Bf5 11. Bd3 Nbd7 12. Nb3 Rfc8 ) 6... c5 7. dxc5 (7. Bxg7 Kxg7 8. dxc5 d4 9. O-O-O Nc6 10. Nb5 Qd5 11. Kb1 Ne4 12. Qe1 Be6 ) 7... Bxh6 8. Qxh6 d4 9. O-O-O Ng4 10. Qf4 e5 11. Qg3 Qa5 ) 5... c6 6. f3 b5 7. Kb1 Nbd7 ) 3... g6 4. Qd3 (4. Qd2 Bg7 5. O-O-O (5. h4 Ne4 6. Nxe4 dxe4 7. e3 (7. O-O-O Nc6 8. e3 h6 9. Bf4 O-O ) 7... c5 8. Ne2 Bg4 ) (5. f3 h6 6. Bxf6 (6. Bf4 c5 7. dxc5 Nbd7 ) (6. Bh4 c5 7. e3 (7. dxc5 O-O 8. Bxf6 (8. O-O-O Nc6 9. Nxd5 (9. Bxf6 Bxf6 10. Nxd5 Bg7 11. e4 e6 12. Ne3 Qg5 13. Ng4 Qxc5 14. Nxh6+ Kh7 15. Ng4 f5 16. Qg5 Kg8 17. Nh6+ Kh7 18. Ng4 Kg8 ) 9... Nxd5 10. Qxd5 Qa5 ) 8... Bxf6 9. Qxh6 Qa5 10. Qd2 Rd8 ) 7... cxd4 8. exd4 O-O ) 6... exf6 7. e4 dxe4 8. fxe4 O-O ) 5... c6 6. f3 b5 7. e4 (7. Bh6 Bxh6 8. Qxh6 Qa5 9. a3 Na6 ) 7... b4 8. Nce2 dxe4 9. Bxf6 exf6 ) 4... Bg7 5. e4 dxe4 6. Nxe4 Bf5 7. Bxf6 exf6
5
  • 1
    Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking.
    – Community Bot
    Commented Jan 29, 2022 at 5:37
  • An important point here seems to be why your failed attempt doesn't work. You say it fails, but not how. Are you trying to make a PGN move tree, and need the result to match that? Or is it something else?
    – user30536
    Commented Jan 29, 2022 at 7:54
  • I am trying to output this kind of syntax: 1. d4 Nf6 2. Nc3 d5 3. Bg5 g6 4. Qd3 Bg7 5. e4 (5. O-O-O c6 (5... Nbd7)) 5... dxe4 6. Nxe4 Bf5 from this kind of syntax: 1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.h4 c5 7.dxc5 Bxh6 8.Qxh6 d4 9.O-O-O Ng4 10.Qf4 e5 11.Qg3 Qa5 1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.h4 c5 7.Bxg7 Kxg7 8.dxc5 d4 9.O-O-O Nc6 10.Nb5 Qd5 11.Kb1 Ne4 12.Qe1 Be6 1.d4 Nf6 2.Nc3 d5 3.Bf4 g6 4.Qd2 Bg7 5.Bh6 O-O 6.O-O-O c5 7.dxc5 Qa5 A text file would do. My script is simply wrong, because I don't have the skills to manipulate strings.
    – db_max
    Commented Jan 29, 2022 at 7:58
  • Chessable has an option named "Browse Repertoire Tree" in the course page to show you the lines as if it is in an opening tree, does this satisfy your need?
    – Minot
    Commented Jan 29, 2022 at 8:20
  • Just a quick note that using any chessable content in any computer program except in a browser on the chessable website itself violates chessable's terms.
    – Hauptideal
    Commented Feb 1, 2022 at 13:14

2 Answers 2

3

Here's some Python code to attempt to get what you want, but you would likely have a richer set of tools to work with if you can represents your data in PGN format. The script reads the input file passed as the single command-line argument.

import re
import sys
# Create and print a tree of variations from individual lines of moves.
# The moves don't have a result terminator and neither does the output.

def createTree(lines : list) -> dict:   
    """ Create a tree from the variations """
    tree = dict()
    for line in lines:
        moves = [m for m in re.sub('[1-9][0-9]*\\.', '', line).split(' ') if m != '']
        node = tree
        index = 0
        while index < len(moves) and moves[index] in node:
            node = node[moves[index]]
            index += 1
        while index < len(moves):
            node[moves[index]] = dict()
            node = node[moves[index]]
            index += 1
    return tree

def printline(node : dict, moveNumber : int, whiteToMove : bool):
    """ Print the lines whose first moves are the keys 
        of the given node.
        Treat one as the main line and the rest as
        sub-variations. The order is arbitrary.
    """
    variations = list(node.keys())
    if variations != []:
        if whiteToMove:
            print(str(moveNumber) + '.', end = ' ')
        print(variations[0], end = ' ')
        for idx in range(1, len(variations)):
            print('(', end = '')
            print(moveNumber, end ='')
            if whiteToMove:
                print('.', end = ' ')
            else:
                print('...', end = ' ')
            print(variations[idx], end = ' ')
            printline(node[variations[idx]], moveNumber if whiteToMove else moveNumber + 1, not whiteToMove)
            print(')', end = ' ')
        if whiteToMove and len(variations) > 1:
            print(str(moveNumber) + '...', end = ' ')
        printline(node[variations[0]], moveNumber if whiteToMove else moveNumber + 1, not whiteToMove)

def printTree(tree : dict):
    """ Each key in tree is the first move of a game.
        Print each first move as a separate line.
    """
    for firstmove in tree:
        print('1.', firstmove, end = ' ')
        printline(tree[firstmove], 1, False)

if len(sys.argv) == 2:
    f = open(sys.argv[1])
    lines = f.read().strip().split('\n')
    tree = createTree(lines)
    printTree(tree)
else:
    print('Usage:', sys.argv[0],'file-of-lines')
2
  • your piece of code worked exactly as I desired, thank you so much. For those who might be interested, create a .py file with @kentdjb's code (e.g. tree.py), create a .txt file with your single-line notation (e.g. veresov.txt), and use this command: python tree.py veresov.txt. In your terminal the tree notation will be printed.
    – db_max
    Commented Jan 30, 2022 at 9:45
  • I am pleased it is what you wanted. The programming technique you were potentially missing is recursion, which can be found in the implementation of my printline function, as trees are a recursive data structure.
    – kentdjb
    Commented Jan 30, 2022 at 14:51
3

You could use https://github.com/permutationlock/merge-pgn to merge multiple games into a single game with variations.

Also you may want to look at the code. It is small and does not rely on string manipulation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.