python-chess icon indicating copy to clipboard operation
python-chess copied to clipboard

Slice of a game between two moves

Open kevinpolisano opened this issue 2 years ago • 0 comments

Hello,

First of all, thank you for this great python library!

My problem is simple: I would like to extract a subgame between the $i$-th move and the $j$-th move.

For example, if the PGN is: 1. e4 e5 2. Nf3 (2. Nc3 d6) 2... d5 3. a4 a5 4. b4 b5, I want to extract a slice between the second move for White (2. Nf3) and the third move for Black (3... a5), so to obtain 2. Nf3 (2. Nc3 d6) 2... d5 3. a4 a5.

I was able to print all the moves in between with a subclass of Visitor (*) but I don't know how to preserve the right numbering? I'm trying to use visit_board and board.fullmove_number but I suppose I need to restore the board as mentioned in the doc:

The board state must be restored before the traversal continues.

How can I fix it?

(*) Actually the code below is not completely satisfying since we could have repeated moves... that's why I'm actually looking for all the moves between two nodes in the main line.

import io
import chess
import chess.pgn

class SubPartVisitor(chess.pgn.BaseVisitor):
    def __init__(self, start_move, end_move):
        self.start_move = start_move
        self.end_move = end_move
        self.moves = []
        self.in_subpart = False
        self.numbering = 0

    def visit_move(self, board, move):
        if not self.in_subpart and move == self.start_move:
            self.in_subpart = True
            self.numbering = 1

        if self.in_subpart:
            move_str = f"{self.numbering}. {board.san(move)}"
            self.moves.append(move_str)

            if move == self.end_move:
                self.in_subpart = False
                return False

    def begin_variation(self):
        self.moves.append("(")

    def end_variation(self):
        self.moves.append(")")

    def visit_board(self, board):
        self.numbering = board.fullmove_number

    def result(self):
        return self.moves

if __name__ == "__main__":
    pgn_text = "1. e4 e5 2. Nf3 (2. Nc3 d6) 2... d5 3. a4 a5 4. b4 b5"
    
    start_move = chess.Move.from_uci("g1f3")  # 2. Nf3
    end_move = chess.Move.from_uci("a7a5")    # 3. a5

    pgn = io.StringIO(pgn_text)
    game = chess.pgn.read_game(pgn)

    subpart_visitor = SubPartVisitor(start_move, end_move)
    game.accept(subpart_visitor)

    moves_in_subpart = subpart_visitor.result()
    print(" ".join(moves_in_subpart))

Output: 1. Nf3 ( 2. Nc3 2. d6 ) 3. d5 3. a4 3. a5

Otherwise I found a way to visit recursively the nodes as follows:

def traverse(node):
     while node:
      board = node.board()
      if len(node.variations)==1:
        if node.turn()==False:
          print(str(board.fullmove_number) + '.')
        print(node.san())
      else:
       print(node.san())
       for child in node.variations[1:]:
        node = node.next()
        board = node.board()
        print(board.fullmove_number)
        print(node.san())
        print("(")
        traverse(child)
        print(")")
      node = node.next()

The structure is OK but I have also a problem with the numbering...

kevinpolisano avatar Jul 28 '23 08:07 kevinpolisano