niklasf/python-chess

Help with PGN to FEN strings with move list

Opened this issue · 1 comments

I would like to achieve the following:

  1. Use a PGN-file as input.
  2. Output individual FEN strings for each position from a chosen perspective, including variations. The FEN position should be before the opposite color made their move.
  3. Include a move list after the FEN string (the first move would be the opposite color, and the second move would be the move from the chosen perspective).
  4. If any moves had annotations (comments/[%csl ...]/[%cal ...]) those should be preserved in the move list as well.

Example:

With the following PGN as input, and black as the chosen perspective…

[Event "?"]
[Site "?"]
[Date "????.??.??"]
[Round "?"]
[White "?"]
[Black "?"]
[Result "*"]

1. e4 c5 { Sicilian } { [%csl Gd4][%cal Gc5d4] } 2. Nf3 (2. Nc3 { Closed Sicilian } 2... d6) 2... d6 *

…the desired output would be:

[FEN "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"]
1. e4 c5 { Sicilian } { [%csl Gd4][%cal Gc5d4] }

[FEN "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2"]
2. Nf3 d6

[FEN "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2"]
2. Nc3 { Closed Sicilian } 2... d6

I hope that all made sense.

Is this possible? I would be grateful for any pointers on how to achieve it.

I figured out the first parts!

How can I include not only moves but also the move numbers and comments/annotations from the PGN? If anyone could provide some pointers that would be great.

import chess
import chess.pgn

pgn_file = '/path'
repertoire_color = 'black'

if repertoire_color == 'black':
    setup_positions = chess.WHITE  # FEN positions will be before opponents move
elif repertoire_color == 'white':
    setup_positions = chess.BLACK  # FEN positions will be before opponents move

with open(pgn_file) as pgn:
    parsed_pgn = chess.pgn.read_game(pgn)

def extract_positions(node, color):
    if node.board().turn == color:
        fen = node.board().fen()
        
        if node.variations:
            # Get opponent move
            for variation in node.variations:
                opponent_move = variation.move
                # Convert move to algebraic notation
                algebraic_opponent_move = node.board().san(opponent_move)
                
                if variation.variations:
                # For each found move, check if there's a response in the repertoire
                    response_move = variation.variations[0].move
                    if response_move:
                    # Convert move to algebraic notation
                        algebraic_response_move = variation.board().san(response_move)
                        
                        # Print the position and move pair
                        print(f'[FEN "{fen}"]\n{algebraic_opponent_move} {algebraic_response_move}\n')
    
    # Recursively process nodes to extract all positions (both main line and sidelines)
    for variation in node.variations:
        extract_positions(variation, color)

extract_positions(parsed_pgn, setup_positions)