{{--
    文件功能：五子棋对战面板组件

    支持两种模式：
    - PvP（随机对战）：实时 WebSocket 同步双方落子
    - PvE（人机对战）：服务端 AI 同步返回落子坐标

    @author ChatRoom Laravel
    @version 1.0.0
--}}

{{-- ─── 五子棋面板遮罩 ─── --}}
<div id="gomoku-panel" x-data="gomokuPanel()" x-show="show" x-cloak>
    <div
        style="position:fixed; inset:0; background:rgba(0,0,0,.65); z-index:9940;
                display:flex; align-items:center; justify-content:center;">

        <div
            style="width:520px; max-width:96vw; max-height:96vh; border-radius:12px; overflow:hidden;
                    box-shadow:0 12px 48px rgba(0,0,0,.45); font-family:'Microsoft YaHei',SimSun,sans-serif;
                    background:#fff; display:flex; flex-direction:column;">

            {{-- ─── 标题栏 ─── --}}
            <div
                style="background:linear-gradient(135deg,#1e3a5f,#2d6096); padding:10px 16px;
                        display:flex; align-items:center; gap:10px; flex-shrink:0;">
                <div style="font-size:20px;">♟️</div>
                <div style="flex:1;">
                    <div style="color:#fff; font-weight:bold; font-size:14px;" x-text="title">五子棋对战</div>
                    <div style="color:rgba(255,255,255,.75); font-size:11px; margin-top:1px;" x-text="subtitle"></div>
                </div>
                {{-- 奖励显示 --}}
                <div x-show="rewardGold > 0"
                    style="background:rgba(255,193,7,.2); border:1px solid rgba(255,193,7,.5); border-radius:20px;
                           padding:3px 10px; color:#ffd54f; font-size:11px; font-weight:bold;">
                    🏆 <span x-text="'奖励 ' + rewardGold + ' 金币'"></span>
                </div>
                {{-- 倒计时 --}}
                <div x-show="gameStatus === 'playing' && moveTimeout > 0"
                    style="background:rgba(255,255,255,.15); border-radius:20px; padding:3px 10px;
                           color:#fff; font-size:12px; font-weight:bold; min-width:36px; text-align:center;"
                    :style="moveTimeout <= 10 ? 'color:#ff6b6b; animation:pulse 1s infinite' : ''"
                    x-text="moveTimeout + 's'">
                </div>
                <span @click="closePanel()"
                    style="cursor:pointer; font-size:20px; color:#fff; opacity:.8; line-height:1;"
                    onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=.8">&times;</span>
            </div>

            {{-- ─── 玩家信息栏 ─── --}}
            <div x-show="gameStatus !== 'idle'"
                style="background:#f8fafc; border-bottom:1px solid #d0e4f5; padding:8px 16px;
                        display:flex; align-items:center; justify-content:space-between; flex-shrink:0;">
                {{-- 黑棋玩家 --}}
                <div style="display:flex; align-items:center; gap:6px;">
                    <span
                        style="width:20px; height:20px; border-radius:50%; background:#1a1a1a;
                                 border:2px solid #555; display:inline-block; flex-shrink:0;"></span>
                    <div>
                        <div style="font-size:12px; font-weight:bold; color:#1a1a1a;" x-text="blackName">黑棋</div>
                        <div style="font-size:10px; color:#666;">先手</div>
                    </div>
                </div>
                {{-- 中间状态 --}}
                <div style="text-align:center;">
                    <div style="font-size:11px; color:#336699; font-weight:bold;" x-text="turnText"></div>
                    <div style="font-size:10px; color:#888; margin-top:2px;" x-text="stepCount + ' 步'"></div>
                </div>
                {{-- 白棋玩家 --}}
                <div style="display:flex; align-items:center; gap:6px; flex-direction:row-reverse;">
                    <span
                        style="width:20px; height:20px; border-radius:50%; background:#fff;
                                 border:2px solid #888; display:inline-block; flex-shrink:0;"></span>
                    <div style="text-align:right;">
                        <div style="font-size:12px; font-weight:bold; color:#333;" x-text="whiteName">白棋</div>
                        <div style="font-size:10px; color:#666;" x-text="mode === 'pve' ? 'AI' : '后手'"></div>
                    </div>
                </div>
            </div>

            {{-- ─── 内容区 ─── --}}
            <div
                style="flex:1; overflow-y:auto; background:#f0e9d8; display:flex; flex-direction:column;
                        align-items:center; justify-content:center; padding:12px; gap:10px;">

                {{-- 模式选择界面（idle 状态） --}}
                <div x-show="gameStatus === 'idle'" style="width:100%; max-width:400px;">
                    <div style="text-align:center; margin-bottom:16px;">
                        <div style="font-size:28px; margin-bottom:6px;">♟️</div>
                        <div style="font-size:14px; font-weight:bold; color:#225588;">选择游戏模式</div>
                    </div>

                    {{-- PvP 随机对战 --}}
                    <div @click="startPvP()"
                        style="background:linear-gradient(135deg,#1e3a5f,#2d6096); border-radius:12px;
                               padding:14px 18px; margin-bottom:10px; cursor:pointer; transition:all .22s;
                               box-shadow:0 2px 8px rgba(30,58,95,.18); position:relative; overflow:hidden;"
                        onmouseover="this.style.transform='translateY(-3px)'; this.style.boxShadow='0 8px 24px rgba(30,58,95,.36)'"
                        onmouseout="this.style.transform=''; this.style.boxShadow='0 2px 8px rgba(30,58,95,.18)'">
                        {{-- 背景装饰 --}}
                        <div
                            style="position:absolute;right:-10px;top:-10px;font-size:56px;opacity:.08;line-height:1;pointer-events:none;">
                            ⚔️</div>
                        <div style="font-size:14px; font-weight:bold; color:#fff; margin-bottom:5px;">
                            ⚔️ 随机对战（PvP）
                        </div>
                        <div style="font-size:11px; color:rgba(255,255,255,.75); line-height:1.7;">
                            向聊天室发起挑战邀请，等待其他玩家接受<br>
                            胜利奖励：<strong style="color:#ffd54f;" x-text="(_pvpReward ?? 80) + ' 金币'"></strong>
                        </div>
                    </div>

                    {{-- PvE 人机对战：4档独立彩色卡片 --}}
                    <div style="margin-bottom:2px;">
                        <div
                            style="font-size:11px; font-weight:bold; color:#666; margin-bottom:8px; letter-spacing:.5px;">
                            🤖 人机对战（AI）— 选择难度
                        </div>
                        <div style="display:grid; grid-template-columns:1fr 1fr; gap:10px;">
                            <template x-for="level in aiLevels" :key="level.id">
                                <div @click="startPvE(level)"
                                    style="border-radius:12px; padding:14px 16px; cursor:pointer;
                                           transition:all .25s cubic-bezier(0.34, 1.56, 0.64, 1); position:relative; overflow:hidden;
                                           background:#fff; border:1.5px solid #eaeaea; box-shadow:0 3px 12px rgba(0,0,0,.03);"
                                    @mouseover="$el.style.transform='translateY(-4px)';
                                                $el.style.boxShadow='0 10px 24px '+level.color+'30';
                                                $el.style.borderColor=level.color"
                                    @mouseout="$el.style.transform='';
                                               $el.style.boxShadow='0 3px 12px rgba(0,0,0,.03)';
                                               $el.style.borderColor='#eaeaea'">

                                    {{-- 底部彩色装饰条 --}}
                                    <div style="position:absolute; bottom:0; left:0; width:100%; height:4px;"
                                        :style="'background:' + level.color"></div>

                                    {{-- 大图标装饰（右上角部分溢出隐藏更具高级感） --}}
                                    <div style="position:absolute;right:-8px;top:-6px;font-size:46px;opacity:.06;line-height:1;pointer-events:none;transform:rotate(12deg);"
                                        x-text="level.icon"></div>

                                    {{-- 难度名称 --}}
                                    <div style="font-size:14px; font-weight:900; margin-bottom:5px; display:flex; align-items:center; gap:6px;"
                                        :style="'color:' + level.color">
                                        <span x-text="level.icon" style="font-size:16px;"></span>
                                        <span x-text="level.name"></span>
                                    </div>

                                    {{-- 胜利奖励 --}}
                                    <div style="font-size:12px; font-weight:bold; color:#444; margin-bottom:2px;">
                                        🏆 赢 <span style="font-family:monospace; font-size:13px;"
                                            :style="'color:' + level.color" x-text="'+' + level.reward + ' 金'"></span>
                                    </div>

                                    {{-- 入场费 --}}
                                    <div style="font-size:10px; color:#999; display:flex; align-items:center; gap:3px;">
                                        <template x-if="level.fee > 0">
                                            <span>🎫 入场费 <span x-text="level.fee"></span></span>
                                        </template>
                                        <template x-if="level.fee === 0">
                                            <span
                                                style="color:#16a34a; background:#dcfce7; padding:1px 6px; border-radius:10px;">免费</span>
                                        </template>
                                    </div>
                                </div>
                            </template>
                        </div>
                    </div>
                </div>

                {{-- 等待对手接受（PvP waiting） --}}
                <div x-show="gameStatus === 'waiting'" style="text-align:center; padding:20px;">
                    <div style="font-size:40px; animation:spin 2s linear infinite; display:inline-block;">⏳</div>
                    <div style="font-size:14px; font-weight:bold; color:#225588; margin:12px 0 6px;">
                        等待对手接受挑战…
                    </div>
                    <div style="font-size:11px; color:#888; margin-bottom:16px;">
                        邀请将在 <span x-text="inviteTimeout" style="color:#336699; font-weight:bold;"></span> 秒后超时
                    </div>
                    <button @click="cancelInvite()"
                        style="padding:8px 24px; border:1.5px solid #d0e4f5; border-radius:20px;
                               background:#f0f6ff; color:#336699; font-size:12px; cursor:pointer;
                               font-family:inherit; transition:all .15s;"
                        onmouseover="this.style.background='#ddeeff'" onmouseout="this.style.background='#f0f6ff'">
                        取消邀请
                    </button>
                </div>

                {{-- 棋盘（playing / finished 状态） --}}
                <div x-show="gameStatus === 'playing' || gameStatus === 'finished'">
                    <canvas id="gomoku-canvas"
                        style="cursor:pointer; border-radius:4px; box-shadow:0 2px 12px rgba(0,0,0,.25);"
                        @click="handleCanvasClick($event)" @mousemove="handleCanvasHover($event)"
                        @mouseleave="hoverPos = null; redrawBoard()">
                    </canvas>
                </div>

                {{-- 对局结果 --}}
                <div x-show="gameStatus === 'finished'"
                    style="background:#fff; border-radius:10px; padding:14px 20px; text-align:center;
                           box-shadow:0 2px 12px rgba(0,0,0,.1); min-width:220px;">
                    <div style="font-size:22px; margin-bottom:6px;" x-text="resultEmoji"></div>
                    <div style="font-size:14px; font-weight:bold; color:#225588; margin-bottom:4px;"
                        x-text="resultText"></div>
                    <div x-show="resultGold > 0" style="font-size:12px; color:#e6a800; font-weight:bold;"
                        x-text="'💰 获得 ' + resultGold + ' 金币'"></div>
                </div>

            </div>

            {{-- ─── 底部按钮栏 ─── --}}
            <div
                style="padding:10px 14px; background:#fff; border-top:1px solid #d0e4f5;
                        display:flex; justify-content:center; gap:8px; flex-shrink:0;">

                {{-- 认输按钮（对局中才显示） --}}
                <button
                    x-show="gameStatus === 'playing' && isMyTurn === false || (gameStatus === 'playing' && mode === 'pvp')"
                    @click="resign()"
                    style="padding:8px 18px; border:1.5px solid #ffd0d0; border-radius:20px;
                           background:#fff5f5; color:#dc2626; font-size:12px; cursor:pointer;
                           font-family:inherit; transition:all .15s;"
                    onmouseover="this.style.background='#fee2e2'" onmouseout="this.style.background='#fff5f5'">
                    🏳️ 认输
                </button>

                {{-- 再来一局 --}}
                <button x-show="gameStatus === 'finished'" @click="resetToIdle()"
                    style="padding:8px 18px; border:none; border-radius:20px;
                           background:linear-gradient(135deg,#2d6096,#336699); color:#fff;
                           font-size:12px; cursor:pointer; font-family:inherit; transition:all .15s;
                           box-shadow:0 2px 8px rgba(51,102,153,.3);"
                    onmouseover="this.style.opacity='.85'" onmouseout="this.style.opacity='1'">
                    🔄 再来一局
                </button>

                {{-- 关闭 --}}
                <button @click="closePanel()"
                    style="padding:8px 18px; border:1.5px solid #e0e0e0; border-radius:20px;
                           background:#f9fafb; color:#666; font-size:12px; cursor:pointer;
                           font-family:inherit; transition:all .15s;"
                    onmouseover="this.style.background='#f3f4f6'" onmouseout="this.style.background='#f9fafb'">
                    关闭
                </button>
            </div>
        </div>
    </div>
</div>

<style>
    @keyframes spin {
        from {
            transform: rotate(0deg);
        }

        to {
            transform: rotate(360deg);
        }
    }

    @keyframes pulse {

        0%,
        100% {
            opacity: 1;
        }

        50% {
            opacity: .5;
        }
    }

    [x-cloak] {
        display: none !important;
    }
</style>

<script>
    /**
     * 五子棋面板 Alpine.js 组件
     */
    function gomokuPanel() {
        return {
            // ─── 基础状态 ───
            show: false,
            mode: 'pvp', // 'pvp' | 'pve'
            gameStatus: 'idle', // 'idle' | 'waiting' | 'playing' | 'finished'
            gameId: null,
            myColor: null, // 1=黑 2=白

            // ─── 玩家信息 ───
            blackName: '',
            whiteName: '',
            currentTurn: 1, // 1=黑 2=白
            stepCount: 0,

            // ─── AI 配置 ───
            aiLevels: [{
                    id: 1,
                    name: '简单',
                    icon: '🟢',
                    reward: 20,
                    fee: 0,
                    color: '#16a34a'
                },
                {
                    id: 2,
                    name: '普通',
                    icon: '🟡',
                    reward: 50,
                    fee: 10,
                    color: '#ca8a04'
                },
                {
                    id: 3,
                    name: '困难',
                    icon: '🔴',
                    reward: 120,
                    fee: 30,
                    color: '#dc2626'
                },
                {
                    id: 4,
                    name: '专家',
                    icon: '⚡',
                    reward: 300,
                    fee: 80,
                    color: '#7c3aed'
                },
            ],

            // ─── 棋盘 ───
            board: [], // 15×15 矩阵
            hoverPos: null, // {row, col} 悬停位置
            lastMove: null, // {row, col} 最后落子（高亮用）
            CELL: 30, // 格子像素大小
            PAD: 22, // 棋盘内边距

            // ─── 对局结果 ───
            rewardGold: 0,
            resultEmoji: '',
            resultText: '',
            resultGold: 0,

            // ─── 超时计时器 ───
            inviteTimeout: 60,
            moveTimeout: 0,
            _inviteTimer: null,
            _moveTimer: null,

            // ─── WebSocket 监听器 ───
            _echoChannel: null,

            // PvP 胜利奖励（open() 后从接口更新）
            _pvpReward: 80,

            // ─── 计算属性 ───
            get title() {
                if (this.gameStatus === 'idle') return '五子棋';
                if (this.gameStatus === 'waiting') return '等待对手…';
                if (this.gameStatus === 'finished') return '对局结束';
                return this.mode === 'pvp' ? '五子棋 PvP 对战' : '五子棋 AI 对战';
            },
            get subtitle() {
                if (this.gameStatus === 'idle') return '选择游戏模式开始';
                if (this.gameStatus === 'waiting') return '已发出对战邀请';
                if (this.gameStatus === 'finished') return '对局已结束';
                return this.isMyTurn ? '● 轮到您落子' : '○ 等待对方落子…';
            },
            get isMyTurn() {
                if (this.mode === 'pve') return this.currentTurn === 1;
                return this.currentTurn === this.myColor;
            },
            get turnText() {
                if (this.gameStatus === 'finished') return '对局结束';
                return this.currentTurn === 1 ? '● 黑棋回合' : '○ 白棋回合';
            },

            // ─── 打开/关闭 ───

            /** 从外部打开面板 */
            async open() {
                this.show = true;
                this.resetToIdle();
                this.$nextTick(() => this.initCanvas());

                // ① 并行加载后台配置 + 检查活跃对局
                const [cfg, active] = await Promise.all([
                    fetch('/gomoku/config').then(r => r.json()).catch(() => null),
                    fetch('/gomoku/active').then(r => r.json()).catch(() => null),
                ]);

                // 更新 AI 难度列表（后台配置）
                if (cfg?.ok && Array.isArray(cfg.pve_levels)) {
                    const icons = ['🟢', '🟡', '🔴', '⚡'];
                    const colors = ['#16a34a', '#ca8a04', '#dc2626', '#7c3aed'];
                    this.aiLevels = cfg.pve_levels.map((lv, i) => ({
                        id: lv.level,
                        name: lv.name,
                        icon: icons[i] ?? '♟️',
                        reward: lv.reward,
                        fee: lv.fee,
                        color: colors[i] ?? '#336699',
                    }));
                    this._pvpReward = cfg.pvp_reward ?? 80;
                }

                // ② 如有进行中对局，弹出恢复选择（全局弹窗）
                if (active?.has_active) {
                    const modeLabel = active.mode === 'pve' ?
                        `人机对战（${active.white_name}）` :
                        `PvP 对战（${active.black_name} vs ${active.white_name}）`;

                    const resume = await window.chatDialog.confirm(
                        `您有一局未完成的${modeLabel}，确定继续吗？\n取消则直接认输并结束本局。`,
                        '♟️ 恢复对局',
                        '#1e3a5f'
                    );

                    if (resume) {
                        // 继续对局：恢复棋盘状态
                        this.gameId = active.game_id;
                        this.mode = active.mode;
                        this.myColor = active.your_color;
                        this.currentTurn = active.current_turn;
                        this.board = active.board;
                        this.blackName = active.black_name;
                        this.whiteName = active.white_name;
                        this.gameStatus = active.status === 'waiting' ? 'waiting' : 'playing';
                        this.rewardGold = active.mode === 'pvp' ? (this._pvpReward ?? 80) : 0;
                        this.$nextTick(() => this.redrawBoard());
                        // PvP 恢复后重新订阅私有频道
                        if (active.mode === 'pvp' && active.status === 'playing') {
                            this.subscribeToGame(active.game_id);
                        }
                    } else {
                        // 认输：调接口后提示
                        await fetch(`/gomoku/${active.game_id}/resign`, {
                            method: 'POST',
                            headers: {
                                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')
                                    ?.content ?? '',
                                'Accept': 'application/json',
                            },
                        }).catch(() => {});
                        await window.chatDialog.alert('已认输，对局结束。', '♟️ 对局结束', '#336699');
                    }
                }
            },

            /** 初始化面板并直接加入对局（被邀请方） */
            async openAndJoin(gameId) {
                this.show = true;
                this.$nextTick(async () => {
                    this.initCanvas();
                    await this.joinGame(gameId);
                });
            },

            /** 关闭面板 */
            closePanel() {
                // 对局中时不能直接关闭（防误操作）
                if (this.gameStatus === 'playing') {
                    window.chatDialog?.confirm('对局进行中，关闭面板将不会认输，确定关闭？').then(ok => {
                        if (ok) {
                            this.cleanUp();
                            this.show = false;
                        }
                    });
                    return;
                }
                this.cleanUp();
                this.show = false;
            },

            /** 重置到选择模式界面 */
            resetToIdle() {
                this.cleanUp();
                this.gameStatus = 'idle';
                this.gameId = null;
                this.myColor = null;
                this.board = Array.from({
                    length: 15
                }, () => Array(15).fill(0));
                this.lastMove = null;
                this.stepCount = 0;
                this.rewardGold = 0;
                this.resultEmoji = '';
                this.resultText = '';
                this.resultGold = 0;
                this.$nextTick(() => this.redrawBoard());
            },

            /** 清理定时器和 WebSocket 监听 */
            cleanUp() {
                clearInterval(this._inviteTimer);
                clearInterval(this._moveTimer);
                if (this._echoChannel && window.Echo) {
                    window.Echo.leave(`gomoku.${this.gameId}`);
                    this._echoChannel = null;
                }
            },

            // ─── 游戏开始 ───

            /** 发起 PvP 随机对战 */
            async startPvP() {
                const roomId = window.chatContext?.roomId;
                if (!roomId) return;

                const res = await this.post('/gomoku/create', {
                    mode: 'pvp',
                    room_id: roomId
                });
                if (!res?.ok) {
                    window.chatDialog?.alert(res?.message || '发起失败，请稍后重试');
                    return;
                }
                this.gameId = res.game_id;
                this.mode = 'pvp';
                this.myColor = 1; // 发起方执黑
                this.blackName = window.chatContext?.username || '我';
                this.whiteName = '等待中…';
                this.gameStatus = 'waiting';
                this.inviteTimeout = 60;

                // 启动邀请倒计时
                this._inviteTimer = setInterval(() => {
                    this.inviteTimeout--;
                    if (this.inviteTimeout <= 0) {
                        clearInterval(this._inviteTimer);
                        if (this.gameStatus === 'waiting') {
                            this.gameStatus = 'idle';
                            window.chatDialog?.alert('邀请已超时，无人接受挑战。');
                        }
                    }
                }, 1000);

                // 监听对手加入事件（通过房间频道广播触发）
                this.waitForOpponent();
            },

            /** 发起 PvE 人机对战 */
            async startPvE(level) {
                const roomId = window.chatContext?.roomId;
                if (!roomId) return;

                const res = await this.post('/gomoku/create', {
                    mode: 'pve',
                    room_id: roomId,
                    ai_level: level.id,
                });
                if (!res?.ok) {
                    window.chatDialog?.alert(res?.message || '创建失败，请稍后重试');
                    return;
                }
                this.gameId = res.game_id;
                this.mode = 'pve';
                this.myColor = 1;
                this.blackName = window.chatContext?.username || '我';
                this.whiteName = `AI（${level.name}）`;
                this.rewardGold = level.reward;
                this.board = Array.from({
                    length: 15
                }, () => Array(15).fill(0));
                this.currentTurn = 1;
                this.gameStatus = 'playing';
                this.$nextTick(() => this.redrawBoard());
            },

            /** 加入 PvP 对战（接受邀请方） */
            async joinGame(gameId) {
                const res = await this.post(`/gomoku/${gameId}/join`, {});
                if (!res?.ok) {
                    window.chatDialog?.alert(res?.message || '加入失败');
                    this.show = false;
                    return;
                }
                // 重新获取对局状态
                await this.syncState(gameId);
                this.subscribeToGame(gameId);
            },

            /** 等待对手通过轮询或 WebSocket 通知加入 */
            waitForOpponent() {
                // 每 3 秒轮询一次对局状态
                const maxAttempts = 20;
                let attempts = 0;
                const poll = setInterval(async () => {
                    if (this.gameStatus !== 'waiting') {
                        clearInterval(poll);
                        return;
                    }
                    attempts++;
                    if (attempts > maxAttempts) {
                        clearInterval(poll);
                        return;
                    }
                    const res = await fetch(`/gomoku/${this.gameId}/state`).then(r => r.json()).catch(() =>
                        null);
                    if (res?.status === 'playing') {
                        clearInterval(poll);
                        this.whiteName = res.opponent_name || '对手';
                        this.currentTurn = res.current_turn;
                        this.gameStatus = 'playing';
                        this.subscribeToGame(this.gameId);
                        this.$nextTick(() => this.redrawBoard());
                    }
                }, 3000);
            },

            /** 同步对局状态（连接时）*/
            async syncState(gameId) {
                const res = await fetch(`/gomoku/${gameId}/state`).then(r => r.json()).catch(() => null);
                if (!res?.ok) return;
                this.gameId = gameId;
                this.mode = res.mode;
                this.myColor = res.your_color;
                this.currentTurn = res.current_turn;
                this.board = res.board;
                this.gameStatus = res.status;
                this.blackName = res.black_name || '黑棋';
                this.whiteName = res.white_name || '白棋';
                this.rewardGold = res.mode === 'pvp' ? 80 : res.reward_gold ?? 0;
                this.$nextTick(() => this.redrawBoard());
            },

            // ─── WebSocket 监听 ───

            /** 订阅对局私有频道 */
            subscribeToGame(gameId) {
                if (!window.Echo) return;
                this._echoChannel = window.Echo.private(`gomoku.${gameId}`)
                    .listen('.gomoku.moved', (e) => {
                        this.onRemoteMove(e);
                    })
                    .listen('.gomoku.finished', (e) => {
                        this.onGameFinished(e);
                    });
            },

            /** 收到远端落子 */
            onRemoteMove(e) {
                // 仅 PvP 模式会通过 WebSocket 接收对方落子
                if (this.mode !== 'pvp') return;
                this.board[e.row][e.col] = e.color;
                this.lastMove = {
                    row: e.row,
                    col: e.col
                };
                this.currentTurn = e.current_turn;
                this.stepCount++;
                this.redrawBoard();
            },

            /** 游戏结束通知（WebSocket）*/
            onGameFinished(e) {
                if (e.game_id !== this.gameId) return;
                this.gameStatus = 'finished';
                this.showResult(e.winner, e.winner_name, e.reason, e.reward_gold);
            },

            // ─── 落子逻辑 ───

            /** Canvas 点击事件：计算坐标并落子 */
            async handleCanvasClick(event) {
                if (this.gameStatus !== 'playing' || !this.isMyTurn) return;

                const canvas = document.getElementById('gomoku-canvas');
                if (!canvas) return;
                const rect = canvas.getBoundingClientRect();
                const x = event.clientX - rect.left;
                const y = event.clientY - rect.top;

                const col = Math.round((x - this.PAD) / this.CELL);
                const row = Math.round((y - this.PAD) / this.CELL);

                if (row < 0 || row >= 15 || col < 0 || col >= 15) return;
                if (this.board[row][col] !== 0) return;

                // 乐观更新（先本地显示，等服务端响应）
                this.board[row][col] = this.myColor;
                this.lastMove = {
                    row,
                    col
                };
                this.stepCount++;
                this.redrawBoard();

                // 切换回合（PvP 时前端先切，等服务端广播校正）
                if (this.mode === 'pvp') {
                    this.currentTurn = this.myColor === 1 ? 2 : 1;
                } else {
                    // PvE 不立即切换，等 AI 落子返回
                    this.currentTurn = 2;
                }

                const res = await this.post(`/gomoku/${this.gameId}/move`, {
                    row,
                    col
                });

                if (!res?.ok) {
                    // 落子被拒：回滚
                    this.board[row][col] = 0;
                    this.stepCount--;
                    this.currentTurn = this.myColor;
                    this.redrawBoard();
                    return;
                }

                if (res.finished) {
                    this.gameStatus = 'finished';
                    this.showResult(res.winner, '', res.reason, res.reward_gold);
                    return;
                }

                // PvE：处理 AI 落子
                if (this.mode === 'pve' && res.ai_moved) {
                    // 延迟 600ms 展示 AI "思考"效果
                    setTimeout(() => {
                        const ai = res.ai_moved;
                        this.board[ai.row][ai.col] = 2;
                        this.lastMove = {
                            row: ai.row,
                            col: ai.col
                        };
                        this.stepCount++;
                        this.currentTurn = 1;
                        this.redrawBoard();
                    }, 600);
                }
            },

            /** Canvas 鼠标移动：悬停预览 */
            handleCanvasHover(event) {
                if (this.gameStatus !== 'playing' || !this.isMyTurn) return;
                const canvas = document.getElementById('gomoku-canvas');
                if (!canvas) return;
                const rect = canvas.getBoundingClientRect();
                const x = event.clientX - rect.left;
                const y = event.clientY - rect.top;
                const col = Math.round((x - this.PAD) / this.CELL);
                const row = Math.round((y - this.PAD) / this.CELL);
                if (row >= 0 && row < 15 && col >= 0 && col < 15 && this.board[row][col] === 0) {
                    this.hoverPos = {
                        row,
                        col
                    };
                } else {
                    this.hoverPos = null;
                }
                this.redrawBoard();
            },

            // ─── 其他操作 ───

            /** 认输 */
            async resign() {
                const ok = await window.chatDialog?.confirm('确定认输？').catch(() => false) ?? false;
                if (!ok) return;
                const res = await this.post(`/gomoku/${this.gameId}/resign`, {});
                if (res?.finished) {
                    this.gameStatus = 'finished';
                    this.showResult(res.winner, '', 'resign', 0);
                }
            },

            /** 取消邀请 */
            async cancelInvite() {
                await this.post(`/gomoku/${this.gameId}/cancel`, {});
                clearInterval(this._inviteTimer);
                this.gameStatus = 'idle';
            },

            /** 显示对局结果 */
            showResult(winner, winnerName, reason, gold) {
                const isWinner = (winner === this.myColor);
                const isDraw = (winner === 0);
                this.resultGold = gold ?? 0;
                if (isDraw) {
                    this.resultEmoji = '🤝';
                    this.resultText = '平局！势均力敌';
                } else if (reason === 'resign') {
                    this.resultEmoji = isWinner ? '🏆' : '😔';
                    this.resultText = isWinner ? '对手认输，您获胜！' : '您认输了';
                } else {
                    this.resultEmoji = isWinner ? '🏆' : '😔';
                    this.resultText = isWinner ?
                        (this.mode === 'pve' ? '恭喜！您击败了 AI！' : '恭喜！您获胜了！') :
                        (this.mode === 'pve' ? 'AI 获胜，再接再厉！' : '惜败，再来一局！');
                }
                if (isWinner && gold > 0 && window.chatContext) {
                    window.chatContext.userJjb = (window.chatContext.userJjb ?? 0) + gold;
                }
            },

            // ─── Canvas 绘制 ───

            /** 初始化 Canvas 尺寸 */
            initCanvas() {
                const canvas = document.getElementById('gomoku-canvas');
                if (!canvas) return;
                const size = this.CELL * 14 + this.PAD * 2;
                canvas.width = size;
                canvas.height = size;
                this.redrawBoard();
            },

            /** 重绘棋盘 */
            redrawBoard() {
                const canvas = document.getElementById('gomoku-canvas');
                if (!canvas) return;
                const ctx = canvas.getContext('2d');
                const {
                    CELL,
                    PAD
                } = this;
                const SIZE = CELL * 14 + PAD * 2;

                // 棋盘背景
                ctx.fillStyle = '#dcb866';
                ctx.fillRect(0, 0, SIZE, SIZE);

                // 棋盘格线
                ctx.strokeStyle = '#a0783a';
                ctx.lineWidth = 0.8;
                for (let i = 0; i < 15; i++) {
                    const x = PAD + i * CELL;
                    ctx.beginPath();
                    ctx.moveTo(x, PAD);
                    ctx.lineTo(x, PAD + 14 * CELL);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.moveTo(PAD, x);
                    ctx.lineTo(PAD + 14 * CELL, x);
                    ctx.stroke();
                }

                // 星位（天元 + 四角星）
                const starPoints = [
                    [3, 3],
                    [3, 11],
                    [11, 3],
                    [11, 11],
                    [7, 7]
                ];
                ctx.fillStyle = '#7a5c28';
                starPoints.forEach(([r, c]) => {
                    ctx.beginPath();
                    ctx.arc(PAD + c * CELL, PAD + r * CELL, 3.5, 0, Math.PI * 2);
                    ctx.fill();
                });

                // 坐标标记
                const cols = 'ABCDEFGHJKLMNOP';
                ctx.fillStyle = '#7a5c28';
                ctx.font = '9px monospace';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                for (let i = 0; i < 15; i++) {
                    ctx.fillText(cols[i], PAD + i * CELL, PAD - 12);
                    ctx.fillText(15 - i, PAD - 14, PAD + i * CELL);
                }

                // 棋子
                this.board.forEach((row, r) => {
                    row.forEach((cell, c) => {
                        if (cell === 0) return;
                        const x = PAD + c * CELL;
                        const y = PAD + r * CELL;
                        const isLast = this.lastMove?.row === r && this.lastMove?.col === c;

                        // 棋子渐变（立体感）
                        const grad = ctx.createRadialGradient(x - 3, y - 3, 1, x, y, CELL * 0.45);
                        if (cell === 1) {
                            grad.addColorStop(0, '#888');
                            grad.addColorStop(1, '#111');
                        } else {
                            grad.addColorStop(0, '#fff');
                            grad.addColorStop(1, '#ccc');
                        }
                        ctx.beginPath();
                        ctx.arc(x, y, CELL * 0.44, 0, Math.PI * 2);
                        ctx.fillStyle = grad;
                        ctx.fill();
                        ctx.strokeStyle = cell === 1 ? '#000' : '#aaa';
                        ctx.lineWidth = 0.5;
                        ctx.stroke();

                        // 最后落子标记
                        if (isLast) {
                            ctx.beginPath();
                            ctx.arc(x, y, 4, 0, Math.PI * 2);
                            ctx.fillStyle = cell === 1 ? '#fff' : '#666';
                            ctx.fill();
                        }
                    });
                });

                // 悬停预览棋子（半透明）
                if (this.hoverPos && this.isMyTurn && this.gameStatus === 'playing') {
                    const {
                        row: hr,
                        col: hc
                    } = this.hoverPos;
                    if (this.board[hr][hc] === 0) {
                        const hx = PAD + hc * CELL;
                        const hy = PAD + hr * CELL;
                        ctx.beginPath();
                        ctx.arc(hx, hy, CELL * 0.44, 0, Math.PI * 2);
                        ctx.fillStyle = this.myColor === 1 ? 'rgba(0,0,0,0.35)' : 'rgba(255,255,255,0.55)';
                        ctx.fill();
                        ctx.strokeStyle = this.myColor === 1 ? 'rgba(0,0,0,.5)' : 'rgba(150,150,150,.5)';
                        ctx.lineWidth = 1;
                        ctx.stroke();
                    }
                }
            },

            // ─── 网络请求辅助 ───

            /** 封装 POST 请求 */
            async post(url, data) {
                try {
                    const res = await fetch(url, {
                        method: 'POST',
                        headers: {
                            'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
                            'Content-Type': 'application/json',
                            'Accept': 'application/json',
                        },
                        body: JSON.stringify(data),
                    });
                    return await res.json();
                } catch {
                    return null;
                }
            },
        };
    }

    /** 从外部打开五子棋面板 */
    window.openGomokuPanel = function() {
        const panel = document.getElementById('gomoku-panel');
        if (panel) Alpine.$data(panel).open();
    };

    /** 接受 PvP 邀请并打开棋盘 */
    window.acceptGomokuInvite = function(gameId) {
        const panel = document.getElementById('gomoku-panel');
        if (panel) Alpine.$data(panel).openAndJoin(gameId);
    };
</script>
