Guest User

Arknights Endfield pull sim

a guest
Mar 10th, 2026
143
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 29.05 KB | None | 0 0
  1. import random
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. from collections import defaultdict
  5.  
  6. class ArknightsEndfieldPitySimulator:
  7.     def __init__(self, num_banners=10):
  8.         # Базовые параметры гачи (только 6★)
  9.         self.base_rate_6 = 0.008  # 0.8% базовый шанс 6★
  10.         self.soft_pity_start = 65  # Начало софт-пити
  11.         self.hard_pity = 80  # Хард-пити (гарантия 6★)
  12.        
  13.         # Награды за количество круток
  14.         self.emergency_pity = 30  # Экстренная вербовка (10 бесплатных круток)
  15.         self.dossier_pity = 60  # Досье охотника (10 бесплатных круток)
  16.        
  17.         # Система 50/50
  18.         self.rate_up_chance = 0.5  # 50% шанс получить ивентового персонажа
  19.        
  20.         # Бесплатные крутки с магазина (обновляются каждый баннер)
  21.         self.free_shop_pulls = 10
  22.        
  23.         # Количество баннеров
  24.         self.num_banners = num_banners
  25.        
  26.     def calculate_6star_chance(self, pity_counter, is_emergency=False):
  27.         """
  28.        Рассчет шанса выпадения 6★
  29.        is_emergency: для экстренной вербовки всегда базовый шанс 0.8%
  30.        """
  31.         if is_emergency:
  32.             return self.base_rate_6  # Экстренная вербовка всегда 0.8%
  33.        
  34.         if pity_counter < self.soft_pity_start:
  35.             return self.base_rate_6
  36.         else:
  37.             # Увеличение на 5% за каждую крутку после 65
  38.             extra_rolls = pity_counter - self.soft_pity_start + 1
  39.             return min(1.0, self.base_rate_6 + 0.05 * extra_rolls)
  40.    
  41.     def simulate_pull(self, pity_counter, count_for_pity=True, is_emergency=False):
  42.         """
  43.        Симуляция одной крутки (только проверка на 6★)
  44.        count_for_pity: влияет ли крутка на счетчик гаранта
  45.        is_emergency: для экстренной вербовки - всегда базовый шанс
  46.        Возвращает: (выпал_6★, новый_pity)
  47.        """
  48.         # Проверяем выпадение 6★ с учетом типа крутки
  49.         if random.random() < self.calculate_6star_chance(pity_counter, is_emergency):
  50.             # Выпал 6★
  51.             if count_for_pity:
  52.                 return True, 0
  53.             else:
  54.                 return True, pity_counter  # pity не меняется
  55.         else:
  56.             # Не выпал 6★
  57.             if count_for_pity:
  58.                 return False, pity_counter + 1
  59.             else:
  60.                 return False, pity_counter
  61.    
  62.     def check_50_50(self, is_target_banner):
  63.         """Проверка 50/50 на получение ивентового персонажа"""
  64.         return random.random() < self.rate_up_chance
  65.    
  66.     def simulate_banner(self, is_target_banner, strategy, banner_index,
  67.                         current_pity,
  68.                         has_dossier_from_prev=False):
  69.         """
  70.        Симуляция одного баннера
  71.        Возвращает: платные крутки, полученных персонажей, новый pity, использована ли экстренная
  72.        """
  73.         pulls_spent = 0  # ТОЛЬКО платные крутки
  74.         total_pulls_for_rewards = 0  # Крутки, которые считаются для получения наград (НЕ включая экстренную вербовку)
  75.        
  76.         got_target = False
  77.         event_6star_count = 0
  78.         total_6star_count = 0
  79.        
  80.         # Получили ли досье на ЭТОМ баннере (для следующего)
  81.         got_dossier_this_banner = False
  82.        
  83.         # Счетчик пити
  84.         local_pity = current_pity
  85.        
  86.         # 1. Досье с прошлого баннера (всегда используем полностью, если есть)
  87.         if has_dossier_from_prev:
  88.             for _ in range(10):
  89.                 got_six, local_pity = self.simulate_pull(local_pity, count_for_pity=True, is_emergency=False)
  90.                
  91.                 if got_six:
  92.                     total_6star_count += 1
  93.                     if self.check_50_50(is_target_banner):
  94.                         event_6star_count += 1
  95.                         if is_target_banner:
  96.                             got_target = True
  97.            
  98.             total_pulls_for_rewards += 10  # Досье учитывается для наград
  99.        
  100.         # 2. Бесплатные крутки с магазина (всегда используем все 10)
  101.         for _ in range(self.free_shop_pulls):
  102.             got_six, local_pity = self.simulate_pull(local_pity, count_for_pity=True, is_emergency=False)
  103.            
  104.             if got_six:
  105.                 total_6star_count += 1
  106.                 if self.check_50_50(is_target_banner):
  107.                     event_6star_count += 1
  108.                     if is_target_banner:
  109.                         got_target = True
  110.        
  111.         total_pulls_for_rewards += self.free_shop_pulls  # Магазин учитывается для наград
  112.        
  113.         # Флаг, что мы уже использовали экстренную вербовку на этом баннере
  114.         emergency_used_this_banner = False
  115.        
  116.         # 3. ОСНОВНЫЕ КРУТКИ (платные) согласно стратегии
  117.         while True:
  118.             # Проверка на спарк (120 круток, учитываются только те, что идут в награды)
  119.             if is_target_banner and total_pulls_for_rewards >= 120 and not got_target:
  120.                 got_target = True
  121.                 event_6star_count += 1
  122.                 total_6star_count += 1
  123.                 break
  124.            
  125.             # Проверка условий остановки
  126.             stop_pulling = self.should_stop_pulling(
  127.                 strategy, is_target_banner, total_pulls_for_rewards,
  128.                 local_pity, got_target, emergency_used_this_banner
  129.             )
  130.            
  131.             if stop_pulling:
  132.                 break
  133.            
  134.             # Платная крутка
  135.             pulls_spent += 1
  136.             total_pulls_for_rewards += 1  # Платные крутки учитываются для наград
  137.            
  138.             got_six, local_pity = self.simulate_pull(local_pity, count_for_pity=True, is_emergency=False)
  139.            
  140.             if got_six:
  141.                 total_6star_count += 1
  142.                 if self.check_50_50(is_target_banner):
  143.                     event_6star_count += 1
  144.                     if is_target_banner:
  145.                         got_target = True
  146.            
  147.             # Проверка на экстренную вербовку (30 круток в счетчике наград)
  148.             if total_pulls_for_rewards >= self.emergency_pity and not emergency_used_this_banner:
  149.                 emergency_used_this_banner = True
  150.                
  151.                 # ЭКСТРЕННАЯ ВЕРБОВКА - 10 круток
  152.                 # Важно: всегда базовый шанс 0.8%, не зависит от pity!
  153.                 for _ in range(10):
  154.                     got_six, _ = self.simulate_pull(local_pity, count_for_pity=False, is_emergency=True)
  155.                    
  156.                     if got_six:
  157.                         total_6star_count += 1
  158.                         if self.check_50_50(is_target_banner):
  159.                             event_6star_count += 1
  160.                             if is_target_banner:
  161.                                 got_target = True
  162.                
  163.                 # НЕ увеличиваем total_pulls_for_rewards!
  164.                 # Экстренная вербовка не идет в счет для новых наград
  165.            
  166.             # Проверка на досье (60 круток в счетчике наград)
  167.             if total_pulls_for_rewards >= self.dossier_pity and not got_dossier_this_banner:
  168.                 got_dossier_this_banner = True
  169.        
  170.         return {
  171.             'pulls_spent': pulls_spent,
  172.             'new_pity': local_pity,
  173.             'got_target': got_target,
  174.             'event_6star_count': event_6star_count,
  175.             'total_6star_count': total_6star_count,
  176.             'got_dossier_for_next': got_dossier_this_banner,
  177.             'emergency_used': emergency_used_this_banner
  178.         }
  179.    
  180.     def should_stop_pulling(self, strategy, is_target, total_pulls_for_rewards,
  181.                            current_pity, got_target, emergency_used):
  182.         """Проверка нужно ли остановить пуллы"""
  183.        
  184.         # Для стратегии 3: не останавливаемся до 60, даже если получили цель
  185.         if strategy == 3:
  186.             if total_pulls_for_rewards < 60:
  187.                 return False  # Крутим до 60 на любых баннерах, независимо от получения цели
  188.             elif is_target:
  189.                 # На целевых после 60 крутим до получения цели (если еще не получили)
  190.                 # Если уже получили цель до 60, то после 60 все равно останавливаемся
  191.                 return got_target  # Останавливаемся только если цель уже получена
  192.             else:
  193.                 # На нецелевых останавливаемся после 60
  194.                 return True
  195.        
  196.         # Для стратегий 1 и 2: стандартная логика
  197.         if is_target and got_target:
  198.             return True
  199.        
  200.         if current_pity >= self.hard_pity - 1:
  201.             return False
  202.        
  203.         if strategy == 1:
  204.             # Стратегия 1: Только целевые баннеры
  205.             if is_target:
  206.                 return False  # На целевых крутим до получения цели
  207.             else:
  208.                 return True   # На нецелевых только бесплатные (уже использованы)
  209.            
  210.         elif strategy == 2:
  211.             # Стратегия 2: На нецелевых крутим до получения экстренной вербовки
  212.             if is_target:
  213.                 return False  # На целевых крутим до получения цели
  214.             else:
  215.                 # На нецелевых крутим пока не получим экстренную вербовку
  216.                 if emergency_used:
  217.                     return True  # Уже получили и использовали экстренную - стоп
  218.                 else:
  219.                     # Не получили еще - продолжаем до 30
  220.                     return total_pulls_for_rewards >= self.emergency_pity
  221.        
  222.         return True
  223.    
  224.     def simulate_all_banners(self, target_banner_indices, strategy):
  225.         """
  226.        Симуляция всех баннеров
  227.        target_banner_indices: список индексов баннеров, где нужен персонаж
  228.        Возвращает: результаты одной симуляции
  229.        """
  230.         total_pulls_spent = 0  # Суммируются ВСЕ платные крутки за все баннеры
  231.         total_free_pulls = 0   # Суммируются ВСЕ бесплатные крутки за все баннеры
  232.        
  233.         targets_obtained = 0
  234.         total_event_6stars = 0
  235.         total_6stars_all = 0
  236.        
  237.         current_pity = 0
  238.         has_dossier_from_prev = False
  239.        
  240.         for banner_idx in range(self.num_banners):
  241.             is_target = banner_idx in target_banner_indices
  242.            
  243.             banner_result = self.simulate_banner(
  244.                 is_target, strategy, banner_idx,
  245.                 current_pity,
  246.                 has_dossier_from_prev
  247.             )
  248.            
  249.             # Добавляем платные крутки с этого баннера в общую сумму
  250.             total_pulls_spent += banner_result['pulls_spent']
  251.            
  252.             # Подсчет бесплатных круток на этом баннере
  253.             # Магазин всегда 10
  254.             free_this_banner = 10
  255.            
  256.             # Досье с прошлого баннера (10 круток)
  257.             if has_dossier_from_prev:
  258.                 free_this_banner += 10
  259.            
  260.             # Экстренная вербовка (10 круток) если использовали
  261.             if banner_result['emergency_used']:
  262.                 free_this_banner += 10
  263.            
  264.             total_free_pulls += free_this_banner
  265.            
  266.             if banner_result['got_target']:
  267.                 targets_obtained += 1
  268.            
  269.             total_event_6stars += banner_result['event_6star_count']
  270.             total_6stars_all += banner_result['total_6star_count']
  271.            
  272.             current_pity = banner_result['new_pity']
  273.             has_dossier_from_prev = banner_result['got_dossier_for_next']
  274.        
  275.         return {
  276.             'total_pulls': total_pulls_spent,  # Сумма ВСЕХ платных круток за все баннеры
  277.             'total_free': total_free_pulls,    # Сумма ВСЕХ бесплатных круток за все баннеры
  278.             'targets_obtained': targets_obtained,
  279.             'total_event_6stars': total_event_6stars,
  280.             'total_6stars': total_6stars_all
  281.         }
  282.    
  283.     def run_multiple_simulations(self, target_banner_indices, num_simulations=10000):
  284.         """Запуск множества симуляций"""
  285.         results = {1: defaultdict(list), 2: defaultdict(list), 3: defaultdict(list)}
  286.        
  287.         for sim in range(num_simulations):
  288.             if sim % 1000 == 0:
  289.                 print(f"Прогресс: {sim}/{num_simulations} симуляций")
  290.                
  291.             for strategy in [1, 2, 3]:
  292.                 sim_result = self.simulate_all_banners(target_banner_indices, strategy)
  293.                
  294.                 # Сохраняем результаты этой симуляции
  295.                 for key in ['total_pulls', 'total_free', 'targets_obtained',
  296.                            'total_event_6stars', 'total_6stars']:
  297.                     results[strategy][key].append(sim_result[key])
  298.        
  299.         return results
  300.    
  301.     def print_statistics(self, results, target_banners_count):
  302.         """Вывод статистики"""
  303.         print("\n" + "=" * 100)
  304.         print(f"📊 РЕЗУЛЬТАТЫ СИМУЛЯЦИИ (10,000 симуляций, {target_banners_count} целевых баннеров из {self.num_banners})")
  305.         print("=" * 100)
  306.        
  307.         strategies_desc = {
  308.             1: "🔵 СТРАТЕГИЯ 1: Только целевые баннеры",
  309.             2: "🟢 СТРАТЕГИЯ 2: На нецелевых крутим ДО ЭКСТРЕННОЙ ВЕРБОВКИ (30) + сама вербовка",
  310.             3: "🔴 СТРАТЕГИЯ 3: МИНИМУМ 60 НА КАЖДОМ БАННЕРЕ (не останавливаемся до 60, даже если получили цель)"
  311.         }
  312.        
  313.         for strategy in [1, 2, 3]:
  314.             print(f"\n{strategies_desc[strategy]}")
  315.             print("-" * 80)
  316.            
  317.             pulls = results[strategy]['total_pulls']
  318.             free = results[strategy]['total_free']
  319.             targets = results[strategy]['targets_obtained']
  320.             event_6stars = results[strategy]['total_event_6stars']
  321.             total_6stars = results[strategy]['total_6stars']
  322.            
  323.             print(f"  💰 ПЛАТНЫЕ КРУТКИ (всего за {self.num_banners} баннеров):")
  324.             print(f"     Средние затраты: {np.mean(pulls):.1f}")
  325.             print(f"     Медиана: {np.median(pulls):.1f}")
  326.             print(f"     90-й перцентиль: {np.percentile(pulls, 90):.1f}")
  327.             print(f"     Минимум: {np.min(pulls):.0f}")
  328.             print(f"     Максимум: {np.max(pulls):.0f}")
  329.            
  330.             print(f"\n  🎁 БЕСПЛАТНЫЕ КРУТКИ (всего за {self.num_banners} баннеров):")
  331.             print(f"     Среднее количество: {np.mean(free):.1f}")
  332.             print(f"     Медиана: {np.median(free):.1f}")
  333.             print(f"     Минимум: {np.min(free):.0f}")
  334.             print(f"     Максимум: {np.max(free):.0f}")
  335.            
  336.             print(f"\n  🎯 ЦЕЛЕВЫЕ ПЕРСОНАЖИ (получены на целевых баннерах):")
  337.             print(f"     В среднем: {np.mean(targets):.2f} из {target_banners_count}")
  338.             print(f"     Вероятность получить всех: {np.mean([1 if t == target_banners_count else 0 for t in targets])*100:.1f}%")
  339.             print(f"     Риск без целей: {np.mean([1 if t == 0 else 0 for t in targets])*100:.1f}%")
  340.            
  341.             print(f"\n  🌟 ВСЕ ИВЕНТОВЫЕ 6★ (включая полученных на нецелевых баннерах):")
  342.             print(f"     В среднем: {np.mean(event_6stars):.2f}")
  343.             print(f"     На 1000 платных круток: {np.mean(event_6stars)/np.mean(pulls)*1000:.2f}")
  344.             print(f"     На 1000 ВСЕХ круток: {np.mean(event_6stars)/(np.mean(pulls)+np.mean(free))*1000:.2f}")
  345.            
  346.             print(f"\n  💎 ВСЕГО 6★ (ивентовые + стандартные):")
  347.             print(f"     В среднем: {np.mean(total_6stars):.2f}")
  348.             print(f"     На 1000 платных круток: {np.mean(total_6stars)/np.mean(pulls)*1000:.2f}")
  349.    
  350.     def plot_results(self, results, target_count):
  351.         """Построение графиков"""
  352.         fig, axes = plt.subplots(2, 4, figsize=(20, 12))
  353.        
  354.         colors = {1: 'blue', 2: 'green', 3: 'red'}
  355.         labels = {1: 'Стратегия 1', 2: 'Стратегия 2 (до 30)', 3: 'Стратегия 3 (мин 60)'}
  356.        
  357.         # 1. Распределение платных круток
  358.         ax = axes[0, 0]
  359.         for s in [1, 2, 3]:
  360.             ax.hist(results[s]['total_pulls'], bins=50, alpha=0.5,
  361.                    color=colors[s], label=labels[s], density=True)
  362.         ax.set_xlabel(f'Всего платных круток за {self.num_banners} баннеров')
  363.         ax.set_ylabel('Плотность')
  364.         ax.set_title('Распределение платных круток')
  365.         ax.legend()
  366.         ax.grid(True, alpha=0.3)
  367.        
  368.         # 2. Распределение бесплатных круток
  369.         ax = axes[0, 1]
  370.         for s in [1, 2, 3]:
  371.             ax.hist(results[s]['total_free'], bins=30, alpha=0.5,
  372.                    color=colors[s], label=labels[s], density=True)
  373.         ax.set_xlabel(f'Всего бесплатных круток за {self.num_banners} баннеров')
  374.         ax.set_ylabel('Плотность')
  375.         ax.set_title('Распределение бесплатных круток')
  376.         ax.legend()
  377.         ax.grid(True, alpha=0.3)
  378.        
  379.         # 3. Целевые персонажи
  380.         ax = axes[0, 2]
  381.         x = np.arange(3)
  382.         targets_mean = [np.mean(results[s]['targets_obtained']) for s in [1, 2, 3]]
  383.         targets_std = [np.std(results[s]['targets_obtained']) for s in [1, 2, 3]]
  384.        
  385.         ax.bar(x, targets_mean, yerr=targets_std, capsize=5,
  386.                color=[colors[s] for s in [1, 2, 3]], alpha=0.7)
  387.         ax.set_xticks(x)
  388.         ax.set_xticklabels(['Стратегия 1', 'Стратегия 2', 'Стратегия 3'])
  389.         ax.set_ylabel('Среднее количество')
  390.         ax.set_title(f'Получено ЦЕЛЕВЫХ персонажей (из {target_count})')
  391.         ax.axhline(y=target_count, color='black', linestyle='--', alpha=0.5)
  392.         ax.grid(True, alpha=0.3)
  393.        
  394.         # 4. Всего ивентовых 6★
  395.         ax = axes[0, 3]
  396.         event_mean = [np.mean(results[s]['total_event_6stars']) for s in [1, 2, 3]]
  397.         event_std = [np.std(results[s]['total_event_6stars']) for s in [1, 2, 3]]
  398.        
  399.         ax.bar(x, event_mean, yerr=event_std, capsize=5,
  400.                color=[colors[s] for s in [1, 2, 3]], alpha=0.7)
  401.         ax.set_xticks(x)
  402.         ax.set_xticklabels(['Стратегия 1', 'Стратегия 2', 'Стратегия 3'])
  403.         ax.set_ylabel('Среднее количество')
  404.         ax.set_title('Все полученные ИВЕНТОВЫЕ 6★')
  405.         ax.grid(True, alpha=0.3)
  406.        
  407.         # 5. Эффективность по целевым
  408.         ax = axes[1, 0]
  409.         target_eff = [np.mean(results[s]['targets_obtained']) / np.mean(results[s]['total_pulls']) * 1000
  410.                      for s in [1, 2, 3]]
  411.         ax.bar(x, target_eff, color=[colors[s] for s in [1, 2, 3]], alpha=0.7)
  412.         ax.set_xticks(x)
  413.         ax.set_xticklabels(['Стратегия 1', 'Стратегия 2', 'Стратегия 3'])
  414.         ax.set_ylabel('Целей на 1000 платных круток')
  415.         ax.set_title('Эффективность (целевые)')
  416.         for i, v in enumerate(target_eff):
  417.             ax.text(i, v + 0.1, f'{v:.2f}', ha='center')
  418.         ax.grid(True, alpha=0.3)
  419.        
  420.         # 6. Эффективность по ивентовым
  421.         ax = axes[1, 1]
  422.         event_eff = [np.mean(results[s]['total_event_6stars']) / np.mean(results[s]['total_pulls']) * 1000
  423.                     for s in [1, 2, 3]]
  424.         ax.bar(x, event_eff, color=[colors[s] for s in [1, 2, 3]], alpha=0.7)
  425.         ax.set_xticks(x)
  426.         ax.set_xticklabels(['Стратегия 1', 'Стратегия 2', 'Стратегия 3'])
  427.         ax.set_ylabel('Ивентовых на 1000 платных круток')
  428.         ax.set_title('Эффективность (все ивентовые)')
  429.         for i, v in enumerate(event_eff):
  430.             ax.text(i, v + 0.1, f'{v:.2f}', ha='center')
  431.         ax.grid(True, alpha=0.3)
  432.        
  433.         # 7. Соотношение платных и бесплатных
  434.         ax = axes[1, 2]
  435.         pulls_mean = [np.mean(results[s]['total_pulls']) for s in [1, 2, 3]]
  436.         free_mean = [np.mean(results[s]['total_free']) for s in [1, 2, 3]]
  437.        
  438.         ax.bar(x - 0.2, pulls_mean, width=0.4, label='Платные', alpha=0.7, color='blue')
  439.         ax.bar(x + 0.2, free_mean, width=0.4, label='Бесплатные', alpha=0.7, color='green')
  440.         ax.set_xticks(x)
  441.         ax.set_xticklabels(['Стратегия 1', 'Стратегия 2', 'Стратегия 3'])
  442.         ax.set_ylabel('Среднее количество круток')
  443.         ax.set_title('Платные vs Бесплатные')
  444.         ax.legend()
  445.         ax.grid(True, alpha=0.3)
  446.        
  447.         # 8. Риск и успех
  448.         ax = axes[1, 3]
  449.         success = [np.mean([1 if t == target_count else 0 for t in results[s]['targets_obtained']])*100
  450.                   for s in [1, 2, 3]]
  451.         risk = [np.mean([1 if t == 0 else 0 for t in results[s]['targets_obtained']])*100
  452.                for s in [1, 2, 3]]
  453.        
  454.         ax.bar(x - 0.2, success, width=0.4, label='Успех (все цели)', alpha=0.7, color='green')
  455.         ax.bar(x + 0.2, risk, width=0.4, label='Риск (0 целей)', alpha=0.7, color='red')
  456.         ax.set_xticks(x)
  457.         ax.set_xticklabels(['Стратегия 1', 'Стратегия 2', 'Стратегия 3'])
  458.         ax.set_ylabel('Вероятность (%)')
  459.         ax.set_title('Вероятность успеха и риска')
  460.         ax.legend()
  461.         ax.grid(True, alpha=0.3)
  462.        
  463.         plt.tight_layout()
  464.         plt.show()
  465.  
  466. # Основная программа
  467. if __name__ == "__main__":
  468.     # ===== НАСТРОЙКИ =====
  469.     NUM_BANNERS = 10  # Количество баннеров для симуляции
  470.     # ====================
  471.    
  472.     # Создаем симулятор с указанным количеством баннеров
  473.     simulator = ArknightsEndfieldPitySimulator(num_banners=NUM_BANNERS)
  474.    
  475.     # Задаем конкретные целевые баннеры (например каждый второй)
  476.     target_banners = [1, 3, 5, 7, 9]
  477.     target_count = len(target_banners)
  478.    
  479.     print("=" * 100)
  480.     print(f"🎯 СИМУЛЯТОР ARKNIGHTS: ENDFIELD - 3 СТРАТЕГИИ ({NUM_BANNERS} БАННЕРОВ)")
  481.     print("=" * 100)
  482.     print(f"\nЦелевые баннеры: {target_banners}")
  483.     print(f"Всего баннеров: {NUM_BANNERS}")
  484.     print(f"Целевых: {target_count}, Нецелевых: {NUM_BANNERS - target_count}")
  485.    
  486.     print("\n📋 СТРАТЕГИИ:")
  487.     print("   1️⃣  СТРАТЕГИЯ 1: Только целевые баннеры")
  488.     print("       - На целевых: крутим до получения ЦЕЛЕВОГО 6★")
  489.     print("       - На нецелевых: только бесплатные крутки (магазин, досье)")
  490.    
  491.     print("\n   2️⃣  СТРАТЕГИЯ 2: Экстренная вербовка на нецелевых")
  492.     print("       - На целевых: крутим до получения ЦЕЛЕВОГО 6★")
  493.     print("       - На нецелевых: крутим ДО ЭКСТРЕННОЙ ВЕРБОВКИ (30 круток) + используем её")
  494.    
  495.     print("\n   3️⃣  СТРАТЕГИЯ 3: Минимум 60 на КАЖДОМ баннере")
  496.     print("       - На ВСЕХ баннерах: крутим минимум ДО 60 круток (ДАЖЕ ЕСЛИ ПОЛУЧИЛИ ЦЕЛЬ РАНЬШЕ)")
  497.     print("       - На целевых: после 60 продолжаем до получения ЦЕЛЕВОГО 6★ (если еще не получили)")
  498.    
  499.     print("\n📌 ВАЖНЫЕ ПРАВИЛА:")
  500.     print("   • Экстренная вербовка (10 круток):")
  501.     print("     - Всегда имеет базовый шанс 0.8% (не зависит от soft pity)")
  502.     print("     - НЕ учитывается в счетчике для новых наград")
  503.     print("     - НЕ влияет на pity")
  504.     print("   • Досье и магазин учитываются в счетчике наград")
  505.     print("   • На нецелевых баннерах НЕ останавливаемся при получении любого 6★")
  506.    
  507.     print("\n🔄 Запуск симуляции (10,000 итераций)...")
  508.     results = simulator.run_multiple_simulations(target_banners, num_simulations=10000)
  509.    
  510.     simulator.print_statistics(results, target_count)
  511.     simulator.plot_results(results, target_count)
  512.    
  513.     print("\n" + "=" * 100)
  514.     print("🏆 СРАВНЕНИЕ СТРАТЕГИЙ:")
  515.     print("=" * 100)
  516.    
  517.     # Собираем данные для сравнения
  518.     comparison = []
  519.     for s in [1, 2, 3]:
  520.         pulls = np.mean(results[s]['total_pulls'])
  521.         free = np.mean(results[s]['total_free'])
  522.         targets = np.mean(results[s]['targets_obtained'])
  523.         events = np.mean(results[s]['total_event_6stars'])
  524.         success_rate = np.mean([1 if t == target_count else 0 for t in results[s]['targets_obtained']])*100
  525.         risk_rate = np.mean([1 if t == 0 else 0 for t in results[s]['targets_obtained']])*100
  526.        
  527.         comparison.append({
  528.             'Стратегия': s,
  529.             'Платные': f"{pulls:.0f}",
  530.             'Бесплатные': f"{free:.0f}",
  531.             'Целей': f"{targets:.2f}",
  532.             'Успех': f"{success_rate:.1f}%",
  533.             'Риск': f"{risk_rate:.1f}%",
  534.             'Ивентовых': f"{events:.2f}",
  535.             'Целей/1000': f"{targets/pulls*1000:.2f}"
  536.         })
  537.    
  538.     # Выводим таблицу
  539.     print("\n{:<12} {:<10} {:<12} {:<10} {:<10} {:<8} {:<12} {:<12}".format(
  540.         "Стратегия", "Платные", "Бесплатные", "Целей", "Успех", "Риск", "Ивентовых", "Целей/1000"))
  541.     print("-" * 96)
  542.    
  543.     for row in comparison:
  544.         print("{:<12} {:<10} {:<12} {:<10} {:<10} {:<8} {:<12} {:<12}".format(
  545.             row['Стратегия'], row['Платные'], row['Бесплатные'], row['Целей'],
  546.             row['Успех'], row['Риск'], row['Ивентовых'], row['Целей/1000']))
  547.    
  548.    
Advertisement
Add Comment
Please, Sign In to add comment