Evaluation: depth 15 statt 10s, HTTP/1.1 Chunked-Streaming, Abbruch-Support
This commit is contained in:
114
server.py
114
server.py
@@ -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):
|
||||
self.proc.stdin.write(cmd + "\n")
|
||||
self.proc.stdin.flush()
|
||||
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):
|
||||
data = json.dumps(result).encode("utf-8") + b"\n"
|
||||
self.wfile.write(data)
|
||||
self.wfile.flush()
|
||||
try:
|
||||
data = json.dumps(result).encode("utf-8") + b"\n"
|
||||
_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"
|
||||
)
|
||||
self.wfile.flush()
|
||||
try:
|
||||
_write_chunk(
|
||||
json.dumps({"error": str(e)}).encode("utf-8")
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
self._send_json({"error": "Not found"}, 404)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user