Spaces:
Runtime error
Runtime error
yingqianjiang-lingoace
commited on
Commit
·
bcf0302
1
Parent(s):
6cc9c22
Add application file
Browse files- .gitignore +1 -0
- Character.py +242 -0
- CharacterStatistics.py +245 -0
- PluginManager.py +13 -0
- WorldSimulation.py +100 -0
- app.py +125 -0
- config.py +22 -0
- plugins/BattlePlugin.py +60 -0
- plugins/BirthPlugin.py +26 -0
- plugins/CharacterCreationPlugin.py +18 -0
- plugins/CharacterGrowthPlugin.py +28 -0
- plugins/CultivationPlugin.py +31 -0
- plugins/DisasterPlugin.py +18 -0
- plugins/MarriagePlugin.py +23 -0
- plugins/ResourceDepletionPlugin.py +34 -0
- requirements.txt +3 -0
- run.py +22 -0
- utils.py +26 -0
.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
__pycache__
|
Character.py
ADDED
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
import math
|
3 |
+
from utils import get_random_name, get_random_clan_name
|
4 |
+
from config import P_DIE_WHEN_LOSE, IMMORTAL_RANK
|
5 |
+
|
6 |
+
cultivation_rank_map = ["炼气期", "筑基期", "结丹期", "元婴期", "化神期", "成仙者"]
|
7 |
+
class Character:
|
8 |
+
def __init__(self, name, gender, special_constitution, spiritual_roots, clan=None, partner=None, parents=None):
|
9 |
+
self.name = name
|
10 |
+
self.gender = gender
|
11 |
+
self.real_age = 0
|
12 |
+
self.apparent_age = 0
|
13 |
+
self.cultivation_level = 0 # 等级
|
14 |
+
self.cultivation_rank = 0 # 层次
|
15 |
+
self.special_constitution = special_constitution
|
16 |
+
self.spiritual_roots = spiritual_roots
|
17 |
+
self.experience_points = 0
|
18 |
+
self.combat_power = self.calculate_combat_power()
|
19 |
+
self.partner = partner
|
20 |
+
self.is_alive = True
|
21 |
+
self.buff = False
|
22 |
+
self.history = []
|
23 |
+
self.special_history = []
|
24 |
+
self.consume_spiritual_energy = 0
|
25 |
+
self.parents = parents
|
26 |
+
self.children = []
|
27 |
+
self.clan = clan # 宗族
|
28 |
+
|
29 |
+
def die(self):
|
30 |
+
self.history.append(f"{self.real_age}岁,死亡")
|
31 |
+
self.special_history.append(f"{self.real_age}岁,死亡")
|
32 |
+
self.is_alive = False
|
33 |
+
|
34 |
+
def cultivate(self, experience_points):
|
35 |
+
if not self.is_alive:
|
36 |
+
print("角色已经死亡,无法进行修炼。")
|
37 |
+
return
|
38 |
+
# 成仙者不再修炼
|
39 |
+
if self.cultivation_rank >= IMMORTAL_RANK:
|
40 |
+
return
|
41 |
+
|
42 |
+
self.experience_points += experience_points
|
43 |
+
|
44 |
+
# 根据经验值计算等级,等级越高,升级需要的经验值就越多
|
45 |
+
self.cultivation_level = math.floor(self.experience_points / ((1 + self.cultivation_rank) * 1000 + (1 + self.cultivation_level) * 100))
|
46 |
+
|
47 |
+
# 判断是否达到突破修为层次的条件
|
48 |
+
if self.cultivation_level >= 10:
|
49 |
+
# 使用一个随机数来表示突破的概率,当前rank越高,则突破成功的概率越低
|
50 |
+
success_probability = 0.9 / (self.cultivation_rank * 2 + 1)
|
51 |
+
if random.random() < success_probability:
|
52 |
+
cultivation_level = self.cultivation_level
|
53 |
+
self.cultivation_rank += 1
|
54 |
+
self.cultivation_level = 0
|
55 |
+
self.experience_points = 0
|
56 |
+
self.history.append(f"{self.real_age}岁,突破成功,在{cultivation_level}级, 到达{self.view_rank()}")
|
57 |
+
self.special_history.append(f"{self.real_age}岁,突破成功,在{cultivation_level}级, 到达{self.view_rank()}")
|
58 |
+
else:
|
59 |
+
self.history.append(f"{self.real_age}岁,突破失败,在{self.cultivation_level}级")
|
60 |
+
|
61 |
+
def marry(self, partner):
|
62 |
+
# 成仙者不再结婚
|
63 |
+
if self.cultivation_rank >= IMMORTAL_RANK:
|
64 |
+
return
|
65 |
+
if not self.is_alive:
|
66 |
+
print("角色已经死亡,无法结婚。")
|
67 |
+
return
|
68 |
+
self.partner = partner
|
69 |
+
partner.partner = self
|
70 |
+
|
71 |
+
# 结婚合并宗族
|
72 |
+
if self.clan and partner.clan:
|
73 |
+
if random.random() < 0.5:
|
74 |
+
partner.clan = self.clan
|
75 |
+
else:
|
76 |
+
self.clan = partner.clan
|
77 |
+
else:
|
78 |
+
self.clan = self.clan or partner.clan or get_random_clan_name()
|
79 |
+
partner.clan = self.clan
|
80 |
+
|
81 |
+
self.history.append(f"{self.real_age}岁,结婚了")
|
82 |
+
self.partner.history.append(f"{self.partner.real_age}岁,结婚了")
|
83 |
+
self.special_history.append(f"{self.real_age}岁,结婚了")
|
84 |
+
self.partner.special_history.append(f"{self.partner.real_age}岁,结婚了")
|
85 |
+
|
86 |
+
def give_birth(self):
|
87 |
+
# 成仙者不会生育
|
88 |
+
if self.cultivation_rank >= IMMORTAL_RANK:
|
89 |
+
return
|
90 |
+
if not self.is_alive:
|
91 |
+
print("角色已经死亡,无法生育。")
|
92 |
+
return
|
93 |
+
if not self.partner:
|
94 |
+
print("角色没有结婚,无法生育。")
|
95 |
+
return
|
96 |
+
|
97 |
+
# 合欢体质加buff
|
98 |
+
if self.special_constitution[1] == 1:
|
99 |
+
self.buff = True
|
100 |
+
|
101 |
+
|
102 |
+
# 小孩有一定几率遗传父亲或母亲的特殊体质和灵根
|
103 |
+
special_constitution = [a if random.random() < 0.5 else b for (a, b) in zip(self.special_constitution, self.partner.special_constitution)]
|
104 |
+
spiritual_roots = [a if random.random() < 0.5 else b for (a, b) in zip(self.spiritual_roots, self.partner.spiritual_roots)]
|
105 |
+
|
106 |
+
# spiritual_roots 一定几率突变
|
107 |
+
spiritual_roots = [random.choice([0, 1]) if random.random() < 0.01 else v for v in spiritual_roots]
|
108 |
+
|
109 |
+
child = Character(get_random_name(), random.choice(["男", "女"]), special_constitution, spiritual_roots, clan=self.clan, parents=[self, self.partner])
|
110 |
+
self.history.append(f"{self.real_age}岁,生下小孩{child.name}")
|
111 |
+
self.partner.history.append(f"{self.partner.real_age}岁,生下小孩{child.name}")
|
112 |
+
self.children.append(child)
|
113 |
+
self.partner.children.append(child)
|
114 |
+
return child
|
115 |
+
|
116 |
+
def grow(self):
|
117 |
+
# 成仙者不会衰老
|
118 |
+
if self.cultivation_rank >= IMMORTAL_RANK:
|
119 |
+
return
|
120 |
+
self.real_age += 1
|
121 |
+
|
122 |
+
# 根据修为层次 和 是否拥有木灵根 计算表观年龄
|
123 |
+
# 修为层次越高,外表看起来越年轻
|
124 |
+
ratio = 1 + self.cultivation_rank
|
125 |
+
|
126 |
+
# 灵龟和蜉蝣体质
|
127 |
+
if self.special_constitution[2] == 1: # 灵龟体质
|
128 |
+
ratio *= 2
|
129 |
+
elif self.special_constitution[3] == 1: # 蜉蝣体质
|
130 |
+
ratio *= 0.5
|
131 |
+
|
132 |
+
# 根据修为层次计算加成比例,初始加成为10%
|
133 |
+
bonus = 0.1 * (self.cultivation_rank * 0 + 1)
|
134 |
+
if self.spiritual_roots[1] == 1: # 拥有木灵根(灵根的第二位),最大寿命有加成
|
135 |
+
ratio *= 1 + bonus
|
136 |
+
|
137 |
+
self.apparent_age = math.floor(self.real_age / ratio)
|
138 |
+
|
139 |
+
def calculate_combat_power(self):
|
140 |
+
# 根据修为层次和修为等级计算战斗力参数:等级越高,各数值都越高;层次对等级是碾压效果
|
141 |
+
attack_power = (self.cultivation_rank + 1) * 30 + 1 * (1 + self.cultivation_level)
|
142 |
+
defense_power = (self.cultivation_rank + 1) * 30 + 1 * (1 + self.cultivation_level)
|
143 |
+
attack_speed = (self.cultivation_rank + 1) * 30 + 1 * (1 + self.cultivation_level)
|
144 |
+
health_points = (self.cultivation_rank + 1) * 60 + 2 * (1 + self.cultivation_level)
|
145 |
+
|
146 |
+
# 根据修为层次计算加成比例,初始加成为10%
|
147 |
+
bonus = 0.1 * (self.cultivation_rank * 0 + 1)
|
148 |
+
|
149 |
+
# 根据灵根调整战斗力参数,灵根分别为:[金木水火土], 分别对以下属性有加成:攻击速度, 最大寿命, 生命值, 攻击力, 防御力
|
150 |
+
if self.spiritual_roots[0] == 1: # 金
|
151 |
+
attack_speed *= 1 + bonus
|
152 |
+
|
153 |
+
if self.spiritual_roots[2] == 1: # 水
|
154 |
+
health_points *= 1 + bonus
|
155 |
+
|
156 |
+
if self.spiritual_roots[3] == 1: # 火
|
157 |
+
attack_power *= 1 + bonus
|
158 |
+
|
159 |
+
if self.spiritual_roots[4] == 1: # 土
|
160 |
+
defense_power *= 1 + bonus
|
161 |
+
|
162 |
+
return {
|
163 |
+
'attack_power': attack_power,
|
164 |
+
'defense_power': defense_power,
|
165 |
+
'attack_speed': attack_speed,
|
166 |
+
'health_points': health_points
|
167 |
+
}
|
168 |
+
|
169 |
+
def before_battle(self):
|
170 |
+
self.combat_power = self.calculate_combat_power()
|
171 |
+
|
172 |
+
def attack(self, opponent):
|
173 |
+
# 成仙者不会打架
|
174 |
+
if self.cultivation_rank >= IMMORTAL_RANK:
|
175 |
+
return
|
176 |
+
if not self.is_alive:
|
177 |
+
print("角色已经死亡,无法攻击。")
|
178 |
+
return
|
179 |
+
|
180 |
+
# 战斗体质加buff
|
181 |
+
if self.special_constitution[0] == 1:
|
182 |
+
self.buff = True
|
183 |
+
|
184 |
+
# TODO: 考虑攻击速度
|
185 |
+
damage = self.combat_power['attack_power'] - opponent.combat_power['defense_power']
|
186 |
+
if damage > 0:
|
187 |
+
opponent.combat_power['health_points'] -= damage
|
188 |
+
|
189 |
+
def check_is_alive(self):
|
190 |
+
if self.combat_power['health_points'] <= 0:
|
191 |
+
# 一定概率会死亡
|
192 |
+
if random.random() < P_DIE_WHEN_LOSE:
|
193 |
+
self.die()
|
194 |
+
return False
|
195 |
+
return True
|
196 |
+
|
197 |
+
def __str__(self):
|
198 |
+
# Display current attributes
|
199 |
+
history = "\n".join(self.history)
|
200 |
+
attributes = [
|
201 |
+
f"Name: {self.name}",
|
202 |
+
f"Gender: {self.gender}",
|
203 |
+
f"Real Age: {self.real_age}",
|
204 |
+
f"Apparent Age: {self.apparent_age}",
|
205 |
+
f"Cultivation Level: {self.cultivation_level}",
|
206 |
+
f"Cultivation Rank: ({self.cultivation_rank}){self.view_rank()}",
|
207 |
+
f"Special Constitution: {self.special_constitution}",
|
208 |
+
f"Spiritual Roots: {self.spiritual_roots}",
|
209 |
+
f"Experience Points: {self.experience_points}",
|
210 |
+
f"Combat Power: {self.combat_power}",
|
211 |
+
f"Partner: {self.partner.name if self.partner is not None else ''}",
|
212 |
+
f"Is Alive: {self.is_alive}",
|
213 |
+
f"Buff: {self.buff}",
|
214 |
+
f"Children: {[child.name for child in self.children]}",
|
215 |
+
f"History: {history}",
|
216 |
+
]
|
217 |
+
return "\n".join(attributes)
|
218 |
+
|
219 |
+
def view_rank(self):
|
220 |
+
if self.cultivation_rank <= len(cultivation_rank_map):
|
221 |
+
return f"{cultivation_rank_map[self.cultivation_rank]}({self.cultivation_level})"
|
222 |
+
else:
|
223 |
+
return "天外飞仙"
|
224 |
+
|
225 |
+
def to_list(self):
|
226 |
+
numerical_attributes = [
|
227 |
+
self.real_age,
|
228 |
+
self.apparent_age,
|
229 |
+
self.cultivation_level,
|
230 |
+
self.cultivation_rank,
|
231 |
+
self.experience_points,
|
232 |
+
self.combat_power["attack_power"],
|
233 |
+
self.combat_power["defense_power"],
|
234 |
+
self.combat_power["attack_speed"],
|
235 |
+
self.combat_power["health_points"],
|
236 |
+
*self.special_constitution, # [1, 0, 0, 0], 数组解构拼接
|
237 |
+
*self.spiritual_roots, # [1, 0, 0, 0, 0], 数组解构拼接
|
238 |
+
1 if self.is_alive else 0, # bool转int
|
239 |
+
1 if self.buff else 0, # bool转int
|
240 |
+
len(self.children),
|
241 |
+
]
|
242 |
+
return numerical_attributes
|
CharacterStatistics.py
ADDED
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import matplotlib.pyplot as plt
|
3 |
+
|
4 |
+
class CharacterStatistics:
|
5 |
+
def __init__(self, characters):
|
6 |
+
self.characters = characters
|
7 |
+
self.characters_array = np.array([c.to_list() for c in characters])
|
8 |
+
|
9 |
+
# 前n名排行榜
|
10 |
+
def find_highest_cultivation(self, n=1):
|
11 |
+
sorted_characters = sorted(self.characters, key=lambda c: (c.cultivation_rank, c.cultivation_level), reverse=True)
|
12 |
+
return sorted_characters[:n]
|
13 |
+
|
14 |
+
# 宗族人数排行榜
|
15 |
+
def rank_top_clans(self, top_n=4):
|
16 |
+
clan_size = {}
|
17 |
+
for c in self.characters:
|
18 |
+
clan_size[c.clan] = clan_size.get(c.clan, 0) + 1
|
19 |
+
|
20 |
+
sorted_clans = sorted(clan_size.items(), key=lambda x: x[1], reverse=True)[:top_n]
|
21 |
+
|
22 |
+
return sorted_clans
|
23 |
+
|
24 |
+
# real_age、apparent_age、cultivation_level、cultivation_rank 分布图
|
25 |
+
def plot_attribute_distribution(self):
|
26 |
+
attributes = ['Real Age', 'Apparent Age', 'Cultivation Level', 'Cultivation Rank']
|
27 |
+
attribute_indices = [0, 1, 2, 3]
|
28 |
+
fig, axs = plt.subplots(2, 2, figsize=(10, 8))
|
29 |
+
axs = axs.flatten()
|
30 |
+
for i, ax in enumerate(axs):
|
31 |
+
ax.hist(self.characters_array[:, attribute_indices[i]], bins=20, edgecolor='black')
|
32 |
+
ax.set_xlabel(attributes[i])
|
33 |
+
ax.set_ylabel('Frequency')
|
34 |
+
plt.tight_layout()
|
35 |
+
plt.show()
|
36 |
+
|
37 |
+
# real_age、apparent_age、cultivation_level、cultivation_rank、孩子数量 统计平均数
|
38 |
+
def calculate_average_attributes(self):
|
39 |
+
real_age_mean = np.mean(self.characters_array[:, 0])
|
40 |
+
apparent_age_mean = np.mean(self.characters_array[:, 1])
|
41 |
+
cultivation_level_mean = np.mean(self.characters_array[:, 2])
|
42 |
+
cultivation_rank_mean = np.mean(self.characters_array[:, 3])
|
43 |
+
child_count_mean = np.mean(self.characters_array[:, 19])
|
44 |
+
return real_age_mean, apparent_age_mean, cultivation_level_mean, cultivation_rank_mean, child_count_mean
|
45 |
+
|
46 |
+
# 画出 cultivation_rank (x轴) 和 4项combat_power的关系(y轴)
|
47 |
+
def plot_combat_power_vs_cultivation_rank(self):
|
48 |
+
|
49 |
+
fig, axs = plt.subplots(2, 2, figsize=(10, 8))
|
50 |
+
axs = axs.flatten()
|
51 |
+
|
52 |
+
for i, combat_power in enumerate(['Attack Power', 'Defense Power', 'Attack Speed', 'Health Points']):
|
53 |
+
|
54 |
+
combat_power_data = []
|
55 |
+
for rank in range(6):
|
56 |
+
rank_data = self.characters_array[self.characters_array[:,3]==rank, i+5]
|
57 |
+
combat_power_data.append(rank_data)
|
58 |
+
|
59 |
+
axs[i].boxplot(combat_power_data)
|
60 |
+
axs[i].set_title(combat_power)
|
61 |
+
axs[i].set_xlabel('Cultivation Rank')
|
62 |
+
axs[i].set_ylabel('Value')
|
63 |
+
|
64 |
+
plt.tight_layout()
|
65 |
+
plt.show()
|
66 |
+
|
67 |
+
def calculate_average_special_constitution(self):
|
68 |
+
special_constitution_mean = np.mean(self.characters_array[:, 9:13], axis=0)
|
69 |
+
return special_constitution_mean
|
70 |
+
|
71 |
+
def calculate_average_spiritual_roots(self):
|
72 |
+
spiritual_roots_mean = np.mean(self.characters_array[:, 13:18], axis=0)
|
73 |
+
return spiritual_roots_mean
|
74 |
+
|
75 |
+
# 统计无灵根者的数量
|
76 |
+
def count_zero_spiritual_roots(self):
|
77 |
+
zero_spiritual_roots_count = np.sum(np.all(self.characters_array[:, 13:18] == 0, axis=1))
|
78 |
+
return zero_spiritual_roots_count
|
79 |
+
|
80 |
+
# 灵根数量分布图
|
81 |
+
def plot_sum_spiritual_root_distribution(self):
|
82 |
+
spiritual_roots_sum = np.sum(self.characters_array[:, 13:18], axis=1)
|
83 |
+
|
84 |
+
plt.hist(spiritual_roots_sum, bins=5, range=(0,5))
|
85 |
+
plt.xlabel('Number of Spiritual Roots')
|
86 |
+
plt.ylabel('Number of Characters')
|
87 |
+
plt.title('Distribution of Spiritual Roots')
|
88 |
+
|
89 |
+
plt.show()
|
90 |
+
|
91 |
+
# 各灵根人口分布(x轴:5种灵根,y轴:人数)
|
92 |
+
def plot_spiritual_roots_distribution(self):
|
93 |
+
|
94 |
+
spiritual_roots = self.characters_array[:, 13:18]
|
95 |
+
|
96 |
+
means = np.mean(spiritual_roots, axis=0)
|
97 |
+
roots = ['Metal', 'Wood', 'Water', 'Fire', 'Earth']
|
98 |
+
|
99 |
+
plt.bar(roots, means)
|
100 |
+
plt.xlabel('Spiritual Roots')
|
101 |
+
plt.ylabel('Percentage of Characters')
|
102 |
+
plt.title('Distribution of Spiritual Roots in Population')
|
103 |
+
|
104 |
+
plt.show()
|
105 |
+
|
106 |
+
# 宗族人数分布图
|
107 |
+
def plot_clan_size_distribution(self):
|
108 |
+
clan_size = {}
|
109 |
+
for c in self.characters:
|
110 |
+
clan_size[c.clan] = clan_size.get(c.clan, 0) + 1
|
111 |
+
|
112 |
+
sizes = list(clan_size.values())
|
113 |
+
plt.hist(sizes, bins=20)
|
114 |
+
plt.xlabel('Clan Size')
|
115 |
+
plt.ylabel('Number of Clans')
|
116 |
+
plt.show()
|
117 |
+
|
118 |
+
def summarize(self):
|
119 |
+
print("===== Character Statistics Summary =====")
|
120 |
+
|
121 |
+
# 打印平均属性
|
122 |
+
print("Average Attributes:")
|
123 |
+
real_age, apparent_age, cultivation_level, cultivation_rank, child_count = self.calculate_average_attributes()
|
124 |
+
print(f"Real Age: {real_age:.2f}")
|
125 |
+
print(f"Apparent Age: {apparent_age:.2f}")
|
126 |
+
print(f"Cultivation Level: {cultivation_level:.2f}")
|
127 |
+
print(f"Cultivation Rank: {cultivation_rank:.2f}")
|
128 |
+
print(f"Child Count: {child_count:.2f}")
|
129 |
+
|
130 |
+
# 打印平均特质
|
131 |
+
print("\nAverage Special Constitutions:")
|
132 |
+
special_names = ['战斗', '合欢', '灵龟', '蜉蝣']
|
133 |
+
for i, v in enumerate(self.calculate_average_special_constitution()):
|
134 |
+
print(f"{special_names[i]}: {v:.2%}")
|
135 |
+
|
136 |
+
# 打印平均灵根
|
137 |
+
print("\nAverage Spiritual Roots:")
|
138 |
+
root_names = ['金', '木', '水', '火', '土']
|
139 |
+
for i, v in enumerate(self.calculate_average_spiritual_roots()):
|
140 |
+
print(f"{root_names[i]}: {v:.2%}")
|
141 |
+
|
142 |
+
# 打印统计图
|
143 |
+
print("\nPlotting graphs...")
|
144 |
+
self.plot_attribute_distribution()
|
145 |
+
# self.plot_combat_power_vs_cultivation_rank()
|
146 |
+
self.plot_spiritual_roots_distribution()
|
147 |
+
self.plot_sum_spiritual_root_distribution()
|
148 |
+
self.plot_clan_size_distribution()
|
149 |
+
self.print_top_cultivators()
|
150 |
+
# 攻击力排行榜
|
151 |
+
self.print_rank('attack_power', name='Attack Power')
|
152 |
+
# 防御力排行榜
|
153 |
+
self.print_rank('defense_power', name='Defense Power')
|
154 |
+
self.print_top_clans()
|
155 |
+
|
156 |
+
print("\n===== End Summary =====\n")
|
157 |
+
|
158 |
+
# 返回Markdown字符串的总结输出
|
159 |
+
def summarize_markdown(self):
|
160 |
+
md = "## Character Statistics Summary\n\n"
|
161 |
+
md += "### Average Attributes:\n\n"
|
162 |
+
real_age, apparent_age, cultivation_level, cultivation_rank, child_count = self.calculate_average_attributes()
|
163 |
+
md += f"Real Age: {real_age:.2f}\n\n"
|
164 |
+
md += f"Apparent Age: {apparent_age:.2f}\n\n"
|
165 |
+
md += f"Cultivation Level: {cultivation_level:.2f}\n\n"
|
166 |
+
md += f"Cultivation Rank: {cultivation_rank:.2f}\n\n"
|
167 |
+
md += f"Child Count: {child_count:.2f}\n\n"
|
168 |
+
|
169 |
+
md += "### Average Special Constitutions:\n\n"
|
170 |
+
special_names = ['战斗', '合欢', '灵龟', '蜉蝣']
|
171 |
+
for i, v in enumerate(self.calculate_average_special_constitution()):
|
172 |
+
md += f"{special_names[i]}: {v:.2%}\n\n"
|
173 |
+
|
174 |
+
md += "### Average Spiritual Roots:\n\n"
|
175 |
+
root_names = ['金', '木', '水', '火', '土']
|
176 |
+
for i, v in enumerate(self.calculate_average_spiritual_roots()):
|
177 |
+
md += f"{root_names[i]}: {v:.2%}\n\n"
|
178 |
+
|
179 |
+
# md += "### Plotting graphs...\n\n"
|
180 |
+
# md += "#### Attribute Distribution\n\n"
|
181 |
+
# md += "![Attribute Distribution](./attribute_distribution.png)\n\n"
|
182 |
+
# md += "#### Spiritual Roots Distribution\n\n"
|
183 |
+
# md += "![Spiritual Roots Distribution](./spiritual_roots_distribution.png)\n\n"
|
184 |
+
# md += "#### Sum Spiritual Roots Distribution\n\n"
|
185 |
+
# md += "![Sum Spiritual Roots Distribution](./sum_spiritual_roots_distribution.png)\n\n"
|
186 |
+
# md += "#### Clan Size Distribution\n\n"
|
187 |
+
# md += "![Clan Size Distribution](./clan_size_distribution.png)\n\n"
|
188 |
+
md += "#### Top Cultivators\n\n"
|
189 |
+
md += self.print_top_cultivators_markdown()
|
190 |
+
md += "#### Attack Power Ranking\n\n"
|
191 |
+
md += self.print_rank_markdown('attack_power', name='Attack Power')
|
192 |
+
md += "#### Defense Power Ranking\n\n"
|
193 |
+
md += self.print_rank_markdown('defense_power', name='Defense Power')
|
194 |
+
md += "#### Top Clans\n\n"
|
195 |
+
md += self.print_top_clans_markdown()
|
196 |
+
|
197 |
+
return md
|
198 |
+
|
199 |
+
def print_top_cultivators(self, top_n=10):
|
200 |
+
|
201 |
+
print("\n== Top Cultivators ==")
|
202 |
+
print("{:<10} {:>10} {:>10} {:>10} {:>10}".format('Name', 'Clan', 'Rank', 'Level', '境界'))
|
203 |
+
for c in self.find_highest_cultivation(top_n):
|
204 |
+
print("{:<10} {:>10} {:>10} {:>10} {:>10}".format(c.name, c.clan, c.cultivation_rank, c.cultivation_level, c.view_rank()))
|
205 |
+
|
206 |
+
def print_top_cultivators_markdown(self, n=10):
|
207 |
+
md = f"| Name | Clan | Cultivation Rank | Cultivation Level | 境界\n"
|
208 |
+
md += "| ---- | ---- | ---------------- | ---------------- | ----\n"
|
209 |
+
for c in self.find_highest_cultivation(n):
|
210 |
+
md += f"| {c.name} | {c.clan} | {c.cultivation_rank:.2f} | {c.cultivation_level:.2f} | {c.view_rank()}\n"
|
211 |
+
return md
|
212 |
+
|
213 |
+
def print_rank(self, rank_key, top_n=10, name=None):
|
214 |
+
|
215 |
+
print(f"\n== {'Name' if name is None else name} Rank ==")
|
216 |
+
|
217 |
+
sorted_characters = sorted(self.characters, key=lambda c: c.combat_power[rank_key], reverse=True)
|
218 |
+
|
219 |
+
print("{:<10} {:>10} {:>10} {:>10} {:>10}".format('Name', rank_key if name is None else name, 'Rank', 'Level', '境界'))
|
220 |
+
|
221 |
+
for c in sorted_characters[:top_n]:
|
222 |
+
print("{:<10} {:>10}{:>10} {:>10} {:>10}".format(c.name, c.combat_power[rank_key], c.cultivation_rank, c.cultivation_level, c.view_rank()))
|
223 |
+
|
224 |
+
def print_rank_markdown(self, attr, name=None, n=10):
|
225 |
+
md = f"| Name | {attr if name is None else name} | Cultivation Rank | Cultivation Level | 境界\n"
|
226 |
+
md += "| ---- | ---- | ---------------- | ---------------- | ----\n"
|
227 |
+
sorted_characters = sorted(self.characters, key=lambda c: c.combat_power[attr], reverse=True)
|
228 |
+
for c in sorted_characters[:n]:
|
229 |
+
md += f"| {c.name} | {c.combat_power[attr]:.2f} | {c.cultivation_rank:.2f} | {c.cultivation_level:.2f} | {c.view_rank()}\n"
|
230 |
+
return md
|
231 |
+
|
232 |
+
def print_top_clans(self, top_n=4):
|
233 |
+
|
234 |
+
print("\n== Top Clans ==")
|
235 |
+
print("{:<10} {:>10}".format('Clan', 'Members'))
|
236 |
+
|
237 |
+
for clan, size in self.rank_top_clans(top_n):
|
238 |
+
print("{:<10} {:>10}".format(clan, size))
|
239 |
+
|
240 |
+
def print_top_clans_markdown(self, n=10):
|
241 |
+
md = "| Clan | Clan Size |\n"
|
242 |
+
md += "| -------- | --------- |\n"
|
243 |
+
for clan, size in self.rank_top_clans(n):
|
244 |
+
md += f"| {clan} | {size} |\n"
|
245 |
+
return md
|
PluginManager.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class PluginManager:
|
2 |
+
def __init__(self):
|
3 |
+
self.plugins = []
|
4 |
+
|
5 |
+
def add_plugin(self, plugin):
|
6 |
+
self.plugins.append(plugin)
|
7 |
+
|
8 |
+
def remove_plugin(self, plugin):
|
9 |
+
self.plugins.remove(plugin)
|
10 |
+
|
11 |
+
def execute_plugins(self, *args, **kwargs):
|
12 |
+
for plugin in self.plugins:
|
13 |
+
plugin.execute(*args, **kwargs)
|
WorldSimulation.py
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from config import DISASTER_FREQUENCY, MARRIAGE_RATE, DISASTER_PROB, NORMAL_BIRTH_RATE, LOW_BIRTH_RATE, NORMAL_BATTLE_RATE, HIGH_BATTLE_RATE
|
2 |
+
from Character import Character
|
3 |
+
from plugins.CharacterCreationPlugin import CharacterCreationPlugin
|
4 |
+
from plugins.CultivationPlugin import CultivationPlugin
|
5 |
+
from plugins.MarriagePlugin import MarriagePlugin
|
6 |
+
from plugins.BirthPlugin import BirthPlugin
|
7 |
+
from plugins.CharacterGrowthPlugin import CharacterGrowthPlugin
|
8 |
+
from plugins.DisasterPlugin import DisasterPlugin
|
9 |
+
from plugins.BattlePlugin import BattlePlugin
|
10 |
+
from plugins.ResourceDepletionPlugin import ResourceDepletionPlugin
|
11 |
+
|
12 |
+
class WorldSimulation:
|
13 |
+
def __init__(self, special_constitution_ratio, spiritual_roots_ratio, initial_population, world_spiritual_energy, resources = 100000, resources_threshold = 10):
|
14 |
+
# 初始化插件
|
15 |
+
self.character_creation_plugin = CharacterCreationPlugin(special_constitution_ratio, spiritual_roots_ratio)
|
16 |
+
self.cultivation_plugin = CultivationPlugin()
|
17 |
+
self.marriage_plugin = MarriagePlugin(MARRIAGE_RATE)
|
18 |
+
self.birth_plugin = BirthPlugin(NORMAL_BIRTH_RATE)
|
19 |
+
self.character_growth_plugin = CharacterGrowthPlugin(100)
|
20 |
+
self.disaster_plugin = DisasterPlugin(DISASTER_FREQUENCY, DISASTER_PROB)
|
21 |
+
self.battle_plugin = BattlePlugin(NORMAL_BATTLE_RATE)
|
22 |
+
self.resource_depletion_plugin = ResourceDepletionPlugin(resources_threshold)
|
23 |
+
|
24 |
+
# 初始化参数
|
25 |
+
self.initial_population = initial_population
|
26 |
+
self.resources = resources # 总资源量
|
27 |
+
self.resources_threshold = resources_threshold # 人均最少资源需求
|
28 |
+
self.init_world_spiritual_energy = world_spiritual_energy
|
29 |
+
self.world_spiritual_energy = world_spiritual_energy
|
30 |
+
self.characters = []
|
31 |
+
self.dead_characters = []
|
32 |
+
self.nth_round = 0
|
33 |
+
self.world_log = []
|
34 |
+
|
35 |
+
def log(self, msg):
|
36 |
+
self.world_log.append(msg)
|
37 |
+
|
38 |
+
def simulate_round(self):
|
39 |
+
self.character_growth_plugin.execute(self.characters, self.character_die)
|
40 |
+
self.disaster_plugin.execute(self.characters, self.nth_round, self.character_die)
|
41 |
+
|
42 |
+
# 每人可用资源
|
43 |
+
per_capita_resources = self.resources / len(self.characters)
|
44 |
+
if per_capita_resources < self.resources_threshold:
|
45 |
+
self.birth_plugin.set_birth_rate(LOW_BIRTH_RATE)
|
46 |
+
self.battle_plugin.set_battle_rate(HIGH_BATTLE_RATE)
|
47 |
+
else:
|
48 |
+
self.birth_plugin.set_birth_rate(NORMAL_BIRTH_RATE)
|
49 |
+
self.battle_plugin.set_battle_rate(NORMAL_BATTLE_RATE)
|
50 |
+
|
51 |
+
self.resource_depletion_plugin.execute(per_capita_resources, self.characters, self.character_die)
|
52 |
+
|
53 |
+
self.battle_plugin.execute(self.characters, self.character_die)
|
54 |
+
self.birth_plugin.execute(self.characters)
|
55 |
+
self.marriage_plugin.execute(self.characters)
|
56 |
+
self.cultivation_plugin.execute(self.characters, self.world_spiritual_energy, self.init_world_spiritual_energy, self.consume_spiritual_energy)
|
57 |
+
|
58 |
+
def start_simulation(self):
|
59 |
+
self.nth_round = 0
|
60 |
+
self.create_initial_population()
|
61 |
+
|
62 |
+
def run_simulation(self, num_rounds):
|
63 |
+
md = ""
|
64 |
+
for _ in range(num_rounds):
|
65 |
+
self.nth_round += 1
|
66 |
+
print(f"第{self.nth_round}轮模拟:")
|
67 |
+
md += f"第{self.nth_round}轮模拟:\n"
|
68 |
+
self.simulate_round()
|
69 |
+
print(f"当前世界灵气总量:{self.world_spiritual_energy}")
|
70 |
+
print(f"当前存活角色数量:{len(self.characters)}")
|
71 |
+
print("")
|
72 |
+
md += f"当前世界灵气总量:{self.world_spiritual_energy}\n"
|
73 |
+
md += f"当前存活角色数量:{len(self.characters)}\n\n"
|
74 |
+
if len(self.characters) == 0:
|
75 |
+
print("所有角色死亡,模拟结束")
|
76 |
+
md += "所有角色死亡,模拟结束\n"
|
77 |
+
break
|
78 |
+
return md
|
79 |
+
|
80 |
+
def add_custom_character(self, name, gender, special_constitution, spiritual_roots):
|
81 |
+
character = Character(name, gender, special_constitution, spiritual_roots)
|
82 |
+
self.characters.append(character)
|
83 |
+
|
84 |
+
def create_initial_population(self):
|
85 |
+
# 创建角色
|
86 |
+
for _ in range((self.initial_population - len(self.characters))):
|
87 |
+
character = self.character_creation_plugin.create_character()
|
88 |
+
self.characters.append(character)
|
89 |
+
|
90 |
+
def character_die(self, character):
|
91 |
+
character.die()
|
92 |
+
self.world_spiritual_energy += character.consume_spiritual_energy # 灵气回归
|
93 |
+
if character.partner:
|
94 |
+
character.partner.history.append(f"{character.partner.real_age}岁,配偶{character.name}({character.real_age}岁)死亡")
|
95 |
+
character.partner.partner = None # 解除配偶关系
|
96 |
+
self.characters.remove(character)
|
97 |
+
self.dead_characters.append(character)
|
98 |
+
|
99 |
+
def consume_spiritual_energy(self, amount):
|
100 |
+
self.world_spiritual_energy -= amount
|
app.py
ADDED
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from CharacterStatistics import CharacterStatistics
|
3 |
+
from WorldSimulation import WorldSimulation
|
4 |
+
|
5 |
+
# 创建一个世界模拟对象
|
6 |
+
simulation = None
|
7 |
+
num_rounds = 0
|
8 |
+
|
9 |
+
def initialize_world(c1, c2, c3, c4, r1, r2, r3, r4, r5, initial_population=3000, world_spiritual_energy=1000000):
|
10 |
+
global simulation
|
11 |
+
special_constitution_ratio = [c1, c2, c3, c4]
|
12 |
+
spiritual_roots_ratio = [r1, r2, r3, r4, r5]
|
13 |
+
initial_population = int(initial_population)
|
14 |
+
simulation = WorldSimulation(special_constitution_ratio, spiritual_roots_ratio, initial_population, world_spiritual_energy)
|
15 |
+
|
16 |
+
return f"世界初始化成功:\n\n初始人口:{initial_population}\n\n世界灵气能量:{world_spiritual_energy}"
|
17 |
+
|
18 |
+
|
19 |
+
def add_custom_character(name, gender, special_constitution, spiritual_roots):
|
20 |
+
special_constitution = [1 if i in special_constitution else 0 for i in range(4)]
|
21 |
+
spiritual_roots = [1 if i in spiritual_roots else 0 for i in range(5)]
|
22 |
+
simulation.add_custom_character(name, gender, special_constitution, spiritual_roots)
|
23 |
+
return str(simulation.characters[-1])
|
24 |
+
|
25 |
+
def start_simulation():
|
26 |
+
simulation.start_simulation()
|
27 |
+
return f"世界人口数量达到{len(simulation.characters)},可以运行模拟了。"
|
28 |
+
|
29 |
+
def run_simulation(rounds):
|
30 |
+
global num_rounds
|
31 |
+
num_rounds = rounds
|
32 |
+
if simulation is None:
|
33 |
+
return "请先初始化世界"
|
34 |
+
return simulation.run_simulation(num_rounds)
|
35 |
+
|
36 |
+
def get_character_info(character_index):
|
37 |
+
if character_index < len(simulation.characters):
|
38 |
+
return str(simulation.characters[character_index])
|
39 |
+
else:
|
40 |
+
return f"没有第{character_index}个角色,目前共有{len(simulation.characters)}个存活角色"
|
41 |
+
|
42 |
+
def get_dead_character_info(character_index):
|
43 |
+
if character_index < len(simulation.dead_characters):
|
44 |
+
return str(simulation.dead_characters[character_index])
|
45 |
+
else:
|
46 |
+
return f"没有第{character_index}个死亡角色,目前共有{len(simulation.dead_characters)}个死亡角色"
|
47 |
+
|
48 |
+
def get_world_stats():
|
49 |
+
stats = CharacterStatistics(simulation.characters)
|
50 |
+
return stats.summarize_markdown()
|
51 |
+
|
52 |
+
with gr.Blocks() as demo:
|
53 |
+
# 初始化世界
|
54 |
+
gr.Markdown("## 初始化世界")
|
55 |
+
with gr.Row():
|
56 |
+
initial_population_input = gr.Number(label="初始人口", value=3000, precision=0)
|
57 |
+
world_spiritual_energy_input = gr.Number(label="世界灵气能量", value=1000000)
|
58 |
+
# 特殊体质
|
59 |
+
gr.Markdown("特殊体质比例")
|
60 |
+
with gr.Row():
|
61 |
+
special_constitution_ratio_input = [
|
62 |
+
gr.Number(label="战斗体质", minimum=0, maximum=1, value=0.001),
|
63 |
+
gr.Number(label="合欢体质", minimum=0, maximum=1, value=0.001),
|
64 |
+
gr.Number(label="灵龟体质", minimum=0, maximum=1, value=0.001),
|
65 |
+
gr.Number(label="蜉蝣体质", minimum=0, maximum=1, value=0.001)]
|
66 |
+
# 灵根
|
67 |
+
gr.Markdown("灵根比例")
|
68 |
+
with gr.Row():
|
69 |
+
spiritual_roots_ratio_input = [
|
70 |
+
gr.Number(label="金", minimum=0, maximum=1, value=0.04),
|
71 |
+
gr.Number(label="木", minimum=0, maximum=1, value=0.04),
|
72 |
+
gr.Number(label="水", minimum=0, maximum=1, value=0.04),
|
73 |
+
gr.Number(label="火", minimum=0, maximum=1, value=0.04),
|
74 |
+
gr.Number(label="土", minimum=0, maximum=1, value=0.04)]
|
75 |
+
initialize_button = gr.Button("初始化世界")
|
76 |
+
output_text = gr.Markdown(label="初始化结果")
|
77 |
+
initialize_button.click(fn=initialize_world, inputs=[*special_constitution_ratio_input, *spiritual_roots_ratio_input, initial_population_input, world_spiritual_energy_input], outputs=output_text)
|
78 |
+
|
79 |
+
# 添加角色
|
80 |
+
with gr.Accordion("添加角色"):
|
81 |
+
gr.Markdown("每次添加一个,添加完可以继续添加。")
|
82 |
+
name_input = gr.Textbox(label="姓名")
|
83 |
+
gender_input = gr.Radio(["男", "女"], label="性别")
|
84 |
+
special_constitution_input = gr.CheckboxGroup(["战斗体质", "合欢体质", "灵龟体质", "蜉蝣体质"], label="特殊体质", type="index")
|
85 |
+
spiritual_roots_input = gr.CheckboxGroup(["金", "木", "水", "火", "土"], label="灵根", type="index")
|
86 |
+
add_character_button = gr.Button("添加角色")
|
87 |
+
new_character_info_output = gr.Textbox(label="新角色信息")
|
88 |
+
add_character_button.click(fn=add_custom_character, inputs=[name_input,
|
89 |
+
gender_input,
|
90 |
+
special_constitution_input, spiritual_roots_input], outputs=new_character_info_output)
|
91 |
+
|
92 |
+
# 开始模拟
|
93 |
+
start_simulation_button = gr.Button("生成初始人口")
|
94 |
+
output_text = gr.Markdown("")
|
95 |
+
start_simulation_button.click(fn=start_simulation, inputs=[], outputs=output_text)
|
96 |
+
|
97 |
+
# 运行模拟
|
98 |
+
gr.Markdown("## 运行模拟\n\n输入轮数,运行模拟,可以多次运行。")
|
99 |
+
with gr.Row():
|
100 |
+
rounds_input = gr.Number(label="轮数", precision=0, value=1)
|
101 |
+
run_simulation_button = gr.Button("运行模拟")
|
102 |
+
simulation_result_output = gr.Textbox(label="模拟结果")
|
103 |
+
run_simulation_button.click(fn=run_simulation, inputs=[rounds_input], outputs=[simulation_result_output])
|
104 |
+
|
105 |
+
# 查看统计信息
|
106 |
+
gr.Markdown("## 查看统计信息")
|
107 |
+
get_stats_button = gr.Button("查看统计信息")
|
108 |
+
with gr.Box():
|
109 |
+
world_stats_output = gr.Markdown("")
|
110 |
+
get_stats_button.click(fn=get_world_stats, inputs=[], outputs=[world_stats_output])
|
111 |
+
|
112 |
+
# 查看角色信息
|
113 |
+
with gr.Row():
|
114 |
+
character_info_output = gr.Number(label="角色信息", info="输入角色编号", precision=0)
|
115 |
+
get_character_info_button = gr.Button("查看角色信息")
|
116 |
+
character_info_output_display = gr.Textbox(label="角色信息")
|
117 |
+
get_character_info_button.click(fn=get_character_info, inputs=[character_info_output], outputs=[character_info_output_display])
|
118 |
+
|
119 |
+
with gr.Row():
|
120 |
+
dead_character_info_output = gr.Number(label="死亡角色信息", info="输入死亡角色编号", precision=0)
|
121 |
+
get_dead_character_info_button = gr.Button("查看死亡角色信息")
|
122 |
+
dead_character_info_output_display = gr.Textbox(label="死亡角色信息")
|
123 |
+
get_dead_character_info_button.click(fn=get_dead_character_info, inputs=[dead_character_info_output], outputs=[dead_character_info_output_display])
|
124 |
+
|
125 |
+
demo.launch(debug=True)
|
config.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
P_DIE_WHEN_LOSE = 0.6 # 战败死亡的概率
|
2 |
+
MAX_BATTLE_ROUND = 100 # 战斗最大回合数
|
3 |
+
MARRIAGE_RATE = 0.3 # 每名适龄青年每次模拟找对象的概率
|
4 |
+
|
5 |
+
# 当每人资源大于阈值时,生育率正常,战斗率正常
|
6 |
+
NORMAL_BIRTH_RATE = 0.2
|
7 |
+
NORMAL_BATTLE_RATE = 0.3
|
8 |
+
|
9 |
+
# 当每人资源低于阈值时,生育率线性下降,战斗率线性上升
|
10 |
+
LOW_BIRTH_RATE = 0.05
|
11 |
+
HIGH_BATTLE_RATE = 0.8
|
12 |
+
|
13 |
+
# 每10轮有5%概率发生灾难
|
14 |
+
DISASTER_PROB = 0.05
|
15 |
+
DISASTER_FREQUENCY = 10
|
16 |
+
|
17 |
+
# 每50轮有10%概率发生战争
|
18 |
+
WAR_PROB = 0.1
|
19 |
+
WAR_FREQUENCY = 50
|
20 |
+
|
21 |
+
# 成仙等级
|
22 |
+
IMMORTAL_RANK = 5
|
plugins/BattlePlugin.py
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
|
3 |
+
class BattlePlugin:
|
4 |
+
def __init__(self, probability, min_battle_age=10, max_battle_round=100):
|
5 |
+
self.probability = probability
|
6 |
+
self.min_battle_age = min_battle_age # 最小参战年龄
|
7 |
+
self.max_battle_round = max_battle_round # 最大战斗回合数
|
8 |
+
|
9 |
+
def simulate_battle(self, character, opponent):
|
10 |
+
battle_round = 0
|
11 |
+
|
12 |
+
character.before_battle()
|
13 |
+
opponent.before_battle()
|
14 |
+
while character.check_is_alive() and opponent.check_is_alive() and battle_round < self.max_battle_round:
|
15 |
+
battle_round += 1
|
16 |
+
|
17 |
+
# 根据攻击速度确定行动顺序
|
18 |
+
if character.combat_power["attack_speed"] >= opponent.combat_power["attack_speed"]:
|
19 |
+
character.attack(opponent)
|
20 |
+
if opponent.check_is_alive():
|
21 |
+
opponent.attack(character)
|
22 |
+
else:
|
23 |
+
opponent.attack(character)
|
24 |
+
if character.check_is_alive():
|
25 |
+
character.attack(opponent)
|
26 |
+
|
27 |
+
# 胜利或平手判断
|
28 |
+
if character.check_is_alive() and opponent.check_is_alive():
|
29 |
+
# print(f"{character.name}和{opponent.name}打成平手!")
|
30 |
+
return None
|
31 |
+
elif character.check_is_alive():
|
32 |
+
print(f"{character.name}战胜了{opponent.name}!")
|
33 |
+
return (character, opponent)
|
34 |
+
elif opponent.check_is_alive():
|
35 |
+
print(f"{opponent.name}战胜了{character.name}!")
|
36 |
+
return (opponent, character)
|
37 |
+
|
38 |
+
def perform_battles(self, characters, character_die):
|
39 |
+
eligible_characters = [character for character in characters if character.apparent_age > self.min_battle_age]
|
40 |
+
for _ in range(int(len(eligible_characters) * self.probability)):
|
41 |
+
character = random.choice(eligible_characters)
|
42 |
+
opponent = random.choice(eligible_characters)
|
43 |
+
if character != opponent and character.clan != opponent.clan:
|
44 |
+
result = self.simulate_battle(character, opponent)
|
45 |
+
if result is not None:
|
46 |
+
(winner, loser) = result
|
47 |
+
winner.cultivate(100)
|
48 |
+
winner.history.append(f"{winner.real_age}岁({winner.view_rank()}),战胜了{loser.name}({loser.view_rank()})")
|
49 |
+
loser.history.append(f"{loser.real_age}岁({loser.view_rank()}),被{winner.name}({winner.view_rank()})打败了")
|
50 |
+
if not loser.is_alive:
|
51 |
+
print(f"{loser.name}因失血过多死亡了!")
|
52 |
+
character_die(loser)
|
53 |
+
return
|
54 |
+
|
55 |
+
def set_battle_rate(self, probability):
|
56 |
+
self.probability = probability
|
57 |
+
|
58 |
+
# 统一插件接口
|
59 |
+
def execute(self, *args, **kwargs):
|
60 |
+
self.perform_battles(*args, **kwargs)
|
plugins/BirthPlugin.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
|
3 |
+
class BirthPlugin:
|
4 |
+
def __init__(self, birth_rate, min_birth_age=20, birth_age_range=15):
|
5 |
+
self.birth_rate = birth_rate
|
6 |
+
self.birth_age = min_birth_age
|
7 |
+
self.birth_age_range = birth_age_range
|
8 |
+
|
9 |
+
def perform_births(self, characters):
|
10 |
+
new_birth_count = 0
|
11 |
+
for character in characters:
|
12 |
+
if character.partner and character.apparent_age > self.birth_age and character.apparent_age < self.birth_age + self.birth_age_range and character.partner.apparent_age > self.birth_age and character.partner.apparent_age < self.birth_age + self.birth_age_range:
|
13 |
+
if random.random() < self.birth_rate / 2:
|
14 |
+
child = character.give_birth()
|
15 |
+
if child:
|
16 |
+
characters.append(child)
|
17 |
+
new_birth_count += 1
|
18 |
+
|
19 |
+
print(f"共{new_birth_count}对夫妻生了孩子!")
|
20 |
+
|
21 |
+
def set_birth_rate(self, birth_rate):
|
22 |
+
self.birth_rate = birth_rate
|
23 |
+
|
24 |
+
# 统一插件接口
|
25 |
+
def execute(self, *args, **kwargs):
|
26 |
+
self.perform_births(*args, **kwargs)
|
plugins/CharacterCreationPlugin.py
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
from Character import Character
|
3 |
+
from utils import get_random_name
|
4 |
+
|
5 |
+
class CharacterCreationPlugin:
|
6 |
+
def __init__(self, special_constitution_ratio, spiritual_roots_ratio):
|
7 |
+
self.special_constitution_ratio = special_constitution_ratio
|
8 |
+
self.spiritual_roots_ratio = spiritual_roots_ratio
|
9 |
+
|
10 |
+
def create_character(self):
|
11 |
+
# 根据 special_constitution_ratio 随机确定这个角色拥有哪些特殊体质
|
12 |
+
special_constitution = [1 if random.random() < ratio else 0 for ratio in self.special_constitution_ratio]
|
13 |
+
|
14 |
+
# 根据 spiritual_roots_ratio 随机确定这个角色拥有哪些灵根
|
15 |
+
spiritual_roots = [1 if random.random() < ratio else 0 for ratio in self.spiritual_roots_ratio]
|
16 |
+
|
17 |
+
character = Character(get_random_name(), random.choice(["男", "女"]), special_constitution, spiritual_roots)
|
18 |
+
return character
|
plugins/CharacterGrowthPlugin.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
class CharacterGrowthPlugin:
|
3 |
+
def __init__(self, max_apparent_age):
|
4 |
+
self.max_apparent_age = max_apparent_age
|
5 |
+
|
6 |
+
def grow_characters(self, characters, character_die_callback):
|
7 |
+
die_count = 0
|
8 |
+
for character in characters:
|
9 |
+
# 检查角色是否存活
|
10 |
+
if not character.is_alive:
|
11 |
+
print(f"{character.name}死亡了!")
|
12 |
+
character_die_callback(character)
|
13 |
+
continue
|
14 |
+
|
15 |
+
# 角色成长
|
16 |
+
character.grow()
|
17 |
+
|
18 |
+
# 表观年龄>60岁的角色有死亡风险,年纪越大风险越高,>100岁99%会死亡
|
19 |
+
if character.apparent_age > 60:
|
20 |
+
death_probability = min(0.01 * (character.apparent_age - 60), 0.99)
|
21 |
+
if random.random() < death_probability:
|
22 |
+
character_die_callback(character)
|
23 |
+
die_count += 1
|
24 |
+
print(f"{die_count}人寿终正寝了")
|
25 |
+
|
26 |
+
def execute(self, *args, **kwargs):
|
27 |
+
self.grow_characters(*args, **kwargs)
|
28 |
+
|
plugins/CultivationPlugin.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
class CultivationPlugin:
|
3 |
+
def __init__(self, cultivation_speed=1.0):
|
4 |
+
self.cultivation_speed = cultivation_speed
|
5 |
+
|
6 |
+
def cultivate_characters(self, characters, world_spiritual_energy, init_world_spiritual_energy, consume_spiritual_energy_callback):
|
7 |
+
for character in characters:
|
8 |
+
if sum(character.spiritual_roots) > 0:
|
9 |
+
cultivation_speed = self.cultivation_speed * [0, 1.2, 1, 0.8, 0.6, 0.5][sum(character.spiritual_roots)] # 灵根数量惩罚
|
10 |
+
|
11 |
+
# 根据特殊体质修炼速度进行调整
|
12 |
+
if character.special_constitution[2] == 1: # 灵龟体质
|
13 |
+
cultivation_speed *= 0.5
|
14 |
+
elif character.special_constitution[3] == 1: # 蜉蝣体质
|
15 |
+
cultivation_speed *= 2
|
16 |
+
|
17 |
+
if world_spiritual_energy > 0:
|
18 |
+
cultivation_speed *= world_spiritual_energy / init_world_spiritual_energy
|
19 |
+
success_rate = 1 - 0.2 * random.random()
|
20 |
+
character.cultivate(1000 * cultivation_speed * success_rate)
|
21 |
+
|
22 |
+
consume_amount = 10 * cultivation_speed * success_rate
|
23 |
+
consume_spiritual_energy_callback(consume_amount) # 消耗灵气
|
24 |
+
character.consume_spiritual_energy += consume_amount
|
25 |
+
|
26 |
+
else:
|
27 |
+
# 没有灵气,无法修炼了
|
28 |
+
pass
|
29 |
+
|
30 |
+
def execute(self, characters, world_spiritual_energy, init_world_spiritual_energy, consume_spiritual_energy):
|
31 |
+
self.cultivate_characters(characters, world_spiritual_energy, init_world_spiritual_energy, consume_spiritual_energy)
|
plugins/DisasterPlugin.py
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
|
3 |
+
class DisasterPlugin:
|
4 |
+
def __init__(self, disaster_frequency, disaster_prob):
|
5 |
+
self.disaster_frequency = disaster_frequency
|
6 |
+
self.disaster_prob = disaster_prob
|
7 |
+
|
8 |
+
def trigger_disaster(self, characters, character_die_callback):
|
9 |
+
print("发生自然灾难...")
|
10 |
+
num_killed = int(len(characters) * self.disaster_prob)
|
11 |
+
killed = random.sample(characters, num_killed)
|
12 |
+
for c in killed:
|
13 |
+
c.history.append(f"{c.real_age}岁,死于自然灾难")
|
14 |
+
character_die_callback(c)
|
15 |
+
|
16 |
+
def execute(self, characters, round_num, character_die_callback):
|
17 |
+
if round_num % self.disaster_frequency == 0 and random.random() < self.disaster_prob:
|
18 |
+
self.trigger_disaster(characters, character_die_callback)
|
plugins/MarriagePlugin.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
class MarriagePlugin:
|
3 |
+
def __init__(self, marriage_rate):
|
4 |
+
self.marriage_rate = marriage_rate
|
5 |
+
|
6 |
+
def perform_marriages(self, characters):
|
7 |
+
# 随机一些表观年龄>20的角色结婚
|
8 |
+
eligible_characters = [character for character in characters if character.apparent_age > 20 and character.partner is None]
|
9 |
+
new_couple_count = 0
|
10 |
+
for _ in range(int(len(eligible_characters) * self.marriage_rate)):
|
11 |
+
character = random.choice(eligible_characters)
|
12 |
+
partner = random.choice(eligible_characters)
|
13 |
+
if character != partner and character.gender != partner.gender and character.partner is None and partner.partner is None:
|
14 |
+
character.marry(partner)
|
15 |
+
new_couple_count += 1
|
16 |
+
print(f"{new_couple_count}对新人结婚了")
|
17 |
+
|
18 |
+
def set_marriage_rate(self, marriage_rate):
|
19 |
+
self.marriage_rate = marriage_rate
|
20 |
+
|
21 |
+
# 统一插件接口
|
22 |
+
def execute(self, *args, **kwargs):
|
23 |
+
self.perform_marriages(*args, **kwargs)
|
plugins/ResourceDepletionPlugin.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
class ResourceDepletionPlugin:
|
3 |
+
def __init__(self, depletion_threshold, death_rate = 0.3):
|
4 |
+
self.depletion_threshold = depletion_threshold
|
5 |
+
self.death_rate = death_rate
|
6 |
+
|
7 |
+
def trigger_disaster(self, characters, character_die_callback):
|
8 |
+
print("资源耗尽! 发生灾难...")
|
9 |
+
|
10 |
+
# 按照宗族大小排序
|
11 |
+
clan_size = {}
|
12 |
+
for c in characters:
|
13 |
+
clan_size[c.clan] = clan_size.get(c.clan, 0) + 1
|
14 |
+
|
15 |
+
clans = sorted(clan_size.items(), key=lambda x: x[1], reverse=True)
|
16 |
+
|
17 |
+
# 前 50% 大小的宗族,死亡 30%, 后 50% 大小的宗族,死亡 60%
|
18 |
+
for i, (clan, size) in enumerate(clans):
|
19 |
+
if i < len(clans) // 2:
|
20 |
+
num_killed = int(size * self.death_rate)
|
21 |
+
else:
|
22 |
+
num_killed = int(size * (self.death_rate * 2))
|
23 |
+
|
24 |
+
# 随机选择死亡成员
|
25 |
+
clan_members = [c for c in characters if c.clan == clan]
|
26 |
+
killed = random.sample(clan_members, num_killed)
|
27 |
+
|
28 |
+
for c in killed:
|
29 |
+
character_die_callback(c)
|
30 |
+
|
31 |
+
def execute(self, resources, characters, character_die_callback):
|
32 |
+
# 检查资源是否耗尽,如果耗尽则触发灾难
|
33 |
+
if resources < self.depletion_threshold:
|
34 |
+
self.trigger_disaster(characters, character_die_callback)
|
requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
numpy
|
3 |
+
matplotlib
|
run.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from CharacterStatistics import CharacterStatistics
|
2 |
+
from WorldSimulation import WorldSimulation
|
3 |
+
|
4 |
+
# 创建一个世界模拟对象
|
5 |
+
simulation = WorldSimulation(special_constitution_ratio=[0.001, 0.001, 0.001, 0.001], spiritual_roots_ratio=[0.04, 0.04, 0.04, 0.04, 0.04], initial_population=3000, world_spiritual_energy=1000000)
|
6 |
+
|
7 |
+
# 运行模拟
|
8 |
+
simulation.add_custom_character("CoraBaby1", "女", special_constitution=[1, 0, 0, 1], spiritual_roots=[1, 1, 1, 1, 1])
|
9 |
+
simulation.add_custom_character("CoraBaby2", "女", special_constitution=[0, 1, 0, 1], spiritual_roots=[1, 1, 1, 1, 1])
|
10 |
+
|
11 |
+
simulation.start_simulation()
|
12 |
+
simulation.run_simulation(num_rounds=10)
|
13 |
+
|
14 |
+
# 查看世界统计
|
15 |
+
stats = CharacterStatistics(simulation.characters)
|
16 |
+
print(stats.summarize_markdown())
|
17 |
+
|
18 |
+
print(simulation.characters[0])
|
19 |
+
print(simulation.characters[1])
|
20 |
+
|
21 |
+
print(simulation.dead_characters[0])
|
22 |
+
print(simulation.dead_characters[1])
|
utils.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import string
|
2 |
+
import random
|
3 |
+
|
4 |
+
def get_random_name():
|
5 |
+
vowels = "aeiou"
|
6 |
+
consonants = "".join(set(string.ascii_lowercase) - set(vowels))
|
7 |
+
name = random.choice(consonants).upper()
|
8 |
+
name += random.choice(vowels)
|
9 |
+
name += random.choice(consonants)
|
10 |
+
name += random.choice(vowels)
|
11 |
+
|
12 |
+
return name
|
13 |
+
|
14 |
+
def get_random_clan_name():
|
15 |
+
vowels = "aeiou"
|
16 |
+
consonants = "".join(set(string.ascii_lowercase) - set(vowels))
|
17 |
+
name = random.choice(consonants).upper()
|
18 |
+
name += random.choice(vowels)
|
19 |
+
name += random.choice(consonants)
|
20 |
+
name += random.choice(vowels)
|
21 |
+
name += random.choice(consonants)
|
22 |
+
name += random.choice(vowels)
|
23 |
+
name += random.choice(consonants)
|
24 |
+
name += random.choice(vowels)
|
25 |
+
|
26 |
+
return name
|