Files
2026-04-25 18:50:05 +08:00

285 lines
16 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{--
文件功能:五子棋对战面板组件
支持两种模式:
- PvP(随机对战):实时 WebSocket 同步双方落子
- PvE(人机对战):服务端 AI 同步返回落子坐标
@author ChatRoom Laravel
@version 1.0.0
--}}
{{-- ─── 五子棋面板遮罩 ─── --}}
<div id="gomoku-panel"
x-data="gomokuPanel()"
x-show="show"
x-cloak
data-gomoku-base-url="/gomoku"
data-gomoku-config-url="{{ route('gomoku.config') }}"
data-gomoku-active-url="{{ route('gomoku.active') }}"
data-gomoku-create-url="{{ route('gomoku.create') }}">
<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>
{{-- 五子棋主面板脚本已迁移到 resources/js/chat-room/gomoku-panel.js --}}