update pack

This commit is contained in:
bridge
2025-11-21 23:39:54 +08:00
parent 6ab05edab2
commit 870037d811
5 changed files with 108 additions and 136 deletions

View File

@@ -1,4 +1,3 @@
pygame>=2.0.0
PyYAML>=6.0
litellm>=1.0.0
omegaconf>=2.3.0

View File

@@ -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())

View File

@@ -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()

View File

@@ -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,12 +192,6 @@ 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) {

View File

@@ -11,6 +11,9 @@ export default defineConfig({
},
}),
],
build: {
assetsDir: 'web_static', // 避免与游戏原本的 /assets 目录冲突
},
server: {
proxy: {
'/api': {