Genshin Banner 4* Prediction Script v1.5

Run Settings
LanguagePython
Language Version
Run Command
#!/usr/bin/env python3 # Genshin Banner Prediction Script by @atomicptr # Last update 2021-10-11, predictions for the next banner # NOTE: Scroll down there is an area where you might have to update data # CHANGELOG: # * [1.5.15] Add Hu Tao banner # * [1.5.14] Add Tartaglia banner, add scoring for first character reappearance # * [1.5.13] Add Kokomi banner, add Thoma # * [1.5.12] Add Baal banner, set next character to Kokomi # * [1.5.11] Add Klee, Ayaka and Yoimiya banners, set next character to Baal # * [1.5.10] Add Eula banner, set next banner to klee, add kazuha # * [1.5.9] Add Zhongli banner and set next banner to Eula # * [1.5.8] Set next banner to zhongli # * [1.5.7] Added Childe banner rerun, add Eula, Yan Fei and Ayaka data # * [1.5.6] Added Venti banner # * [1.5.5] Added new rule, each banner has to have at least one female and male character in it # * [1.5.4] Added Hu Tao banner, remove now broken rule with duplicate element (at least with 5*), # add rosaria, set next character to venti # * [1.5.3] Set next character to Hu Tao # * [1.5.2] Added Xiao and Keqing banner Source: https://genshin.mihoyo.com/m/en/news/detail/8418 # * [1.5.1] Only display Xiao since he is the next character # * [1.5.0] Remove having to set the next banner index # * [1.5.0] Add ability to set multiple characters as the next rate up character # OLD VERSIONS: # * v1.4.0 - https://glot.io/snippets/fusjr7nqed (contains all the other old ones) from itertools import combinations SCRIPT_VERSION = "v1.5.15" # weapon types WEAPON_TYPE_BOW = "Bow" WEAPON_TYPE_CATALYST = "Catalyst" WEAPON_TYPE_CLAYMORE = "Claymore" WEAPON_TYPE_POLEARM = "Polearm" WEAPON_TYPE_SWORD = "Sword" # elements ELEMENT_ANEMO = "Anemo" ELEMENT_CRYO = "Cryo" ELEMENT_ELECTRO = "Electro" ELEMENT_DENDRO = "Dendro" ELEMENT_GEO = "Geo" ELEMENT_HYDRO = "Hydro" ELEMENT_PYRO = "Pyro" # indices INDEX_WEAPON_TYPE = 0 INDEX_ELEMENT = 1 INDEX_STARS = 2 INDEX_IS_WAIFU = 3 # data for all 4 star characters CHARACTER_ALBEDO = "Albedo" CHARACTER_AMBER = "Amber" CHARACTER_AYAKA = "Ayaka" CHARACTER_BAAL = "Baal" CHARACTER_BARBARA = "Barbara" CHARACTER_BEIDOU = "Beidou" CHARACTER_BENNET = "Bennet" CHARACTER_CHONGYUN = "Chongyun" CHARACTER_DILUC = "Diluc" CHARACTER_DIONA = "Diona" CHARACTER_EULA = "Eula" CHARACTER_FISCHL = "Fischl" CHARACTER_GANYU = "Ganyu" CHARACTER_HUTAO = "Hu Tao" CHARACTER_JEAN = "Jean" CHARACTER_KAEYA = "Kaeya" CHARACTER_KAZUHA = "Kazuha" CHARACTER_KEQING = "Keqing" CHARACTER_KLEE = "Klee" CHARACTER_KOKOMI = "Kokomi" CHARACTER_KUJOU = "Kujou" CHARACTER_LISA = "Lisa" CHARACTER_MONA = "Mona" CHARACTER_NINGGUANG = "Ningguang" CHARACTER_NOELLE = "Noelle" CHARACTER_QIQI = "Qiqi" CHARACTER_RAZOR = "Razor" CHARACTER_ROSARIA = "Rosaria" CHARACTER_SAYU = "Sayu" CHARACTER_SUCROSE = "Sucrose" CHARACTER_TARTAGLIA = "Tartaglia" CHARACTER_THOMA = "Thoma" CHARACTER_VENTI = "Venti" CHARACTER_XIANGLING = "Xiangling" CHARACTER_XIAO = "Xiao" CHARACTER_XINQIU = "Xinqiu" CHARACTER_XINYAN = "Xinyan" CHARACTER_YANFEI = "Yan Fei" CHARACTER_YOIMIYA = "Yoimiya" CHARACTER_ZHONGLI = "Zhongli" # because i keep forgetting the real name CHARACTER_CHILDE = CHARACTER_TARTAGLIA STARTER_CHARACTERS = [CHARACTER_AMBER, CHARACTER_KAEYA, CHARACTER_LISA] FREE_PERMANENT_CHARACTERS = [CHARACTER_XIANGLING, CHARACTER_BARBARA, CHARACTER_AMBER, CHARACTER_KAEYA, CHARACTER_LISA] CHARACTER_DATA = { CHARACTER_ALBEDO: (WEAPON_TYPE_SWORD, ELEMENT_GEO, 5, False), CHARACTER_AMBER: (WEAPON_TYPE_BOW, ELEMENT_PYRO, 4, True), CHARACTER_AYAKA: (WEAPON_TYPE_SWORD, ELEMENT_CRYO, 5, True), CHARACTER_BAAL: (WEAPON_TYPE_POLEARM, ELEMENT_ELECTRO, 5, True), CHARACTER_BARBARA: (WEAPON_TYPE_CATALYST, ELEMENT_HYDRO, 4, True), CHARACTER_BEIDOU: (WEAPON_TYPE_CLAYMORE, ELEMENT_ELECTRO, 4, True), CHARACTER_BENNET: (WEAPON_TYPE_SWORD, ELEMENT_PYRO, 4, False), CHARACTER_CHONGYUN: (WEAPON_TYPE_CLAYMORE, ELEMENT_CRYO, 4, False), CHARACTER_DILUC: (WEAPON_TYPE_CLAYMORE, ELEMENT_PYRO, 5, False), CHARACTER_DIONA: (WEAPON_TYPE_BOW, ELEMENT_CRYO, 4, True), CHARACTER_EULA: (WEAPON_TYPE_CLAYMORE, ELEMENT_CRYO, 5, True), CHARACTER_FISCHL: (WEAPON_TYPE_BOW, ELEMENT_ELECTRO, 4, True), CHARACTER_GANYU: (WEAPON_TYPE_BOW, ELEMENT_CRYO, 5, True), CHARACTER_HUTAO: (WEAPON_TYPE_POLEARM, ELEMENT_PYRO, 5, True), CHARACTER_JEAN: (WEAPON_TYPE_SWORD, ELEMENT_ANEMO, 5, True), CHARACTER_KAEYA: (WEAPON_TYPE_SWORD, ELEMENT_HYDRO, 4, False), CHARACTER_KAZUHA: (WEAPON_TYPE_SWORD, ELEMENT_ANEMO, 5, False), CHARACTER_KEQING: (WEAPON_TYPE_SWORD, ELEMENT_ELECTRO, 5, True), CHARACTER_KLEE: (WEAPON_TYPE_CATALYST, ELEMENT_PYRO, 5, True), CHARACTER_KOKOMI: (WEAPON_TYPE_CATALYST, ELEMENT_HYDRO, 5, True), CHARACTER_KUJOU: (WEAPON_TYPE_BOW, ELEMENT_ELECTRO, 4, True), CHARACTER_LISA: (WEAPON_TYPE_CATALYST, ELEMENT_ELECTRO, 4, True), CHARACTER_MONA: (WEAPON_TYPE_CATALYST, ELEMENT_HYDRO, 5, True), CHARACTER_NINGGUANG: (WEAPON_TYPE_CATALYST, ELEMENT_GEO, 4, True), CHARACTER_NOELLE: (WEAPON_TYPE_CLAYMORE, ELEMENT_GEO, 4, True), CHARACTER_QIQI: (WEAPON_TYPE_SWORD, ELEMENT_CRYO, 5, True), CHARACTER_RAZOR: (WEAPON_TYPE_CLAYMORE, ELEMENT_ELECTRO, 4, False), CHARACTER_ROSARIA: (WEAPON_TYPE_POLEARM, ELEMENT_CRYO, 4, True), CHARACTER_SAYU: (WEAPON_TYPE_CLAYMORE, ELEMENT_ANEMO, 4, True), CHARACTER_SUCROSE: (WEAPON_TYPE_CATALYST, ELEMENT_ANEMO, 4, True), CHARACTER_TARTAGLIA: (WEAPON_TYPE_BOW, ELEMENT_HYDRO, 5, False), CHARACTER_THOMA: (WEAPON_TYPE_POLEARM, ELEMENT_PYRO, 4, False), CHARACTER_VENTI: (WEAPON_TYPE_BOW, ELEMENT_ANEMO, 5, False), CHARACTER_XIANGLING: (WEAPON_TYPE_POLEARM, ELEMENT_PYRO, 4, True), CHARACTER_XIAO: (WEAPON_TYPE_POLEARM, ELEMENT_ANEMO, 5, False), CHARACTER_XINQIU: (WEAPON_TYPE_SWORD, ELEMENT_HYDRO, 4, False), CHARACTER_XINYAN: (WEAPON_TYPE_CLAYMORE, ELEMENT_PYRO, 4, True), CHARACTER_YANFEI: (WEAPON_TYPE_CATALYST, ELEMENT_PYRO, 4, True), CHARACTER_YOIMIYA: (WEAPON_TYPE_BOW, ELEMENT_PYRO, 5, True), CHARACTER_ZHONGLI: (WEAPON_TYPE_POLEARM, ELEMENT_GEO, 5, False), } CHARACTERS_4_STARS = [char for char in list(CHARACTER_DATA.keys()) if CHARACTER_DATA[char][INDEX_STARS] == 4] def filter_4_stars(characters): return [char for char in characters if CHARACTER_DATA[char][INDEX_STARS] == 4] # banner data ##################################################################################################### #################### NOTE: EDIT THIS AREA IF YOU WANT TO CHANGE THE RESULTS ######################## ##################################################################################################### # if character is completely unknown just Use [None] or None NEXT_BANNER_5_STAR = [CHARACTER_ALBEDO] BANNER_DATA = { 1: (CHARACTER_VENTI, CHARACTER_BARBARA, CHARACTER_FISCHL, CHARACTER_XIANGLING), 2: (CHARACTER_KLEE, CHARACTER_NOELLE, CHARACTER_XINQIU, CHARACTER_SUCROSE), 3: (CHARACTER_TARTAGLIA, CHARACTER_DIONA, CHARACTER_NINGGUANG, CHARACTER_BEIDOU), 4: (CHARACTER_ZHONGLI, CHARACTER_XINYAN, CHARACTER_CHONGYUN, CHARACTER_RAZOR), 5: (CHARACTER_ALBEDO, CHARACTER_FISCHL, CHARACTER_SUCROSE, CHARACTER_BENNET), 6: (CHARACTER_GANYU, CHARACTER_NOELLE, CHARACTER_XINQIU, CHARACTER_XIANGLING), 7: (CHARACTER_XIAO, CHARACTER_BEIDOU, CHARACTER_DIONA, CHARACTER_XINYAN), 8: (CHARACTER_KEQING, CHARACTER_BARBARA, CHARACTER_BENNET, CHARACTER_NINGGUANG), 9: (CHARACTER_HUTAO, CHARACTER_CHONGYUN, CHARACTER_XINQIU, CHARACTER_XIANGLING), 10: (CHARACTER_VENTI, CHARACTER_NOELLE, CHARACTER_RAZOR, CHARACTER_SUCROSE), 11: (CHARACTER_TARTAGLIA, CHARACTER_BARBARA, CHARACTER_FISCHL, CHARACTER_ROSARIA), 12: (CHARACTER_ZHONGLI, CHARACTER_DIONA, CHARACTER_NOELLE, CHARACTER_YANFEI), 13: (CHARACTER_EULA, CHARACTER_BEIDOU, CHARACTER_XINQIU, CHARACTER_XINYAN), 14: (CHARACTER_KLEE, CHARACTER_FISCHL, CHARACTER_SUCROSE, CHARACTER_BARBARA), 15: (CHARACTER_KAZUHA, CHARACTER_ROSARIA, CHARACTER_RAZOR, CHARACTER_BENNET), 16: (CHARACTER_AYAKA, CHARACTER_CHONGYUN, CHARACTER_NINGGUANG, CHARACTER_YANFEI), 17: (CHARACTER_YOIMIYA, CHARACTER_SAYU, CHARACTER_DIONA, CHARACTER_XINYAN), 18: (CHARACTER_BAAL, CHARACTER_KUJOU, CHARACTER_SUCROSE, CHARACTER_XIANGLING), 19: (CHARACTER_KOKOMI, CHARACTER_BEIDOU, CHARACTER_ROSARIA, CHARACTER_XINQIU), 20: (CHARACTER_TARTAGLIA, CHARACTER_CHONGYUN, CHARACTER_NINGGUANG, CHARACTER_YANFEI), 21: (CHARACTER_HUTAO, CHARACTER_THOMA, CHARACTER_SAYU, CHARACTER_DIONA), } ##################################################################################################### ##################################################################################################### ##################################################################################################### NEXT_BANNER_INDEX = max(BANNER_DATA.keys()) + 1 # rules # Arguments: # characters - List of all characters potentially in this banner # Returns: # True when the given characters don't violate the rule # IMPORTANT: These rules are what we've seen so far and mihoyo might break them in the future def rule_no_starter_characters(characters, next5star=None): """ Character is not a starter character """ for char in characters: if char in STARTER_CHARACTERS: return False return True def rule_no_duplicate_element(characters, next5star=None): """ No character in group is allowed to have the same element """ elements = [] for char in characters: is5star = CHARACTER_DATA[char][INDEX_STARS] == 5 if is5star: # ignore 5* characters continue element = CHARACTER_DATA[char][INDEX_ELEMENT] if element in elements: return False elements.append(element) return True def rule_character_wasnt_on_the_last_banner(characters, next5star=None): """ No character was available in the last banner """ previous_banner_index = NEXT_BANNER_INDEX - 1 if not previous_banner_index in BANNER_DATA: return False banner = BANNER_DATA[previous_banner_index] banner = filter_4_stars(banner) for char in characters: if char in banner: return False return True def rule_unique_combination(characters, next5star=None): """ Character list has to be unique aka never appeared exactly like this on a previous banner """ for banner_index in BANNER_DATA: group = BANNER_DATA[banner_index] group = filter_4_stars(group) if sorted(characters) == sorted(group): return False return True def rule_at_least_two_different_weapon_types_with_next_5_star(characters, next5star=None): """ The characters should have at least 2 different weapon types otherwise mihoyo can't sell a weapon banner with 2 different types either... """ # if the next banner character isn't set, trying to evaluate this rule is pointless # because it is possible to have all 4* characters with the same weapon type (See banner #4) if next5star is None: return True weapon_type_counter = {} characters = characters + (next5star,) for char in characters: weapon_type = CHARACTER_DATA[char][INDEX_WEAPON_TYPE] if not weapon_type in weapon_type_counter: weapon_type_counter[weapon_type] = 0 weapon_type_counter[weapon_type] += 1 return len(weapon_type_counter.keys()) >= 2 def rule_has_at_least_one_waifu(characters, next5star=None): for char in characters: if CHARACTER_DATA[char][INDEX_IS_WAIFU]: return True return False # NOTE You can comment out rules like "No starter characters" to see what happens if that rule is actually not a thing RULES = [ rule_no_starter_characters, rule_no_duplicate_element, rule_character_wasnt_on_the_last_banner, rule_unique_combination, rule_at_least_two_different_weapon_types_with_next_5_star, rule_has_at_least_one_waifu, ] def is_valid(characters, next5star=None, rules=RULES): for rule in rules: if not rule(characters, next5star): return False return True def which_rule_failed(characters, next5star=None): failed = [] for rule in RULES: if not rule(characters, next5star): failed.append(rule.__name__) return failed # scoring # The higher the score the more likely it is a certain banner will appear SCORE_CHARACTER_ABSENCE = 2 SCORE_FREE_CHARACTERS = -1 SCORE_FIRST_4STAR_REAPPEARANCE = 15 def score_character_being_absent(characters): """ The longer a character has been absent the higher the overall score """ newest_eligible_banner_index = NEXT_BANNER_INDEX - 2 if not newest_eligible_banner_index in BANNER_DATA: return False character_scores = {} score_counter = 1 for index in range(newest_eligible_banner_index, 0, -1): group = BANNER_DATA[index] group = filter_4_stars(group) for char in characters: if char in character_scores: continue if char in group: character_scores[char] = score_counter score_counter += SCORE_CHARACTER_ABSENCE if len(character_scores.keys()) == 3: break for char in characters: # if character hasn't ever appeared in a banner before they get awared the maximum points if not char in character_scores: character_scores[char] = len(BANNER_DATA.keys()) return sum(character_scores.values()) def score_free_character_reductions(characters): total = 0 for char in characters: if char in FREE_PERMANENT_CHARACTERS: total += SCORE_FREE_CHARACTERS return total def score_first_4star_reappearance(characters): def find_first_character_appearance(char): for index in BANNER_DATA: banner = BANNER_DATA[index] if char in banner: return index return -1 for char in characters: first_banner = find_first_character_appearance(char) banners_since = NEXT_BANNER_INDEX - first_banner - 1 if banners_since == 2 or banners_since == 3: # this can probably only apply to one character return SCORE_FIRST_4STAR_REAPPEARANCE return 0 SCORE_FUNCTIONS = [ score_character_being_absent, score_free_character_reductions, score_first_4star_reappearance, ] def calculate_score(characters): total = 0 for score_func in SCORE_FUNCTIONS: total += score_func(characters) return total def sanity_check(): # idiot test, all old banners should be valid rules_for_idiot_test = [ rule for rule in RULES if not rule.__name__ in [ "rule_character_wasnt_on_the_last_banner", "rule_unique_combination", "rule_character_shouldnt_be_in_shop", "rule_no_duplicate_element_considering_next_5_star", ] ] for banner_index in BANNER_DATA: group = BANNER_DATA[banner_index] if not is_valid(group, None, rules_for_idiot_test): failed_rules = which_rule_failed(group) print("ERROR: Banner", banner_index, group, "is apparently invalid? Check your rules...", failed_rules) exit(1) def calculate_next_banner(character_5star=None): # get all character combinations possible_combinations = combinations(CHARACTERS_4_STARS, 3) valid_groups = [] for group in possible_combinations: if is_valid(group, character_5star): valid_groups.append((group, calculate_score(group))) sorted_valid_groups = sorted(valid_groups, key=lambda group_with_value: group_with_value[1], reverse=True) return sorted_valid_groups def main(): print("# Genshin Banner Prediction Script", SCRIPT_VERSION, "by @atomicptr") sanity_check() character_5stars = NEXT_BANNER_5_STAR if not isinstance(NEXT_BANNER_5_STAR, list): character_5stars = [NEXT_BANNER_5_STAR] for next5star in character_5stars: print("\n## Banner prediction for rate up character:", next5star) valid_groups = calculate_next_banner(next5star) print("Found", len(valid_groups), "valid combinations.") print("\nHighest scoring groups:") top_groups = valid_groups[:5] # find lowest score, we want to include all combinations that have the same score as well... lowest_score = min([scored_pair[1] for scored_pair in top_groups]) # remove lowest score for now... top_groups = [scored_pair for scored_pair in top_groups if scored_pair[1] != lowest_score] # and finally readd them top_groups = top_groups + [scored_pair for scored_pair in valid_groups if scored_pair[1] == lowest_score] for group in valid_groups[:5]: print(group) if __name__ == "__main__": main()
Editor Settings
Theme
Key bindings
Full width
Lines