239 lines
9.6 KiB
Python
239 lines
9.6 KiB
Python
"""
|
||
斗地主 Headless 浏览器测试
|
||
三方玩家:headless人类 + 规则AI + DeepSeek AI
|
||
"""
|
||
import asyncio
|
||
import sys
|
||
import os
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
|
||
from playwright.async_api import async_playwright
|
||
|
||
SERVER_URL = "http://129.211.173.87:8900"
|
||
|
||
print("=" * 70)
|
||
print(" 🎮 斗地主 Headless 浏览器测试")
|
||
print("=" * 70)
|
||
print(f"\n🌐 服务器: {SERVER_URL}")
|
||
print("\n👥 三方对战:")
|
||
print(" 1️⃣ Headless人类 (浏览器自动化)")
|
||
print(" 2️⃣ 规则AI (本地Minimax算法)")
|
||
print(" 3️⃣ DeepSeek AI (大模型API)")
|
||
|
||
async def run_headless_test():
|
||
async with async_playwright() as p:
|
||
browser = await p.chromium.launch(headless=True)
|
||
context = await browser.new_context(viewport={'width': 1280, 'height': 800})
|
||
|
||
# 创建页面
|
||
page = await context.new_page()
|
||
|
||
try:
|
||
print("\n📡 连接到游戏服务器...")
|
||
await page.goto(f"http://129.211.173.87:8900/test_client.html", timeout=30000)
|
||
|
||
# 等待页面加载
|
||
await page.wait_for_selector('#serverUrl', timeout=10000)
|
||
|
||
# 设置服务器地址
|
||
await page.fill('#serverUrl', SERVER_URL)
|
||
await page.click('button:has-text("连接")')
|
||
|
||
# 等待连接成功
|
||
await page.wait_for_selector('#connectionStatus:has-text("已连接")', timeout=10000)
|
||
print("✅ 已连接到服务器")
|
||
|
||
# 设置玩家名称
|
||
await page.fill('#playerName', 'Headless玩家')
|
||
|
||
# 创建房间(带AI配置)
|
||
print("\n🏠 创建房间(含2个AI)...")
|
||
await page.evaluate("""
|
||
() => {
|
||
socket.emit('create_room', {
|
||
game_type: 'doudizhu',
|
||
player_name: 'Headless玩家',
|
||
config: { max_players: 3 },
|
||
ai_config: {
|
||
ai_count: 2,
|
||
ai_types: ['rule', 'deepseek']
|
||
}
|
||
});
|
||
}
|
||
""")
|
||
|
||
# 等待房间创建
|
||
await asyncio.sleep(2)
|
||
|
||
# 获取房间信息
|
||
room_info = await page.evaluate("""
|
||
() => {
|
||
return {
|
||
code: document.getElementById('roomCode')?.value || 'N/A',
|
||
players: currentRoom?.players?.map(p => ({
|
||
name: p.name,
|
||
is_ai: p.is_ai,
|
||
id: p.id?.substring(0, 8)
|
||
})) || []
|
||
};
|
||
}
|
||
""")
|
||
|
||
room_code = room_info.get('code', 'N/A')
|
||
players = room_info.get('players', [])
|
||
|
||
print(f"✅ 房间创建成功: {room_code}")
|
||
print(f"\n👥 房间玩家 ({len(players)}/3):")
|
||
for p in players:
|
||
ai_tag = "🤖" if p['is_ai'] else "👤"
|
||
print(f" {ai_tag} {p['name']} ({p['id']})")
|
||
|
||
if len(players) != 3:
|
||
print("❌ 玩家数量不正确,测试中止")
|
||
await browser.close()
|
||
return
|
||
|
||
# 准备
|
||
print("\n✅ 准备游戏...")
|
||
await page.click('button:has-text("准备")')
|
||
await asyncio.sleep(0.5)
|
||
|
||
# 开始游戏
|
||
print("🚀 开始游戏...")
|
||
await page.click('button:has-text("开始游戏")')
|
||
|
||
# 监听游戏事件
|
||
print("\n🎲 游戏进行中...")
|
||
print("-" * 70)
|
||
|
||
turn_count = 0
|
||
game_ended = False
|
||
|
||
# 设置事件监听
|
||
await page.evaluate("""
|
||
() => {
|
||
window.gameEvents = [];
|
||
|
||
socket.on('game_started', (data) => {
|
||
window.gameEvents.push({type: 'started', data});
|
||
});
|
||
|
||
socket.on('state_update', (data) => {
|
||
window.gameEvents.push({type: 'update', data, time: Date.now()});
|
||
});
|
||
|
||
socket.on('game_ended', (data) => {
|
||
window.gameEvents.push({type: 'ended', data});
|
||
});
|
||
}
|
||
""")
|
||
|
||
# 等待游戏开始
|
||
await asyncio.sleep(2)
|
||
|
||
# 游戏主循环(观察模式,让人类玩家偶尔操作)
|
||
for i in range(60): # 最多60秒
|
||
# 获取事件
|
||
events = await page.evaluate("() => { const e = window.gameEvents; window.gameEvents = []; return e; }")
|
||
|
||
for event in events:
|
||
if event['type'] == 'started':
|
||
print("🎮 游戏开始!")
|
||
|
||
elif event['type'] == 'update':
|
||
data = event['data']
|
||
turn_count += 1
|
||
action = data.get('action', 'unknown')
|
||
player_id = data.get('player_id', '')[:8]
|
||
result = data.get('result', {})
|
||
game_state = data.get('game_state', {})
|
||
doudizhu = game_state.get('doudizhu', {})
|
||
|
||
# 获取玩家名称
|
||
player_name = "Unknown"
|
||
for p in players:
|
||
if p['id'] == player_id:
|
||
player_name = p['name'][:10]
|
||
break
|
||
|
||
if result.get('success'):
|
||
if action == 'play':
|
||
card_type = result.get('card_type', {})
|
||
remaining = result.get('remaining_cards', 0)
|
||
print(f"{turn_count:2d}. [{player_name:10}] 出牌: {card_type.get('name', '?'):12} 剩余: {remaining:2d}张")
|
||
|
||
elif action == 'call':
|
||
want = result.get('data', {}).get('want', False)
|
||
if result.get('lord_id'):
|
||
lord_name = "?"
|
||
for p in players:
|
||
if p['id'][:8] in str(result.get('lord_id')):
|
||
lord_name = p['name']
|
||
print(f"{turn_count:2d}. [{player_name:10}] 🎩 叫地主! 地主: {lord_name}")
|
||
else:
|
||
print(f"{turn_count:2d}. [{player_name:10}] 叫地主: {'是' if want else '否'}")
|
||
|
||
elif action == 'pass':
|
||
print(f"{turn_count:2d}. [{player_name:10}] 不出")
|
||
|
||
# 检查是否是当前玩家回合
|
||
current_turn = doudizhu.get('current_turn', '')
|
||
my_id = players[0]['id'] if players else ''
|
||
|
||
if current_turn[:8] == my_id[:8]:
|
||
# 是人类玩家的回合,执行简单操作
|
||
phase = doudizhu.get('phase', '')
|
||
|
||
if 'calling' in phase:
|
||
# 叫地主
|
||
await asyncio.sleep(0.5)
|
||
await page.evaluate("""
|
||
() => {
|
||
socket.emit('game_action', {
|
||
action: 'call',
|
||
data: { want: true }
|
||
});
|
||
}
|
||
""")
|
||
print(f" -> Headless玩家叫地主")
|
||
|
||
elif event['type'] == 'ended':
|
||
print("\n" + "=" * 70)
|
||
winner = event['data'].get('winner', 'N/A')
|
||
print(f"🏆 游戏结束! 获胜者: {winner[:8]}")
|
||
game_ended = True
|
||
break
|
||
|
||
if game_ended:
|
||
break
|
||
|
||
await asyncio.sleep(1)
|
||
|
||
if not game_ended:
|
||
print("\n" + "=" * 70)
|
||
print(f"⏱️ 测试超时 (60秒)")
|
||
print(f"📊 共进行了 {turn_count} 步")
|
||
|
||
# 截图
|
||
screenshot_path = '/tmp/doudizhu_test_result.png'
|
||
await page.screenshot(path=screenshot_path, full_page=True)
|
||
print(f"\n📸 截图已保存: {screenshot_path}")
|
||
|
||
await browser.close()
|
||
|
||
print("\n" + "=" * 70)
|
||
print("✅ Headless测试完成!")
|
||
print("=" * 70)
|
||
|
||
except Exception as e:
|
||
print(f"\n❌ 测试出错: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
await browser.close()
|
||
raise
|
||
|
||
# 运行测试
|
||
if __name__ == '__main__':
|
||
asyncio.run(run_headless_test())
|