Chess / app.py
Taperx's picture
Create app.py
96f9c6a verified
Raw
History Blame Contribute Delete
3.98 kB
import os
import subprocess
import threading
import json
import requests
import gradio as gr
LICHESS_TOKEN = os.environ.get("LICHESS_TOKEN")
ENGINE_PATH = "./veloct"
def compile_engine():
print("Compiling engine locally inside Hugging Face runtime...")
try:
if os.path.exists(ENGINE_PATH):
return
compile_cmd = [
"g++", "-O3", "-std=c++17", "-DNDEBUG", "-DUSE_PTHREADS",
"-pthread", "veloct.cpp", "-o", "veloct", "-latomic"
]
result = subprocess.run(compile_cmd, capture_output=True, text=True)
if result.returncode == 0:
print("Engine compiled successfully!")
os.chmod(ENGINE_PATH, 0o755)
else:
print(f"Compilation failed:\n{result.stderr}")
except Exception as e:
print(f"Compilation error: {e}")
def send_move(game_id, move):
url = f"https://lichess.org/api/bot/game/{game_id}/move/{move}"
headers = {"Authorization": f"Bearer {LICHESS_TOKEN}"}
requests.post(url, headers=headers)
def engine_think(game_id, fen, moves_string):
try:
proc = subprocess.Popen(
[ENGINE_PATH], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, bufsize=1
)
proc.stdin.write("uci\n")
proc.stdin.write("isready\n")
pos_cmd = f"position fen {fen}" if fen else "position startpos"
if moves_string:
pos_cmd += f" moves {moves_string}"
proc.stdin.write(f"{pos_cmd}\n")
proc.stdin.write("go movetime 1500\n")
proc.stdin.flush()
while True:
line = proc.stdout.readline()
if not line:
break
if line.startswith("bestmove"):
best_move = line.split()[1]
if best_move != "(none)":
send_move(game_id, best_move)
break
proc.terminate()
except Exception as e:
print(f"Engine execution error: {e}")
def stream_game(game_id):
url = f"https://lichess.org/api/bot/game/stream/{game_id}"
headers = {"Authorization": f"Bearer {LICHESS_TOKEN}"}
response = requests.get(url, headers=headers, stream=True)
fen = None
for line in response.iter_lines():
if line:
event = json.loads(line.decode('utf-8'))
if event.get("type") == "gameFull":
fen = event.get("initialFen")
state = event.get("state")
else:
state = event
moves = state.get("moves", "")
moves_list = moves.split() if moves else []
if len(moves_list) % 2 == 0:
threading.Thread(target=engine_think, args=(game_id, fen, moves)).start()
def listen_events():
url = "https://lichess.org/api/stream/event"
headers = {"Authorization": f"Bearer {LICHESS_TOKEN}"}
print("Connecting to Lichess Bot Stream API...")
try:
response = requests.get(url, headers=headers, stream=True)
for line in response.iter_lines():
if line:
event = json.loads(line.decode('utf-8'))
if event.get("type") == "challenge":
challenge_id = event["challenge"]["id"]
requests.post(f"https://lichess.org/api/challenge/{challenge_id}/accept", headers=headers)
elif event.get("type") == "gameStart":
game_id = event["game"]["id"]
threading.Thread(target=stream_game, args=(game_id,)).start()
except Exception as e:
print(f"Stream error: {e}")
if LICHESS_TOKEN:
compile_engine()
threading.Thread(target=listen_events, daemon=True).start()
else:
print("Warning: LICHESS_TOKEN missing.")
with gr.Blocks() as demo:
gr.Markdown("# 🤖 VeloCT Engine Lichess Bot")
gr.Markdown("Status: Running execution loop in background runtime container.")
demo.launch()