442 lines
16 KiB
HTML
442 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
<meta http-equiv="Pragma" content="no-cache">
|
|
<meta http-equiv="Expires" content="0">
|
|
<title>🎮 斗地主测试客户端 (v2)</title>
|
|
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
|
|
<style>
|
|
* { box-sizing: border-box; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
background: #f5f5f5;
|
|
}
|
|
h1 { color: #333; text-align: center; }
|
|
.card {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin: 15px 0;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
.status {
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
margin: 10px 0;
|
|
font-weight: bold;
|
|
}
|
|
.status.connected { background: #d4edda; color: #155724; }
|
|
.status.disconnected { background: #f8d7da; color: #721c24; }
|
|
input, button {
|
|
padding: 10px;
|
|
margin: 5px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
}
|
|
button {
|
|
background: #007bff;
|
|
color: white;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
button:hover { background: #0056b3; }
|
|
button:disabled { background: #ccc; cursor: not-allowed; }
|
|
.log {
|
|
background: #f8f9fa;
|
|
border: 1px solid #e9ecef;
|
|
border-radius: 4px;
|
|
padding: 10px;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
}
|
|
.log-entry {
|
|
margin: 2px 0;
|
|
padding: 4px;
|
|
border-radius: 2px;
|
|
}
|
|
.log-entry.send { background: #e3f2fd; }
|
|
.log-entry.receive { background: #f3e5f5; }
|
|
.log-entry.error { background: #ffebee; }
|
|
.flex { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
.flex > * { flex: 1; min-width: 150px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>🎮 Net-Games 测试客户端</h1>
|
|
|
|
<!-- 连接状态 -->
|
|
<div class="card">
|
|
<h3>连接状态</h3>
|
|
<div id="connectionStatus" class="status disconnected">未连接</div>
|
|
<div class="flex">
|
|
<input type="text" id="serverUrl" value="http://129.211.173.87:8900" placeholder="服务器地址">
|
|
<button onclick="connect()">连接</button>
|
|
<button onclick="disconnect()">断开</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 快速开始 -->
|
|
<div class="card" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
|
<h3 style="margin-top: 0;">⚡ 快速开始(推荐)</h3>
|
|
<p style="margin: 5px 0; opacity: 0.9;">一键创建房间、添加AI、准备并开始游戏</p>
|
|
<div class="flex" style="align-items: center;">
|
|
<input type="text" id="quickPlayerName" value="蔡叔叔" placeholder="你的昵称" style="background: rgba(255,255,255,0.9);">
|
|
<select id="quickAiCount" style="background: rgba(255,255,255,0.9);">
|
|
<option value="1">1个AI</option>
|
|
<option value="2" selected>2个AI</option>
|
|
</select>
|
|
<select id="quickAiType1" style="background: rgba(255,255,255,0.9);">
|
|
<option value="rule" selected>AI-1: 规则</option>
|
|
<option value="deepseek">AI-1: DeepSeek</option>
|
|
</select>
|
|
<select id="quickAiType2" style="background: rgba(255,255,255,0.9);">
|
|
<option value="rule">AI-2: 规则</option>
|
|
<option value="deepseek" selected>AI-2: DeepSeek</option>
|
|
</select>
|
|
</div>
|
|
<button onclick="quickStart()" style="width: 100%; margin: 10px 0; padding: 15px; font-size: 16px; background: #00d4aa;">🚀 一键开始游戏</button>
|
|
</div>
|
|
|
|
<!-- 房间操作 -->
|
|
<div class="card">
|
|
<h3>🏠 手动房间操作</h3>
|
|
<div class="flex">
|
|
<input type="text" id="playerName" value="测试玩家" placeholder="玩家名称">
|
|
</div>
|
|
<div class="flex" style="align-items: center;">
|
|
<label style="min-width: 80px;">AI数量:</label>
|
|
<select id="aiCount">
|
|
<option value="0">0 (纯真人)</option>
|
|
<option value="1">1</option>
|
|
<option value="2" selected>2</option>
|
|
</select>
|
|
<label style="min-width: 80px;">AI类型:</label>
|
|
<select id="aiType">
|
|
<option value="rule" selected>规则AI</option>
|
|
<option value="deepseek">DeepSeek</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex">
|
|
<button onclick="createRoom()" id="btnCreate">创建房间</button>
|
|
<input type="text" id="roomCode" placeholder="房间号" maxlength="6">
|
|
<button onclick="joinRoom()" id="btnJoin">加入房间</button>
|
|
<button onclick="leaveRoom()" id="btnLeave">离开房间</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 游戏控制 -->
|
|
<div class="card">
|
|
<h3>游戏控制</h3>
|
|
<div class="flex">
|
|
<button onclick="setReady()" id="btnReady">准备</button>
|
|
<button onclick="startGame()" id="btnStart">开始游戏</button>
|
|
</div>
|
|
<div class="flex" style="margin-top: 10px;">
|
|
<input type="number" id="guessNumber" placeholder="输入数字 1-100" min="1" max="100">
|
|
<button onclick="makeGuess()">猜数字</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 当前状态 -->
|
|
<div class="card">
|
|
<h3>当前状态</h3>
|
|
<div id="gameState">等待连接...</div>
|
|
</div>
|
|
|
|
<!-- 日志 -->
|
|
<div class="card">
|
|
<h3>通信日志 <button onclick="clearLog()">清空</button></h3>
|
|
<div id="log" class="log"></div>
|
|
</div>
|
|
|
|
<script>
|
|
let socket = null;
|
|
let currentRoom = null;
|
|
let currentPlayer = null;
|
|
let quickStartRoom = false; // ⚡ 快速开始标记
|
|
|
|
function log(message, type = 'info') {
|
|
const logEl = document.getElementById('log');
|
|
const entry = document.createElement('div');
|
|
entry.className = `log-entry ${type}`;
|
|
const time = new Date().toLocaleTimeString();
|
|
entry.textContent = `[${time}] ${message}`;
|
|
logEl.insertBefore(entry, logEl.firstChild);
|
|
}
|
|
|
|
function updateState(state) {
|
|
document.getElementById('gameState').innerHTML = `<pre>${JSON.stringify(state, null, 2)}</pre>`;
|
|
}
|
|
|
|
function updateConnectionStatus(connected) {
|
|
const statusEl = document.getElementById('connectionStatus');
|
|
if (connected) {
|
|
statusEl.textContent = '已连接';
|
|
statusEl.className = 'status connected';
|
|
} else {
|
|
statusEl.textContent = '未连接';
|
|
statusEl.className = 'status disconnected';
|
|
}
|
|
}
|
|
|
|
function connect() {
|
|
const url = document.getElementById('serverUrl').value;
|
|
log(`正在连接到 ${url}...`);
|
|
|
|
socket = io(url);
|
|
|
|
socket.on('connect', () => {
|
|
log(`连接成功! Socket ID: ${socket.id}`, 'receive');
|
|
updateConnectionStatus(true);
|
|
});
|
|
|
|
socket.on('disconnect', () => {
|
|
log('连接已断开', 'error');
|
|
updateConnectionStatus(false);
|
|
currentRoom = null;
|
|
currentPlayer = null;
|
|
});
|
|
|
|
socket.on('error', (data) => {
|
|
log(`错误: ${JSON.stringify(data)}`, 'error');
|
|
});
|
|
|
|
// 房间事件
|
|
socket.on('connected', (data) => {
|
|
log(`服务器消息: ${data.message}`, 'receive');
|
|
});
|
|
|
|
socket.on('room_created', (data) => {
|
|
log(`房间创建成功: ${JSON.stringify(data)}`, 'receive');
|
|
if (data.success) {
|
|
currentRoom = data.room;
|
|
currentPlayer = data.player;
|
|
document.getElementById('roomCode').value = currentRoom.code;
|
|
updateState(data.room);
|
|
|
|
// ⚡ 快速开始:自动准备并开始
|
|
if (quickStartRoom) {
|
|
setTimeout(() => {
|
|
log('🤖 自动准备...');
|
|
socket.emit('set_ready', { ready: true });
|
|
|
|
setTimeout(() => {
|
|
log('🎮 自动开始游戏...');
|
|
socket.emit('start_game');
|
|
quickStartRoom = false;
|
|
}, 600);
|
|
}, 300);
|
|
}
|
|
}
|
|
});
|
|
|
|
socket.on('room_joined', (data) => {
|
|
log(`加入房间成功: ${JSON.stringify(data)}`, 'receive');
|
|
if (data.success) {
|
|
currentRoom = data.room;
|
|
currentPlayer = data.player;
|
|
updateState(data.room);
|
|
}
|
|
});
|
|
|
|
socket.on('room_left', (data) => {
|
|
log(`离开房间: ${JSON.stringify(data)}`, 'receive');
|
|
currentRoom = null;
|
|
currentPlayer = null;
|
|
});
|
|
|
|
socket.on('room_updated', (data) => {
|
|
log(`房间更新: ${JSON.stringify(data)}`, 'receive');
|
|
if (data.room) {
|
|
currentRoom = data.room;
|
|
updateState(data.room);
|
|
}
|
|
});
|
|
|
|
socket.on('player_joined', (data) => {
|
|
log(`玩家加入: ${JSON.stringify(data)}`, 'receive');
|
|
});
|
|
|
|
socket.on('player_left', (data) => {
|
|
log(`玩家离开: ${JSON.stringify(data)}`, 'receive');
|
|
});
|
|
|
|
socket.on('player_disconnected', (data) => {
|
|
log(`玩家离线: ${JSON.stringify(data)}`, 'receive');
|
|
});
|
|
|
|
// 游戏事件
|
|
socket.on('game_started', (data) => {
|
|
log(`游戏开始: ${JSON.stringify(data)}`, 'receive');
|
|
if (data.room) {
|
|
currentRoom = data.room;
|
|
updateState(data.room);
|
|
}
|
|
});
|
|
|
|
socket.on('state_update', (data) => {
|
|
log(`状态更新: ${JSON.stringify(data)}`, 'receive');
|
|
if (data.game_state) {
|
|
updateState(data.game_state);
|
|
}
|
|
});
|
|
|
|
socket.on('game_ended', (data) => {
|
|
log(`游戏结束: ${JSON.stringify(data)}`, 'receive');
|
|
});
|
|
|
|
socket.on('action_failed', (data) => {
|
|
log(`操作失败: ${JSON.stringify(data)}`, 'error');
|
|
});
|
|
|
|
socket.on('pong', (data) => {
|
|
log(`心跳响应: ${JSON.stringify(data)}`, 'receive');
|
|
});
|
|
}
|
|
|
|
function disconnect() {
|
|
if (socket) {
|
|
socket.disconnect();
|
|
socket = null;
|
|
}
|
|
}
|
|
|
|
function createRoom() {
|
|
if (!socket) {
|
|
log('请先连接服务器', 'error');
|
|
return;
|
|
}
|
|
const playerName = document.getElementById('playerName').value || '测试玩家';
|
|
const aiCount = parseInt(document.getElementById('aiCount').value);
|
|
const aiType = document.getElementById('aiType').value;
|
|
|
|
// 构建AI配置
|
|
const ai_players = [];
|
|
for (let i = 0; i < aiCount; i++) {
|
|
ai_players.push({
|
|
name: `AI-${i+1}(${aiType})`,
|
|
ai_type: aiType
|
|
});
|
|
}
|
|
|
|
socket.emit('create_room', {
|
|
game_type: 'doudizhu',
|
|
player_name: playerName,
|
|
config: { max_players: 3 },
|
|
ai_players: ai_players
|
|
});
|
|
log(`发送: create_room (游戏:斗地主, AI:${aiCount}个${aiType})`, 'send');
|
|
}
|
|
|
|
function joinRoom() {
|
|
if (!socket) {
|
|
log('请先连接服务器', 'error');
|
|
return;
|
|
}
|
|
const roomCode = document.getElementById('roomCode').value.toUpperCase();
|
|
const playerName = document.getElementById('playerName').value || '测试玩家';
|
|
if (!roomCode) {
|
|
log('请输入房间号', 'error');
|
|
return;
|
|
}
|
|
socket.emit('join_room', {
|
|
room_code: roomCode,
|
|
player_name: playerName
|
|
});
|
|
log(`发送: join_room (${roomCode})`, 'send');
|
|
}
|
|
|
|
function leaveRoom() {
|
|
if (!socket) return;
|
|
socket.emit('leave_room');
|
|
log(`发送: leave_room`, 'send');
|
|
}
|
|
|
|
function setReady() {
|
|
if (!socket) return;
|
|
socket.emit('set_ready', { ready: true });
|
|
log(`发送: set_ready`, 'send');
|
|
}
|
|
|
|
function startGame() {
|
|
if (!socket) return;
|
|
socket.emit('start_game');
|
|
log(`发送: start_game`, 'send');
|
|
}
|
|
|
|
function makeGuess() {
|
|
if (!socket) return;
|
|
const number = parseInt(document.getElementById('guessNumber').value);
|
|
if (isNaN(number) || number < 1 || number > 100) {
|
|
log('请输入 1-100 之间的数字', 'error');
|
|
return;
|
|
}
|
|
socket.emit('game_action', {
|
|
action: 'guess',
|
|
data: { number: number }
|
|
});
|
|
log(`发送: game_action (guess ${number})`, 'send');
|
|
}
|
|
|
|
function clearLog() {
|
|
document.getElementById('log').innerHTML = '';
|
|
}
|
|
|
|
// ⚡ 快速开始:一键创建房间并开始
|
|
function quickStart() {
|
|
if (!socket) {
|
|
log('请先连接服务器', 'error');
|
|
return;
|
|
}
|
|
if (currentRoom) {
|
|
log('您已在房间中,请先离开当前房间', 'error');
|
|
return;
|
|
}
|
|
|
|
const playerName = document.getElementById('quickPlayerName').value || '蔡叔叔';
|
|
const aiCount = parseInt(document.getElementById('quickAiCount').value);
|
|
const aiType1 = document.getElementById('quickAiType1').value;
|
|
const aiType2 = document.getElementById('quickAiType2').value;
|
|
|
|
// 构建AI配置
|
|
const ai_players = [];
|
|
if (aiCount >= 1) {
|
|
ai_players.push({ name: `AI-1(${aiType1})`, ai_type: aiType1 });
|
|
}
|
|
if (aiCount >= 2) {
|
|
ai_players.push({ name: `AI-2(${aiType2})`, ai_type: aiType2 });
|
|
}
|
|
|
|
quickStartRoom = true;
|
|
|
|
socket.emit('create_room', {
|
|
game_type: 'doudizhu',
|
|
player_name: playerName,
|
|
config: { max_players: 3 },
|
|
ai_players: ai_players
|
|
});
|
|
log(`🚀 快速开始: 创建斗地主房间 (${aiCount}个AI)`, 'send');
|
|
}
|
|
|
|
// 每30秒发送心跳
|
|
setInterval(() => {
|
|
if (socket && socket.connected) {
|
|
socket.emit('ping', { timestamp: Date.now() });
|
|
}
|
|
}, 30000);
|
|
|
|
// 页面加载时自动连接(如果配置了自动连接)
|
|
// connect();
|
|
</script>
|
|
</body>
|
|
</html>
|