Files
SimpleFarm/tools/ws_demo.py
T
snowy-yang a7725f8812 chore: 工程化改造前的基线快照
在引入 uv 依赖管理、ruff 静态检查与格式化、pyright 类型检查之前,
先把当前可正常运行的状态固定下来,作为后续大规模自动改动的回滚点。

此时状态:97 项测试全绿,HTTP + WebSocket 多牧场服务器可用。
2026-09-18 21:12:40 +08:00

100 lines
4.1 KiB
Python
Raw 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
"""WebSocket 通道演示脚本:证明"服务器推、客户端拉/发"这一对方向都通。
它同时是一个"外部客户端"的样板——完全不知道游戏内部结构,只知道协议:
连接 → 收 welcome(含状态)→ 发一个动作 → 收 result 回执 → 收 state 推送
用法:
python tools/ws_demo.py --url http://127.0.0.1:8765 --farm demo --seconds 8
"""
from __future__ import annotations
import argparse
import asyncio
import json
import os
import sys
import urllib.parse
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def ws_url(endpoint: str, farm: str, token: str | None) -> str:
parts = urllib.parse.urlsplit(endpoint if "://" in endpoint else "http://" + endpoint)
scheme = "wss" if parts.scheme == "https" else "ws"
query = {"farm": farm, "name": "ws-demo"}
if token:
query["token"] = token
return f"{scheme}://{parts.netloc}/ws?{urllib.parse.urlencode(query)}"
async def run(endpoint: str, farm: str, token: str | None, seconds: float) -> int:
import websockets
url = ws_url(endpoint, farm, token)
print(f"连接 {url}")
async with websockets.connect(url, open_timeout=8) as socket:
welcome = json.loads(await socket.recv())
state = welcome.get("state", {})
print(f"← welcome:牧场 {welcome['farm']} · 第 {state.get('day')} 天 · "
f"金币 {state.get('gold')} · 候选动作 {len(welcome.get('actions', []))} 个")
async def sender() -> None:
"""通过 WebSocket 发一个动作,等回执。"""
await asyncio.sleep(0.3)
request = {"type": "action", "id": 1, "action": "sleep", "params": {}}
print(f"→ action:{json.dumps(request, ensure_ascii=False)}")
await socket.send(json.dumps(request))
deadline = asyncio.get_event_loop().time() + seconds
send_task = asyncio.create_task(sender())
counts: dict[str, int] = {}
try:
while asyncio.get_event_loop().time() < deadline:
try:
raw = await asyncio.wait_for(socket.recv(), timeout=seconds)
except asyncio.TimeoutError:
break
message = json.loads(raw)
kind = message.get("type", "?")
counts[kind] = counts.get(kind, 0) + 1
if kind == "result":
print(f"← result(id={message.get('id')}):{message.get('message')} "
f"[通过 WebSocket 提交的动作,ok={message.get('ok')}]")
elif kind == "state":
snapshot = message.get("state", {})
print(f"← state(seq={message.get('seq')}):第 {snapshot.get('day')} 天 · "
f"金币 {snapshot.get('gold')} · 行动点 {snapshot.get('ap')} · "
f"在线 {message.get('online')}")
elif kind == "day":
print(f"← day:进入第 {message.get('day')} 天,事件 {message.get('events')}")
elif kind == "notice":
print(f"← notice:{message.get('text')}")
else:
print(f"← {kind}")
finally:
send_task.cancel()
print(f"\n消息统计:{counts}")
if counts.get("result", 0) < 1 or counts.get("state", 0) < 1:
print("✗ WebSocket 通道没有按预期工作(result / state 消息缺失)")
return 1
print("✓ WebSocket 双向通道正常:动作可上行,状态会主动下行")
return 0
def main() -> int:
parser = argparse.ArgumentParser(description="WebSocket 通道演示")
parser.add_argument("--url", default="http://127.0.0.1:8765")
parser.add_argument("--farm", default="ws-demo")
parser.add_argument("--token", default=None)
parser.add_argument("--seconds", type=float, default=8.0)
args = parser.parse_args()
return asyncio.run(run(args.url, args.farm, args.token, args.seconds))
if __name__ == "__main__":
sys.exit(main())