update pack
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
pygame>=2.0.0
|
||||
PyYAML>=6.0
|
||||
litellm>=1.0.0
|
||||
omegaconf>=2.3.0
|
||||
|
||||
113
src/run/run.py
113
src/run/run.py
@@ -1,113 +0,0 @@
|
||||
import random
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
from typing import List, Tuple, Dict, Any, Sequence, Optional
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
|
||||
# 依赖项目内部模块
|
||||
from src.front.front import Front
|
||||
from src.sim.simulator import Simulator
|
||||
from src.sim.new_avatar import make_avatars
|
||||
from src.classes.world import World
|
||||
from src.classes.map import Map
|
||||
from src.classes.tile import TileType
|
||||
from src.classes.avatar import Avatar, Gender
|
||||
from src.classes.calendar import Month, Year, MonthStamp, create_month_stamp
|
||||
from src.classes.cultivation import CultivationProgress
|
||||
from src.classes.root import Root
|
||||
from src.classes.age import Age
|
||||
from src.run.create_map import create_cultivation_world_map, add_sect_headquarters
|
||||
from src.classes.name import get_random_name, get_random_name_for_sect
|
||||
from src.utils.id_generator import get_avatar_id
|
||||
from src.utils.config import CONFIG
|
||||
from src.classes.sect import sects_by_id
|
||||
from src.classes.alignment import Alignment
|
||||
from src.run.log import get_logger
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.technique import get_technique_by_sect, attribute_to_root
|
||||
|
||||
|
||||
def clamp(value: int, lo: int, hi: int) -> int:
|
||||
return max(lo, min(hi, value))
|
||||
|
||||
|
||||
def circle_points(cx: int, cy: int, r: int, width: int, height: int) -> List[Tuple[int, int]]:
|
||||
pts: List[Tuple[int, int]] = []
|
||||
r2 = r * r
|
||||
for y in range(clamp(cy - r, 0, height - 1), clamp(cy + r, 0, height - 1) + 1):
|
||||
for x in range(clamp(cx - r, 0, width - 1), clamp(cx + r, 0, width - 1) + 1):
|
||||
if (x - cx) * (x - cx) + (y - cy) * (y - cy) <= r2:
|
||||
pts.append((x, y))
|
||||
return pts
|
||||
|
||||
def random_gender() -> Gender:
|
||||
return Gender.MALE if random.random() < 0.5 else Gender.FEMALE
|
||||
|
||||
|
||||
def sample_existed_sects(all_sects: Sequence, needed_sects: int) -> list:
|
||||
"""
|
||||
按权重无放回抽样本局启用的宗门;当权重和为0时退回均匀无放回抽样。
|
||||
返回长度不超过 max_sects。
|
||||
"""
|
||||
if needed_sects <= 0 or not all_sects:
|
||||
return []
|
||||
k = min(needed_sects, len(all_sects))
|
||||
pool = list(all_sects)
|
||||
base_weights = [max(0.0, s.weight) for s in pool]
|
||||
if sum(base_weights) <= 0:
|
||||
random.shuffle(pool)
|
||||
return pool[:k]
|
||||
result: list = []
|
||||
for _ in range(k):
|
||||
weights = [max(0.0, s.weight) for s in pool]
|
||||
chosen = random.choices(pool, weights=weights, k=1)[0]
|
||||
result.append(chosen)
|
||||
pool.remove(chosen)
|
||||
return result
|
||||
|
||||
def make_avatars(world: World, count: int = 12, current_month_stamp: MonthStamp = MonthStamp(100 * 12), existed_sects: Optional[List] = None) -> dict[str, Avatar]:
|
||||
# 迁移到 src/sim/new_avatar.py
|
||||
from src.sim.new_avatar import make_avatars as _new_make
|
||||
# 在地图上添加本局宗门总部(保持原行为)
|
||||
if existed_sects:
|
||||
add_sect_headquarters(world.map, existed_sects)
|
||||
return _new_make(world, count=count, current_month_stamp=current_month_stamp, existed_sects=existed_sects)
|
||||
|
||||
|
||||
async def main():
|
||||
# 为了每次更丰富,使用随机种子;如需复现可将 seed 固定
|
||||
|
||||
# 初始化日志系统(会自动清理旧日志)
|
||||
logger = get_logger()
|
||||
print(f"日志系统已初始化,日志文件:{logger.log_file_path}")
|
||||
|
||||
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 = sample_existed_sects(all_sects, needed_sects)
|
||||
|
||||
# 创建角色,传入当前年份确保年龄与生日匹配,使用配置文件中的NPC数量
|
||||
all_avatars = make_avatars(world, count=CONFIG.game.init_npc_num, current_month_stamp=world.month_stamp, existed_sects=existed_sects)
|
||||
world.avatar_manager.avatars.update(all_avatars)
|
||||
|
||||
front = Front(
|
||||
simulator=sim,
|
||||
step_interval_ms=750,
|
||||
window_title="Cultivation World — Front Demo",
|
||||
existed_sects=existed_sects,
|
||||
)
|
||||
await front.run_async()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import sys
|
||||
import os
|
||||
import asyncio
|
||||
import webbrowser
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import List, Optional
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Query
|
||||
@@ -170,6 +171,17 @@ async def lifespan(app: FastAPI):
|
||||
init_game()
|
||||
# 启动后台任务
|
||||
asyncio.create_task(game_loop())
|
||||
|
||||
# 自动打开浏览器
|
||||
host = "127.0.0.1"
|
||||
port = 8002
|
||||
url = f"http://{host}:{port}"
|
||||
print(f"Ready! Opening browser at {url}")
|
||||
try:
|
||||
webbrowser.open(url)
|
||||
except Exception as e:
|
||||
print(f"Failed to open browser: {e}")
|
||||
|
||||
yield
|
||||
# 关闭时清理(如果需要)
|
||||
|
||||
@@ -184,16 +196,36 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# 挂载静态资源
|
||||
ASSETS_PATH = os.path.join(os.path.dirname(__file__), '..', '..', 'assets')
|
||||
# 路径处理:兼容开发环境和 PyInstaller 打包环境
|
||||
if getattr(sys, 'frozen', False):
|
||||
# PyInstaller 打包模式
|
||||
base_path = sys._MEIPASS
|
||||
# 在 pack.ps1 中,我们把 web/dist 映射到了 web_dist
|
||||
WEB_DIST_PATH = os.path.join(base_path, 'web_dist')
|
||||
# assets 同理
|
||||
ASSETS_PATH = os.path.join(base_path, 'assets')
|
||||
else:
|
||||
# 开发模式
|
||||
base_path = os.path.join(os.path.dirname(__file__), '..', '..')
|
||||
WEB_DIST_PATH = os.path.join(base_path, 'web', 'dist')
|
||||
ASSETS_PATH = os.path.join(base_path, 'assets')
|
||||
|
||||
# 1. 挂载游戏资源 (图片等)
|
||||
if os.path.exists(ASSETS_PATH):
|
||||
app.mount("/assets", StaticFiles(directory=ASSETS_PATH), name="assets")
|
||||
else:
|
||||
print(f"Warning: Assets path not found: {ASSETS_PATH}")
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"status": "online", "app": "Cultivation World Simulator Backend"}
|
||||
# 2. 挂载前端静态页面 (Web Dist)
|
||||
if os.path.exists(WEB_DIST_PATH):
|
||||
print(f"Serving Web UI from: {WEB_DIST_PATH}")
|
||||
app.mount("/", StaticFiles(directory=WEB_DIST_PATH, html=True), name="web_dist")
|
||||
else:
|
||||
print(f"Warning: Web dist path not found: {WEB_DIST_PATH}. Please run 'npm run build' in web directory.")
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"status": "online", "app": "Cultivation World Simulator Backend (Headless / Dev Mode)"}
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket):
|
||||
@@ -523,7 +555,9 @@ def api_load_game(req: LoadGameRequest):
|
||||
def start():
|
||||
"""启动服务的入口函数"""
|
||||
# 改为 8002 端口
|
||||
uvicorn.run("src.server.main:app", host="0.0.0.0", port=8002, reload=False)
|
||||
# 使用 127.0.0.1 更加安全且避免防火墙弹窗
|
||||
# 注意:直接传递 app 对象而不是字符串,避免 PyInstaller 打包后找不到模块的问题
|
||||
uvicorn.run(app, host="127.0.0.1", port=8002)
|
||||
|
||||
if __name__ == "__main__":
|
||||
start()
|
||||
|
||||
@@ -27,8 +27,40 @@ $BuildDir = Join-Path $RepoRoot ("tmp\build\" + $tag)
|
||||
$SpecDir = Join-Path $RepoRoot ("tmp\spec\" + $tag)
|
||||
New-Item -ItemType Directory -Force -Path $DistDir, $BuildDir, $SpecDir | Out-Null
|
||||
|
||||
# --- Web Frontend Build ---
|
||||
$WebDir = Join-Path $RepoRoot "web"
|
||||
$WebDistDir = Join-Path $WebDir "dist"
|
||||
|
||||
Write-Host "Checking Web Frontend..." -ForegroundColor Cyan
|
||||
if (Test-Path $WebDir) {
|
||||
Push-Location $WebDir
|
||||
try {
|
||||
if (-not (Test-Path "node_modules")) {
|
||||
Write-Host "Installing npm dependencies..."
|
||||
# Use cmd /c to ensure npm is found on Windows
|
||||
cmd /c "npm install"
|
||||
}
|
||||
Write-Host "Building web frontend..."
|
||||
cmd /c "npm run build"
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Web build failed."
|
||||
exit 1
|
||||
}
|
||||
} catch {
|
||||
Write-Error "Web build process failed: $_"
|
||||
exit 1
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
} else {
|
||||
Write-Error "Web directory not found at $WebDir"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Entry and app name
|
||||
$EntryPy = Join-Path $RepoRoot "src\run\run.py"
|
||||
# CHANGED: Use server main.py instead of run.py
|
||||
$EntryPy = Join-Path $RepoRoot "src\server\main.py"
|
||||
$AppName = "CultivationWorld"
|
||||
|
||||
if (-not (Test-Path $EntryPy)) {
|
||||
@@ -59,14 +91,40 @@ $argsList = @(
|
||||
"--onedir",
|
||||
"--clean",
|
||||
"--noconfirm",
|
||||
"--windowed",
|
||||
# "--windowed", <-- REMOVED: We want a console window for the server so user can close it
|
||||
"--console",
|
||||
"--distpath", $DistDir,
|
||||
"--workpath", $BuildDir,
|
||||
"--specpath", $SpecDir,
|
||||
"--paths", $RepoRoot,
|
||||
"--additional-hooks-dir", $AdditionalHooksPath,
|
||||
"--add-data", "${AssetsPath};assets",
|
||||
"--add-data", "${StaticPath};static",
|
||||
|
||||
# Data Files
|
||||
"--add-data", "${AssetsPath};assets", # Game Assets (Images) -> _internal/assets
|
||||
"--add-data", "${WebDistDir};web_dist", # Web Frontend -> _internal/web_dist
|
||||
"--add-data", "${StaticPath};static", # Configs -> _internal/static (backup)
|
||||
|
||||
# Excludes
|
||||
"--exclude-module", "pygame", # Exclude heavy library not needed for server
|
||||
"--exclude-module", "matplotlib", # Plotting library often pulled by pandas
|
||||
"--exclude-module", "tkinter", # Python default GUI
|
||||
"--exclude-module", "PyQt5", # Qt GUI
|
||||
"--exclude-module", "PyQt6",
|
||||
"--exclude-module", "PySide2",
|
||||
"--exclude-module", "PySide6",
|
||||
"--exclude-module", "wx", # wxPython
|
||||
"--exclude-module", "notebook", # Jupyter notebook
|
||||
"--exclude-module", "ipython",
|
||||
"--exclude-module", "boto3", # AWS SDK (huge, for Bedrock/S3)
|
||||
"--exclude-module", "botocore",
|
||||
"--exclude-module", "s3transfer",
|
||||
"--exclude-module", "azure", # Azure SDK
|
||||
"--exclude-module", "huggingface_hub", # HuggingFace (for local models)
|
||||
"--exclude-module", "transformers", # Transformers (huge)
|
||||
"--exclude-module", "tensorflow",
|
||||
"--exclude-module", "torch", # PyTorch (massive if present)
|
||||
|
||||
# Hidden imports for LLM
|
||||
"--hidden-import", "tiktoken_ext.openai_public",
|
||||
"--hidden-import", "tiktoken_ext",
|
||||
"--collect-all", "tiktoken",
|
||||
@@ -110,12 +168,9 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
# Copy static and assets to exe directory
|
||||
# Copy static to exe directory (Config needs to be next to exe for CWD access)
|
||||
if (Test-Path $ExeDir) {
|
||||
if (Test-Path $AssetsPath) {
|
||||
Copy-Item -Path $AssetsPath -Destination $ExeDir -Recurse -Force
|
||||
Write-Host "✓ Copied assets to exe directory" -ForegroundColor Green
|
||||
}
|
||||
# NOTE: We DO NOT copy 'assets' to root anymore. They are inside _internal via --add-data.
|
||||
|
||||
if (Test-Path $StaticPath) {
|
||||
Copy-Item -Path $StaticPath -Destination $ExeDir -Recurse -Force
|
||||
@@ -137,15 +192,9 @@ try {
|
||||
Write-Host "✓ Deleted entire build directory: $BuildDirRoot" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# $SpecDirRoot = Join-Path $RepoRoot "tmp\spec"
|
||||
# if (Test-Path $SpecDirRoot) {
|
||||
# Remove-Item -Path $SpecDirRoot -Recurse -Force
|
||||
# Write-Host "✓ Deleted entire spec directory: $SpecDirRoot" -ForegroundColor Green
|
||||
# }
|
||||
|
||||
Write-Host "`n=== Package completed ===" -ForegroundColor Cyan
|
||||
Write-Host "Distribution directory: " (Resolve-Path $DistDir).Path
|
||||
if (Test-Path $ExeDir) {
|
||||
Write-Host "Executable directory: " (Resolve-Path $ExeDir).Path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ export default defineConfig({
|
||||
},
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
assetsDir: 'web_static', // 避免与游戏原本的 /assets 目录冲突
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
|
||||
Reference in New Issue
Block a user