""" Chess AI - Hugging Face Spaces Entry point: app.py """ import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import streamlit as st import chess import chess.svg import base64 st.set_page_config( page_title="Chess AI", page_icon="♟️", layout="centered", initial_sidebar_state="expanded", ) st.markdown(""" """, unsafe_allow_html=True) # ─── Cached resource loading ───────────────────────────────────────────────── @st.cache_resource(show_spinner="Đang tải Chess Engine...") def load_engine(): from src import config, engine return config, engine @st.cache_resource(show_spinner="Đang tải ML Model...") def load_ml_agent(): try: from src.ai_battle import MLAgent return MLAgent(), None except Exception as e: return None, str(e) # ─── Session state ──────────────────────────────────────────────────────────── def init_state(): defaults = { "board": chess.Board(), "move_log": [], "game_over": False, "status_msg": "Lượt của bạn ♙", "depth": 2, "game_mode": "human_vs_minimax", "input_key": 0, } for k, v in defaults.items(): if k not in st.session_state: st.session_state[k] = v init_state() # ─── Helpers ───────────────────────────────────────────────────────────────── def board_to_html(board: chess.Board, last_move: chess.Move = None) -> str: arrows = [] if last_move: arrows.append(chess.svg.Arrow(last_move.from_square, last_move.to_square, color="#f0c06088")) svg = chess.svg.board( board, size=460, arrows=arrows, colors={ "square light": "#ede0c8", "square dark": "#b58863", "square light lastmove": "#cdd16e", "square dark lastmove": "#aaa23a", } ) b64 = base64.b64encode(svg.encode()).decode() return f'' def get_last_move(board): try: return board.peek() except IndexError: return None def reset_game(): st.session_state.board = chess.Board() st.session_state.move_log = [] st.session_state.game_over = False st.session_state.status_msg = "Lượt của bạn ♙" st.session_state.input_key = 0 def check_game_over(board: chess.Board) -> bool: if board.is_checkmate(): winner = "Đen" if board.turn == chess.WHITE else "Trắng" st.session_state.status_msg = f"♚ Chiếu hết! {winner} thắng!" st.session_state.game_over = True elif board.is_stalemate(): st.session_state.status_msg = "🤝 Hòa (Stalemate)" st.session_state.game_over = True elif board.is_insufficient_material(): st.session_state.status_msg = "🤝 Hòa (thiếu quân)" st.session_state.game_over = True elif board.is_seventyfive_moves(): st.session_state.status_msg = "🤝 Hòa (75 nước)" st.session_state.game_over = True return st.session_state.game_over def do_minimax_move(board: chess.Board): config, engine = load_engine() move = engine.find_best_move(board, config.STANDARD_WEIGHTS, st.session_state.depth) if move and move in board.legal_moves: st.session_state.move_log.append(f"Minimax (d{st.session_state.depth}): {move.uci()}") board.push(move) if not check_game_over(board): st.session_state.status_msg = "Lượt của bạn ♙" def do_ml_move(board: chess.Board): ml_agent, err = load_ml_agent() if err: st.error(f"ML Agent lỗi: {err}") return move = ml_agent.get_move(board, time_limit=15.0) if move and move in board.legal_moves: st.session_state.move_log.append(f"ML Agent: {move.uci()}") board.push(move) if not check_game_over(board): st.session_state.status_msg = "Lượt của bạn ♙" else: import random legal = list(board.legal_moves) if legal: fb = random.choice(legal) board.push(fb) st.session_state.move_log.append(f"ML Agent (fallback): {fb.uci()}") check_game_over(board) # ─── Sidebar ────────────────────────────────────────────────────────────────── with st.sidebar: st.markdown("## ♟️ Chess AI") st.markdown("---") new_mode = st.radio( "Chế độ chơi", ["Người vs Minimax", "Người vs ML Agent"], ) new_mode_key = "human_vs_minimax" if "Minimax" in new_mode else "human_vs_ml" # Reset tự động nếu đổi chế độ if new_mode_key != st.session_state.game_mode: st.session_state.game_mode = new_mode_key reset_game() if new_mode_key == "human_vs_minimax": st.session_state.depth = st.slider("Độ sâu Minimax", 1, 4, 2) st.caption("Depth 1-2: nhanh · Depth 3-4: mạnh hơn nhưng chậm") st.markdown("---") if st.button("🔄 Ván mới"): reset_game() st.rerun() st.markdown("---") st.markdown("**Hướng dẫn**") st.markdown("Chọn ô nguồn → ô đích → nhấn **Đi** \nPhong cấp tự động lên Hậu") st.markdown("---") st.markdown( '📂 GitHub', unsafe_allow_html=True, ) # ─── Main ───────────────────────────────────────────────────────────────────── st.markdown("# Chess AI") board = st.session_state.board col_board, col_ctrl = st.columns([3, 2], gap="medium") with col_board: st.markdown(board_to_html(board, get_last_move(board)), unsafe_allow_html=True) st.markdown( f'
{st.session_state.status_msg}
', unsafe_allow_html=True, ) if board.is_check() and not st.session_state.game_over: st.warning("⚠️ Chiếu!") with col_ctrl: # ── Input nước đi (người luôn chơi Trắng) if not st.session_state.game_over and board.turn == chess.WHITE: st.markdown("### Nước đi của bạn") legal_from = sorted({ chess.SQUARE_NAMES[m.from_square] for m in board.legal_moves }) from_sq = st.selectbox("Từ ô", [""] + legal_from, key=f"from_sq_{st.session_state.input_key}") valid_to = [] if from_sq: try: from_idx = chess.parse_square(from_sq) valid_to = sorted({ chess.SQUARE_NAMES[m.to_square] for m in board.legal_moves if m.from_square == from_idx }) except ValueError: pass to_sq = st.selectbox("Đến ô", [""] + valid_to, key=f"to_sq_{st.session_state.input_key}") if st.button("▶ Đi", disabled=not (from_sq and to_sq)): try: uci = f"{from_sq}{to_sq}" piece = board.piece_at(chess.parse_square(from_sq)) if piece and piece.piece_type == chess.PAWN: if (board.turn == chess.WHITE and to_sq[1] == "8") or \ (board.turn == chess.BLACK and to_sq[1] == "1"): uci += "q" move = chess.Move.from_uci(uci) if move in board.legal_moves: st.session_state.move_log.append(f"Bạn: {uci}") board.push(move) st.session_state.input_key += 1 if not check_game_over(board): st.session_state.status_msg = "AI đang suy nghĩ..." st.rerun() else: st.error("Nước đi không hợp lệ!") except ValueError: pass # from_sq rỗng hoặc không hợp lệ, im lặng bỏ qua except Exception as e: st.error(f"Lỗi: {e}") # ── AI tự động đi khi đến lượt Đen if not st.session_state.game_over and board.turn == chess.BLACK: placeholder = st.empty() with st.spinner("AI đang suy nghĩ..."): if st.session_state.game_mode == "human_vs_minimax": do_minimax_move(board) else: do_ml_move(board) placeholder.empty() st.rerun() # ── Lịch sử st.markdown("---") st.markdown("### Lịch sử") log_html = "
".join(st.session_state.move_log[-30:][::-1]) or "Chưa có" st.markdown(f'
{log_html}
', unsafe_allow_html=True) # ── Thông tin quân còn lại st.markdown("---") for name, pt in [("♙Tốt", chess.PAWN), ("♘Mã", chess.KNIGHT), ("♗Tượng", chess.BISHOP), ("♖Xe", chess.ROOK), ("♛Hậu", chess.QUEEN)]: w = len(board.pieces(pt, chess.WHITE)) b = len(board.pieces(pt, chess.BLACK)) st.markdown( f'{name} ⬜{w} ⬛{b}', unsafe_allow_html=True, ) st.markdown( f'Nước #{board.fullmove_number}', unsafe_allow_html=True, )