diff --git a/debug_client.py b/debug_client.py new file mode 100644 index 0000000..46e2e76 --- /dev/null +++ b/debug_client.py @@ -0,0 +1,13 @@ +import urllib.request +import json + +try: + with urllib.request.urlopen("http://localhost:8000/api/state") as response: + print(f"Status: {response.status}") + print(response.read().decode('utf-8')) +except urllib.error.HTTPError as e: + print(f"HTTP Error: {e.code}") + print(e.read().decode('utf-8')) +except Exception as e: + print(f"Error: {e}") + diff --git a/requirements.txt b/requirements.txt index d6facf8..45190e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,7 @@ PyYAML>=6.0 litellm>=1.0.0 omegaconf>=2.3.0 json5>=0.9.0 -pandas>=2.0.0 \ No newline at end of file +pandas>=2.0.0 +fastapi>=0.100.0 +uvicorn>=0.20.0 +websockets>=11.0 diff --git a/src/server/__init__.py b/src/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/server/main.py b/src/server/main.py new file mode 100644 index 0000000..f72e77a --- /dev/null +++ b/src/server/main.py @@ -0,0 +1,161 @@ +import sys +import os +import asyncio +from contextlib import asynccontextmanager +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +import uvicorn + +# 确保可以导入 src 模块 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + +from src.sim.simulator import Simulator +from src.classes.world import World +from src.classes.calendar import Month, Year, create_month_stamp +from src.run.create_map import create_cultivation_world_map, add_sect_headquarters +from src.sim.new_avatar import make_avatars as _new_make +from src.utils.config import CONFIG +from src.classes.sect import sects_by_id +import random + +# 全局游戏实例 +game_instance = { + "world": None, + "sim": None +} + +def init_game(): + """初始化游戏世界,逻辑复用自 src/run/run.py""" + print("正在初始化游戏世界...") + game_map = create_cultivation_world_map() + world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY)) + sim = Simulator(world) + + # 宗门初始化逻辑 + all_sects = list(sects_by_id.values()) + needed_sects = int(getattr(CONFIG.game, "sect_num", 0) or 0) + + # 简单的宗门抽样逻辑 (简化版) + existed_sects = [] + if needed_sects > 0 and all_sects: + pool = list(all_sects) + random.shuffle(pool) + existed_sects = pool[:needed_sects] + + if existed_sects: + add_sect_headquarters(world.map, existed_sects) + + # 创建角色 + # 注意:这里直接调用 new_avatar 的 make_avatars,避免循环导入 + all_avatars = _new_make(world, count=CONFIG.game.init_npc_num, current_month_stamp=world.month_stamp, existed_sects=existed_sects) + world.avatar_manager.avatars.update(all_avatars) + + game_instance["world"] = world + game_instance["sim"] = sim + print("游戏世界初始化完成!") + +@asynccontextmanager +async def lifespan(app: FastAPI): + # 启动时初始化 + init_game() + yield + # 关闭时清理(如果需要) + +app = FastAPI(lifespan=lifespan) + +# 允许跨域,方便前端开发 +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +@app.get("/") +def read_root(): + return {"status": "online", "app": "Cultivation World Simulator Backend"} + +@app.get("/api/state") +def get_state(): + """获取当前世界的一个快照(调试模式)""" + try: + # 1. 基础检查 + world = game_instance.get("world") + if world is None: + return {"step": 1, "error": "No world"} + + # 2. 时间检查 + y = 0 + m = 0 + try: + y = int(world.month_stamp.get_year()) + m = int(world.month_stamp.get_month().value) + except Exception as e: + return {"step": 2, "error": str(e)} + + # 3. 角色列表检查 + av_list = [] + try: + raw_avatars = list(world.avatar_manager.avatars.values())[:50] # 缩小范围 + for a in raw_avatars: + # 极其保守的取值 + aid = str(getattr(a, "id", "no_id")) + aname = str(getattr(a, "name", "no_name")) + # 修正:使用 pos_x/pos_y + ax = int(getattr(a, "pos_x", 0)) + ay = int(getattr(a, "pos_y", 0)) + aaction = "unknown" + + # 动作检查 + curr = getattr(a, "current_action", None) + if curr: + act = getattr(curr, "action", None) + if act: + aaction = getattr(act, "name", "unnamed_action") + else: + aaction = str(curr) + + av_list.append({ + "id": aid, + "name": aname, + "x": ax, + "y": ay, + "action": str(aaction) + }) + except Exception as e: + return {"step": 3, "error": str(e)} + + return { + "status": "ok", + "year": y, + "month": m, + "avatar_count": len(world.avatar_manager.avatars), + "avatars": av_list + } + + except Exception as e: + return {"step": 0, "error": "Fatal: " + str(e)} + +@app.post("/api/step") +async def step_world(): + """手动触发一帧(一个月)""" + sim = game_instance["sim"] + if not sim: + return {"error": "Sim not initialized"} + + events = await sim.step() + + return { + "message": "Step executed", + "event_count": len(events), + "events_sample": [str(e) for e in events[:5]] + } + +def start(): + """启动服务的入口函数""" + # 改为 8002 端口 + uvicorn.run("src.server.main:app", host="0.0.0.0", port=8002, reload=False) + +if __name__ == "__main__": + start() diff --git a/src/server/main_test.py b/src/server/main_test.py new file mode 100644 index 0000000..ad3d39d --- /dev/null +++ b/src/server/main_test.py @@ -0,0 +1,16 @@ +from fastapi import FastAPI +import uvicorn + +app = FastAPI() + +@app.get("/") +def read_root(): + return {"Hello": "World"} + +@app.get("/api/state") +def get_state(): + return {"status": "ok"} + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8001) +