Spaces:
Running
Running
Create prechess.py
Browse files- prechess.py +151 -0
prechess.py
ADDED
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
|
3 |
+
|
4 |
+
@tool
|
5 |
+
def PNG2FENTool(png_file: str) -> str:
|
6 |
+
"""Tool for converting a PNG file containing a chess board to a FEN position string.
|
7 |
+
Args:
|
8 |
+
png_file (str): The path to the PNG file.
|
9 |
+
Returns:
|
10 |
+
str: The FEN position string representing the chess board.
|
11 |
+
"""
|
12 |
+
# Raises:
|
13 |
+
# - FileNotFoundError:
|
14 |
+
# If the PNG file does not exist.
|
15 |
+
# - ValueError:
|
16 |
+
# If the PNG file cannot be processed or does not contain a valid chess board.
|
17 |
+
|
18 |
+
try:
|
19 |
+
# Open and preprocess image with modern Pillow
|
20 |
+
img = Image.open(png_file)
|
21 |
+
img = ImageOps.exif_transpose(img).convert("L")
|
22 |
+
|
23 |
+
# Use LANCZOS instead of ANTIALIAS
|
24 |
+
img = img.resize((img.width*2, img.height*2), Image.Resampling.LANCZOS)
|
25 |
+
|
26 |
+
# Save temp file for OCR
|
27 |
+
temp_path = "chess_temp.png"
|
28 |
+
img.save(temp_path)
|
29 |
+
|
30 |
+
# Perform OCR
|
31 |
+
import easyocr
|
32 |
+
reader = easyocr.Reader(['en'])
|
33 |
+
result = reader.readtext(png_file, detail=0)
|
34 |
+
fen_candidates = [text for text in result if validate_fen_format(text)]
|
35 |
+
|
36 |
+
if not fen_candidates:
|
37 |
+
raise ValueError("No valid FEN found in image")
|
38 |
+
|
39 |
+
return fen_candidates[0]
|
40 |
+
|
41 |
+
except Exception as e:
|
42 |
+
raise ValueError(f"OCR processing failed: {str(e)}")
|
43 |
+
|
44 |
+
|
45 |
+
# try:
|
46 |
+
# # Open the PNG file using PIL
|
47 |
+
# image = Image.open(png_file)
|
48 |
+
#
|
49 |
+
# # Use pytesseract to extract text from the image
|
50 |
+
# text = pytesseract.image_to_string(image)
|
51 |
+
#
|
52 |
+
# # Process the extracted text to get the FEN position string
|
53 |
+
# fen_position = process_text_to_fen(text)
|
54 |
+
#
|
55 |
+
# return fen_position
|
56 |
+
#
|
57 |
+
except FileNotFoundError:
|
58 |
+
raise FileNotFoundError("PNG file not found.")
|
59 |
+
#
|
60 |
+
# except Exception as e:
|
61 |
+
# raise ValueError("Error processing PNG file: " + str(e))
|
62 |
+
|
63 |
+
def process_text_to_fen(text):
|
64 |
+
"""
|
65 |
+
Processes the extracted text from the image to obtain the FEN position string.
|
66 |
+
Parameters:
|
67 |
+
- text: str
|
68 |
+
The extracted text from the image.
|
69 |
+
Returns:
|
70 |
+
- str:
|
71 |
+
The FEN position string representing the chess board.
|
72 |
+
Raises:
|
73 |
+
- ValueError:
|
74 |
+
If the extracted text does not contain a valid chess board.
|
75 |
+
"""
|
76 |
+
|
77 |
+
# Process the text to remove any unnecessary characters or spaces
|
78 |
+
processed_text = text.strip().replace("\n", "").replace(" ", "")
|
79 |
+
|
80 |
+
# Check if the processed text matches the expected format of a FEN position string
|
81 |
+
if not validate_fen_format(processed_text):
|
82 |
+
raise ValueError("Invalid chess board.")
|
83 |
+
|
84 |
+
return processed_text
|
85 |
+
|
86 |
+
def validate_fen_format(fen_string):
|
87 |
+
"""
|
88 |
+
Validates if a given string matches the format of a FEN (Forsyth–Edwards Notation) position string.
|
89 |
+
Parameters:
|
90 |
+
- fen_string: str
|
91 |
+
The string to be validated.
|
92 |
+
Returns:
|
93 |
+
- bool:
|
94 |
+
True if the string matches the FEN format, False otherwise.
|
95 |
+
"""
|
96 |
+
|
97 |
+
# FEN format: 8 sections separated by '/'
|
98 |
+
sections = fen_string.split("/")
|
99 |
+
if len(sections) != 8:
|
100 |
+
return False
|
101 |
+
|
102 |
+
# Check if each section contains valid characters
|
103 |
+
for section in sections:
|
104 |
+
if not validate_section(section):
|
105 |
+
return False
|
106 |
+
|
107 |
+
return True
|
108 |
+
|
109 |
+
def validate_section(section):
|
110 |
+
"""
|
111 |
+
Validates if a given section of a FEN (Forsyth–Edwards Notation) position string contains valid characters.
|
112 |
+
Parameters:
|
113 |
+
- section: str
|
114 |
+
The section to be validated.
|
115 |
+
Returns:
|
116 |
+
- bool:
|
117 |
+
True if the section contains valid characters, False otherwise.
|
118 |
+
"""
|
119 |
+
|
120 |
+
# Valid characters: digits 1-8 or letters 'r', 'n', 'b', 'q', 'k', 'p', 'R', 'N', 'B', 'Q', 'K', 'P'
|
121 |
+
valid_chars = set("12345678rnbqkpRNBQKP")
|
122 |
+
return all(char in valid_chars for char in section)
|
123 |
+
|
124 |
+
import chess
|
125 |
+
import chess.engine
|
126 |
+
|
127 |
+
class ChessEngineTool(Tool):
|
128 |
+
name = "chess_engine"
|
129 |
+
description = "Analyzes a chess position (FEN) with Stockfish and returns the best move."
|
130 |
+
inputs = {
|
131 |
+
"fen": {"type": "string", "description": "FEN string of the position."},
|
132 |
+
"time_limit": {"type": "number", "description": "Time in seconds for engine analysis.", "nullable": True}
|
133 |
+
}
|
134 |
+
output_type = "string"
|
135 |
+
|
136 |
+
def forward(self, fen: str, time_limit: float = 0.1) -> str:
|
137 |
+
# figure out where the binary actually is
|
138 |
+
sf_bin = shutil.which("stockfish") or "/usr/games/stockfish"
|
139 |
+
if not sf_bin:
|
140 |
+
raise RuntimeError(
|
141 |
+
f"Cannot find stockfish on PATH or at /usr/games/stockfish. "
|
142 |
+
"Did you install it in apt.txt or via apt-get?"
|
143 |
+
)
|
144 |
+
|
145 |
+
board = chess.Board(fen)
|
146 |
+
engine = chess.engine.SimpleEngine.popen_uci(sf_bin)
|
147 |
+
result = engine.play(board, chess.engine.Limit(time=time_limit))
|
148 |
+
engine.quit()
|
149 |
+
return board.san(result.move)
|
150 |
+
|
151 |
+
|