CoolFace
Apppublic

johnthy/Chess-Dummy-Model

sourceHugging Faceotherupdated 1y agoView on Hugging Face
0likes
streamlit_app.py488 linesDownload Raw Back to src
1import streamlit as st2import chess3import chess.svg4import numpy as np5import torch6import torch.nn7from sklearn.linear_model import LinearRegression8from streamlit_extras.let_it_rain import rain9 10 11 12def evaluate_board(board: chess.Board) -> float:13 14    material = {15        chess.PAWN: 100,16        chess.KNIGHT: 320,17        chess.BISHOP: 330,18        chess.ROOK: 500,19        chess.QUEEN: 900,20        chess.KING: 0,21    }22 23    PST_PAWN = np.array([24        0,  0,  0,  0,  0,  0,  0,  0,25        50, 50, 50, 50, 50, 50, 50, 50,26        10, 10, 20, 30, 30, 20, 10, 10,27        5,  5, 10, 25, 25, 10,  5,  5,28        0,  0,  0, 20, 20,  0,  0,  0,29        5, -5,-10,  0,  0,-10, -5,  5,30        5, 10, 10,-20,-20, 10, 10,  5,31        0,  0,  0,  0,  0,  0,  0,  032    ])33    PST_KNIGHT = np.array([34        -50,-40,-30,-30,-30,-30,-40,-50,35        -40,-20,  0,  0,  0,  0,-20,-40,36        -30,  0, 10, 15, 15, 10,  0,-30,37        -30,  5, 15, 20, 20, 15,  5,-30,38        -30,  0, 15, 20, 20, 15,  0,-30,39        -30,  5, 10, 15, 15, 10,  5,-30,40        -40,-20,  0,  5,  5,  0,-20,-40,41        -50,-40,-30,-30,-30,-30,-40,-5042    ])43    PST_BISHOP = np.array([44        -20,-10,-10,-10,-10,-10,-10,-20,45        -10,  5,  0,  0,  0,  0,  5,-10,46        -10, 10, 10, 10, 10, 10, 10,-10,47        -10,  0, 10, 10, 10, 10,  0,-10,48        -10,  5,  5, 10, 10,  5,  5,-10,49        -10,  0,  5, 10, 10,  5,  0,-10,50        -10,  0,  0,  0,  0,  0,  0,-10,51        -20,-10,-10,-10,-10,-10,-10,-2052    ])53    PST_ROOK = np.array([54         0,  0,  0,  0,  0,  0,  0,  0,55         5, 10, 10, 10, 10, 10, 10,  5,56        -5,  0,  0,  0,  0,  0,  0, -5,57        -5,  0,  0,  0,  0,  0,  0, -5,58        -5,  0,  0,  0,  0,  0,  0, -5,59        -5,  0,  0,  0,  0,  0,  0, -5,60        -5,  0,  0,  0,  0,  0,  0, -5,61         0,  0,  0,  5,  5,  0,  0,  062    ])63    PST_QUEEN = np.array([64        -20,-10,-10, -5, -5,-10,-10,-20,65        -10,  0,  0,  0,  0,  0,  0,-10,66        -10,  0,  5,  5,  5,  5,  0,-10,67         -5,  0,  5,  5,  5,  5,  0, -5,68          0,  0,  5,  5,  5,  5,  0, -5,69        -10,  5,  5,  5,  5,  5,  0,-10,70        -10,  0,  5,  0,  0,  0,  0,-10,71        -20,-10,-10, -5, -5,-10,-10,-2072    ])73    PST_KING = np.array([74        -30,-40,-40,-50,-50,-40,-40,-30,75        -30,-40,-40,-50,-50,-40,-40,-30,76        -30,-40,-40,-50,-50,-40,-40,-30,77        -30,-40,-40,-50,-50,-40,-40,-30,78        -20,-30,-30,-40,-40,-30,-30,-20,79        -10,-20,-20,-20,-20,-20,-20,-10,80         20, 20,  0,  0,  0,  0, 20, 20,81         20, 30, 10,  0,  0, 10, 30, 2082    ])83 84    pst_by_piece = {85        chess.PAWN: PST_PAWN,86        chess.KNIGHT: PST_KNIGHT,87        chess.BISHOP: PST_BISHOP,88        chess.ROOK: PST_ROOK,89        chess.QUEEN: PST_QUEEN,90        chess.KING: PST_KING,91    }92 93    score = 0.094    for square in chess.SQUARES:95        piece = board.piece_at(square)96        if piece:97            base = material[piece.piece_type]98            pst = pst_by_piece[piece.piece_type]99            idx = square if piece.color == chess.WHITE else chess.square_mirror(square)100            val = base + pst[idx]101            score += val if piece.color == chess.WHITE else -val102 103 104    b_w = board.copy()105    b_w.turn = chess.WHITE106    b_b = board.copy()107    b_b.turn = chess.BLACK108    mobility = (len(list(b_w.legal_moves)) - len(list(b_b.legal_moves))) * 2.0109    score += mobility110 111 112    try:113        if board.has_kingside_castling_rights(chess.WHITE) or board.has_queenside_castling_rights(chess.WHITE):114            score += 15115        if board.has_kingside_castling_rights(chess.BLACK) or board.has_queenside_castling_rights(chess.BLACK):116            score -= 15117    except Exception:118        pass119 120    return score / 100.0 121 122 123def extract_features(board: chess.Board) -> np.ndarray:124    features = np.zeros(12)125    for square in chess.SQUARES:126        piece = board.piece_at(square)127        if piece:128            idx = (piece.piece_type - 1) + 6 * (1 if piece.color == chess.BLACK else 0)129            features[idx] += 1130    return features.reshape(1, -1)131 132 133def train_simple_model() -> LinearRegression:134    X = np.random.rand(100, 12) * 10135    y = np.sum(X[:, :6], axis=1) - np.sum(X[:, 6:], axis=1)136    model = LinearRegression()137    model.fit(X, y)138    return model139 140 141class SimpleNN(torch.nn.Module):142    def __init__(self) -> None:143        super(SimpleNN, self).__init__()144        self.fc = torch.nn.Linear(12, 1)145 146    def forward(self, x: torch.Tensor) -> torch.Tensor:147        return self.fc(x)148 149 150def train_torch_model(model, epochs=100, lr=0.01):151    152    return model153 154 155 156def evaluate_combined(board: chess.Board, sklearn_model: LinearRegression, torch_model: SimpleNN) -> float:157   158    return float(evaluate_board(board))159 160 161# Minimax with alpha-beta pruning162def minimax_best_move(board: chess.Board, depth: int, sklearn_model: LinearRegression, torch_model: SimpleNN, ai_color: bool) -> chess.Move | None:163    def search(node: chess.Board, d: int, alpha: float, beta: float) -> float:164        if d == 0 or node.is_game_over():165            score = evaluate_combined(node, sklearn_model, torch_model)166            return score if ai_color == chess.WHITE else -score167 168        legal = list(node.legal_moves)169        if not legal:170            score = evaluate_combined(node, sklearn_model, torch_model)171            return score if ai_color == chess.WHITE else -score172 173        maximizing = (node.turn == ai_color)174        best_val = -float('inf') if maximizing else float('inf')175 176        def move_key(m: chess.Move):177            return 0 if node.is_capture(m) else 1178        legal.sort(key=move_key)179 180        for mv in legal:181            node.push(mv)182            val = search(node, d - 1, alpha, beta)183            node.pop()184            if maximizing:185                if val > best_val:186                    best_val = val187                if best_val > alpha:188                    alpha = best_val189                if alpha >= beta:190                    break191            else:192                if val < best_val:193                    best_val = val194                if best_val < beta:195                    beta = best_val196                if alpha >= beta:197                    break198        return best_val199 200    best_move = None201    best_score = -float('inf')202    moves = list(board.legal_moves)203    if not moves:204        return None205 206    def top_key(m: chess.Move):207        return 0 if board.is_capture(m) else 1208    moves.sort(key=top_key)209 210    for mv in moves:211        board.push(mv)212        score = search(board, depth - 1, -float('inf'), float('inf'))213        board.pop()214        if score > best_score:215            best_score = score216            best_move = mv217    return best_move218 219 220 221 222 223def ai_move(board: chess.Board, sklearn_model: LinearRegression, torch_model: SimpleNN):224    legal_moves = list(board.legal_moves)225    if not legal_moves:226        return None227 228    best_move = None229    best_score = -float('inf')230 231    for move in legal_moves:232        board.push(move)233 234        basic_score = evaluate_board(board)235 236        features = extract_features(board)237        sklearn_score = sklearn_model.predict(features)[0]238 239        features_tensor = torch.tensor(features, dtype=torch.float32)240        with torch.no_grad():241            torch_score = torch_model(features_tensor).item()242 243        combined_score = (basic_score + sklearn_score + torch_score) / 3244        if combined_score > best_score:245            best_score = combined_score246            best_move = move247 248        board.pop()249 250    return best_move251 252 253def main() -> None:254    st.set_page_config(page_title=" Chess AI Dummy", page_icon="♟️", layout="centered")255    st.markdown(256        """257        <style>258        .title-box {background: linear-gradient(90deg,#00c6ff 0%,#0072ff 100%); padding: 14px 18px; border-radius: 12px; color: #fff; margin-bottom: 14px;}259        .badge {display:inline-block; padding:6px 10px; border-radius:999px; font-weight:600;}260        .badge-turn {background:#ffe08a; color:#5a3e00;}261        .badge-eval-pos {background:#b6f3c0; color:#0f5132;}262        .badge-eval-neg {background:#ffb3b3; color:#58151c;}263        .panel {background: #ffffff; border: 1px solid #ececec; border-radius: 12px; padding: 12px 14px;}264        .move-list {max-height: 320px; overflow-y: auto; line-height: 1.7;}265        hr {border: none; height: 1px; background: linear-gradient(90deg, rgba(0,0,0,0) 0%, #e6e6e6 50%, rgba(0,0,0,0) 100%);} 266        .spacer-sm {height: 12px;}267        </style>268        """,269        unsafe_allow_html=True,270    )271    st.markdown("<div class='title-box'><h2 style='margin:0'>Chess AI Dummy (VIBE CODED)</h2></div>", unsafe_allow_html=True)272    st.markdown("<div class='spacer-sm'></div>", unsafe_allow_html=True)273 274    if 'board' not in st.session_state:275        st.session_state.board = chess.Board()276    if 'player_color' not in st.session_state:277        st.session_state.player_color = chess.WHITE278    if 'sklearn_model' not in st.session_state:279        st.session_state.sklearn_model = train_simple_model()280    if 'torch_model' not in st.session_state:281        st.session_state.torch_model = SimpleNN()282    if 'move_input' not in st.session_state:283        st.session_state.move_input = ""284    if 'clear_move' not in st.session_state:285        st.session_state.clear_move = False286    if 'ai_check' not in st.session_state:287        st.session_state.ai_check = False288    if 'ai_check_san' not in st.session_state:289        st.session_state.ai_check_san = ""290    board = st.session_state.board291    player_color = st.session_state.player_color292 293    294    depth = 4295 296    # Right panel helpers297    def get_san_history(b: chess.Board):298        temp = chess.Board()299        sans = []300        for mv in b.move_stack:301            sans.append(temp.san(mv))302            temp.push(mv)303        return sans304 305    cur_eval = evaluate_combined(board, st.session_state.sklearn_model, st.session_state.torch_model)306    eval_badge = f"<span class='badge {'badge-eval-pos' if cur_eval>=0 else 'badge-eval-neg'}'>Eval: {cur_eval:+.2f}</span>"307    turn_badge = f"<span class='badge badge-turn'>{'Your' if board.turn==player_color else 'AI'} turn</span>"308 309    left, right = st.columns([2, 1], gap="large")310    with left:311        st.markdown(f"{turn_badge} &nbsp; {eval_badge}", unsafe_allow_html=True)312        svg = chess.svg.board(board=board, size=460)313        st.markdown(svg, unsafe_allow_html=True)314        # Label with inline info popover next to it315        lbl_col, info_col = st.columns([1, 0.15])316        with lbl_col:317            st.write("Enter Move (SAN)")318        with info_col:319            try:320                with st.popover("👇"):321                    st.markdown(322                        """323                        Use notation such as: e4 or Nf3.324 325                        Use O-O for kingside castle, O-O-O for queenside castle.326 327                        For pawn captures, write from-square then to-square (e.g., c4b5).328                        """329                    )330            except Exception:331                with st.expander("👇 Move input help"):332                    st.markdown(333                        """334                        Use notation such as: e4 or Nf3.335 336                        Use O-O for kingside castle, O-O-O for queenside castle.337 338                        For pawn captures, write from-square then to-square (e.g., c4b5).339                        """340                    )341     342        if st.session_state.clear_move:343            st.session_state.move_input = ""344            st.session_state.clear_move = False345        move_input = st.text_input("Your move:", key="move_input")346        # Show AI Check notification just below the input347        if st.session_state.ai_check:348            st.markdown(349                "<div style='background:#ffe3e3;border:1px solid #ffb3b3;"350                "padding:8px 12px;border-radius:10px;color:#7a1212;display:inline-block;"351                "margin-top:6px;'>"352                + f"♚ Check! ({st.session_state.ai_check_san})" + "</div>",353                unsafe_allow_html=True,354            )355            # reset flag after surfacing once356            st.session_state.ai_check = False357            st.session_state.ai_check_san = ""358 359    if board.is_game_over():360        st.markdown("<h2 style='color:#FF5733;'>🎉 Game over! 🎉</h2>", unsafe_allow_html=True)361        if board.is_checkmate():362            winner = "White" if board.turn == chess.BLACK else "Black"363            if winner == "White":364                st.markdown(365                    "<div style='background: linear-gradient(90deg, #f8ffae 0%, #43c6ac 100%);"366                    "padding: 1em; border-radius: 10px; color: #222; font-size: 1.2em;'>"367                    "♔ <b>Checkmate! White wins.</b> <br>Great job if you played as White!<br>"368                    "If not, try to spot where you could have improved your defense."369                    "</div>", unsafe_allow_html=True)370                rain(371                    emoji="♔",372                    font_size=54,373                    falling_speed=5,374                    animation_length="infinite"375                )376            else:377                st.markdown(378                    "<div style='background: linear-gradient(90deg, #a1c4fd 0%, #c2e9fb 100%);"379                    "padding: 1em; border-radius: 10px; color: #222; font-size: 1.2em;'>"380                    "♚ <b>Checkmate! Black wins.</b> <br>If you played as Black, congratulations!<br>"381                    "If not, review the game to see how you can avoid checkmate next time."382                    "</div>", unsafe_allow_html=True)383                rain(384                    emoji="♚",385                    font_size=54,386                    falling_speed=5,387                    animation_length="infinite"388                )389        elif board.is_stalemate():390            st.markdown(391                "<div style='background: linear-gradient(90deg, #f7971e 0%, #ffd200 100%);"392                "padding: 1em; border-radius: 10px; color: #333; font-size: 1.2em;'>"393                "🤝 <b>Stalemate!</b> <br>It's a draw. Both sides played well and neither could force a win.<br>"394                "Try to find a way to break the deadlock in your next game!"395                "</div>", unsafe_allow_html=True)396            rain(397                emoji="🤝",398                font_size=44,399                falling_speed=4,400                animation_length="infinite"401            )402        else:403            st.markdown(404                "<div style='background: linear-gradient(90deg, #e0c3fc 0%, #8ec5fc 100%);"405                "padding: 1em; border-radius: 10px; color: #333; font-size: 1.2em;'>"406                "🤷 <b>Draw!</b> <br>The game ended in a draw. Sometimes, a draw is the best result for both players.<br>"407                "Analyze the final position to see if either side missed a win."408                "</div>", unsafe_allow_html=True)409            rain(410                emoji="🤷",411                font_size=44,412                falling_speed=4,413                animation_length="infinite"414            )415        st.markdown("<hr>", unsafe_allow_html=True)416        if st.button("Reset Game"):417            st.session_state.board = chess.Board()418            st.rerun()419        return420 421    with right:422        sans = get_san_history(board)423        rows = []424        for i in range(0, len(sans), 2):425            white_mv = sans[i]426            black_mv = sans[i+1] if i+1 < len(sans) else ""427            if black_mv:428                row = f"<span style='color:#000000'>{white_mv} - {black_mv}</span>"429            else:430                row = f"<span style='color:#000000'>{white_mv}</span>"431            rows.append(row)432        history_html = "".join([f"<div>{row}</div>" for row in rows])433        st.markdown(434            "<div class='panel'>"435            "<b style='color:#000000'>Move history</b>"436            "<div class='move-list'>" + history_html + "</div>"437            "</div>",438            unsafe_allow_html=True439        )440        441    if board.turn == player_color:442        if move_input:443            try:444                move = board.parse_san(move_input.strip())445                if move in board.legal_moves:446                    board.push(move)447                    st.session_state.board = board448                    st.session_state.clear_move = True449                    st.rerun()450                else:451                    st.error("Illegal move!")452                    st.session_state.clear_move = True453            except ValueError:454                st.error("Invalid move format!")455                st.session_state.clear_move = True456 457    with left:458        with st.write("AI is thinking..."):459            ai_color = not player_color460            move = minimax_best_move(board, depth, st.session_state.sklearn_model, st.session_state.torch_model, ai_color)461            462            if move is None:463                move = ai_move(board, st.session_state.sklearn_model, st.session_state.torch_model)464            465            if move:466                san_text = board.san(move)467                board.push(move)468                st.session_state.board = board469                st.session_state.clear_move = True470            if board.is_check():471                st.session_state.ai_check = True472                st.session_state.ai_check_san = san_text473            st.rerun()474 475            else:476                with left:477                    st.write("No legal moves for AI.")478 479 480         481 482 483if __name__ == "__main__":484    main()485 486    487 488