From 31a137951e89448b2bca42ceab9cf278bb7f29cb Mon Sep 17 00:00:00 2001 From: OneBST Date: Fri, 10 Mar 2023 21:30:56 +0800 Subject: [PATCH] add artifacts calc example, organize code about artifacts, add genshin artifacts data --- GGanalysis/__init__.py | 3 +- GGanalysis/games/arknights/gacha_model.py | 45 +-- GGanalysis/games/genshin_impact/__init__.py | 2 +- .../games/genshin_impact/artifact_data.py | 122 +++++++- .../games/genshin_impact/artifact_model.py | 176 ++++++++--- .../games/genshin_impact/gacha_model.py | 66 ++-- GGanalysis/scored_item.py | 291 +++--------------- GGanalysis/scored_item_tools.py | 223 ++++++++++++++ artifact_example.py | 39 +++ example.py => gacha_example.py | 0 10 files changed, 624 insertions(+), 343 deletions(-) create mode 100644 GGanalysis/scored_item_tools.py create mode 100644 artifact_example.py rename example.py => gacha_example.py (100%) diff --git a/GGanalysis/__init__.py b/GGanalysis/__init__.py index 2ee51a4..7bb5970 100644 --- a/GGanalysis/__init__.py +++ b/GGanalysis/__init__.py @@ -5,4 +5,5 @@ from GGanalysis.distribution_1d import * from GGanalysis.gacha_layers import * from GGanalysis.basic_models import * -from GGanalysis.stationary_dist import * \ No newline at end of file +from GGanalysis.stationary_dist import * +from GGanalysis.scored_item import * \ No newline at end of file diff --git a/GGanalysis/games/arknights/gacha_model.py b/GGanalysis/games/arknights/gacha_model.py index 13a5a29..7d6dbcf 100644 --- a/GGanalysis/games/arknights/gacha_model.py +++ b/GGanalysis/games/arknights/gacha_model.py @@ -11,8 +11,9 @@ ''' __all__ = [ - 'pity_6star', - 'pity_5star', + 'PITY_6STAR', + 'PITY_5STAR', + 'P_5STAR_AVG', 'common_6star', 'single_up_6star', 'dual_up_specific_6star', @@ -24,25 +25,27 @@ ] # 设置六星概率递增表 -pity_6star = np.zeros(100) -pity_6star[1:51] = 0.02 -pity_6star[51:99] = np.arange(1, 49) * 0.02 + 0.02 -pity_6star[99] = 1 +PITY_6STAR = np.zeros(100) +PITY_6STAR[1:51] = 0.02 +PITY_6STAR[51:99] = np.arange(1, 49) * 0.02 + 0.02 +PITY_6STAR[99] = 1 # 设置五星概率递增表(五星保底会被六星挤掉,所以需要做一点近似) -pity_5star = np.zeros(42, dtype=float) -pity_5star[:16] = 0.08 -pity_5star[16:21] = np.arange(1, 6) * 0.02 + 0.08 -pity_5star[21:41] = np.arange(1, 21) * 0.04 + 0.18 -pity_5star[41] = 1 +PITY_5STAR = np.zeros(42, dtype=float) +PITY_5STAR[:16] = 0.08 +PITY_5STAR[16:21] = np.arange(1, 6) * 0.02 + 0.08 +PITY_5STAR[21:41] = np.arange(1, 21) * 0.04 + 0.18 +PITY_5STAR[41] = 1 +# 设置五星综合概率,用于近似计算 +P_5STAR_AVG = 0.08948 # 获取普通六星 -common_6star = PityModel(pity_6star) +common_6star = PityModel(PITY_6STAR) # 获取单UP六星 -single_up_6star = PityBernoulliModel(pity_6star, 1/2) +single_up_6star = PityBernoulliModel(PITY_6STAR, 1/2) # 获取双UP六星中的特定六星 -dual_up_specific_6star = PityBernoulliModel(pity_6star, 1/4) +dual_up_specific_6star = PityBernoulliModel(PITY_6STAR, 1/4) # 获取限定UP六星中的限定六星 -limited_up_6star = PityBernoulliModel(pity_6star, 0.35) +limited_up_6star = PityBernoulliModel(PITY_6STAR, 0.35) # 方舟限定池同时获取限定六星及陪跑六星 class AK_Limit_Model(CommonGachaModel): @@ -63,15 +66,15 @@ def _build_parameter_list(self, pull_state: int=0, up_guarantee: int=0) -> list: ] return parameter_list # 同时获取两个UP六星 -both_up_6star = AK_Limit_Model(pity_6star, 0.7, collect_item=2) +both_up_6star = AK_Limit_Model(PITY_6STAR, 0.7, collect_item=2) -# 实际上五星综合概率为8.948% 但由于和概率上升相耦合,这里取公示的8%作为低值估算 +# 五星公示概率为的8%,实际上综合概率为8.948% 这里按照综合概率近似计算 # 获取普通五星 -common_5star = BernoulliGachaModel(0.08) +common_5star = BernoulliGachaModel(P_5STAR_AVG) # 获取单UP五星 -single_up_specific_5star = BernoulliGachaModel(0.08/2) +single_up_specific_5star = BernoulliGachaModel(P_5STAR_AVG/2) # 获取双UP五星中的特定五星 -dual_up_specific_5star = BernoulliGachaModel(0.08/2/2) +dual_up_specific_5star = BernoulliGachaModel(P_5STAR_AVG/2/2) # 获取三UP五星中的特定五星 -triple_up_specific_5star = BernoulliGachaModel(0.08/2/3) +triple_up_specific_5star = BernoulliGachaModel(P_5STAR_AVG/2/3) diff --git a/GGanalysis/games/genshin_impact/__init__.py b/GGanalysis/games/genshin_impact/__init__.py index ee51b53..156bd69 100644 --- a/GGanalysis/games/genshin_impact/__init__.py +++ b/GGanalysis/games/genshin_impact/__init__.py @@ -1,2 +1,2 @@ from .gacha_model import * -# from .artifact_model import * \ No newline at end of file +from .artifact_model import * \ No newline at end of file diff --git a/GGanalysis/games/genshin_impact/artifact_data.py b/GGanalysis/games/genshin_impact/artifact_data.py index 46c75d4..8c0b39b 100644 --- a/GGanalysis/games/genshin_impact/artifact_data.py +++ b/GGanalysis/games/genshin_impact/artifact_data.py @@ -1,6 +1,62 @@ +''' +原神圣遗物数值表 +类型别名如下 + 数值生命值 hp + 数值攻击力 atk + 数值防御力 def + 百分比生命值 hpp + 百分比攻击力 atkp + 百分比防御力 defp + 元素精通 em + 元素充能效率 er + 暴击率 cr + 暴击伤害 cd + 治疗加成 hb +''' +ARTIFACT_TYPES = ['flower', 'plume', 'sands', 'goblet', 'circlet'] + +# 所有主词条掉落权重表 +# 掉落权重取自 tesiacoil 的整理 +# 主词条 https://wiki.biligame.com/ys/掉落系统学/常用数据#主词条 +P_MAIN_STAT = { + 'flower': {'hp': 1000}, + 'plume': {'atk': 1000}, + 'sands': { + 'hpp': 1334, + 'atkp': 1333, + 'defp': 1333, + 'em': 500, + 'er': 500, + }, + 'goblet': { + 'hpp': 770, + 'atkp': 770, + 'defp': 760, + 'pyroDB': 200, + 'electroDB': 200, + 'cryoDB': 200, + 'hydroD B': 200, + 'dendroDB': 200, + 'anemoDB': 200, + 'geoDB': 200, + 'physicalDB': 200, + 'em': 100, + }, + 'circlet': { + 'hpp': 1100, + 'atkp': 1100, + 'defp': 1100, + 'cr': 500, + 'cd': 500, + 'hb': 500, + 'em': 200, + } +} # 所有副词条权重表 -p_sub_stat = { +# 掉落权重取自 tesiacoil 的整理 +# 副词条 https://wiki.biligame.com/ys/掉落系统学/常用数据#副词条 +P_SUB_STAT = { 'hp': 150, 'atk': 150, 'def': 150, @@ -13,7 +69,60 @@ 'cd': 75, } -test_weight = { +# 五星圣遗物不同来源多词条占比 https://genshin-impact.fandom.com/wiki/Loot_System/Artifact_Drop_Distribution +P_DROP_STATE = { + 'domains_drop': 0.2, + 'normal_boos_drop': 0.34, + 'weekly_boss_drop': 0.34, + 'converted_by_alchemy_table': 0.34, +} + +# 五星圣遗物主词条满级时属性 https://genshin-impact.fandom.com/wiki/Artifact/Stats +MAIN_STAT_MAX = { + 'hp': 4780, + 'atk': 311, + 'hpp': 0.466, + 'atkp': 0.466, + 'defp': 0.583, + 'em': 186.5, + 'er': 0.518, + 'pyroDB': 0.466, + 'electroDB': 0.466, + 'cryoDB': 0.466, + 'hydroD B': 0.466, + 'dendroDB': 0.466, + 'anemoDB': 0.466, + 'geoDB': 0.466, + 'physicalDB': 0.583, + 'cr': 31.1, + 'cd': 62.2, + 'hb': 35.9, +} + +# 五星副词条单次升级最大值 +# 全部的精确值见 https://nga.178.com/read.php?tid=31774495 +SUB_STAT_MAX = { + 'hp': 298.75, + 'atk': 19.45, + 'def': 23.14, + 'hpp': 0.0583, + 'atkp': 0.0583, + 'defp': 0.0729, + 'em': 23.31 , + 'er': 0.0648, + 'cr': 0.0389, + 'cd': 0.0777, +} + +# 圣遗物强化经验翻倍概率 https://wiki.biligame.com/ys/掉落系统学/常用数据#主词条 +P_EXP_MULTI = { + '1': 0.9, + '2': 0.09, + '5': 0.01, +} + +# 默认打分权重,这个值可以是 [0,1] 的浮点数 +DEFAULT_STAT_SCORE = { 'hp': 0, 'atk': 0, 'def': 0, @@ -24,4 +133,13 @@ 'er': 0, 'cr': 1, 'cd': 1, +} + +# 默认主属性选择 +DEFAULT_MAIN_STAT = { + 'flower': 'hp', + 'plume': 'atk', + 'sands': 'atkp', + 'goblet': 'pyroDB', + 'circlet': 'cr', } \ No newline at end of file diff --git a/GGanalysis/games/genshin_impact/artifact_model.py b/GGanalysis/games/genshin_impact/artifact_model.py index bdc5e4b..0dc5e3e 100644 --- a/GGanalysis/games/genshin_impact/artifact_model.py +++ b/GGanalysis/games/genshin_impact/artifact_model.py @@ -1,5 +1,5 @@ from GGanalysis.scored_item import ScoredItem, ScoredItemSet -from GGanalysis.games.genshin_impact.artifact_data import p_sub_stat, test_weight +from GGanalysis.games.genshin_impact.artifact_data import * from itertools import permutations from functools import lru_cache from copy import deepcopy @@ -8,12 +8,16 @@ 原神圣遗物类 部分代码修改自 https://github.com/ideless/reliq ''' +__all__ = [ + 'GenshinArtifact', + 'GenshinArtifactSet' +] # 副词条档位 MINOR_RANKS = [7, 8, 9, 10] # 权重倍数乘数,必须为整数,越大计算越慢精度越高 RANK_MULTI = 1 - +# 全局圣遗物副词条权重 STATS_WEIGHTS = {} def set_using_weight(new_weight: dict): '''更换采用权重时要刷新缓存,注意得分权重必须小于等于1''' @@ -23,7 +27,7 @@ def set_using_weight(new_weight: dict): get_init_state.cache_clear() get_state_level_up.cache_clear() -def get_total_weight(weights: dict): +def dict_weight_sum(weights: dict): '''获得同字典中所有键的和''' ans = 0 for key in weights.keys(): @@ -31,9 +35,9 @@ def get_total_weight(weights: dict): return ans def get_combinations_p(stats_p: dict, select_num=4): - '''获得不同副词条组合的概率''' + '''获得拥有4个副词条的五星圣遗物不同副词条组合的概率''' ans = {} - weight_all = get_total_weight(stats_p) + weight_all = dict_weight_sum(stats_p) for perm in permutations(list(stats_p.keys()), select_num): # 枚举并计算该排列的出现概率 p, s = 1, weight_all @@ -50,7 +54,7 @@ def get_combinations_p(stats_p: dict, select_num=4): @lru_cache(maxsize=65536) def get_init_state(stat_comb, default_weight=0) -> ScoredItem: - '''这个函数计算初始4词条得分分布,及每个词条的条件期望''' + '''获得拥有4个副词条的五星圣遗物初始得分分布,及得分下每个副词条的条件期望''' score_dist = np.zeros(40*RANK_MULTI+1) sub_stat_exp = {} for m in stat_comb: @@ -93,7 +97,7 @@ def get_init_state(stat_comb, default_weight=0) -> ScoredItem: @lru_cache(maxsize=65536) def get_state_level_up(stat_comb, default_weight=0) -> ScoredItem: - '''这个函数计算4词条下升一级的分数分布及每个每个分数下副词条的期望''' + '''这个函数计算4词条下升1级的分数分布及每个每个分数下副词条的期望''' score_dist = np.zeros(10*RANK_MULTI+1) sub_stat_exp = {} for m in stat_comb: @@ -124,19 +128,34 @@ def get_state_level_up(stat_comb, default_weight=0) -> ScoredItem: class GenshinArtifact(ScoredItem): '''原神圣遗物类''' - def __init__(self, main_stat: str='', sub_stats_p: dict={}, stats_weight: dict={}) -> None: + def __init__(self, + type: str='flower', # 道具类型 + type_p=1/5, # 每次获得道具是是类型道具概率 + main_stat: str=None, # 主词条属性 + sub_stats_select_weight: dict=P_SUB_STAT, # 副词条抽取权重 + stats_score: dict=DEFAULT_STAT_SCORE, # 词条评分权重 + drop_source: str='domains_drop', # 圣遗物掉落来源,和初始词条数相关 + ) -> None: + # 计算获得主词条概率 + self.type = type + if main_stat is not None: + self.main_stat = main_stat + else: + self.main_stat = DEFAULT_MAIN_STAT[self.type] + drop_p = type_p * P_MAIN_STAT[self.type][self.main_stat] / dict_weight_sum(P_MAIN_STAT[self.type]) # 确定可选副词条 - self.main_stat = main_stat - self.sub_stats_p = deepcopy(sub_stats_p) - if self.main_stat in self.sub_stats_p: - del self.sub_stats_p[self.main_stat] - # 权重改变时应清除缓存 - self.stats_weight = stats_weight - if self.stats_weight != STATS_WEIGHTS: - set_using_weight(self.stats_weight) + self.sub_stats_weight = deepcopy(sub_stats_select_weight) + if self.main_stat in self.sub_stats_weight: + del self.sub_stats_weight[self.main_stat] + # 确定副词条四件概率 + self.p_4sub = P_DROP_STATE[drop_source] + # 词条权重改变时应清除缓存 + self.stats_score = stats_score + if self.stats_score != STATS_WEIGHTS: + set_using_weight(self.stats_score) # 计算副词条组合概率 ans = ScoredItem() - self.sub_stats_combinations = get_combinations_p(stats_p=self.sub_stats_p, select_num=4) + self.sub_stats_combinations = get_combinations_p(stats_p=self.sub_stats_weight, select_num=4) # 遍历所有情况累加 for stat_comb in list(self.sub_stats_combinations.keys()): temp_base = get_init_state(stat_comb) @@ -144,51 +163,120 @@ def __init__(self, main_stat: str='', sub_stats_p: dict={}, stats_weight: dict={ # 初始3词条和初始四词条的情况 temp_3 = temp_base * temp_level_up * temp_level_up * temp_level_up * temp_level_up temp_4 = temp_3 * temp_level_up - ans += (0.8 * temp_3 + 0.2 * temp_4) * self.sub_stats_combinations[stat_comb] - self.score_dist = ans.score_dist - self.sub_stats_exp = ans.sub_stats_exp + ans += ((1-self.p_4sub) * temp_3 + self.p_4sub * temp_4) * self.sub_stats_combinations[stat_comb] + super().__init__(ans.score_dist, ans.sub_stats_exp, drop_p) +# 导入所需的最优组合组件 +from GGanalysis.scored_item_tools import select_best_combination, remove_worst_combination, get_mix_dist class GenshinArtifactSet(ScoredItemSet): - def __init__(self, item_set=...) -> None: + def __init__(self, + main_stat: dict=DEFAULT_MAIN_STAT, + stats_score: dict=DEFAULT_STAT_SCORE, + drop_source: str='domains_drop', + ) -> None: + # 初始化道具 + self.main_stat = main_stat + self.stats_score = stats_score + self.drop_source = drop_source + item_set = {} + for type in ARTIFACT_TYPES: + item_set[type] = GenshinArtifact(type=type, type_p=1/10, main_stat=main_stat[type], stats_score=stats_score, drop_source=drop_source) super().__init__(item_set) + def select_best_2piece(self, n=1) -> ScoredItem: + '''选择刷取n次后的最优2件套''' + item_set = self.repeat(n) + ans = select_best_combination(item_set, 2) + return ans + + def select_best_4piece(self, n=1) -> ScoredItem: + '''选择刷取n次后的最优4件套''' + item_set = self.repeat(n) + ans = remove_worst_combination(item_set) + return ans + + def get_4piece_under_condition(self, n, base_n=1500, base_p=1) -> ScoredItem: + ''' + 计算在一定基础概率分布下,刷4件套+基础分布散件的分数分布 + use_n 表示当前刷特定本的件数 + base_n 表示可用套装散件占刷出散件的比例 + type_p 用于表示每次掉落中有多大比例是适用于基础概率分布中的 + ''' + base_drop_p = {} + for type in ARTIFACT_TYPES: + base_drop_p[type] = base_p * (1/5) * P_MAIN_STAT[type][self.main_stat[type]] / dict_weight_sum(P_MAIN_STAT[type]) + # 因为是按照字母序返回的列表,所以是对齐的,直接调用函数即可 + return get_mix_dist(self.repeat(n), self.repeat(base_n, p=base_drop_p)) + + if __name__ == '__main__': pass ''' # 测速代码 import time - from my_implementation_ideles_conv_sub_stats import combine_score_items s = time.time() - for i in range(1): + for i in range(10): get_init_state.cache_clear() get_state_level_up.cache_clear() - # test = GenshinArtifact('hp', p_sub_stat, test_weight) - flower = GenshinArtifact('hp', p_sub_stat, test_weight) - plume = GenshinArtifact('atk', p_sub_stat, test_weight) - sands = GenshinArtifact('atkp', p_sub_stat, test_weight) - goblet = GenshinArtifact('pyroDB', p_sub_stat, test_weight) - circlet = GenshinArtifact('cr', p_sub_stat, test_weight) - set = [flower, plume, sands, goblet, circlet] - temp = flower * plume * sands * goblet * circlet + artifact_set = GenshinArtifactSet() + item_set_score = artifact_set.combine_set() t = time.time() print(t-s) ''' ''' - # 套装备选4+1绘图 + # 基本正确性检验代码 from matplotlib import pyplot as plt - test = GenshinArtifact('hp', p_sub_stat, test_weight) - test = test.repeat(1000, 1/5) - ans = np.outer(test.score_dist.dist, test.score_dist.dist) - plt.matshow(ans) - # print(sum(test.score_dist.dist), test.score_dist.dist[0]) - # plt.plot(test.score_dist.dist, color='C1') + # flower = GenshinArtifact(type='flower') + # plt.plot(flower.score_dist.dist, color='C1') + # plt.plot(flower.repeat(1).score_dist.dist, color='C0') + # plt.show() + artifact_set = GenshinArtifactSet() + item_set_score = artifact_set.combine_set(n=1) + plt.plot(item_set_score.score_dist.dist, color='C1') plt.show() ''' + ''' + # 测试非整数分数权重 + TEST_STAT_SCORE = { + 'hp': 0, + 'atk': 0, + 'def': 0, + 'hpp': 0, + 'atkp': 0.5, + 'defp': 0, + 'em': 0, + 'er': 0, + 'cr': 1, + 'cd': 1, + } + from matplotlib import pyplot as plt + from GGanalysis.scored_item import check_subexp_sum + flower = GenshinArtifact(type='flower', stats_score=TEST_STAT_SCORE) + score = check_subexp_sum(flower, TEST_STAT_SCORE) + print(score) + print('atkp', flower.sub_stats_exp['atkp']) + print('cr', flower.sub_stats_exp['cr']) + print('cd', flower.sub_stats_exp['cd']) + plt.plot(flower.score_dist.dist, color='C1') + plt.show() + ''' + # 检查4+1正确性 + # 按照年/12的方式平均,这种情况下一个月可以刷291.54件圣遗物(期望),取290为一个月的数量,3480为一年的数量 + # 但是玩家一年也不会全部体力都去刷圣遗物,取1500作为散件指标吧 + from matplotlib import pyplot as plt + artifact_set = GenshinArtifactSet() + planed_item = 290 + extra_item = 0 + item_set_score_5 = artifact_set.combine_set(n=planed_item) + item_set_score_4plus1 = artifact_set.get_4piece_under_condition(n=planed_item, base_n=1500) + print(sum(item_set_score_4plus1.score_dist.dist)) + print(item_set_score_4plus1.exp) + plt.plot(item_set_score_5.score_dist.dist, color='C0') + plt.plot(item_set_score_4plus1.score_dist.dist, color='C1') + plt.show() + + # TODO 加入相对当前的提升,超过提升部分才纳入计算(或者说把目前的分数作为base,低于或等于这个分数的都合并到一起) - # ans_p = 0 - # test = get_combinations_p(P_MINOR, 4) - # for key in test.keys(): - # if 'atkp' in key or 'cr' in key or 'cd' in key: - # ans_p += test[key] - # print(ans_p, 1-ans_p) + # TODO 参考ideless的方法增加筛选策略 + # TODO 增加在现有圣遗物基础上的提升计算 diff --git a/GGanalysis/games/genshin_impact/gacha_model.py b/GGanalysis/games/genshin_impact/gacha_model.py index 4d5f4fb..fd9ab79 100644 --- a/GGanalysis/games/genshin_impact/gacha_model.py +++ b/GGanalysis/games/genshin_impact/gacha_model.py @@ -12,10 +12,10 @@ from GGanalysis.basic_models import * __all__ = [ - 'pity_5star', - 'pity_4star', - 'pity_w5star', - 'pity_w4star', + 'PITY_5STAR', + 'PITY_4STAR', + 'PITY_W5STAR', + 'PITY_W4STAR', 'common_5star', 'common_4star', 'up_5star_character', @@ -33,40 +33,40 @@ ] # 原神普通五星保底概率表 -pity_5star = np.zeros(91) -pity_5star[1:74] = 0.006 -pity_5star[74:90] = np.arange(1, 17) * 0.06 + 0.006 -pity_5star[90] = 1 +PITY_5STAR = np.zeros(91) +PITY_5STAR[1:74] = 0.006 +PITY_5STAR[74:90] = np.arange(1, 17) * 0.06 + 0.006 +PITY_5STAR[90] = 1 # 原神普通四星保底概率表 -pity_4star = np.zeros(11) -pity_4star[1:9] = 0.051 -pity_4star[9] = 0.051 + 0.51 -pity_4star[10] = 1 +PITY_4STAR = np.zeros(11) +PITY_4STAR[1:9] = 0.051 +PITY_4STAR[9] = 0.051 + 0.51 +PITY_4STAR[10] = 1 # 原神武器池五星保底概率表 -pity_w5star = np.zeros(78) -pity_w5star[1:63] = 0.007 -pity_w5star[63:77] = np.arange(1, 15) * 0.07 + 0.007 -pity_w5star[77] = 1 +PITY_W5STAR = np.zeros(78) +PITY_W5STAR[1:63] = 0.007 +PITY_W5STAR[63:77] = np.arange(1, 15) * 0.07 + 0.007 +PITY_W5STAR[77] = 1 # 原神武器池四星保底概率表 -pity_w4star = np.zeros(10) -pity_w4star[1:8] = 0.06 -pity_w4star[8] = 0.06 + 0.6 -pity_w4star[9] = 1 +PITY_W4STAR = np.zeros(10) +PITY_W4STAR[1:8] = 0.06 +PITY_W4STAR[8] = 0.06 + 0.6 +PITY_W4STAR[9] = 1 # 定义获取星级物品的模型 -common_5star = PityModel(pity_5star) -common_4star = PityModel(pity_4star) +common_5star = PityModel(PITY_5STAR) +common_4star = PityModel(PITY_4STAR) # 定义原神角色池模型 -up_5star_character = DualPityModel(pity_5star, [0, 0.5, 1]) -up_4star_character = DualPityModel(pity_4star, [0, 0.5, 1]) -up_4star_specific_character = DualPityBernoulliModel(pity_4star, [0, 0.5, 1], 1/3) +up_5star_character = DualPityModel(PITY_5STAR, [0, 0.5, 1]) +up_4star_character = DualPityModel(PITY_4STAR, [0, 0.5, 1]) +up_4star_specific_character = DualPityBernoulliModel(PITY_4STAR, [0, 0.5, 1], 1/3) # 定义原神武器池模型 -common_5star_weapon = PityModel(pity_w5star) -common_4star_weapon = PityModel(pity_w4star) -up_5star_weapon = DualPityModel(pity_w5star, [0, 0.75, 1]) -up_5star_specific_weapon = DualPityBernoulliModel(pity_w5star, [0, 0.75, 1], 1/2) -up_4star_weapon = DualPityModel(pity_w4star, [0, 0.75, 1]) -up_4star_specific_weapon = DualPityBernoulliModel(pity_w4star, [0, 0.75, 1], 1/5) +common_5star_weapon = PityModel(PITY_W5STAR) +common_4star_weapon = PityModel(PITY_W4STAR) +up_5star_weapon = DualPityModel(PITY_W5STAR, [0, 0.75, 1]) +up_5star_specific_weapon = DualPityBernoulliModel(PITY_W5STAR, [0, 0.75, 1], 1/2) +up_4star_weapon = DualPityModel(PITY_W4STAR, [0, 0.75, 1]) +up_4star_specific_weapon = DualPityBernoulliModel(PITY_W4STAR, [0, 0.75, 1], 1/5) # 定轨获取特定UP五星武器 class Genshin5starEPWeaponModel(CommonGachaModel): @@ -86,7 +86,7 @@ def __init__(self) -> None: ['fate2', 'get', 1] ] M = table2matrix(self.state_num, state_trans) - self.layers.append(PityLayer(pity_w5star)) + self.layers.append(PityLayer(PITY_W5STAR)) self.layers.append(MarkovLayer(M)) def __call__(self, item_num: int = 1, multi_dist: bool = False, pull_state = 0, up_guarantee = 0, fate_point = 0, *args: any, **kwds: any) -> Union[FiniteDist, list]: @@ -114,7 +114,7 @@ def _build_parameter_list(self, pull_state: int=0, up_guarantee: int=0, fate_poi class GenshinCommon5starInUPpoolModel(CommonGachaModel): def __init__(self, up_rate=0.5, stander_item=7, dp_lenth=500, need_type=1, max_dist_len=1e5) -> None: super().__init__() - self.layers.append(PityLayer(pity_5star)) + self.layers.append(PityLayer(PITY_5STAR)) self.layers.append(GenshinCommon5starInUPpoolLayer(up_rate, stander_item, dp_lenth, need_type, max_dist_len)) def __call__(self, item_num: int = 1, multi_dist: bool = False, pull_state = 0, is_last_UP=False, *args: any, **kwds: any) -> Union[FiniteDist, list]: return super().__call__(item_num, multi_dist, pull_state, is_last_UP, *args, **kwds) diff --git a/GGanalysis/scored_item.py b/GGanalysis/scored_item.py index 8d300b9..09d14d9 100644 --- a/GGanalysis/scored_item.py +++ b/GGanalysis/scored_item.py @@ -4,7 +4,7 @@ import time ''' - 词条强化型道具类 + 词条评分型道具类 参考 ideless 的思想,将难以处理的多个不同类别的属性,通过线性加权进行打分变成一维问题 本模块中部分计算方法在ideless的基础上重新实现并抽象 ideless 原仓库见 https://github.com/ideless/reliq @@ -12,13 +12,15 @@ class ScoredItem(): '''词条型道具''' - def __init__(self, score_dist: Union[FiniteDist, np.ndarray, list]=FiniteDist([0]), sub_stats_exp: dict={}) -> None: + def __init__(self, score_dist: Union[FiniteDist, np.ndarray, list]=FiniteDist([0]), sub_stats_exp: dict={}, drop_p=1) -> None: '''使用分数分布和副词条期望完成初始化,可选副词条方差''' self.score_dist = FiniteDist(score_dist) self.sub_stats_exp = sub_stats_exp + self.drop_p = drop_p + if self.drop_p < 0 or self.drop_p > 1: + raise ValueError("drop_p should between 0 and 1!") self.fit_sub_stats() self.null_mark = self.is_null() - # TODO 给出分布和方差计算,或者直接提取 score_dist 的结果 def fit_sub_stats(self): '''调整副词条平均值长度以适应分数分布长度''' @@ -32,17 +34,24 @@ def is_null(self): '''判断自身是否为空''' return np.sum(self.score_dist.dist) == 0 - # def sub_stats_clear(self): - # '''清除score_dist为0对应的sub_stats_exp''' - # mask = self.score_dist.dist != 0 - # for key in self.sub_stats_exp.keys(): - # self.sub_stats_exp[key] = self.sub_stats_exp[key] * mask + # 以下模块被 scored_item_tools.get_mix_dist 调用 + def sub_stats_clear(self): + '''清除score_dist为0对应的sub_stats_exp''' + mask = self.score_dist.dist != 0 + for key in self.sub_stats_exp.keys(): + self.sub_stats_exp[key] = self.sub_stats_exp[key] * mask - def repeat(self, n: int, p=1) -> 'ScoredItem': + def repeat(self, n: int=1, p=None) -> 'ScoredItem': '''重复n次获取道具尝试,每次有p概率获得道具后获得的最大值分布''' - if p < 0 or p > 1: - raise ValueError("p should between 0 and 1!") - cdf = (p * np.cumsum(self.score_dist.dist) + 1 - p) ** n + if n == 0: + return ScoredItem([1]) + if p is None: + use_p = self.drop_p + else: + if p < 0 or p > 1: + raise ValueError("p should between 0 and 1!") + use_p = p + cdf = (use_p * np.cumsum(self.score_dist.dist) + 1 - use_p) ** n return ScoredItem(FiniteDist(cdf2dist(cdf)), self.sub_stats_exp) def __getattr__(self, key): # 访问未计算的属性时进行计算 @@ -114,6 +123,13 @@ def __len__(self) -> int: def __str__(self) -> str: return f"Score Dist {self.score_dist.dist} Sub Exp {self.sub_stats_exp}" +def check_subexp_sum(item: ScoredItem, weight: dict) -> np.ndarray: + '''检查副词条的加权和''' + ans = np.zeros(len(item)) + for key in item.sub_stats_exp.keys(): + ans[:len(item.sub_stats_exp[key])] += item.sub_stats_exp[key] * weight[key] + return ans + def combine_items(item_list: list[ScoredItem]): '''返回列表内道具的混合''' ans = ScoredItem([1], {}) @@ -123,248 +139,41 @@ def combine_items(item_list: list[ScoredItem]): class ScoredItemSet(): '''由词条型道具组成的套装''' - def __init__(self, item_set:dict={}, item_p:dict={}) -> None: + def __init__(self, item_set:dict={}) -> None: '''由词条型道具构成的套装抽象''' self.item_set = item_set - self.item_p = item_p - def set_item(self, item_name:str, item: ScoredItem): + def add_item(self, item_name:str, item: ScoredItem): '''添加名为 item_name 的道具''' self.item_set[item_name] = item - def combine_set(self): - '''混合套装中道具''' - # TODO 修改为按照传入key进行混合,默认全部一起混合 + def combine_set(self, select_items: list[str]=None, n=1): + '''计算获取n次道具后套装中道具的最佳得分分布''' ans = ScoredItem([1]) - for key in self.item_set.keys(): - ans *= self.item_set[key] + if select_items is None: + for key in self.item_set.keys(): + ans *= self.item_set[key].repeat(n) + else: + for key in select_items: + ans *= self.item_set[key].repeat(n) return ans - def repeat(self, n): + def repeat(self, n, p: dict=None) -> list[ScoredItem]: '''重复n次获取道具尝试,返回重复后的道具组''' - ans = {} - for key in self.item_set.keys(): - ans[key] = self.item_set[key].repeat(n, self.item_p.get(key, 0)) - return ScoredItemSet(ans) + ans = [] + for key in sorted(list(self.item_set.keys()), key=str.lower): + if p is None: + use_p = self.item_set[key].drop_p + else: + use_p = p[key] + if use_p < 0 or use_p > 1: + raise ValueError("use_p should between 0 and 1!") + ans.append(self.item_set[key].repeat(n, use_p)) + return ans - def item_list(self): + def to_list(self): '''返回以列表形式储存的道具''' return list(self.item_set.values()) - -class ConditionalScore(): - '''返回可行的分数序列''' - def __init__(self, item_idx:Union[list, tuple], score_max:list) -> None: - self.state = [score_max[0]] - self.item_idx = item_idx - self.score_max = score_max - # 生成初始状态 - for i in range(1, len(self.item_idx)): - if self.item_idx[i] > self.item_idx[i-1]: - self.state.append(min(self.score_max[i], self.state[i-1])) - else: - self.state.append(min(self.score_max[i], self.state[i-1])-1) - # 定义合法分数序列集合 - self.possible_sequence = [] - # 枚举并选择合法分数序列 - while self.state[-1] >= 0: - self.possible_sequence.append(deepcopy(self.state)) - self.next_state() - - def next_state(self): - '''切换到下一个可行状态''' - pos = len(self.state) - while pos >= 1: - pos -= 1 - self.state[pos] -= 1 - # TODO 0也是可能的,可能都是0,但是需要特判,要想一下其他地方怎么写 - if self.state[pos] >= 0: - break - # 进行迭代 - for i in range(pos+1, len(self.state)): - if self.item_idx[i] > self.item_idx[i-1]: - self.state[i] = min(self.score_max[i], self.state[i-1]) - else: - self.state[i] = min(self.score_max[i], self.state[i-1])-1 - - def __len__(self) -> int: - return len(self.possible_sequence) - - def __getitem__(self, idx): - return self.possible_sequence[idx] - -def select_best_combination(item_set:list[ScoredItem], chose_num=1): - '''返回选取最优chose_num件后的情况,注意复杂度是关于以chose_num为幂次的多项式''' - '''这个函数只在选择1或者选择2的情况下的容斥处理是对的''' - # 预处理小于等于某分数的概率 - if chose_num > 2: - raise ValueError("chose_num should not greater than 2!") - p_less = [np.cumsum(item.score_dist.dist) for item in item_set] - max_lenth = max([len(item) for item in item_set]) * chose_num + 1 - ans_dist = np.zeros(max_lenth) - ans_sub_exp = {} - start_time = time.time() - for perm in permutations(range(len(item_set)), chose_num): - # 枚举所有排列 - score_max = [len(item_set[i])-1 for i in perm] - all_score_list = ConditionalScore(perm, score_max) - # print('Computing:', perm, 'Tasks:', len(all_score_list)) - for score_list in all_score_list: - p = 1 - score = 0 - for i, s in zip(perm, score_list): - score += s - p *= item_set[i].score_dist[s] - for i in range(len(item_set)): - if i in perm: - continue - if i > perm[-1]: - pos = score_list[-1] - else: - pos = score_list[-1]-1 - if pos < 0: - p = 0 - break - pos = min(pos, len(p_less[i])-1) - p *= p_less[i][pos] - if p == 0: - continue - ans_dist[score] += p - for i, s in zip(perm, score_list): - for key in item_set[i].sub_stats_exp.keys(): - if key not in ans_sub_exp: - ans_sub_exp[key] = np.zeros(max_lenth) - ans_sub_exp[key][score] += p * item_set[i].sub_stats_exp[key][s] - # 求副词条平均值 - for key in ans_sub_exp.keys(): - ans_sub_exp[key] = np.divide(ans_sub_exp[key], ans_dist, \ - out=np.zeros_like(ans_sub_exp[key]), where=ans_dist!=0) - # print('Best combination calc time: {}s'.format(time.time()-start_time)) - return ScoredItem(ans_dist, ans_sub_exp) - -def remove_worst_combination(item_list:list[ScoredItem]) -> ScoredItem: - '''返回去除最差一件后的情况''' - ans_item = ScoredItem([0], {}) - score_max = [len(item_list[i])-1 for i in range(len(item_list))] - for i in range(len(item_list)): - # 枚举最差位置 - for s in range(score_max[i]+1): - c_dist = ScoredItem([1], {}) - # 枚举本件分数 - zero_mark = False - for j in range(len(item_list)): - if i == j: - continue - # 容斥处理 - if j < i: - pos = s - else: - pos = s+1 - if score_max[j] < pos: - # 最大值都没到枚举分数的情况肯定是不行的 - zero_mark = True - break - c_dist *= item_list[j][pos:] - if zero_mark: - continue - ans_item += c_dist * item_list[i].score_dist.dist[s] - return ans_item - -def sim_select_best_k(item_set:list[Union[ScoredItem, np.ndarray]], k=2, sim_pairs=10000) -> np.ndarray: - '''模拟选择k个最优''' - if k > len(item_set): - raise ValueError("k can't greater than item number") - max_lenth = max([len(item)-1 for item in item_set]) * k + 1 - ans_dist = np.zeros(max_lenth, dtype=float) - print("Begin simulate!") - start_time = time.time() - # 每个位置随机抽样 - sim_list = [] - for i, dist in enumerate(item_set): - sim_result = np.random.choice(a=len(dist), size=sim_pairs, \ - p=dist if isinstance(dist, np.ndarray) else dist.score_dist.dist, replace=True) - sim_list.append(sim_result) - sim_array = np.column_stack(sim_list) - sim_array.sort(axis=1) - pos, value = np.unique(np.sum(sim_array[:, -k:], axis=1), return_counts=True) - ans_dist[pos] = value - print('Simulate select time: {}s'.format(time.time()-start_time)) - # 返回分布 - return ans_dist / sim_pairs - -def check_subexp_sum(item: ScoredItem, weight: dict) -> np.ndarray: - '''检查副词条的加权和''' - ans = np.zeros(len(item)) - for key in item.sub_stats_exp.keys(): - ans[:len(item.sub_stats_exp[key])] += item.sub_stats_exp[key] * weight[key] - return ans - -# 下列这两个函数还没想好怎么封装比较好 - -def get_info(score_map: np.ndarray): - ''' - 获得分数差距条件下的主分布矩阵/副分布矩阵/对角概率/累计面积 - 输入矩阵为方阵,0维度为主对象 1维度为副对象(其它选择) - ''' - # 变量声明 - n = score_map.shape[0] - main_dist = np.zeros_like(score_map) - sub_dist = np.zeros_like(score_map) - # 主对象大于副对象的情况 - main_dist[0, :] = np.sum(np.tril(score_map), axis=1) - # 逐对角线计算 - for offset in range(1, n): - main_dist[offset, :] = main_dist[offset-1, :] - diag = score_map.diagonal(offset) - main_dist[offset, 0:n-offset] += diag - sub_dist[offset, offset:] = diag - return main_dist, sub_dist - -def get_mix_dist(listA: list[ScoredItem], listB: list[ScoredItem]): - ''' - 计算可从 listA 中选取一个部位替换为 listB 中对应位置情况下,最优决策下两者混合后的分布 - ''' - if len(listA) != len(listB): - raise ValueError("A B lenth not equal!") - # 计算最长边 - n = max([max(len(m[0]), len(m[1])) for m in zip(listA, listB)]) - m = len(listA) - # for A, B in zip(listA, listB): - # n = max(n, len(A.score_dist), len(B.score_dist)) - # 计算对应的矩阵空间 - info_list = [] - for i, (A, B) in enumerate(zip(listA, listB)): - M = np.outer(pad_zero(A.score_dist.dist, n), \ - pad_zero(B.score_dist.dist, n)) - info_list.append(get_info(M)) - - ans_item = ScoredItem([0], {}) - for i in range(m): - # 枚举选择B的部位 - for s in range(1, n): - # 枚举选择的分数差值,从1到n-1 - # 卷积条件分布,注意这里可能有值为0要特判 - if np.sum(info_list[i][1][s, :]) == 0: - continue - unit_item = ScoredItem(info_list[i][1][s, :], listB[i].sub_stats_exp) - unit_item.sub_stats_clear() - for j in range(m): - # 遍历其他部位 - if i == j: - continue - # 做容斥处理 - if i <= j: - s_pos = s - else: - s_pos = s-1 - unit_item = unit_item * ScoredItem(FiniteDist(info_list[j][0][s_pos, :]), listA[j].sub_stats_exp) - ans_item += unit_item - - # 添加不选择B的情况 - unit_item = ScoredItem([1], {}) - for i in range(m): - unit_item = unit_item * ScoredItem(FiniteDist(info_list[i][0][0, :]), listA[i].sub_stats_exp) - ans_item += unit_item - return ans_item if __name__ == '__main__': pass \ No newline at end of file diff --git a/GGanalysis/scored_item_tools.py b/GGanalysis/scored_item_tools.py new file mode 100644 index 0000000..9f7ac25 --- /dev/null +++ b/GGanalysis/scored_item_tools.py @@ -0,0 +1,223 @@ +from GGanalysis.scored_item import * +''' + 为词条评分类道具开发的组合评估工具 +''' + +__all__ = [ + 'select_best_combination', + 'remove_worst_combination', + 'sim_select_best_k', + 'get_mix_dist', +] +class ConditionalScore(): + '''返回可行的分数序列''' + def __init__(self, item_idx:Union[list, tuple], score_max:list) -> None: + self.state = [score_max[0]] + self.item_idx = item_idx + self.score_max = score_max + # 生成初始状态 + for i in range(1, len(self.item_idx)): + if self.item_idx[i] > self.item_idx[i-1]: + self.state.append(min(self.score_max[i], self.state[i-1])) + else: + self.state.append(min(self.score_max[i], self.state[i-1])-1) + # 定义合法分数序列集合 + self.possible_sequence = [] + # 枚举并选择合法分数序列 + while self.state[-1] >= 0: + self.possible_sequence.append(deepcopy(self.state)) + self.next_state() + + def next_state(self): + '''切换到下一个可行状态''' + pos = len(self.state) + while pos >= 1: + pos -= 1 + self.state[pos] -= 1 + # TODO 0也是可能的,可能都是0,但是需要特判,要想一下其他地方怎么写 + if self.state[pos] >= 0: + break + # 进行迭代 + for i in range(pos+1, len(self.state)): + if self.item_idx[i] > self.item_idx[i-1]: + self.state[i] = min(self.score_max[i], self.state[i-1]) + else: + self.state[i] = min(self.score_max[i], self.state[i-1])-1 + + def __len__(self) -> int: + return len(self.possible_sequence) + + def __getitem__(self, idx): + return self.possible_sequence[idx] + +def select_best_combination(item_set:list[ScoredItem], chose_num=1) -> ScoredItem: + ''' + 返回选取最优chose_num件后的情况,chose_num<=2 + 注意复杂度是关于以chose_num为幂次的多项式, 这个函数只在选择1或者选择2的情况下的容斥处理是对的 + ''' + # 预处理小于等于某分数的概率 + if chose_num > 2: + raise ValueError("chose_num should not greater than 2!") + p_less = [np.cumsum(item.score_dist.dist) for item in item_set] + max_lenth = max([len(item) for item in item_set]) * chose_num + 1 + ans_dist = np.zeros(max_lenth) + ans_sub_exp = {} + start_time = time.time() + for perm in permutations(range(len(item_set)), chose_num): + # 枚举所有排列 + score_max = [len(item_set[i])-1 for i in perm] + all_score_list = ConditionalScore(perm, score_max) + # print('Computing:', perm, 'Tasks:', len(all_score_list)) + for score_list in all_score_list: + p = 1 + score = 0 + for i, s in zip(perm, score_list): + score += s + p *= item_set[i].score_dist[s] + for i in range(len(item_set)): + if i in perm: + continue + if i > perm[-1]: + pos = score_list[-1] + else: + pos = score_list[-1]-1 + if pos < 0: + p = 0 + break + pos = min(pos, len(p_less[i])-1) + p *= p_less[i][pos] + if p == 0: + continue + ans_dist[score] += p + for i, s in zip(perm, score_list): + for key in item_set[i].sub_stats_exp.keys(): + if key not in ans_sub_exp: + ans_sub_exp[key] = np.zeros(max_lenth) + ans_sub_exp[key][score] += p * item_set[i].sub_stats_exp[key][s] + # 求副词条平均值 + for key in ans_sub_exp.keys(): + ans_sub_exp[key] = np.divide(ans_sub_exp[key], ans_dist, \ + out=np.zeros_like(ans_sub_exp[key]), where=ans_dist!=0) + # print('Best combination calc time: {}s'.format(time.time()-start_time)) + return ScoredItem(ans_dist, ans_sub_exp) + +def remove_worst_combination(item_list:list[ScoredItem]) -> ScoredItem: + '''返回去除最差一件后的情况''' + ans_item = ScoredItem([0], {}) + score_max = [len(item_list[i])-1 for i in range(len(item_list))] + for i in range(len(item_list)): + # 枚举最差位置 + for s in range(score_max[i]+1): + c_dist = ScoredItem([1], {}) + # 枚举本件分数 + zero_mark = False + for j in range(len(item_list)): + if i == j: + continue + # 容斥处理 + if j < i: + pos = s + else: + pos = s+1 + if score_max[j] < pos: + # 最大值都没到枚举分数的情况肯定是不行的 + zero_mark = True + break + c_dist *= item_list[j][pos:] + if zero_mark: + continue + ans_item += c_dist * item_list[i].score_dist.dist[s] + return ans_item + +def sim_select_best_k(item_set:list[Union[ScoredItem, np.ndarray]], k=2, sim_pairs=10000) -> np.ndarray: + '''模拟选择k个最优''' + if k > len(item_set): + raise ValueError("k can't greater than item number") + max_lenth = max([len(item)-1 for item in item_set]) * k + 1 + ans_dist = np.zeros(max_lenth, dtype=float) + print("Begin simulate!") + start_time = time.time() + # 每个位置随机抽样 + sim_list = [] + for i, dist in enumerate(item_set): + sim_result = np.random.choice(a=len(dist), size=sim_pairs, \ + p=dist if isinstance(dist, np.ndarray) else dist.score_dist.dist, replace=True) + sim_list.append(sim_result) + sim_array = np.column_stack(sim_list) + sim_array.sort(axis=1) + pos, value = np.unique(np.sum(sim_array[:, -k:], axis=1), return_counts=True) + ans_dist[pos] = value + print('Simulate select time: {}s'.format(time.time()-start_time)) + # 返回分布 + return ans_dist / sim_pairs + +# 下列这两个函数还没想好怎么封装比较好 + +def get_info(score_map: np.ndarray): + ''' + 获得分数差距条件下的主分布矩阵/副分布矩阵/对角概率/累计面积 + 输入矩阵为方阵,0维度为主对象 1维度为副对象(其它选择) + ''' + # 变量声明 + n = score_map.shape[0] + main_dist = np.zeros_like(score_map) + sub_dist = np.zeros_like(score_map) + # 主对象大于副对象的情况 + main_dist[0, :] = np.sum(np.tril(score_map), axis=1) + # 逐对角线计算 + for offset in range(1, n): + main_dist[offset, :] = main_dist[offset-1, :] + diag = score_map.diagonal(offset) + main_dist[offset, 0:n-offset] += diag + sub_dist[offset, offset:] = diag + return main_dist, sub_dist + +def get_mix_dist(listA: list[ScoredItem], listB: list[ScoredItem]) -> ScoredItem: + ''' + 计算可从 listA 中选取一个部位替换为 listB 中对应位置情况下,最优决策下两者混合后的分布 + ''' + if len(listA) != len(listB): + raise ValueError("A B lenth not equal!") + # 计算最长边 + n = max([max(len(m[0]), len(m[1])) for m in zip(listA, listB)]) + m = len(listA) + # for A, B in zip(listA, listB): + # n = max(n, len(A.score_dist), len(B.score_dist)) + # 计算对应的矩阵空间 + info_list = [] + for i, (A, B) in enumerate(zip(listA, listB)): + M = np.outer(pad_zero(A.score_dist.dist, n), \ + pad_zero(B.score_dist.dist, n)) + info_list.append(get_info(M)) + + ans_item = ScoredItem([0], {}) + for i in range(m): + # 枚举选择B的部位 + for s in range(1, n): + # 枚举选择的分数差值,从1到n-1 + # 卷积条件分布,注意这里可能有值为0要特判 + if np.sum(info_list[i][1][s, :]) == 0: + continue + unit_item = ScoredItem(info_list[i][1][s, :], listB[i].sub_stats_exp) + unit_item.sub_stats_clear() + for j in range(m): + # 遍历其他部位 + if i == j: + continue + # 做容斥处理 + if i <= j: + s_pos = s + else: + s_pos = s-1 + unit_item = unit_item * ScoredItem(FiniteDist(info_list[j][0][s_pos, :]), listA[j].sub_stats_exp) + ans_item += unit_item + + # 添加不选择B的情况 + unit_item = ScoredItem([1], {}) + for i in range(m): + unit_item = unit_item * ScoredItem(FiniteDist(info_list[i][0][0, :]), listA[i].sub_stats_exp) + ans_item += unit_item + return ans_item + +if __name__ == '__main__': + pass \ No newline at end of file diff --git a/artifact_example.py b/artifact_example.py new file mode 100644 index 0000000..4042c87 --- /dev/null +++ b/artifact_example.py @@ -0,0 +1,39 @@ +import GGanalysis as gg +import GGanalysis.games.genshin_impact as GI + +# 注意,以下定义的分数指每次副词条强化为最高属性为10,其他情况依次为9、8、7 + +# 定义圣遗物生之花 +flower = GI.GenshinArtifact(type='flower') +print('在获取100件生之花后,获取的最高分数分布') +print(flower.repeat(100, p=1).score_dist.dist) +print('在祝圣秘境获取100件圣遗物后,其中特定套装的生之花的最高分数的分布') +print(flower.repeat(100).score_dist.dist) + +# 定义以默认权重计算的圣遗物套装 +default_weight_artifact = GI.GenshinArtifactSet() +print('在祝圣秘境获取100件圣遗物后,其中特定套装组5件套能获得的最高分数的分布') +print(gg.combine_items(default_weight_artifact.repeat(100)).score_dist.dist) +print('在祝圣秘境获取100件圣遗物,获取散件1500件后,其中特定套装和散件组4+1能获得的最高分数的分布') +print(default_weight_artifact.get_4piece_under_condition(n=100, base_n=1500).score_dist.dist) + +# 自定义属性,原神词条别名说明见 games/genshin_impact/artifact_data.py +# 自定义主词条选择 +MAIN_STAT = { + 'flower': 'hp', + 'plume': 'atk', + 'sands': 'atkp', + 'goblet': 'pyroDB', + 'circlet': 'cd', +} +# 自定义副词条权重 +STAT_SCORE = { + 'atkp': 0.5, + 'em': 0.6, + 'er': 0.3, + 'cr': 1, + 'cd': 1, +} +custom_weight_artifact = GI.GenshinArtifactSet(main_stat=MAIN_STAT, stats_score=STAT_SCORE) +print('自定义条件下,在祝圣秘境获取100件圣遗物,获取散件1500件后,其中特定套装和散件组4+1能获得的最高分数的分布') +print(default_weight_artifact.get_4piece_under_condition(n=100, base_n=1500).score_dist.dist) diff --git a/example.py b/gacha_example.py similarity index 100% rename from example.py rename to gacha_example.py