Evaluation: depth 15 statt 10s, HTTP/1.1 Chunked-Streaming, Abbruch-Support

This commit is contained in:
2026-05-25 20:39:58 +02:00
parent ec1ebaa525
commit 0be816a4ec

106
server.py
View File

@@ -13,6 +13,7 @@ import re
import sys
import threading
import socket
import queue
PORT = int(os.environ.get("PORT", 8111))
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -34,6 +35,11 @@ class StockfishEngine:
def __init__(self, path):
self.path = path
self.proc = None
self._output_queue = queue.Queue()
self._reader_thread = None
self._reader_alive = threading.Event()
self._cmd_lock = threading.Lock()
self._searching = False
def start(self):
if self.proc:
@@ -51,25 +57,78 @@ class StockfishEngine:
bufsize=1,
creationflags=flags,
)
self._reader_alive.set()
self._reader_thread = threading.Thread(target=self._read_output, daemon=True)
self._reader_thread.start()
self._send("uci")
self._read_until("uciok")
self._send("isready")
self._read_until("readyok")
print(f"[STOCKFISH] Engine gestartet: {self.path}")
def _read_output(self):
while self._reader_alive.is_set():
try:
line = self.proc.stdout.readline()
except Exception:
break
if not line:
break
self._output_queue.put(line)
def _send(self, cmd):
with self._cmd_lock:
self.proc.stdin.write(cmd + "\n")
self.proc.stdin.flush()
def _read_until(self, marker):
while True:
line = self.proc.stdout.readline().strip()
if line == marker:
try:
line = self._output_queue.get(timeout=5.0)
except queue.Empty:
return
if line.strip() == marker:
return
def _drain_queue(self):
"""Leert die Queue von allen pending Zeilen."""
drained = 0
while True:
try:
self._output_queue.get_nowait()
drained += 1
except queue.Empty:
break
if drained > 0:
print(f"[STOCKFISH] {drained} alte Zeilen verworfen")
def _stop_and_wait(self):
"""Stoppt laufende Suche und wartet auf bestmove."""
if not self._searching:
return
self._send("stop")
while True:
try:
line = self._output_queue.get(timeout=3.0).strip()
except queue.Empty:
# Stockfish antwortet nicht auf stop (z.B. weil Suche schon beendet)
self._searching = False
return
if line.startswith("bestmove"):
self._searching = False
return
def evaluate(self, fen):
# Alte Suche abbrechen und Queue leeren
self._stop_and_wait()
self._drain_queue()
self._send(f"position fen {fen}")
self._send(f"go movetime 10000")
self._send(f"go depth {STOCKFISH_DEPTH}")
self._searching = True
score_cp = None
score_mate = None
@@ -78,7 +137,11 @@ class StockfishEngine:
last_depth = 0
while True:
line = self.proc.stdout.readline().strip()
try:
line = self._output_queue.get(timeout=1.0).strip()
except queue.Empty:
continue
if not line:
continue
@@ -109,6 +172,7 @@ class StockfishEngine:
if line.startswith("bestmove"):
parts = line.split()
bestmove = parts[1] if len(parts) > 1 else None
self._searching = False
break
yield {
@@ -119,6 +183,7 @@ class StockfishEngine:
}
def stop(self):
self._reader_alive.clear()
if self.proc:
self.proc.terminate()
self.proc = None
@@ -128,6 +193,7 @@ _engine = StockfishEngine(STOCKFISH_PATH)
class Handler(http.server.BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def do_GET(self):
if self.path == "/":
@@ -181,7 +247,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
self.send_header("Content-Type", "application/x-ndjson; charset=utf-8")
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Cache-Control", "no-cache")
self.send_header("Connection", "close")
self.send_header("Transfer-Encoding", "chunked")
self.end_headers()
self.wfile.flush()
@@ -191,28 +257,40 @@ class Handler(http.server.BaseHTTPRequestHandler):
except Exception:
pass
self.close_connection = True
def _write_chunk(chunk_data):
size = len(chunk_data)
self.wfile.write(f"{size:x}\r\n".encode())
self.wfile.write(chunk_data)
self.wfile.write(b"\r\n")
self.wfile.flush()
try:
with _stockfish_lock:
try:
_engine.start()
except FileNotFoundError:
self.wfile.write(
json.dumps({"error": "Stockfish nicht gefunden"}).encode("utf-8") + b"\n"
_write_chunk(
json.dumps({"error": "Stockfish nicht gefunden"}).encode("utf-8")
)
self.wfile.flush()
return
for result in _engine.evaluate(fen):
try:
data = json.dumps(result).encode("utf-8") + b"\n"
self.wfile.write(data)
self.wfile.flush()
_write_chunk(data)
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError, OSError):
# Client hat Verbindung getrennt (neuer Zug angeklickt / Pfeiltasten)
break
# Abschluss-Chunk
_write_chunk(b"")
except Exception as e:
print(f"[STOCKFISH] Fehler: {e}")
self.wfile.write(
json.dumps({"error": str(e)}).encode("utf-8") + b"\n"
try:
_write_chunk(
json.dumps({"error": str(e)}).encode("utf-8")
)
self.wfile.flush()
except Exception:
pass
else:
self._send_json({"error": "Not found"}, 404)