285 lines
16 KiB
PHP
285 lines
16 KiB
PHP
{{--
|
||
文件功能:五子棋对战面板组件
|
||
|
||
支持两种模式:
|
||
- 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">×</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 --}}
|