Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Problem with an iterator
#1
Hello,

I am trying to write a chess program that grabs a random chess position, sends the position to an evaluation engine, and returns both the position and the evaluation.

I am using three libraries: random, which generates random numbers, stockfish, which is a Python API wrapper for the chess engine "stockfish," and chess, which displays a board and parses the human-readable "pgn" file format and translates it to the "fen" format readable by stockfish.

The issue I believe I am running in to is in the generator in the "move_picker" function below. I have tested the "game_picker" function on its own, and it works properly in isolation.



import chess.pgn
import random
import stockfish
from stockfish import Stockfish

stockfish = Stockfish("/Users/User/stockfish_20011801_x64.exe")


pgn = open("Capablanca.pgn")



def game_picker():
    i = 1
    game_val = random.randint(2,550)
    the_game = chess.pgn.read_game(pgn)
    for i in range(game_val):
        the_game = chess.pgn.read_game(pgn)
    return the_game

        
def move_picker():
    Move_val = random.randint(10,40)
    board3 = the_game.board()
    moves_list = iter(the_game.mainline_moves())
    i = 1
    for i in range(Move_val):
       board3.push(next(moves_list))
    fen_position = board3.fen()
    yield fen_position

    
def run_the_game():
    game_picker()
    move_picker()
    fen_position = move_picker()
    stockfish.set_fen_position(fen_position)
    eval = stockfish.get_evaluation()
    eval = eval["value"]
    print("Stockfish says...  " + str(eval))
    display(board3)
    


run_the_game()
When I run this code, it just hangs, and I get a Windows crash report.

The documentation for the chess library is not good. This is all the relevant information they give:

>>> import chess.pgn
>>>
>>> pgn = open("data/pgn/kasparov-deep-blue-1997.pgn")
>>>
>>> first_game = chess.pgn.read_game(pgn)
>>> second_game = chess.pgn.read_game(pgn)
>>>
>>> first_game.headers["Event"]
'IBM Man-Machine, New York USA'
>>>
>>> # Iterate through all moves and play them on a board.
>>> board = first_game.board()
>>> for move in first_game.mainline_moves():
...     board.push(move)
But I don't want to iterate through ALL moves, just through a random number of moves. So when I try it like this, mirroring the code from the documentation as much as possible, I also get an error.

import chess
import chess.pgn
import random
import stockfish
from stockfish import Stockfish

stockfish = Stockfish("/Users/User/stockfish_20011801_x64.exe")


pgn = open("Capablanca.pgn")



def game_picker():
    i = 1
    game_val = random.randint(2,550)
    the_game = chess.pgn.read_game(pgn)
    for i in range(game_val):
        the_game = chess.pgn.read_game(pgn)
    return the_game

        
def move_picker():
    Move_val = random.randint(10,40)
    board3 = the_game.board()
    for Move_val in the_game.mainline_moves():
        board3.push(move)
    return board3

    
def run_the_game():
    game_picker()
    move_picker()
    fen_position = move_picker()
    fen_position = board3.fen()
    stockfish.set_fen_position(fen_position)
    eval = stockfish.get_evaluation()
    eval = eval["value"]
    print("Stockfish says...  " + str(eval))
    display(board3)
    


run_the_game()
Error:
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-9-8c9bfbf62c3a> in <module> 51 52 ---> 53 run_the_game() 54 <ipython-input-9-8c9bfbf62c3a> in run_the_game() 32 game_picker() 33 the_game = game_picker() ---> 34 move_picker() 35 fen_position = move_picker() 36 fen_position = board3.fen() <ipython-input-9-8c9bfbf62c3a> in move_picker() 23 def move_picker(): 24 Move_val = random.randint(10,40) ---> 25 board3 = the_game.board() 26 for Move_val in the_game.mainline_moves(): 27 board3.push(move) NameError: name 'the_game' is not defined
I'm at a loss and any help would be greatly appreciated.
Reply
#2
This is a scope issue, the_game is local to game_picker, but is returned to the calling function.
The problem is that in run_the_game, you do nothing with that return value.
try the following:
import chess
import chess.pgn
import random
import stockfish
from stockfish import Stockfish
 
stockfish = Stockfish("/Users/User/stockfish_20011801_x64.exe")
 
 
pgn = open("Capablanca.pgn")
 
 
 
def game_picker():
    i = 1
    game_val = random.randint(2,550)
    the_game = chess.pgn.read_game(pgn)
    for i in range(game_val):
        the_game = chess.pgn.read_game(pgn)
    return the_game
 
         
def move_picker(the_game):
    Move_val = random.randint(10,40)
    board3 = the_game.board()
    for Move_val in the_game.mainline_moves():
        board3.push(move)
    return board3
 
     
def run_the_game():
    the_game = game_picker()
    move_picker(the_game)
    fen_position = move_picker()
    fen_position = board3.fen()
    stockfish.set_fen_position(fen_position)
    eval = stockfish.get_evaluation()
    eval = eval["value"]
    print("Stockfish says...  " + str(eval))
    display(board3)
     
 
 
run_the_game()
Reply
#3
Thank you very much for the reply. You're absolutely right about the scope issue, I've been researching this for the last couple hours and found the same thing.

When I run the code as you've written, I still get an error.

Error:
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-3-3c4c9f4ff6ec> in <module> 42 43 ---> 44 run_the_game() <ipython-input-3-3c4c9f4ff6ec> in run_the_game() 31 def run_the_game(): 32 the_game = game_picker() ---> 33 move_picker(the_game) 34 fen_position = move_picker() 35 fen_position = board3.fen() <ipython-input-3-3c4c9f4ff6ec> in move_picker(the_game) 25 board3 = the_game.board() 26 for Move_val in the_game.mainline_moves(): ---> 27 board3.push(move) 28 return board3 29 NameError: name 'move' is not defined
And when I replace 'move' as such...

def move_picker(the_game):
    Move_val = random.randint(10,40)
    board3 = the_game.board()
    for Move_val in the_game.mainline_moves():
        board3.push(the_game.mainline_moves())
    return board3


I get the following error...

Error:
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-4-d244ef3bb2bf> in <module> 42 43 ---> 44 run_the_game() <ipython-input-4-d244ef3bb2bf> in run_the_game() 31 def run_the_game(): 32 the_game = game_picker() ---> 33 move_picker(the_game) 34 fen_position = move_picker() 35 fen_position = board3.fen() <ipython-input-4-d244ef3bb2bf> in move_picker(the_game) 25 board3 = the_game.board() 26 for Move_val in the_game.mainline_moves(): ---> 27 board3.push(the_game.mainline_moves()) 28 return board3 29 ~\Anaconda3\lib\site-packages\chess\__init__.py in push(self, move) 2137 """ 2138 # Push move and remember board state. -> 2139 move = self._to_chess960(move) 2140 board_state = self._board_state() 2141 self.castling_rights = self.clean_castling_rights() # Before pushing stack ~\Anaconda3\lib\site-packages\chess\__init__.py in _to_chess960(self, move) 3543 3544 def _to_chess960(self, move: Move) -> Move: -> 3545 if move.from_square == E1 and self.kings & BB_E1: 3546 if move.to_square == G1 and not self.rooks & BB_G1: 3547 return Move(E1, H1) AttributeError: 'Mainline' object has no attribute 'from_square'
Interestingly, if I write the code in a more messy way, I can get it to work. But I would prefer to write this code as a function, since I'd like to use it in other coding.

In this way, it works...

import chess.pgn
import random
import stockfish
from stockfish import Stockfish




pgn = open("Capablanca.pgn")

stockfish = Stockfish("/Users/User/stockfish_20011801_x64.exe")

##


Game_val = random.randint(2,550)
Move_val = random.randint(10,25)



##



def game_picker():
    i=1
    for i in range(Game_val):
        the_game = chess.pgn.read_game(pgn)
        i += 1
    return the_game
        
the_game = game_picker() 
board3 = the_game.board()


def move_picker():
    i=1
    moves_list = iter(the_game.mainline_moves())
    for i in range(Move_val):
        board3.push(next(moves_list))
        i += 1
    return board3

board3 = move_picker()


##

fen_position = board3.fen()


stockfish.set_fen_position(fen_position)
eval = stockfish.get_evaluation()
eval = eval["value"]


   
print("Stockfish says...  " + str(eval))

display(board3)
But I don't understand what's going on, and I need it to be in a function so I can call it in other code.

Any ideas? And thank you again
Reply
#4
This makes no sense:
def move_picker(the_game):
    Move_val = random.randint(10,40)
    board3 = the_game.board()
    for Move_val in the_game.mainline_moves():
        board3.push(the_game.mainline_moves())
    return board3
You never use the the result of this command "Move_val = random.randint(10,40)". What do you expect his to do?

And what is this supposed to do?
    for Move_val in the_game.mainline_moves():
        board3.push(the_game.mainline_moves())
It appears that the_game.mainline_moves() returns some sort of iterator. You get a value from the iterator, and then as before you never use the value and instead push the iterator?

I think maybe you are trying to write this?
def move_picker(the_game):
    moves_remaining = random.randint(10, 40)
    board = the_game.board()
    for move in the_game.mainline_moves():
        board.push(move)
        if (moves_remaining := moves_remaining - 1) <= 0:
            break
    return board
This can be cleaned up a bit by using zip() which allows using multiple iterators at the same time. This example iterates through the moves and iterates through a randomly sized range. The zip ends when either iterator runs out of values.
def move_picker(the_game):
    board = the_game.board()
    for move, _ in zip(the_game.mainline_moves(), range(randint(10, 40))):
        board.append(move)
    return board
Reply
#5
One more "What do you think you are doing?!"
import stockfish
from stockfish import Stockfish
...
stockfish = Stockfish("/Users/User/stockfish_20011801_x64.exe")
As far as I can tell you never use the "import stockfish". You use "from stockfish import Stockfish". If you are thinking you need to import stockfish before you can from stockfish import Stockfish, that is not the case.

But that is not the big question. The big question is why are you hiding your stockfish module by making a variable that has the same name? That is just bad programming practice as it can lead to unexpected and difficult to diagnose errors, and it is just downright confusing. Someone looking at your code will see stockfish as a module. And then they will see it is a variable. But it isn't it a module?

Here's another example where you pick a really bad variable name.
eval = stockfish.get_evaluation()
eval = eval["value"]
eval is a Python command, as in:
print(eval('3+5'))
Avoid using python keywords as variable names. Do not call something a list or a dict or eval or a type or min or max. If I wanted to use eval I could not, because you decided that eval is no longer a function that evaluates an expression, it is something that is returned by stockfish.get_evaluation(). Maybe that is an eval, but it probably isn't the standard eval.

Instead of this:
eval = stockfish.get_evaluation()
eval = eval["value"]
I would write
value = stockfish.get_evaluation()["value"]
grimm1111 likes this post
Reply
#6
(Feb-06-2021, 05:00 AM)deanhystad Wrote: This makes no sense:
def move_picker(the_game):
    Move_val = random.randint(10,40)
    board3 = the_game.board()
    for Move_val in the_game.mainline_moves():
        board3.push(the_game.mainline_moves())
    return board3
You never use the the result of this command "Move_val = random.randint(10,40)". What do you expect his to do?

And what is this supposed to do?
    for Move_val in the_game.mainline_moves():
        board3.push(the_game.mainline_moves())
It appears that the_game.mainline_moves() returns some sort of iterator. You get a value from the iterator, and then as before you never use the value and instead push the iterator?

I think maybe you are trying to write this?
def move_picker(the_game):
    moves_remaining = random.randint(10, 40)
    board = the_game.board()
    for move in the_game.mainline_moves():
        board.push(move)
        if (moves_remaining := moves_remaining - 1) <= 0:
            break
    return board
This can be cleaned up a bit by using zip() which allows using multiple iterators at the same time. This example iterates through the moves and iterates through a randomly sized range. The zip ends when either iterator runs out of values.
def move_picker(the_game):
    board = the_game.board()
    for move, _ in zip(the_game.mainline_moves(), range(randint(10, 40))):
        board.append(move)
    return board


Thank you for taking the time to look at this. Unfortunately, I'm still not getting it to work. Here's the full code I tried to implement with your fixes:

import chess
import chess.pgn
import random
import stockfish
from stockfish import Stockfish
  
stockfish = Stockfish("/Users/User/stockfish_20011801_x64.exe")
  
  
pgn = open("Capablanca.pgn")
  
  
  
def game_picker():
    i = 1
    game_val = random.randint(2,550)
    the_game = chess.pgn.read_game(pgn)
    for i in range(game_val):
        the_game = chess.pgn.read_game(pgn)
    move_picker(the_game)
    return the_game
  
          
def move_picker(the_game):
    board = the_game.board()
    for move, _ in zip(the_game.mainline_moves(), range(random.randint(10, 40))):
        board.append(move)
    return board
  
      
def run_the_game():
    the_game = game_picker()
    move_picker(the_game)
    fen_position = move_picker()
    fen_position = board.fen()
    stockfish.set_fen_position(fen_position)
    eval = stockfish.get_evaluation()
    eval = eval["value"]
    print("Stockfish says...  " + str(eval))
    display(board)
      
  
  
run_the_game()
And here is the error I'm getting...

Error:
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-2-6e188559179e> in <module> 42 43 ---> 44 run_the_game() <ipython-input-2-6e188559179e> in run_the_game() 30 31 def run_the_game(): ---> 32 the_game = game_picker() 33 move_picker(the_game) 34 fen_position = move_picker() <ipython-input-2-6e188559179e> in game_picker() 18 for i in range(game_val): 19 the_game = chess.pgn.read_game(pgn) ---> 20 move_picker(the_game) 21 return the_game 22 <ipython-input-2-6e188559179e> in move_picker(the_game) 25 board = the_game.board() 26 for move, _ in zip(the_game.mainline_moves(), range(random.randint(10, 40))): ---> 27 board.append(move) 28 return board 29 AttributeError: 'Board' object has no attribute 'append'
I'm not sure if this helps, but this is the dir, type, inspect, and a printout of the .mainline_moves method.

Output:
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] <class 'method'> <bound method GameNode.mainline_moves of <Game at 0x26ba3feb108 ('Capablanca, Jose Raul' vs. 'Corzo y Prinzipe, Juan', '1901.??.??')>> FullArgSpec(args=['self'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={'return': 'Mainline[chess.Move]'})
I'm stumped. I've been reading through the documentation for the "chess" library, but it's like one page long and tells me nothing. But if anyone can help me solve this, that would be amazing.

https://python-chess.readthedocs.io/en/latest/pgn.html
Reply
#7
You will want to use board.push(). I don't have the chess package so I used something else for testing. Forgot to switch the code back to push() when I was done. Sorry.
grimm1111 likes this post
Reply
#8
(Feb-06-2021, 05:57 AM)deanhystad Wrote: You will want to use board.push(). I don't have the chess package so I used something else for testing. Forgot to switch the code back to push() when I was done. Sorry.

I just wanted to thank you for your help. I couldn't get the code to work exactly as you had it, but you got me thinking in the right way, and eventually I hacked together something ugly that works. For a novice like me, that's a start anyway. When experienced coders help out newbies like me, it's really great learning. So thanks. Here's what ended up working...

import chess
import chess.pgn
import random
from stockfish import Stockfish
  
sf = Stockfish("/Users/User/stockfish_20011801_x64.exe")
  
  
pgn = open("Capablanca.pgn")
  
  
  
def game_picker():
    i = 1
    game_val = random.randint(2,550)
    the_game = chess.pgn.read_game(pgn)
    for i in range(game_val):
        the_game = chess.pgn.read_game(pgn)
    move_picker(the_game)

  
          
def move_picker(the_game):
    my_board = the_game.board()
    moves_list = []
    Move_val = random.randint(5,50)
    for move in the_game.mainline_moves():
        moves_list.append(move)
    move_me = moves_list[0:Move_val]
    for i in move_me:
        my_board.push(i)
    global fen_position 
    fen_position = my_board.fen()
    print(the_game.headers["White"] + ' vs ' + the_game.headers["Black"])
    print(the_game.headers["Event"])
    display(my_board)
    
      
def run_the_game():
    game_picker()
    sf.set_fen_position(fen_position)
    position_evaluation = sf.get_evaluation()
    position_evaluation = position_evaluation["value"]
    print("Stockfish says...  " + str(position_evaluation))
    
      

run_the_game()
bowlofred likes this post
Reply
#9
This should work. It uses list() to replace the code that built the moves_list.
def move_picker(game):
    board = game.board()
    num_moves = random.randint(5,50)
    for move in list(game.mainline_moves())[:num_moves]:
        board.push(move)

    sf.set_fen_position(board.fen())
    print(game.headers["White"] + ' vs ' + game.headers["Black"])
    print(game.headers["Event"])
    display(board)
Avoid using "global". It looks like the purpose of the global variable fen_position was to return my_board.fen to run_the_game() which did this "sf.set_fen_position(fen_position)". Does it make more sense to move this code from run_the_game() to move_picker()?

The problem with global variables is it isn't apparent where they are used. The only way to learn this information is search for the name, and since there can be local variables with the same name, this can be confusing. It is far better to return the needed information.

This is an example of returning the pieces required to do the bookkeeping. I like that each function has a well defined roll. Pick_game() randomly selects a game to play. It does not play the game. It does not cause the game to be played. It just does what the name implies. Play_game() plays the game. It doesn't pick the game. It doesn't display results. it doesn't update collect information that it doesn't use. Run_the_game() is the organizer and bookkeeper. It delegates selecting and playing the game.
import chess
import chess.pgn
from random import randint
from stockfish import Stockfish
   
sf = Stockfish("/Users/User/stockfish_20011801_x64.exe")

def pick_game(pgn):
    """Play a randomly selected game"""
    game = chess.pgn.read_game(pgn)
    for _ in range(randint(2,550)):
        game = chess.pgn.read_game(pgn)
    return game

def play_game(game):
    """Play randomly selected number of moves of game"""
    board = game.board()
    num_moves = randint(5,50)
    for move in list(game.mainline_moves())[:num_moves]:
        board.push(move)

def run_the_game(game_file):
    with open(game_file) as pgn:
        game = pick_game(pgn)
        play_game(game)
    
    print(game.headers["White"] + ' vs ' + game.headers["Black"])
    print(game.headers["Event"])
    display(game.board())
     
    sf.set_fen_position(game.board().fen())
    position_evaluation = sf.get_evaluation()
    position_evaluation = position_evaluation["value"]
    print("Stockfish says...  " + str(position_evaluation))     
       
run_the_game("Capablanca.pgn")
grimm1111 likes this post
Reply
#10
(Feb-06-2021, 04:44 PM)deanhystad Wrote: This should work. It uses list() to replace the code that built the moves_list.
def move_picker(game):
    board = game.board()
    num_moves = random.randint(5,50)
    for move in list(game.mainline_moves())[:num_moves]:
        board.push(move)

    sf.set_fen_position(board.fen())
    print(game.headers["White"] + ' vs ' + game.headers["Black"])
    print(game.headers["Event"])
    display(board)
Avoid using "global". It looks like the purpose of the global variable fen_position was to return my_board.fen to run_the_game() which did this "sf.set_fen_position(fen_position)". Does it make more sense to move this code from run_the_game() to move_picker()?

The problem with global variables is it isn't apparent where they are used. The only way to learn this information is search for the name, and since there can be local variables with the same name, this can be confusing. It is far better to return the needed information.

This is an example of returning the pieces required to do the bookkeeping. I like that each function has a well defined roll. Pick_game() randomly selects a game to play. It does not play the game. It does not cause the game to be played. It just does what the name implies. Play_game() plays the game. It doesn't pick the game. It doesn't display results. it doesn't update collect information that it doesn't use. Run_the_game() is the organizer and bookkeeper. It delegates selecting and playing the game.
import chess
import chess.pgn
from random import randint
from stockfish import Stockfish
   
sf = Stockfish("/Users/User/stockfish_20011801_x64.exe")

def pick_game(pgn):
    """Play a randomly selected game"""
    game = chess.pgn.read_game(pgn)
    for _ in range(randint(2,550)):
        game = chess.pgn.read_game(pgn)
    return game

def play_game(game):
    """Play randomly selected number of moves of game"""
    board = game.board()
    num_moves = randint(5,50)
    for move in list(game.mainline_moves())[:num_moves]:
        board.push(move)

def run_the_game(game_file):
    with open(game_file) as pgn:
        game = pick_game(pgn)
        play_game(game)
    
    print(game.headers["White"] + ' vs ' + game.headers["Black"])
    print(game.headers["Event"])
    display(game.board())
     
    sf.set_fen_position(game.board().fen())
    position_evaluation = sf.get_evaluation()
    position_evaluation = position_evaluation["value"]
    print("Stockfish says...  " + str(position_evaluation))     
       
run_the_game("Capablanca.pgn")

Point well taken about global variables. I need to learn more about namespaces, scope, and passing variables between functions. I have been doing some reading on this today, so again thank you.

The code as you have it does not work fully. It is only returning the first move of any given game, so there is some issue here...

    for move in list(game.mainline_moves())[:num_moves]:
        board.push(move)
which I think has something to do with the mainline_moves method from the chess library. For some reason I do not understand, when I create a new list and append each move from the mainline moves output, and iterate through that list, then it works fine. But if I try to do like you're doing here and iterate through it directly and feed the output to 'board.push' then it does not function properly.

I have yet to understand that. I do see that '__ITER__' does not appear in the 'dir' for 'mainline_moves' however, but so then why does it work to iterate through it and create a new list? Who knows.

Thank you again
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  prime numbers with iterator and generator cametan_001 8 1,771 Dec-17-2022, 02:41 PM
Last Post: cametan_001
  resetting an iterator to full Skaperen 7 6,808 Feb-20-2022, 11:11 PM
Last Post: Skaperen
  popping an iterator Skaperen 11 3,600 Oct-03-2021, 05:08 PM
Last Post: Skaperen
  q re glob.iglob iterator and close jimr 2 2,175 Aug-23-2021, 10:14 PM
Last Post: perfringo
  Multi-class iterator Pedroski55 2 2,335 Jan-02-2021, 12:29 AM
Last Post: Pedroski55
  is a str object a valid iterator? Skaperen 6 5,539 Jan-27-2020, 08:44 PM
Last Post: Skaperen
  discard one from an iterator Skaperen 1 1,957 Dec-29-2019, 11:02 PM
Last Post: ichabod801
  how do i pass duplicates in my range iterator? pseudo 3 2,294 Dec-18-2019, 03:01 PM
Last Post: ichabod801
  looking for a sprcil iterator Skaperen 7 3,290 Jun-13-2019, 01:40 AM
Last Post: Clunk_Head
  last pass of for x in iterator: Skaperen 13 5,723 May-20-2019, 10:05 PM
Last Post: Yoriz

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020