Spaces:
Running
Running
Update main.py
Browse files
main.py
CHANGED
|
@@ -120,17 +120,38 @@ def health():
|
|
| 120 |
return {"status": "error", "message": "Engine binary not found"}
|
| 121 |
return {"status": "ok", "engine": "Deepcastle"}
|
| 122 |
|
|
|
|
|
|
|
|
|
|
| 123 |
async def get_engine():
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
transport, engine = await chess.engine.popen_uci(ENGINE_PATH)
|
| 127 |
-
if os.path.exists(NNUE_PATH):
|
| 128 |
try:
|
| 129 |
-
|
| 130 |
-
|
|
|
|
| 131 |
except Exception:
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
def get_normalized_score(info) -> tuple[float, Optional[int]]:
|
| 136 |
"""Returns the score from White's perspective in centipawns."""
|
|
@@ -140,19 +161,21 @@ def get_normalized_score(info) -> tuple[float, Optional[int]]:
|
|
| 140 |
if raw.is_mate():
|
| 141 |
m = raw.mate() or 0
|
| 142 |
return (10000.0 if m > 0 else -10000.0), m
|
| 143 |
-
return raw.score() or 0.0, None
|
| 144 |
|
| 145 |
# βββ Engine Inference Route ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 146 |
@app.post("/move", response_model=MoveResponse)
|
| 147 |
async def get_move(request: MoveRequest):
|
| 148 |
-
engine = None
|
| 149 |
try:
|
| 150 |
engine = await get_engine()
|
| 151 |
board = chess.Board(request.fen)
|
| 152 |
limit = chess.engine.Limit(time=request.time, depth=request.depth)
|
| 153 |
|
|
|
|
| 154 |
result = await engine.play(board, limit)
|
| 155 |
-
|
|
|
|
|
|
|
| 156 |
|
| 157 |
# From White's perspective in CP -> converted to Pawns for UI
|
| 158 |
score_cp, mate_in = get_normalized_score(info)
|
|
@@ -194,12 +217,6 @@ async def get_move(request: MoveRequest):
|
|
| 194 |
except Exception as e:
|
| 195 |
print(f"Error: {e}")
|
| 196 |
raise HTTPException(status_code=500, detail=str(e))
|
| 197 |
-
finally:
|
| 198 |
-
if engine:
|
| 199 |
-
try:
|
| 200 |
-
await engine.quit()
|
| 201 |
-
except Exception:
|
| 202 |
-
pass
|
| 203 |
|
| 204 |
|
| 205 |
import math
|
|
@@ -483,12 +500,6 @@ async def analyze_game(request: AnalyzeRequest):
|
|
| 483 |
except Exception as e:
|
| 484 |
print(f"Analysis Error: {e}")
|
| 485 |
raise HTTPException(status_code=500, detail=str(e))
|
| 486 |
-
finally:
|
| 487 |
-
if engine:
|
| 488 |
-
try:
|
| 489 |
-
await engine.quit()
|
| 490 |
-
except Exception:
|
| 491 |
-
pass
|
| 492 |
|
| 493 |
|
| 494 |
if __name__ == "__main__":
|
|
|
|
| 120 |
return {"status": "error", "message": "Engine binary not found"}
|
| 121 |
return {"status": "ok", "engine": "Deepcastle"}
|
| 122 |
|
| 123 |
+
# Global engine instance to save memory and improve performance
|
| 124 |
+
_GLOBAL_ENGINE = None
|
| 125 |
+
|
| 126 |
async def get_engine():
|
| 127 |
+
global _GLOBAL_ENGINE
|
| 128 |
+
if _GLOBAL_ENGINE is not None:
|
|
|
|
|
|
|
| 129 |
try:
|
| 130 |
+
# Check if engine is still alive
|
| 131 |
+
if not _GLOBAL_ENGINE.is_terminated():
|
| 132 |
+
return _GLOBAL_ENGINE
|
| 133 |
except Exception:
|
| 134 |
+
_GLOBAL_ENGINE = None
|
| 135 |
+
|
| 136 |
+
if not os.path.exists(ENGINE_PATH):
|
| 137 |
+
raise HTTPException(status_code=500, detail=f"Engine NOT FOUND at {ENGINE_PATH}")
|
| 138 |
+
|
| 139 |
+
try:
|
| 140 |
+
transport, engine = await chess.engine.popen_uci(ENGINE_PATH)
|
| 141 |
+
|
| 142 |
+
# Configure once
|
| 143 |
+
await engine.configure({"Hash": 256, "Threads": 1}) # More conservative for singleton
|
| 144 |
+
|
| 145 |
+
if os.path.exists(NNUE_PATH):
|
| 146 |
+
try:
|
| 147 |
+
await engine.configure({"EvalFile": NNUE_PATH})
|
| 148 |
+
except Exception:
|
| 149 |
+
pass
|
| 150 |
+
|
| 151 |
+
_GLOBAL_ENGINE = engine
|
| 152 |
+
return engine
|
| 153 |
+
except Exception as e:
|
| 154 |
+
raise HTTPException(status_code=500, detail=f"Failed to start engine: {str(e)}")
|
| 155 |
|
| 156 |
def get_normalized_score(info) -> tuple[float, Optional[int]]:
|
| 157 |
"""Returns the score from White's perspective in centipawns."""
|
|
|
|
| 161 |
if raw.is_mate():
|
| 162 |
m = raw.mate() or 0
|
| 163 |
return (10000.0 if m > 0 else -10000.0), m
|
| 164 |
+
return float(raw.score() or 0.0), None
|
| 165 |
|
| 166 |
# βββ Engine Inference Route ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 167 |
@app.post("/move", response_model=MoveResponse)
|
| 168 |
async def get_move(request: MoveRequest):
|
|
|
|
| 169 |
try:
|
| 170 |
engine = await get_engine()
|
| 171 |
board = chess.Board(request.fen)
|
| 172 |
limit = chess.engine.Limit(time=request.time, depth=request.depth)
|
| 173 |
|
| 174 |
+
# Search for best move
|
| 175 |
result = await engine.play(board, limit)
|
| 176 |
+
|
| 177 |
+
# Get evaluation separately to avoid blocking
|
| 178 |
+
info = await engine.analyse(board, chess.engine.Limit(time=0.1, depth=limit.depth or 12))
|
| 179 |
|
| 180 |
# From White's perspective in CP -> converted to Pawns for UI
|
| 181 |
score_cp, mate_in = get_normalized_score(info)
|
|
|
|
| 217 |
except Exception as e:
|
| 218 |
print(f"Error: {e}")
|
| 219 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
|
| 222 |
import math
|
|
|
|
| 500 |
except Exception as e:
|
| 501 |
print(f"Analysis Error: {e}")
|
| 502 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 503 |
|
| 504 |
|
| 505 |
if __name__ == "__main__":
|