Evaluation: depth 15 statt 10s, HTTP/1.1 Chunked-Streaming, Abbruch-Support
This commit is contained in:
106
server.py
106
server.py
@@ -13,6 +13,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import socket
|
import socket
|
||||||
|
import queue
|
||||||
|
|
||||||
PORT = int(os.environ.get("PORT", 8111))
|
PORT = int(os.environ.get("PORT", 8111))
|
||||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
@@ -34,6 +35,11 @@ class StockfishEngine:
|
|||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self.path = path
|
self.path = path
|
||||||
self.proc = None
|
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):
|
def start(self):
|
||||||
if self.proc:
|
if self.proc:
|
||||||
@@ -51,25 +57,78 @@ class StockfishEngine:
|
|||||||
bufsize=1,
|
bufsize=1,
|
||||||
creationflags=flags,
|
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._send("uci")
|
||||||
self._read_until("uciok")
|
self._read_until("uciok")
|
||||||
self._send("isready")
|
self._send("isready")
|
||||||
self._read_until("readyok")
|
self._read_until("readyok")
|
||||||
|
|
||||||
print(f"[STOCKFISH] Engine gestartet: {self.path}")
|
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):
|
def _send(self, cmd):
|
||||||
|
with self._cmd_lock:
|
||||||
self.proc.stdin.write(cmd + "\n")
|
self.proc.stdin.write(cmd + "\n")
|
||||||
self.proc.stdin.flush()
|
self.proc.stdin.flush()
|
||||||
|
|
||||||
def _read_until(self, marker):
|
def _read_until(self, marker):
|
||||||
while True:
|
while True:
|
||||||
line = self.proc.stdout.readline().strip()
|
try:
|
||||||
if line == marker:
|
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
|
return
|
||||||
|
|
||||||
def evaluate(self, fen):
|
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"position fen {fen}")
|
||||||
self._send(f"go movetime 10000")
|
self._send(f"go depth {STOCKFISH_DEPTH}")
|
||||||
|
self._searching = True
|
||||||
|
|
||||||
score_cp = None
|
score_cp = None
|
||||||
score_mate = None
|
score_mate = None
|
||||||
@@ -78,7 +137,11 @@ class StockfishEngine:
|
|||||||
last_depth = 0
|
last_depth = 0
|
||||||
|
|
||||||
while True:
|
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:
|
if not line:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -109,6 +172,7 @@ class StockfishEngine:
|
|||||||
if line.startswith("bestmove"):
|
if line.startswith("bestmove"):
|
||||||
parts = line.split()
|
parts = line.split()
|
||||||
bestmove = parts[1] if len(parts) > 1 else None
|
bestmove = parts[1] if len(parts) > 1 else None
|
||||||
|
self._searching = False
|
||||||
break
|
break
|
||||||
|
|
||||||
yield {
|
yield {
|
||||||
@@ -119,6 +183,7 @@ class StockfishEngine:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
self._reader_alive.clear()
|
||||||
if self.proc:
|
if self.proc:
|
||||||
self.proc.terminate()
|
self.proc.terminate()
|
||||||
self.proc = None
|
self.proc = None
|
||||||
@@ -128,6 +193,7 @@ _engine = StockfishEngine(STOCKFISH_PATH)
|
|||||||
|
|
||||||
|
|
||||||
class Handler(http.server.BaseHTTPRequestHandler):
|
class Handler(http.server.BaseHTTPRequestHandler):
|
||||||
|
protocol_version = "HTTP/1.1"
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
if self.path == "/":
|
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("Content-Type", "application/x-ndjson; charset=utf-8")
|
||||||
self.send_header("Access-Control-Allow-Origin", "*")
|
self.send_header("Access-Control-Allow-Origin", "*")
|
||||||
self.send_header("Cache-Control", "no-cache")
|
self.send_header("Cache-Control", "no-cache")
|
||||||
self.send_header("Connection", "close")
|
self.send_header("Transfer-Encoding", "chunked")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.flush()
|
self.wfile.flush()
|
||||||
|
|
||||||
@@ -191,28 +257,40 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
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:
|
try:
|
||||||
with _stockfish_lock:
|
with _stockfish_lock:
|
||||||
try:
|
try:
|
||||||
_engine.start()
|
_engine.start()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
self.wfile.write(
|
_write_chunk(
|
||||||
json.dumps({"error": "Stockfish nicht gefunden"}).encode("utf-8") + b"\n"
|
json.dumps({"error": "Stockfish nicht gefunden"}).encode("utf-8")
|
||||||
)
|
)
|
||||||
self.wfile.flush()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
for result in _engine.evaluate(fen):
|
for result in _engine.evaluate(fen):
|
||||||
|
try:
|
||||||
data = json.dumps(result).encode("utf-8") + b"\n"
|
data = json.dumps(result).encode("utf-8") + b"\n"
|
||||||
self.wfile.write(data)
|
_write_chunk(data)
|
||||||
self.wfile.flush()
|
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError, OSError):
|
||||||
|
# Client hat Verbindung getrennt (neuer Zug angeklickt / Pfeiltasten)
|
||||||
|
break
|
||||||
|
# Abschluss-Chunk
|
||||||
|
_write_chunk(b"")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[STOCKFISH] Fehler: {e}")
|
print(f"[STOCKFISH] Fehler: {e}")
|
||||||
self.wfile.write(
|
try:
|
||||||
json.dumps({"error": str(e)}).encode("utf-8") + b"\n"
|
_write_chunk(
|
||||||
|
json.dumps({"error": str(e)}).encode("utf-8")
|
||||||
)
|
)
|
||||||
self.wfile.flush()
|
except Exception:
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
self._send_json({"error": "Not found"}, 404)
|
self._send_json({"error": "Not found"}, 404)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user