Files
nanocode/net-games/test_doudizhu_headless_v2.py
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

270 lines
11 KiB
Python
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.
#!/usr/bin/env python3
"""
斗地主 Headless 浏览器测试 V2
智能轮询版本 - 自动处理人类玩家回合
"""
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"
async def run_headless_test():
"""运行 Headless 浏览器测试"""
print("=" * 70)
print(" 🎮 斗地主 Headless 浏览器测试 V2")
print("=" * 70)
print(f"\n🌐 服务器: {SERVER_URL}")
print("\n👥 三方对战:")
print(" 1️⃣ Headless人类 (浏览器自动化)")
print(" 2️⃣ 规则AI (本地算法)")
print(" 3️⃣ DeepSeek AI (大模型API)")
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page(viewport={'width': 1280, 'height': 900})
try:
# 访问测试客户端
await page.goto(f"{SERVER_URL}/test_client.html")
await asyncio.sleep(1)
# 连接到服务器
print("\n📡 连接到游戏服务器...")
await page.fill('input[placeholder*="服务器地址"]', SERVER_URL)
await page.click('button:has-text("连接")')
await asyncio.sleep(1)
# 设置玩家名称
await page.fill('input[placeholder*="玩家名称"]', 'Headless玩家')
# 创建房间(带AI)
print("🏠 创建房间(含2个AI)...")
await page.evaluate("""
() => {
socket.emit('create_room', {
game_type: 'doudizhu',
player_name: 'Headless玩家',
ai_config: {ai_count: 2, ai_types: ['rule', 'deepseek']}
});
}
""")
await asyncio.sleep(1.5)
# 获取房间信息
room_info = await page.evaluate("""
() => {
const state = window.roomState || {};
return {
code: state.code,
players: state.players || []
};
}
""")
room_code = room_info.get('code', 'UNKNOWN')
players = room_info.get('players', [])
print(f"✅ 房间创建成功: {room_code}")
print(f"\n👥 房间玩家 ({len(players)}/3):")
for p in players:
ai_tag = "🤖" if p.get('is_ai') else "👤"
print(f" {ai_tag} {p.get('name', 'Unknown')} ({p.get('id', 'N/A')[:8]})")
# 准备
print("\n✅ 准备游戏...")
await page.click('button:has-text("准备")')
await asyncio.sleep(0.5)
# 开始游戏
print("🚀 开始游戏...")
await page.click('button:has-text("开始游戏")')
await asyncio.sleep(2)
print("\n🎲 游戏进行中...")
print("-" * 70)
# 游戏主循环
turn_count = 0
my_player_id = None
game_ended = False
for i in range(120): # 最多120秒
# 获取当前游戏状态
game_state = await page.evaluate("""
() => {
const state = window.gameState || {};
return {
phase: state.phase,
current_turn: state.current_turn,
doudizhu: state.doudizhu || {},
players: state.players || {}
};
}
""")
# 获取自己的ID
if not my_player_id:
my_info = await page.evaluate("""
() => window.playerInfo || {}
""")
my_player_id = my_info.get('id', '')
current_turn = game_state.get('current_turn', '')
phase = game_state.get('phase', '')
doudizhu = game_state.get('doudizhu', {})
doudizhu_phase = doudizhu.get('phase', '')
# 获取玩家名称映射
players_map = game_state.get('players', {})
# 检查是否是当前玩家的回合
if current_turn == my_player_id and not game_ended:
turn_count += 1
my_name = "Headless玩家"
if doudizhu_phase == 'calling':
# 叫地主阶段
print(f"{turn_count:2d}. [{my_name:10}] 轮到叫地主...")
await page.evaluate("""
() => {
socket.emit('game_action', {
action: 'call',
data: { want: true }
});
}
""")
print(f" -> 叫地主")
await asyncio.sleep(1)
elif doudizhu_phase == 'playing':
# 出牌阶段 - 简化策略:出最小单张或不出
player_cards = await page.evaluate("""
() => window.playerCards || []
""")
if player_cards:
# 出第一张牌(单张)
card_id = player_cards[0].get('id')
await page.evaluate(f"""
() => {{
socket.emit('game_action', {{
action: 'play',
data: {{ card_ids: ['{card_id}'] }}
}});
}}
""")
print(f"{turn_count:2d}. [{my_name:10}] 出牌: 单张")
else:
# 没牌了,不出
await page.evaluate("""
() => {
socket.emit('game_action', {
action: 'pass',
data: {}
});
}
""")
print(f"{turn_count:2d}. [{my_name:10}] 不出")
await asyncio.sleep(1)
# 监听其他玩家动作
events = await page.evaluate("""
() => {
const e = window.gameEvents || [];
window.gameEvents = [];
return e;
}
""")
for event in events:
if event.get('type') == 'update':
data = event.get('data', {})
action = data.get('action', 'unknown')
player_id = data.get('player_id', '')
result = data.get('result', {})
# 获取玩家名称
player_name = "Unknown"
player_info = players_map.get(player_id, {})
if player_info:
player_name = player_info.get('name', 'Unknown')[:10]
else:
# 从初始玩家列表查找
for p in players:
if p.get('id') == player_id:
player_name = p.get('name', 'Unknown')[:10]
break
if player_id != my_player_id and result.get('success'):
turn_count += 1
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'):
print(f"{turn_count:2d}. [{player_name:10}] 🎩 叫地主!")
else:
print(f"{turn_count:2d}. [{player_name:10}] 叫地主: {'是' if want else '否'}")
elif action == 'pass':
print(f"{turn_count:2d}. [{player_name:10}] 不出")
elif event.get('type') == 'ended':
print("\n" + "=" * 70)
winner = event.get('data', {}).get('winner', 'N/A')
print(f"🏆 游戏结束! 获胜者: {winner[:8] if winner else 'N/A'}")
game_ended = True
break
if game_ended:
break
# 检查游戏是否结束(通过检查winner)
winner_check = await page.evaluate("""
() => {
const state = window.gameState || {};
return state.winner;
}
""")
if winner_check:
print("\n" + "=" * 70)
print(f"🏆 游戏结束! 获胜者: {winner_check[:8]}")
game_ended = True
break
await asyncio.sleep(0.5)
if not game_ended:
print("\n" + "=" * 70)
print(f"⏱️ 测试超时 (120秒)")
print(f"📊 共进行了 {turn_count} 步")
# 截图
screenshot_path = '/tmp/doudizhu_test_result_v2.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())