add log
This commit is contained in:
@@ -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
|
||||
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
|
||||
65
src/classes/age.py
Normal file
65
src/classes/age.py
Normal file
@@ -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})"
|
||||
@@ -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)}
|
||||
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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
"""
|
||||
灵根
|
||||
目前只有五行灵根,金木水火土。
|
||||
目前只有五行:金木水火土。
|
||||
其实和EssenceType很类似
|
||||
但是单独拿出来是因为,之后可能整特殊的复杂灵根
|
||||
所以这里单独定义一个Root类,用来描述灵根。
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from src.classes.essence import EssenceType
|
||||
|
||||
class Root(Enum):
|
||||
"""
|
||||
灵根
|
||||
"""
|
||||
Metal = "金"
|
||||
Wood = "木"
|
||||
Water = "水"
|
||||
Fire = "火"
|
||||
Earth = "土"
|
||||
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,
|
||||
}
|
||||
@@ -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"]
|
||||
|
||||
|
||||
15
src/sim/event.py
Normal file
15
src/sim/event.py
Normal file
@@ -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}"
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user