import os
# Fix for permission denied issue in Hugging Face Spaces
HF_HOME = "/tmp/huggingface"
os.environ["HF_HOME"] = HF_HOME
os.makedirs(HF_HOME, exist_ok=True)
import streamlit as st
st.set_page_config(page_title="Play Chess vs o2", layout="centered")
import chess
import chess.svg
import torch
from o2_model import O2Net, board_to_tensor
from o2_agent import O2Agent
from PIL import Image
import io
import base64
import chess.pgn
import random
from huggingface_hub import hf_hub_download
# Hugging Face model config
MODEL_REPO = "FlameF0X/o2"
MODEL_FILENAME = "o2_agent_latest.pth"
MODEL_CACHE_DIR = HF_HOME
def ensure_model():
try:
model_path = hf_hub_download(
repo_id=MODEL_REPO,
filename=MODEL_FILENAME,
cache_dir=MODEL_CACHE_DIR
)
return model_path
except Exception as e:
st.error(f"Error downloading model: {e}")
return None
@st.cache_resource
def load_agent():
try:
model_path = ensure_model()
if model_path is None or not os.path.isfile(model_path):
st.error(f"Model file not found at {model_path}")
return None
agent = O2Agent()
agent.model.load_state_dict(torch.load(model_path, map_location=torch.device("cpu")))
agent.model.eval()
return agent
except Exception as e:
st.error(f"Error loading model: {e}")
try:
os.remove(model_path)
except:
pass
return None
def render_svg(svg):
b64 = base64.b64encode(svg.encode('utf-8')).decode('utf-8')
return f"", b64
def parse_move_input(move_input, board):
if not move_input:
return None
move_input = move_input.strip()
# Try UCI
try:
if len(move_input) in (4, 5):
move = chess.Move.from_uci(move_input.lower())
if move in board.legal_moves:
return move
except:
pass
# Try SAN
try:
move = board.parse_san(move_input)
if move in board.legal_moves:
return move
except:
pass
# Castling aliases
castling_variations = {
'0-0': 'O-O', '0-0-0': 'O-O-O',
'oo': 'O-O', 'ooo': 'O-O-O',
'o-o': 'O-O', 'o-o-o': 'O-O-O'
}
normalized = castling_variations.get(move_input.lower())
if normalized:
try:
move = board.parse_san(normalized)
if move in board.legal_moves:
return move
except:
pass
return None
# --- Session state ---
if "board" not in st.session_state:
st.session_state.board = chess.Board()
if "history" not in st.session_state:
st.session_state.history = []
agent = None
agent_loaded = False
with st.spinner("Loading o2 model..."):
agent = load_agent()
if agent is not None:
agent_loaded = True
st.success("o2 model loaded successfully!")
else:
st.warning("Model not loaded. Using random AI.")
board = st.session_state.board
history = st.session_state.history
st.title("♟️ Play Chess vs o2")
if not agent_loaded:
st.info("🎲 AI fallback: random move selection.")
if st.button("Reset Game"):
st.session_state.board = chess.Board()
st.session_state.history = []
st.rerun()
col_board, col_pgn = st.columns([2, 1])
with col_board:
board_placeholder = st.empty()
def render_board():
try:
last_move = board.peek() if board.move_stack else None
svg_board = chess.svg.board(board=board, lastmove=last_move, size=400)
board_placeholder.markdown(f'