chore: 工程化改造前的基线快照
在引入 uv 依赖管理、ruff 静态检查与格式化、pyright 类型检查之前, 先把当前可正常运行的状态固定下来,作为后续大规模自动改动的回滚点。 此时状态:97 项测试全绿,HTTP + WebSocket 多牧场服务器可用。
This commit is contained in:
commit
a7725f8812
35 files changed
+11871
No files matched your search
@@ -0,0 +1,424 @@
|
||||
# SimpleFarm · 小型农场游戏服务器
|
||||
|
||||
一个可以在终端里玩的农场经营游戏,同时也是一个**小型多人游戏服务器**:
|
||||
|
||||
* 游戏逻辑跑在**服务端进程**里,可以后台常驻;
|
||||
* 玩家在**终端界面(TUI)**里玩自己的牧场,也可以直接开浏览器玩;
|
||||
* 每个玩家一个独立牧场(存档隔离),互相能看到排行榜;
|
||||
* 同时开放 **HTTP 接口 + WebSocket 实时通道**,AI / 脚本 / 机器人可以和人玩同一局游戏——
|
||||
服务端**不内置任何 AI**,它只提供协议。
|
||||
|
||||
```
|
||||
┌──────────────┐ HTTP /api/* ┌────────────────────────┐
|
||||
│ AI / 脚本 │ ───────────────────────▶ │ │
|
||||
│ 机器人 │ ◀─────────────────────── │ FastAPI 服务器 │
|
||||
└──────────────┘ JSON 响应 │ (可后台常驻) │
|
||||
┌──────────────┐ WebSocket /ws │ │
|
||||
│ 终端界面 TUI │ ◀───────────────────────▶ │ FarmClock 统一时钟 │
|
||||
└──────────────┘ 状态推送 + 动作回执 │ SessionManager 多牧场 │
|
||||
┌──────────────┐ WebSocket /ws │ ConnectionHub 推送 │
|
||||
│ 浏览器客户端 │ ◀───────────────────────▶ │ 存档(每牧场一份) │
|
||||
└──────────────┘ └────────────────────────┘
|
||||
│
|
||||
farm.engine 纯游戏规则
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. 快速开始
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt # 只有服务端需要第三方依赖
|
||||
|
||||
python main.py serve --daemon # ① 后台启动服务器
|
||||
python main.py play --farm 我的农场 # ② 终端里玩(另开一个窗口)
|
||||
python main.py bot --farm ai-1 # ③ 让机器人玩家一起经营
|
||||
python main.py status # ④ 看服务器状态
|
||||
python main.py stop # ⑤ 优雅停机(会把所有牧场存档)
|
||||
```
|
||||
|
||||
浏览器打开 <http://127.0.0.1:8765/> 就是网页版客户端(可多开几个标签页,用不同牧场名,
|
||||
能实时看到对方的操作)。
|
||||
|
||||
不想装任何第三方库、只想单机玩玩:
|
||||
|
||||
```bash
|
||||
python main.py solo # 本地引擎 + 终端界面,不开服务器
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 玩法
|
||||
|
||||
| 系统 | 规则 |
|
||||
| --- | --- |
|
||||
| **时间** | 每个牧场每 15 秒自动推进一天(`--tick` 可调),作物生长、牲畜产出、市场波动都在此刻结算 |
|
||||
| **行动点** | 每天 10 点;播种 / 浇水 / 喂食各 1 点,收获和收取产出免费。用完只能等第二天,或主动 `sleep`(2 秒冷却) |
|
||||
| **水分** | 作物每天消耗 1 点水分(上限 3),水分为 0 停止生长;播种自带 2 点 |
|
||||
| **作物** | 小麦 → 胡萝卜 → 番茄 → 玉米 → 南瓜 → 草莓,按等级解锁,售价越高成熟越慢 |
|
||||
| **牲畜** | 鸡 / 羊 / 牛,每天喂食才会累计产出周期;连续 3 天不喂会**永久离开** |
|
||||
| **市场** | 作物与畜产品价格每天在基础价 ±18% 内波动,高位卖出更赚 |
|
||||
| **土地** | 初始 6 块,每升 1 级 +2 块,上限 16 块,买地价格指数递增 |
|
||||
| **目标** | 净资产(金币+土地+牲畜+库存+地里作物)做到 10000 金币,共 5 个里程碑 |
|
||||
| **丰收** | 收获有 12% 概率触发双倍产出 |
|
||||
|
||||
净资产在 TUI 顶部实时显示,`goal_index` 表示已解锁的里程碑数。
|
||||
|
||||
---
|
||||
|
||||
## 3. 终端界面
|
||||
|
||||
`python main.py play` 打开。核心交互:
|
||||
|
||||
```
|
||||
[↑↓←→]选地 [P]播种 [W]浇水 [H]收获 [E]全部收获
|
||||
[F]喂食 [C]收取 [Tab]换牲畜 [B]买动物 [L]买地 [K]买饲料
|
||||
[N]休息 [空格]暂停 [S]存档 [R]重置 [Q]退出
|
||||
```
|
||||
|
||||
界面用双宽字符感知的对齐算法(`display_width`),在 Windows Terminal / iTerm / VSCode 里边框都不会歪。
|
||||
|
||||
**TUI 是纯客户端**:它通过 WebSocket 订阅状态、通过 WebSocket/HTTP 提交动作,
|
||||
自己不持有任何游戏状态。所以:
|
||||
|
||||
* 服务端可以在另一台机器上,`python main.py play --url http://10.0.0.5:8765 --farm alice`;
|
||||
* 关掉 TUI 不会影响服务器,你的牧场继续按时间生长;
|
||||
* 你在 TUI 里操作的同时,AI 可以对同一个牧场操作,双方立刻能看到对方的结果。
|
||||
|
||||
---
|
||||
|
||||
## 4. 命令行
|
||||
|
||||
| 命令 | 作用 |
|
||||
| --- | --- |
|
||||
| `serve [--daemon]` | 启动服务器(前台 / 后台) |
|
||||
| `stop [--force]` | 优雅停机:先让服务器落盘,再退出 |
|
||||
| `status [--logs] [--json]` | 服务器状态 + 排行榜 + 日志尾部 |
|
||||
| `logs -n 40` | 查看后台日志 |
|
||||
| `play --farm NAME` | 终端界面(TUI) |
|
||||
| `solo` | 离线单机(本地引擎,不开服务器) |
|
||||
| `actions` | **列出当前所有合法动作**(AI 推荐入口) |
|
||||
| `do <动作> '<JSON参数>'` | 执行一个动作 |
|
||||
| `batch '<JSON数组>'` | 一个请求执行多个动作 |
|
||||
| `state [--json]` | 查看状态(`--json` 为机器可读) |
|
||||
| `log -n 20` | 最近事件 |
|
||||
| `watch` | 实时滚动状态与日志(适合盯着 AI 玩) |
|
||||
| `bot --farm ai-1` | 规则机器人替你经营一个牧场 |
|
||||
| `farms` | 牧场排行榜 |
|
||||
| `help` | 玩法与接口说明 |
|
||||
|
||||
实例:
|
||||
|
||||
```bash
|
||||
$ python main.py actions
|
||||
牧场 default · 第 3 天 · 金币 96 · 行动点 7/10
|
||||
共 9 个候选动作:
|
||||
|
||||
[ 1] harvest {"plot": 2} (免费)
|
||||
2 号地的胡萝卜已成熟
|
||||
[ 2] plant {"plot": 4, "crop": "wheat"} (gold:5 ap:1)
|
||||
4 号地空闲,可种小麦
|
||||
...
|
||||
|
||||
$ python main.py do harvest '{"plot":2}'
|
||||
✓ 收获 2 份胡萝卜(丰收!),获得 104 金币 (0.01s)
|
||||
第 3 天 · 金币 200 · 行动点 7/10 · 净资产 512.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 给 AI 的 HTTP 接口
|
||||
|
||||
所有接口都在 `/api/*`,响应统一是 `{"ok": bool, "message": str, ...}`;
|
||||
失败时带机器可读的 `error`(如 `not_enough_gold` / `no_ap` / `not_ripe`)与 HTTP 状态码。
|
||||
|
||||
**多牧场**:用 `?farm=名字` 或请求头 `X-Farm-Id` 指定;不指定就是 `default`。
|
||||
牧场不存在时会**自动创建**,所以每个客户端只需挑一个名字。
|
||||
|
||||
### 5.1 最短决策闭环
|
||||
|
||||
```
|
||||
GET /api/actions?farm=me → 当前所有**合法**动作(含开销与 why)
|
||||
POST /api/actions → {"action":"<上一步的 action>","params":{...}}
|
||||
← 回执里直接带执行后的状态,无需再查一次
|
||||
```
|
||||
|
||||
### 5.2 接口一览
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/health` | 健康检查(唯一不需要令牌的接口) |
|
||||
| GET | `/api/help` | 规则 + 动作清单 + 参数类型 |
|
||||
| GET | `/api/world` | 世界概览:排行榜、在线连接、时钟 |
|
||||
| GET | `/api/farms` | 牧场列表(按净资产排名) |
|
||||
| POST | `/api/farms` | `{"farm":"名字","owner":"昵称","tick":10}` 创建/进入 |
|
||||
| GET | `/api/state` | 完整状态快照 |
|
||||
| GET | `/api/catalog` | 作物 / 牲畜 / 实时价格 / 每单位收益 |
|
||||
| GET | `/api/actions` | 当前合法动作(AI 推荐入口) |
|
||||
| GET | `/api/log?limit=20&since=0` | 事件流(`since` 做增量拉取) |
|
||||
| POST | `/api/actions` | **通用动作入口** |
|
||||
| POST | `/api/batch` | 批量动作(一次最多 50 个) |
|
||||
| POST | `/api/{动作名}` | 便捷入口:`/api/plant` `/api/harvest` … 等 13 个 |
|
||||
| POST | `/api/admin/save` | 全部存档 |
|
||||
| POST | `/api/admin/shutdown` | 优雅停机 |
|
||||
| GET | `/docs` | 自动生成的 Swagger 文档 |
|
||||
|
||||
动作清单(`GET /api/help` 里有完整参数表):
|
||||
|
||||
```
|
||||
plant(plot,crop) water(plot) harvest(plot) harvest_all()
|
||||
buy_animal(kind) feed(animal_id,quantity?) collect(animal_id)
|
||||
buy_feed(quantity?) buy_plot() sleep() pause(paused?) save() reset()
|
||||
```
|
||||
|
||||
### 5.3 请求示例
|
||||
|
||||
```bash
|
||||
# 看现在能做什么
|
||||
curl 'http://127.0.0.1:8765/api/actions?farm=alice'
|
||||
|
||||
# 播种
|
||||
curl -X POST 'http://127.0.0.1:8765/api/actions?farm=alice' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"action":"plant","params":{"plot":1,"crop":"wheat"}}'
|
||||
|
||||
# 一次干三件事(AI 省流)
|
||||
curl -X POST 'http://127.0.0.1:8765/api/batch?farm=alice' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"actions":[{"action":"harvest_all"},
|
||||
{"action":"buy_feed","params":{"quantity":10}},
|
||||
{"action":"plant","params":{"plot":1,"crop":"carrot"}}]}'
|
||||
|
||||
# 要完整快照(默认只回精简状态)
|
||||
curl -X POST 'http://127.0.0.1:8765/api/actions?farm=alice&full=1' ...
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"action": "plant",
|
||||
"message": "1 号地已种下小麦,2 天后成熟",
|
||||
"farm": "alice",
|
||||
"seq": 42,
|
||||
"state": {
|
||||
"day": 3, "gold": 91, "level": 1, "ap": 6, "feed": 5,
|
||||
"ripe_plots": [], "hungry_animals": [], "collectable_animals": [],
|
||||
"net_worth": 401.0, "seconds_to_tick": 11.4, "paused": false, "online": 2
|
||||
},
|
||||
"log": [{"seq": 41, "day": 3, "kind": "action", "text": "在 1 号地播下小麦种子"}]
|
||||
}
|
||||
```
|
||||
|
||||
> `state.ripe_plots` / `hungry_animals` / `collectable_animals` 是特意为 AI 准备的
|
||||
> "待办清单"字段:不用遍历整个快照就知道该干什么。
|
||||
|
||||
**鉴权**:服务器用 `--token` 启动后,所有接口都要带 `X-API-Token: <token>` 头或 `?token=<token>`。
|
||||
没带令牌返回 `401 {"ok":false,"error":"unauthorized"}`。
|
||||
|
||||
---
|
||||
|
||||
## 6. WebSocket 实时通道
|
||||
|
||||
> 为什么是 WebSocket 而不是 SSE?SSE 只能服务端 → 客户端单向推流,
|
||||
> 而这个游戏的上行(播种、喂食)和下行(状态、事件)是同一件业务的两半:
|
||||
> 用 SSE 就得再开一条 HTTP 通道,等于两套鉴权、两套重连、两套错误处理。
|
||||
> WebSocket 一次握手双向跑,还能把动作回执直接顺着同一条连接回给调用方。
|
||||
|
||||
连接:
|
||||
|
||||
```
|
||||
ws://127.0.0.1:8765/ws?farm=alice&token=<可选>&name=<昵称>
|
||||
```
|
||||
|
||||
### 客户端 → 服务器
|
||||
|
||||
| 消息 | 说明 |
|
||||
| --- | --- |
|
||||
| `{"type":"action","id":1,"action":"plant","params":{"plot":1,"crop":"wheat"}}` | 执行动作,回执带同一个 `id` |
|
||||
| `{"type":"sync","id":2}` | 主动拉一次完整状态 + 合法动作 |
|
||||
| `{"type":"help","id":3}` | 拉规则与动作说明 |
|
||||
| `{"type":"ping"}` | 心跳,回 `pong` |
|
||||
|
||||
### 服务器 → 客户端
|
||||
|
||||
| 消息 | 触发时机 |
|
||||
| --- | --- |
|
||||
| `{"type":"welcome", "state":…, "catalog":…, "actions":…, "log":…, "online":1}` | 连接建立 |
|
||||
| `{"type":"state", "seq":…, "state":…, "log":[…]}` | 状态变化时推送(**节流合并到 4 次/秒**) |
|
||||
| `{"type":"day", "day":5, "events":[…]}` | 进入新的一天 |
|
||||
| `{"type":"result", "id":1, "ok":true, "message":…, "snapshot":…}` | 动作回执 |
|
||||
| `{"type":"notice", "text":"web 来到了牧场(在线 2)"}` | 有人加入 / 离开 |
|
||||
| `{"type":"error", "error":"bad_json", "message":…}` | 协议错误 |
|
||||
|
||||
用 Python 直接玩(不依赖本项目代码):
|
||||
|
||||
```python
|
||||
import asyncio, json, websockets
|
||||
|
||||
async def main():
|
||||
async with websockets.connect("ws://127.0.0.1:8765/ws?farm=demo") as ws:
|
||||
welcome = json.loads(await ws.recv())
|
||||
print("第", welcome["state"]["day"], "天")
|
||||
await ws.send(json.dumps({"type": "action", "id": 1, "action": "harvest_all"}))
|
||||
async for raw in ws:
|
||||
msg = json.loads(raw)
|
||||
if msg["type"] == "result":
|
||||
print(msg["message"])
|
||||
elif msg["type"] == "state":
|
||||
print("金币", msg["state"]["gold"])
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 多人模型
|
||||
|
||||
* **一个牧场 = 一个玩家**:独立引擎、独立时钟、独立存档(`saves/<牧场名>.json`)。
|
||||
* 牧场之间完全隔离(金币、土地、市场各自独立),但共享排行榜(`GET /api/farms`)。
|
||||
* 同牧场可以有多人/多程序同时操作,引擎内的 `RLock` 保证串行,不会出现"两个人同时种同一块地"这类竞态。
|
||||
* 所有牧场的时钟由**一个** `FarmClock` 线程统一驱动(而不是每个牧场一条线程)。
|
||||
* 无人在线的牧场在 `--idle-timeout`(默认 30 分钟)后被移出内存,**先落盘再释放**;
|
||||
玩家回来时自动从存档恢复。
|
||||
* 上限 `--max-sessions`(默认 64),超出返回 `503 too_many_farms`。
|
||||
|
||||
局域网联机:
|
||||
|
||||
```bash
|
||||
python main.py serve --daemon --host 0.0.0.0 --port 8765 --token 找个密码
|
||||
python main.py play --url http://192.168.1.20:8765 --farm alice --token 找个密码
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 守护进程
|
||||
|
||||
`serve --daemon` 之后:
|
||||
|
||||
| 项目 | 位置 / 命令 |
|
||||
| --- | --- |
|
||||
| 进程号 | `run/server.pid`(**由服务进程自己写**,见下方说明) |
|
||||
| 日志 | `run/server.log`(`python main.py logs -n 50`) |
|
||||
| 停止 | `python main.py stop`(先走 HTTP 优雅停机,失败再发信号) |
|
||||
| 状态 | `python main.py status` |
|
||||
| 存档 | `saves/<牧场名>.json`(纯 JSON,可直接阅读分析) |
|
||||
| 接口文档 | <http://127.0.0.1:8765/docs> |
|
||||
|
||||
实现上不依赖 systemd / nohup / 任何第三方库:POSIX 用新会话、Windows 用 `DETACHED_PROCESS`,
|
||||
父进程退出后服务继续活着。自动存档每 60 秒一次,停机时再存一次。
|
||||
|
||||
> **为什么 pid 由子进程自己写?** 一开始是父进程把 `Popen().pid` 记进 pid 文件,
|
||||
> 结果在 Windows 上踩了坑:venv 里的 `python.exe` 可能是个**转发器**,
|
||||
> 父进程拿到的 pid 和真正在监听端口的进程不是同一个,`stop` 于是杀错了对象。
|
||||
> 现在改成:守护进程把 `--pid-file` 传给子进程,子进程在端口绑定成功(lifespan 启动)后
|
||||
> 才写入自己的 `os.getpid()`,退出时再删掉。`stop` 则优先走 HTTP 优雅停机,
|
||||
> 拿不到 pid 也能停下来。
|
||||
|
||||
---
|
||||
|
||||
## 9. 代码结构
|
||||
|
||||
```
|
||||
main.py 统一入口:serve / play / do / bot / status ...
|
||||
ai_player.py 外挂式机器人玩家(只用接口,不 import 任何游戏规则)
|
||||
farm/
|
||||
config.py 数值与静态表(作物、牲畜、经济、时间)
|
||||
models.py Plot / Animal / GameState —— 只持有数据
|
||||
engine.py FarmEngine / Scheduler —— 纯游戏规则(不依赖网络与终端)
|
||||
storage.py 存档读写(临时文件 + 原子替换)
|
||||
keys.py 跨平台按键读取
|
||||
net.py 回环请求绕过系统代理的小工具
|
||||
tui.py 终端界面(自适应宽度、双宽字符对齐,与引擎解耦)
|
||||
client.py RemoteFarm —— 把远程服务器伪装成本地引擎(HTTP + WS)
|
||||
bot.py 规则机器人:给候选动作打分(可整体替换为 LLM 策略)
|
||||
server/
|
||||
app.py FastAPI 应用:REST + WebSocket + 多牧场调度
|
||||
actions.py 动作表:所有入口共用的唯一执行通道 + 参数校验
|
||||
session.py 牧场会话与统一时钟
|
||||
hub.py WebSocket 连接池与状态推送(线程 → 事件循环的桥)
|
||||
daemon.py 守护进程:start / stop / status / 日志 / pid
|
||||
webui.py 内嵌网页客户端(单页,原生 JS + WebSocket)
|
||||
__main__.py python -m farm.server(前台运行)
|
||||
tools/
|
||||
preview_tui.py 把 TUI 渲染成文本(--url 可连真实服务器)
|
||||
ws_demo.py 最小 WebSocket 客户端,验证协议
|
||||
tests/
|
||||
test_engine.py 引擎规则与存档(单机层)
|
||||
test_server.py 动作表 / REST / 鉴权 / 多牧场隔离 / WebSocket 端到端
|
||||
```
|
||||
|
||||
分层原则:**规则不认识网络,网络不认识终端。**
|
||||
`farm.engine` 不知道有 HTTP,`tui.py` 不知道有 socket,`server/*` 不知道有终端。
|
||||
所以三端共享完全一致的裁判逻辑,也能各自单独测试。
|
||||
|
||||
---
|
||||
|
||||
## 10. 测试
|
||||
|
||||
```bash
|
||||
python -m unittest discover -s tests -v # 全部(97 项)
|
||||
python -m unittest tests.test_engine # 只测游戏规则,不需要 FastAPI
|
||||
python -m unittest tests.test_server.TestLiveServer # 真起服务器 + 真 WebSocket
|
||||
```
|
||||
|
||||
覆盖范围:
|
||||
|
||||
* 引擎规则、边界与存档兼容(45 项)
|
||||
* 动作表参数校验:缺参 / 多参 / 类型错 / 未知动作(10 项)
|
||||
* 牧场 ID 校验:路径穿越、超长、非法字符(3 项)
|
||||
* 多牧场隔离、排行榜、空闲回收、暂停保护、容量上限(6 项)
|
||||
* REST 语义:状态、动作、批量、日志增量、鉴权 401、坏 JSON、`X-Farm-Id` 头(20 项)
|
||||
* **真网络端到端**:真 uvicorn + 真客户端,验证 HTTP 读写、两个客户端共享一个牧场、
|
||||
动作确实走了 WebSocket、WS 断开后自动退回 HTTP、机器人只靠接口也能经营(7 项)
|
||||
|
||||
另外做过一轮真实环境实测(`--daemon` 起服务,再用命令行/脚本连上去):
|
||||
|
||||
```
|
||||
serve --daemon → status 显示正确 pid → actions 列出 13 个候选动作
|
||||
→ do plant / buy_animal / feed → batch 部分失败不影响其它动作
|
||||
→ 排行榜出现两个互相隔离的牧场(alice 25 金币 / bob 120 金币)
|
||||
→ WebSocket 客户端依次收到 welcome / state / result / day 四类消息
|
||||
→ 机器人从 120 金币经营到 197 金币、净资产 276(0 次失败)
|
||||
→ stop 优雅停机,所有牧场写入 saves/*.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 边界处理与设计取舍
|
||||
|
||||
**输入校验**(唯一入口 `server/actions.py`):
|
||||
|
||||
* 未知动作 → `400 unknown_action`,并列出可用动作;
|
||||
* 缺必需参数 → `missing_param`;多传参数 → `bad_param`(明确拒绝而不是静默忽略);
|
||||
* 类型错误 → `bad_param`,但宽容地把 `"3"` 这类数字字符串转成整数(命令行/表单常见);
|
||||
* 越界地块 → `no_such_plot`;未解锁作物 → `locked`;金币/行动点不足 → `not_enough_gold` / `no_ap`;
|
||||
* 牧场名会被用作文件名 → 白名单校验,挡掉 `../`、`a/b` 之类的路径穿越;
|
||||
* 请求体上限 64KB、批量动作上限 50、`limit` 参数自动夹取到合法区间。
|
||||
|
||||
**并发与容错**:
|
||||
|
||||
* 引擎内 `RLock` 串行化同一牧场,多牧场并行;
|
||||
* WebSocket 每条连接一个出口队列(满 32 条丢最旧的),慢客户端不会拖垮服务端;
|
||||
* 推送节流 250ms,高频操作自动合并成一帧;每条推送带自增 `seq`,客户端据此丢弃乱序帧;
|
||||
* 引擎调用统一丢进线程池,不阻塞事件循环;
|
||||
* 单条连接/单个牧场出错不影响其它牧场;
|
||||
* 存档用"临时文件 + `os.replace`"原子替换,崩溃不会写坏存档;存档读取失败会降级为开新档而不是崩溃。
|
||||
|
||||
**本地请求不走系统代理**(`farm/net.py`):
|
||||
|
||||
机器上设了 `HTTP_PROXY` 却没有 `no_proxy` 时(公司网络、容器、CI 沙箱很常见),
|
||||
`urllib` 会把发给 `127.0.0.1` 的请求也塞给代理,本机游戏服务器会莫名其妙"连不上",
|
||||
报的还是 `502 upstream connect failed` 这种看不出根因的错误。
|
||||
所以客户端与守护进程探测统一走 `farm.net.open_url()`:目标是回环地址就直连,
|
||||
目标是别的机器才尊重环境里的代理设置。(WebSocket 不受影响,`websockets` 默认不读代理变量。)
|
||||
|
||||
**测试产物**:`tools/preview_tui.py` 可以把任意时刻的界面渲染成文本(支持 `--url` 连真实服务器),
|
||||
`tools/ws_demo.py` 是一个最小 WebSocket 客户端,用来确认"动作上行、状态下行"两条路都通。
|
||||
|
||||
**为什么放弃 gRPC**:这套接口是给 LLM/脚本用的,它们几乎都只会捏 HTTP 和 JSON;
|
||||
gRPC 需要 `.proto` 编译产物、需要客户端装 `grpcio`,接入成本换来的性能在这个场景里毫无意义。
|
||||
HTTP + WebSocket 用 `curl` 就能上手,`/docs` 还能自动生成交互式文档。
|
||||
|
||||
*(曾经实现的 gRPC 版本已经移除:`farm/grpc_server.py`、`proto/farm.proto`、`tools/gen_proto.py`。)*
|
||||
Reference in new issue
Block a user