File size: 5,020 Bytes
23ab0d8
3f4cf5e
 
 
7839e5e
4ee9ac9
23ab0d8
2d742d9
3f4cf5e
23ab0d8
 
3f4cf5e
23ab0d8
 
 
 
3f4cf5e
23ab0d8
 
 
 
2d742d9
23ab0d8
0a5571c
 
a13a48e
75a591c
 
 
 
 
 
e164a6e
 
0a5571c
23ab0d8
75a591c
23ab0d8
 
 
 
 
 
 
 
75a591c
23ab0d8
32c185e
3f4cf5e
 
 
7839e5e
4e63bca
23ab0d8
7839e5e
e164a6e
3ae28d9
 
 
 
e164a6e
23ab0d8
e164a6e
 
3f4cf5e
e164a6e
3ae28d9
 
 
 
 
 
e164a6e
75a591c
 
 
 
3ae28d9
75a591c
3ae28d9
 
 
 
 
75a591c
 
e164a6e
 
23ab0d8
e164a6e
3f4cf5e
23ab0d8
7839e5e
 
 
 
 
23ab0d8
3f4cf5e
7839e5e
3f4cf5e
7839e5e
 
 
 
 
3f4cf5e
7839e5e
3f4cf5e
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import importlib.util
import gradio as gr
import chess
import chess.svg
import torch
import spaces
from huggingface_hub import hf_hub_download
from transformers import AutoModel, AutoConfig

MODEL_ID = "Datdanboi25/Charles-the-Chess-Bot"
REVISION = "63e2574705794b40899f642aaa619d1668e1e731"

config = AutoConfig.from_pretrained(MODEL_ID, revision=REVISION, trust_remote_code=True)
model = AutoModel.from_pretrained(
    MODEL_ID, revision=REVISION, config=config, trust_remote_code=True
).eval()

_TOK_PATH = hf_hub_download(MODEL_ID, "tokenization_chess_policy.py", revision=REVISION)
_SPEC = importlib.util.spec_from_file_location("charles_tokenization", _TOK_PATH)
charles_tokenization = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(charles_tokenization)

board = chess.Board()


@spaces.GPU
def get_bot_move_uci(fen: str) -> str | None:
    """สำคัญ: รับ FEN เป็น argument แทนการอ่าน global `board`
    เพราะฟังก์ชันนี้รันใน subprocess แยกของ ZeroGPU
    ต้อง reconstruct board ในนั้นเอง ไม่งั้นจะได้สถานะเก่าค้าง"""
    b = chess.Board(fen)
    legal_moves = list(b.legal_moves)
    if not legal_moves:
        return None

    inputs = charles_tokenization.build_model_inputs(
        [b.copy(stack=False)], include_candidate_uci=True
    )
    ucis = inputs.pop("candidate_uci")[0]

    with torch.inference_mode():
        logits = model(**inputs).logits[0].float()

    moves = [chess.Move.from_uci(u) for u in ucis]
    best_idx = int(torch.argmax(logits))
    return moves[best_idx].uci()


def make_move(user_move):
    global board
    try:
        if board.is_game_over():
            return "เกมจบแล้ว! กรุณากดเริ่มเกมใหม่", chess.svg.board(board)

        move = chess.Move.from_uci(user_move.strip())
        if move not in board.legal_moves:
            check_hint = ""
            if board.is_check():
                check_hint = " ⚠️ ตอนนี้คุณโดนรุกอยู่! ต้องเดินแก้รุกก่อน (ย้ายราชาหนี / เอาตัวบัง / กินตัวที่รุก)"
            return f"ตาเดินไม่ถูกต้องตามกฎ!{check_hint} (ตัวอย่าง: e2e4, g1f3)", chess.svg.board(board)
        board.push(move)

    except ValueError:
        return "รูปแบบตาเดินไม่ถูกต้อง (ต้องเป็นแบบ UCI เช่น e2e4)", chess.svg.board(board)

    if board.is_game_over():
        if board.is_checkmate():
            winner = "คุณ" if board.turn != chess.WHITE else "บอท"
            # board.turn คือฝ่ายที่ต้องเดินแต่เดินไม่ได้ (โดนรุกฆาต) = ฝ่ายแพ้
            loser = "บอท" if board.turn != chess.WHITE else "คุณ"
            return f"♛ รุกฆาต! {loser}แพ้", chess.svg.board(board)
        return "เกมจบแล้ว! เสมอ (Stalemate/Draw)", chess.svg.board(board)

    bot_move_uci = get_bot_move_uci(board.fen())

    if bot_move_uci:
        bot_move = chess.Move.from_uci(bot_move_uci)
        if bot_move in board.legal_moves:
            board.push(bot_move)
            check_note = " (รุก!)" if board.is_check() else ""
            if board.is_checkmate():
                msg = f"บอทเดิน: {bot_move.uci()} — ♛ รุกฆาต! คุณแพ้"
            else:
                msg = f"บอทเดิน: {bot_move.uci()}{check_note}"
        else:
            msg = f"บอทพยายามเดิน {bot_move_uci} แต่ผิดกฎ - ข้ามตานี้"
    else:
        msg = "บอทไม่สามารถเดินหมากได้ - ตาของคุณเดินต่อ"

    return msg, chess.svg.board(board)


def reset_game():
    global board
    board = chess.Board()
    return "เริ่มเกมใหม่เรียบร้อย", chess.svg.board(board)


with gr.Blocks() as demo:
    gr.Markdown("# ♟️ ซ้อมหมากรุกกับ Charles the Chess Bot")
    board_html = gr.HTML(chess.svg.board(board))
    with gr.Row():
        move_input = gr.Textbox(label="ใส่ตาเดินแบบ UCI (เช่น e2e4, g1f3):", placeholder="e2e4")
        submit_btn = gr.Button("เดินหมาก", variant="primary")
        reset_btn = gr.Button("เริ่มเกมใหม่")
    status_text = gr.Textbox(label="สถานะ / ตาเดินของบอท")
    submit_btn.click(make_move, inputs=[move_input], outputs=[status_text, board_html])
    reset_btn.click(reset_game, outputs=[status_text, board_html])

demo.launch()