From a94ea2bd8b21160da932ec137b9dae7ef79aa099 Mon Sep 17 00:00:00 2001 From: bridge Date: Sat, 23 Aug 2025 21:45:05 +0800 Subject: [PATCH] add log --- src/classes/action.py | 27 +++++- src/classes/age.py | 65 ++++++++++++++ src/classes/avatar.py | 46 +++++++++- src/classes/calendar.py | 8 +- src/classes/cultivation.py | 14 +-- src/classes/root.py | 25 ++++-- src/front/front.py | 173 ++++++++++++++++++++++++++++++------- src/sim/event.py | 15 ++++ src/sim/simulator.py | 18 +++- tests/run_front.py | 18 ++-- 10 files changed, 353 insertions(+), 56 deletions(-) create mode 100644 src/classes/age.py create mode 100644 src/sim/event.py diff --git a/src/classes/action.py b/src/classes/action.py index a8157b6..9907cc8 100644 --- a/src/classes/action.py +++ b/src/classes/action.py @@ -2,6 +2,9 @@ from __future__ import annotations from abc import ABC, abstractmethod from typing import TYPE_CHECKING +from src.classes.essence import Essence, EssenceType +from src.classes.root import Root, corres_essence_type + if TYPE_CHECKING: from src.classes.avatar import Avatar from src.classes.world import World @@ -58,4 +61,26 @@ class Move(DefineAction): self.avatar.tile = target_tile else: # 超出边界:不改变位置与tile - pass \ No newline at end of file + pass + +class Cultivate(DefineAction): + """ + 修炼动作,可以增加修仙进度。 + """ + def execute(self, root: Root, essence: Essence): + """ + 修炼 + 获得的exp增加取决于essence的对应灵根的大小。 + """ + essence_type = corres_essence_type[root] + essence_density = essence.get_density(essence_type) + exp = self.get_exp(essence_density) + self.avatar.cultivation_progress.add_exp(exp) + + def get_exp(self, essence_density: int) -> int: + """ + 根据essence的密度,计算获得的exp。 + 公式为:base * essence_density + """ + base = 100 + return base * essence_density \ No newline at end of file diff --git a/src/classes/age.py b/src/classes/age.py new file mode 100644 index 0000000..143ad08 --- /dev/null +++ b/src/classes/age.py @@ -0,0 +1,65 @@ +import random +from src.classes.cultivation import Realm + +class Age: + """ + 角色寿命管理 + 基于境界计算期望寿命,超过期望寿命后有概率老死 + """ + + # 各境界的基础期望寿命(年) + # REALM_LIFESPAN = { + # Realm.Qi_Refinement: 100, # 练气期:100年 + # Realm.Foundation_Establishment: 200, # 筑基期:200年 + # Realm.Core_Formation: 500, # 金丹期:500年 + # Realm.Nascent_Soul: 1000, # 元婴期:1000年 + # } + REALM_LIFESPAN = { + Realm.Qi_Refinement: 50, # 练气期:100年 + Realm.Foundation_Establishment: 60, # 筑基期:200年 + Realm.Core_Formation: 70, # 金丹期:500年 + Realm.Nascent_Soul: 80, # 元婴期:1000年 + } + + def __init__(self, age: int): + self.age = age + + def get_age(self) -> int: + """获取当前年龄""" + return self.age + + def get_expected_lifespan(self, realm: Realm) -> int: + """获取期望寿命""" + return self.REALM_LIFESPAN.get(realm, 100) + + def get_death_probability(self, realm: Realm) -> float: + """ + 计算当月老死的概率 + + 返回: + 老死概率,范围0.0-0.1 + """ + if self.age < self.get_expected_lifespan(realm): + return 0.0 + + # 超过期望寿命的年数 + years_over_lifespan = self.age - self.get_expected_lifespan(realm) + + # 基础概率:每超过1年增加0.01的概率 + death_probability = min(years_over_lifespan * 0.01, 0.1) + + return death_probability + + def death_by_old_age(self, realm: Realm) -> bool: + """ + 判断是否老死 + """ + return random.random() < self.get_death_probability(realm) + + def __str__(self) -> str: + """返回年龄的字符串表示""" + return str(self.age) + + def __repr__(self) -> str: + """返回年龄的详细字符串表示""" + return f"Age({self.age})" diff --git a/src/classes/avatar.py b/src/classes/avatar.py index 55bffc7..79db078 100644 --- a/src/classes/avatar.py +++ b/src/classes/avatar.py @@ -7,8 +7,9 @@ from src.classes.calendar import Month, Year from src.classes.action import Action from src.classes.world import World from src.classes.tile import Tile -from src.classes.cultivation import CultivationProgress +from src.classes.cultivation import CultivationProgress, Realm from src.classes.root import Root +from src.classes.age import Age from src.utils.strings import to_snake_case class Gender(Enum): @@ -34,7 +35,7 @@ class Avatar: id: int birth_month: Month birth_year: Year - age: int + age: Age gender: Gender cultivation_progress: CultivationProgress = field(default_factory=lambda: CultivationProgress(0)) pos_x: int = 0 @@ -74,4 +75,43 @@ class Avatar: 决定做什么。 """ # 目前只做一个事情,就是随机移动。 - return "Move", {"delta_x": random.randint(-1, 1), "delta_y": random.randint(-1, 1)} \ No newline at end of file + return "Move", {"delta_x": random.randint(-1, 1), "delta_y": random.randint(-1, 1)} + + def update_cultivation(self, new_level: int): + """ + 更新修仙进度,并在境界提升时更新寿命 + """ + old_realm = self.cultivation_progress.realm + self.cultivation_progress.level = new_level + self.cultivation_progress.realm = self.cultivation_progress.get_realm(new_level) + + # 如果境界提升了,更新寿命期望 + if self.cultivation_progress.realm != old_realm: + self.age.update_realm(self.cultivation_progress.realm) + + def death_by_old_age(self) -> bool: + """ + 检查是否老死 + + 返回: + 如果老死返回True,否则返回False + """ + return self.age.death_by_old_age(self.cultivation_progress.realm) + + def get_age_info(self) -> dict: + """ + 获取年龄相关信息 + + 返回: + 包含年龄、期望寿命、死亡概率等信息的字典 + """ + current_age, expected_lifespan = self.age.get_lifespan_progress() + death_probability = self.age.get_death_probability() + + return { + "current_age": round(current_age, 2), + "expected_lifespan": expected_lifespan, + "is_elderly": self.age.is_elderly(), + "death_probability": round(death_probability, 4), + "realm": self.cultivation_progress.realm.value + } \ No newline at end of file diff --git a/src/classes/calendar.py b/src/classes/calendar.py index 70be17c..94ac969 100644 --- a/src/classes/calendar.py +++ b/src/classes/calendar.py @@ -1,6 +1,4 @@ from enum import Enum -from dataclasses import dataclass - class Month(Enum): JANUARY = 1 @@ -16,6 +14,12 @@ class Month(Enum): NOVEMBER = 11 DECEMBER = 12 + def __str__(self) -> str: + return str(self.value) + + def __repr__(self) -> str: + return str(self.value) + class Year(int): def __add__(self, other: int) -> 'Year': return Year(int(self) + other) diff --git a/src/classes/cultivation.py b/src/classes/cultivation.py index 0e7ced3..265143c 100644 --- a/src/classes/cultivation.py +++ b/src/classes/cultivation.py @@ -65,7 +65,7 @@ class CultivationProgress: def get_exp_required(self, target_level: int) -> int: """ 计算升级到指定等级需要的经验值 - 使用指数增长公式:base_exp * (growth_rate ^ level) * realm_multiplier + 使用简单的代数加法:base_exp + (level - 1) * increment + realm_bonus 参数: target_level: 目标等级 @@ -77,13 +77,15 @@ class CultivationProgress: return 0 base_exp = 100 # 基础经验值 - growth_rate = 1.15 # 每级增长15% + increment = 50 # 每级增加50点经验值 - # 境界加成倍数:每跨越一个境界,经验需求增加50% - realm_multiplier = 1 + (target_level // 30) * 0.5 + # 基础经验值计算 + exp_required = base_exp + (target_level - 1) * increment - exp_required = int(base_exp * (growth_rate ** target_level) * realm_multiplier) - return exp_required + # 境界加成:每跨越一个境界,额外增加1000点经验值 + realm_bonus = (target_level // 30) * 1000 + + return exp_required + realm_bonus def can_level_up(self) -> bool: """ diff --git a/src/classes/root.py b/src/classes/root.py index 9e1877a..bce261d 100644 --- a/src/classes/root.py +++ b/src/classes/root.py @@ -1,17 +1,28 @@ """ 灵根 -目前只有五行灵根,金木水火土。 +目前只有五行:金木水火土。 +其实和EssenceType很类似 +但是单独拿出来是因为,之后可能整特殊的复杂灵根 +所以这里单独定义一个Root类,用来描述灵根。 """ - from enum import Enum +from src.classes.essence import EssenceType class Root(Enum): """ 灵根 """ - Metal = "金" - Wood = "木" - Water = "水" - Fire = "火" - Earth = "土" \ No newline at end of file + GOLD = "金" + WOOD = "木" + WATER = "水" + FIRE = "火" + EARTH = "土" + +corres_essence_type = { + Root.GOLD: EssenceType.GOLD, + Root.WOOD: EssenceType.WOOD, + Root.WATER: EssenceType.WATER, + Root.FIRE: EssenceType.FIRE, + Root.EARTH: EssenceType.EARTH, +} \ No newline at end of file diff --git a/src/front/front.py b/src/front/front.py index a73b7c7..5fb4d3e 100644 --- a/src/front/front.py +++ b/src/front/front.py @@ -6,6 +6,7 @@ from src.sim.simulator import Simulator from src.classes.world import World from src.classes.tile import TileType from src.classes.avatar import Avatar, Gender +from src.sim.event import Event class Front: @@ -33,6 +34,7 @@ class Front: step_interval_ms: int = 400, window_title: str = "Cultivation World Simulator", font_path: Optional[str] = None, + sidebar_width: int = 300, # 新增:侧边栏宽度 ): self.world = world self.simulator = simulator @@ -41,10 +43,12 @@ class Front: self.step_interval_ms = step_interval_ms self.window_title = window_title self.font_path = font_path + self.sidebar_width = sidebar_width # 新增:侧边栏宽度 # 运行时状态 self._auto_step = True self._last_step_ms = 0 + self.events: List[Event] = [] # 新增:存储事件历史 # 初始化pygame import pygame @@ -52,8 +56,8 @@ class Front: pygame.init() pygame.font.init() - # 计算窗口大小 - width_px = world.map.width * tile_size + margin * 2 + # 计算窗口大小(包含侧边栏) + width_px = world.map.width * tile_size + margin * 2 + sidebar_width height_px = world.map.height * tile_size + margin * 2 self.screen = pygame.display.set_mode((width_px, height_px)) pygame.display.set_caption(window_title) @@ -61,6 +65,8 @@ class Front: # 字体和缓存 self.font = self._create_font(16) self.tooltip_font = self._create_font(14) + self.sidebar_font = self._create_font(12) # 新增:侧边栏字体 + self.status_font = self._create_font(18) # 新增:状态栏字体(更大更清晰) self._region_font_cache: Dict[int, object] = {} # 配色方案 @@ -71,6 +77,12 @@ class Front: "tooltip_bg": (32, 32, 32), "tooltip_bd": (90, 90, 90), "avatar": (240, 220, 90), + "sidebar_bg": (25, 25, 25), # 新增:侧边栏背景色 + "sidebar_border": (60, 60, 60), # 新增:侧边栏边框色 + "event_text": (200, 200, 200), # 新增:事件文字色 + "status_bg": (15, 15, 15), # 新增:状态栏背景色(深色) + "status_border": (50, 50, 50), # 新增:状态栏边框色 + "status_text": (220, 220, 220), # 新增:状态栏文字色(亮色) } # 加载tile图像 @@ -79,6 +91,13 @@ class Front: self.clock = pygame.time.Clock() + def add_events(self, new_events: List[Event]): + """新增:添加新事件到事件历史""" + self.events.extend(new_events) + # 保持最多1000个事件,避免内存占用过大 + if len(self.events) > 1000: + self.events = self.events[-1000:] + def run(self): """主循环""" pygame = self.pygame @@ -110,7 +129,9 @@ class Front: def _step_once(self): """执行一步模拟""" - self.simulator.step() + events = self.simulator.step() # 获取返回的事件 + if events: # 新增:将事件添加到事件历史 + self.add_events(events) self._last_step_ms = 0 def _render(self): @@ -134,27 +155,74 @@ class Front: # 状态信息 self._draw_status_bar() - self._draw_date_info() + + # 新增:绘制侧边栏 + self._draw_sidebar() pygame.display.flip() def _draw_status_bar(self): - """绘制状态栏""" - hint = f"A:自动步进({'开' if self._auto_step else '关'}) SPACE:单步 ESC:退出" - text_surf = self.font.render(hint, True, self.colors["text"]) - self.screen.blit(text_surf, (self.margin, 4)) - - def _draw_date_info(self): - """绘制日期信息""" + """绘制状态栏 - 包含操作指南和年月信息""" + pygame = self.pygame + + # 状态栏配置 + status_y = 8 + status_height = 32 + padding = 8 + + # 绘制状态栏背景 + status_rect = pygame.Rect(0, 0, self.screen.get_width(), status_height) + pygame.draw.rect(self.screen, self.colors["status_bg"], status_rect) + pygame.draw.line(self.screen, self.colors["status_border"], + (0, status_height), (self.screen.get_width(), status_height), 2) + + # 1. 绘制操作指南 + self._draw_operation_guide(status_y, padding) + + # 2. 绘制年月信息 + self._draw_year_month_info(status_y, padding) + + def _draw_operation_guide(self, y_pos: int, padding: int): + """绘制操作指南""" + # 构建操作指南文本 + auto_status = "开" if self._auto_step else "关" + guide_text = f"A:自动步进({auto_status}) SPACE:单步 ESC:退出" + + # 渲染文本 + guide_surf = self.status_font.render(guide_text, True, self.colors["status_text"]) + + # 绘制文本 + x_pos = self.margin + padding + self.screen.blit(guide_surf, (x_pos, y_pos)) + + # 保存操作指南的宽度,供年月信息定位使用 + self._guide_width = guide_surf.get_width() + + def _draw_year_month_info(self, y_pos: int, padding: int): + """绘制年月信息""" + # 获取年月数据 + year = int(self.simulator.year) + month_num = self._get_month_number() + + # 构建年月文本 + ym_text = f"{year}年{month_num:02d}月" + + # 渲染文本 + ym_surf = self.status_font.render(ym_text, True, self.colors["status_text"]) + + # 计算位置:放在操作指南右边,留适当间距 + x_pos = self.margin + self._guide_width + padding * 3 + self.screen.blit(ym_surf, (x_pos, y_pos)) + + def _get_month_number(self) -> int: + """获取月份数字""" try: month_num = list(type(self.simulator.month)).index(self.simulator.month) + 1 + return month_num except Exception: - month_num = 1 - - ym_text = f"{int(self.simulator.year)}年{month_num:02d}月" - ym_surf = self.font.render(ym_text, True, self.colors["text"]) - screen_w, _ = self.screen.get_size() - self.screen.blit(ym_surf, (screen_w - self.margin - ym_surf.get_width(), 4)) + return 1 + + def _draw_map(self): """绘制地图""" @@ -285,7 +353,7 @@ class Front: hovered = None min_dist = float("inf") - for avatar in self.simulator.avatars: + for avatar_id, avatar in self.simulator.avatars.items(): cx, cy = self._avatar_center_pixel(avatar) radius = max(8, self.tile_size // 3) @@ -394,17 +462,9 @@ class Front: image_path = f"assets/tiles/{tile_type.value}.png" if os.path.exists(image_path): - try: - image = pygame.image.load(image_path) - scaled_image = pygame.transform.scale(image, (self.tile_size, self.tile_size)) - self.tile_images[tile_type] = scaled_image - print(f"已加载tile图像: {image_path}") - except Exception as e: - print(f"加载tile图像失败 {image_path}: {e}") - self._create_fallback_surface(tile_type) - else: - print(f"tile图像文件不存在: {image_path}") - self._create_fallback_surface(tile_type) + image = pygame.image.load(image_path) + scaled_image = pygame.transform.scale(image, (self.tile_size, self.tile_size)) + self.tile_images[tile_type] = scaled_image def _create_fallback_surface(self, tile_type): """创建默认的fallback surface""" @@ -445,6 +505,61 @@ class Front: # 退回默认字体 return pygame.font.SysFont(None, size) + def _draw_sidebar(self): + """新增:绘制侧边栏""" + pygame = self.pygame + + # 计算侧边栏位置 + sidebar_x = self.world.map.width * self.tile_size + self.margin * 2 + sidebar_y = self.margin + + # 绘制侧边栏背景 + sidebar_rect = pygame.Rect(sidebar_x, sidebar_y, self.sidebar_width, + self.screen.get_height() - self.margin * 2) + pygame.draw.rect(self.screen, self.colors["sidebar_bg"], sidebar_rect) + pygame.draw.rect(self.screen, self.colors["sidebar_border"], sidebar_rect, 2) + + # 绘制标题 + title_text = "事件历史" + title_surf = self.sidebar_font.render(title_text, True, self.colors["text"]) + title_x = sidebar_x + 10 + title_y = sidebar_y + 10 + self.screen.blit(title_surf, (title_x, title_y)) + + # 绘制分隔线 + line_y = title_y + title_surf.get_height() + 10 + pygame.draw.line(self.screen, self.colors["sidebar_border"], + (sidebar_x + 10, line_y), + (sidebar_x + self.sidebar_width - 10, line_y), 1) + + # 绘制事件列表 + event_y = line_y + 15 + max_events = (self.screen.get_height() - event_y - self.margin) // 20 # 每行20像素 + + # 显示最近的事件(从最新开始) + recent_events = self.events[-max_events:] if len(self.events) > max_events else self.events + + for event in reversed(recent_events): # 最新的在顶部 + event_text = str(event) + + # 如果文本太长,截断它 + if len(event_text) > 35: # 大约35个字符 + event_text = event_text[:32] + "..." + + event_surf = self.sidebar_font.render(event_text, True, self.colors["event_text"]) + self.screen.blit(event_surf, (title_x, event_y)) + event_y += 20 + + # 如果超出显示区域,停止绘制 + if event_y > self.screen.get_height() - self.margin: + break + + # 如果没有事件,显示提示信息 + if not self.events: + no_event_text = "暂无事件" + no_event_surf = self.sidebar_font.render(no_event_text, True, self.colors["event_text"]) + self.screen.blit(no_event_surf, (title_x, event_y)) + __all__ = ["Front"] diff --git a/src/sim/event.py b/src/sim/event.py new file mode 100644 index 0000000..62127e9 --- /dev/null +++ b/src/sim/event.py @@ -0,0 +1,15 @@ +""" +event class +""" +from dataclasses import dataclass + +from src.classes.calendar import Month, Year + +@dataclass +class Event: + year: Year + month: Month + content: str + + def __str__(self) -> str: + return f"{self.year}年{self.month}月: {self.content}" \ No newline at end of file diff --git a/src/sim/simulator.py b/src/sim/simulator.py index e18b1f2..1e34255 100644 --- a/src/sim/simulator.py +++ b/src/sim/simulator.py @@ -1,8 +1,10 @@ from src.classes.calendar import Month, Year, next_month +from src.classes.avatar import Avatar +from src.sim.event import Event class Simulator: def __init__(self): - self.avatars = [] # list[Avatar] + self.avatars = {} # dict of int -> Avatar self.year = Year(1) self.month = Month.JANUARY @@ -14,9 +16,21 @@ class Simulator: 先结算多个角色间互相交互的事件。 再去结算单个角色的事件。 """ + events = [] # list of Event + death_avatar_ids = [] # list of int + # 结算角色行为 - for avatar in self.avatars: + for avatar_id, avatar in self.avatars.items(): avatar.act() + if avatar.death_by_old_age(): + death_avatar_ids.append(avatar_id) + event = Event(self.year, self.month, f"{avatar.name} 老死了,时年{avatar.age.get_age()}岁") + events.append(event) + + for avatar_id in death_avatar_ids: + self.avatars.pop(avatar_id) # 最后结算年月 self.month, self.year = next_month(self.month, self.year) + + return events diff --git a/tests/run_front.py b/tests/run_front.py index 80e19a0..137f9dc 100644 --- a/tests/run_front.py +++ b/tests/run_front.py @@ -19,6 +19,7 @@ from src.classes.action import Move from src.classes.essence import Essence, EssenceType from src.classes.cultivation import CultivationProgress from src.classes.root import Root +from src.classes.age import Age def clamp(value: int, lo: int, hi: int) -> int: @@ -709,18 +710,22 @@ def random_gender() -> Gender: return Gender.MALE if random.random() < 0.5 else Gender.FEMALE -def make_avatars(world: World, count: int = 12) -> list[Avatar]: - avatars: list[Avatar] = [] +def make_avatars(world: World, count: int = 12) -> dict[int, Avatar]: + avatars: dict[int, Avatar] = {} width, height = world.map.width, world.map.height for i in range(count): name = f"NPC{i+1:03d}" birth_year = Year(random.randint(1990, 2010)) birth_month = random.choice(list(Month)) - age = random.randint(16, 60) + age_years = random.randint(16, 60) gender = random_gender() # 随机生成level,范围从0到120(对应四个大境界) level = random.randint(0, 120) + cultivation_progress = CultivationProgress(level) + + # 创建Age实例,传入年龄和境界 + age = Age(age_years) # 找一个非海域的出生点 for _ in range(200): @@ -740,14 +745,14 @@ def make_avatars(world: World, count: int = 12) -> list[Avatar]: birth_year=birth_year, age=age, gender=gender, - cultivation_progress=CultivationProgress(level), + cultivation_progress=cultivation_progress, pos_x=x, pos_y=y, root=random.choice(list(Root)), # 随机选择灵根 ) avatar.tile = world.map.get_tile(x, y) avatar.bind_action(Move) - avatars.append(avatar) + avatars[i] = avatar return avatars @@ -760,7 +765,7 @@ def main(): world = World(map=game_map) sim = Simulator() - sim.avatars.extend(make_avatars(world, count=14)) + sim.avatars.update(make_avatars(world, count=14)) front = Front( world=world, @@ -769,6 +774,7 @@ def main(): margin=8, step_interval_ms=350, window_title="Cultivation World — Front Demo", + sidebar_width=350, # 新增:设置侧边栏宽度 ) front.run()