@tool def PNG2FENTool(png_file: str) -> str: """Tool for converting a PNG file containing a chess board to a FEN position string. Args: png_file (str): The path to the PNG file. Returns: str: The FEN position string representing the chess board. """ # Raises: # - FileNotFoundError: # If the PNG file does not exist. # - ValueError: # If the PNG file cannot be processed or does not contain a valid chess board. try: # Open and preprocess image with modern Pillow img = Image.open(png_file) img = ImageOps.exif_transpose(img).convert("L") # Use LANCZOS instead of ANTIALIAS img = img.resize((img.width*2, img.height*2), Image.Resampling.LANCZOS) # Save temp file for OCR temp_path = "chess_temp.png" img.save(temp_path) # Perform OCR import easyocr reader = easyocr.Reader(['en']) result = reader.readtext(png_file, detail=0) fen_candidates = [text for text in result if validate_fen_format(text)] if not fen_candidates: raise ValueError("No valid FEN found in image") return fen_candidates[0] except Exception as e: raise ValueError(f"OCR processing failed: {str(e)}") # try: # # Open the PNG file using PIL # image = Image.open(png_file) # # # Use pytesseract to extract text from the image # text = pytesseract.image_to_string(image) # # # Process the extracted text to get the FEN position string # fen_position = process_text_to_fen(text) # # return fen_position # except FileNotFoundError: raise FileNotFoundError("PNG file not found.") # # except Exception as e: # raise ValueError("Error processing PNG file: " + str(e)) def process_text_to_fen(text): """ Processes the extracted text from the image to obtain the FEN position string. Parameters: - text: str The extracted text from the image. Returns: - str: The FEN position string representing the chess board. Raises: - ValueError: If the extracted text does not contain a valid chess board. """ # Process the text to remove any unnecessary characters or spaces processed_text = text.strip().replace("\n", "").replace(" ", "") # Check if the processed text matches the expected format of a FEN position string if not validate_fen_format(processed_text): raise ValueError("Invalid chess board.") return processed_text def validate_fen_format(fen_string): """ Validates if a given string matches the format of a FEN (Forsyth–Edwards Notation) position string. Parameters: - fen_string: str The string to be validated. Returns: - bool: True if the string matches the FEN format, False otherwise. """ # FEN format: 8 sections separated by '/' sections = fen_string.split("/") if len(sections) != 8: return False # Check if each section contains valid characters for section in sections: if not validate_section(section): return False return True def validate_section(section): """ Validates if a given section of a FEN (Forsyth–Edwards Notation) position string contains valid characters. Parameters: - section: str The section to be validated. Returns: - bool: True if the section contains valid characters, False otherwise. """ # Valid characters: digits 1-8 or letters 'r', 'n', 'b', 'q', 'k', 'p', 'R', 'N', 'B', 'Q', 'K', 'P' valid_chars = set("12345678rnbqkpRNBQKP") return all(char in valid_chars for char in section) import chess import chess.engine class ChessEngineTool(Tool): name = "chess_engine" description = "Analyzes a chess position (FEN) with Stockfish and returns the best move." inputs = { "fen": {"type": "string", "description": "FEN string of the position."}, "time_limit": {"type": "number", "description": "Time in seconds for engine analysis.", "nullable": True} } output_type = "string" def forward(self, fen: str, time_limit: float = 0.1) -> str: # figure out where the binary actually is sf_bin = shutil.which("stockfish") or "/usr/games/stockfish" if not sf_bin: raise RuntimeError( f"Cannot find stockfish on PATH or at /usr/games/stockfish. " "Did you install it in apt.txt or via apt-get?" ) board = chess.Board(fen) engine = chess.engine.SimpleEngine.popen_uci(sf_bin) result = engine.play(board, chess.engine.Limit(time=time_limit)) engine.quit() return board.san(result.move)