Files
cultivation-world-simulator/src/run/run.py
2025-10-12 21:49:21 +08:00

170 lines
6.8 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.
import random
import asyncio
import sys
import os
from typing import List, Tuple, Dict, Any
# 添加项目根目录到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.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.utils.names 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 make_avatars(world: World, count: int = 12, current_month_stamp: MonthStamp = MonthStamp(100 * 12)) -> dict[str, Avatar]:
avatars: dict[str, Avatar] = {}
width, height = world.map.width, world.map.height
# 依据配置决定宗门人数占比:当 init_npc_num > sect_num 时启用宗门逻辑
num_total = int(count)
max_sects = int(getattr(CONFIG.game, "sect_num", 0) or 0)
use_sects = num_total > max_sects and max_sects > 0
# 约 2/3 为宗门弟子1/3 为散修
sect_member_target = int(num_total * 2 / 3) if use_sects else 0
# 随机抽取启用的宗门列表
enabled_sects = list(sects_by_id.values())
random.shuffle(enabled_sects)
enabled_sects = enabled_sects[:max_sects] if use_sects else []
# 在地图上添加启用宗门的总部(仅显示名称与描述)
if enabled_sects:
add_sect_headquarters(world.map, enabled_sects)
# 循环均匀分配宗门成员(轮询宗门)
sect_assign_index = 0
sect_member_count = 0
for i in range(count):
# 随机生成年龄范围从16到60岁
age_years = random.randint(16, 60)
# 根据当前时间戳和年龄计算出生时间戳
birth_month_stamp = current_month_stamp - age_years * 12 + random.randint(0, 11) # 在出生年内随机选择月份
gender = random_gender()
# 分配宗门或散修
assigned_sect = None
if use_sects and sect_member_count < sect_member_target and enabled_sects:
assigned_sect = enabled_sects[sect_assign_index % len(enabled_sects)]
sect_assign_index += 1
sect_member_count += 1
# 根据宗门生成姓名
name = get_random_name_for_sect(gender, assigned_sect)
# 随机生成level范围从0到120对应四个大境界
level = random.randint(0, 120)
cultivation_progress = CultivationProgress(level)
# 创建Age实例传入年龄与当前境界
age = Age(age_years, cultivation_progress.realm)
# 找一个非海域的出生点
for _ in range(200):
x = random.randint(0, width - 1)
y = random.randint(0, height - 1)
t = world.map.get_tile(x, y)
if t.type not in (TileType.WATER, TileType.SEA, TileType.MOUNTAIN, TileType.VOLCANO, TileType.SWAMP, TileType.CAVE, TileType.RUINS):
break
else:
x, y = random.randint(0, width - 1), random.randint(0, height - 1)
avatar = Avatar(
world=world,
name=name,
id=get_avatar_id(),
birth_month_stamp=MonthStamp(birth_month_stamp),
age=age,
gender=gender,
cultivation_progress=cultivation_progress,
pos_x=x,
pos_y=y,
root=random.choice(list(Root)), # 随机选择灵根
sect=assigned_sect,
)
avatar.tile = world.map.get_tile(x, y)
# 依据宗门设定阵营(若有宗门则与宗门阵营一致,否则保留默认随机)
if assigned_sect is not None:
avatar.alignment = assigned_sect.alignment
# 宗门弟子:按宗门功法随机
t = get_technique_by_sect(assigned_sect)
avatar.technique = t
# 将灵根改为功法对应灵根(邪功法不变)
mapped_root = attribute_to_root(avatar.technique.attribute)
if mapped_root is not None:
avatar.root = mapped_root
avatars[avatar.id] = avatar
# # —— 为演示添加少量示例关系 ——
avatar_list = list(avatars.values())
if len(avatar_list) >= 2:
avatar_list[0].set_relation(avatar_list[1], Relation.ENEMY)
if len(avatar_list) >= 4:
avatar_list[2].set_relation(avatar_list[3], Relation.FRIEND)
if len(avatar_list) >= 6:
# 师徒有向第5位是师傅第6位是徒弟
avatar_list[4].set_relation(avatar_list[5], Relation.MASTER)
if len(avatar_list) >= 8:
# 道侣
avatar_list[6].set_relation(avatar_list[7], Relation.LOVERS)
return avatars
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)
# 创建角色传入当前年份确保年龄与生日匹配使用配置文件中的NPC数量
world.avatar_manager.avatars.update(make_avatars(world, count=CONFIG.game.init_npc_num, current_month_stamp=world.month_stamp))
front = Front(
simulator=sim,
tile_size=24, # 每个tile扩大约25%像素与tile数量缩减相抵窗口不变
margin=8,
step_interval_ms=750,
window_title="Cultivation World — Front Demo",
sidebar_width=350,
)
await front.run_async()
if __name__ == "__main__":
asyncio.run(main())