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()