كيفية اختيار المعدات لعبة الفارسية في بيثون

نتعلم أن نجد الأفضل للسارق لدينا بمساعدة البرمجة. نكتشف أيضًا ما إذا كان البرنامج يقودنا "من طرف الأنف".

صورة

الغرض: تعلم كيفية محاكاة الجزء الضروري من آليات اللعبة في المختبر خطوة بخطوة ، للحصول على البيانات اللازمة واستخلاص النتائج منها.

ما تحتاجه: Python 3 ، بيئة للعمل مع التعليمات البرمجية (لدي PyCharm).

في الألعاب ، يرغب العديد من الأشخاص في تحقيق أقصى استفادة من شخصياتهم ، ولهذا تحتاج إلى اختيار أفضل مجموعة من المعدات ، والتي غالبًا ما تكون كثيرة. دعونا نحاول كتابة خوارزمية خاصة بنا لاختبار مجموعات مختلفة من المعدات وجمع البيانات.

في البداية ، ألهمتني لعبة "World of Warcraft: Classic" (أخذت الرموز من هناك ) ، ولكن في هذه العملية قمت ببعض التبسيط. رابط للمشروع بأكمله في نهاية المقال.

الخطوة 1 - تقييم منطقة البحث


لنفترض أن لدينا شخصية في فئة Rogue. من الضروري التقاط معدات له حيث سيلحق بها أكبر قدر من الضرر للعدو. نحن مهتمون بأمور الفتحات "الأسلحة في اليد اليمنى" (4 قطع) ، "الأسلحة في اليد اليسرى" (4 قطع) ، "القفازات" (2 قطعة) ، "الرأس" (3 قطع) ، "الصدر" (3 قطع) ، "أرجل" (3 قطع) ، "قدم" (2 قطعة). سنضع مجموعاتهم المختلفة على الشخصية ونحاكي المعركة. وإذا طبقت فكرة البحث الشامل (الذي سنبدأ به) ، لتقييم جميع المجموعات ، سيكون عليك إنفاق 4 * 4 * 2 * 3 * 3 * 3 * 2 = 1728 معارك على الأقل .

لإجراء تقييم أكثر دقة لأفضل المجموعات ، ستحتاج إلى إجراء معارك إضافية.

لذا ، في هذه المرحلة يمكننا بالفعل تقديم مخطط المشروع على النحو التالي:

صورة

الخطوة 2 - تحليل ميكانيكا اللعبة


لنبدأ بالحرف. لديه مثل هذه الخصائص التي تؤثر على الضرر الذي حدث وعلى بعضهم البعض:

  1. قوة الهجوم - يتم تحويلها مباشرة إلى الضرر الناتج عن ضربة طبيعية (1 إلى 1). محسوبة بالصيغة: نقاط قوة الهجوم + نقاط القوة + نقاط البراعة
  2. القوة - +1 لمهاجمة القوة وكل شيء (ما يجب القيام به ، هذا هو تصميم اللعبة)
  3. البراعة - +1 لمهاجمة القوة ، وكذلك كل 20 وحدة من الرشاقة تضيف فرصة حرجة 1 ٪
  4. جزيرة كريت. الصدفة - فرصة التسبب في ضرر مزدوج إذا لم تكن الضربة تنزلق وتفوت
  5. دقة - فرصة متزايدة لضرب الخصم
  6. الإتقان - تقلل كل وحدة إتقان من احتمال حدوث ضربة منزلقة بنسبة 4٪ (وهو ما يعادل مبدئيًا 40٪ ، مما يعني أن 10 وحدات من الإتقان تستبعد تمامًا احتمال حدوث ضربة منزلقة)

يوضح الرسم البياني أدناه القيم الأساسية للسارق لدينا وكيفية تغيير عنصر من المعدات يغيرها:

صورة

لذا فقد حان الوقت لبدء كتابة التعليمات البرمجية. نحن نصف ما نعرفه بالفعل في فئة المارقة. ستقوم طريقة set_stats_without_equip باستعادة حالة الحرف بدون معدات ، وهو أمر مفيد عند تغيير المجموعات. لن يتم استدعاء الطريقتين calculate_critical_percent و calculate_glancing_percent في المستقبل إلا إذا لزم الأمر ، مع تحديث قيم الخصائص المحددة.

السطور الأولى من الفصل
class Rogue:
    """    ."""

    def __init__(self):

        #    ( -     ):
        self.basic_stat_agility = 50
        self.basic_stat_power = 40
        self.basic_stat_hit = 80
        self.basic_stat_crit = 20
        self.basic_stat_mastery = 0

        #     :
        self.set_stats_without_equip()


    #       :
    def set_stats_without_equip(self):
        self.stat_agility = self.basic_stat_agility
        self.stat_power = self.basic_stat_power
        self.stat_attackpower = self.stat_agility + self.stat_power
        self.stat_hit = self.basic_stat_hit
        self.direct_crit_bonus = 0
        self.calculate_critical_percent()
        self.stat_mastery = self.basic_stat_mastery
        self.calculate_glancing_percent()


    #      :
    def calculate_critical_percent(self):
        self.stat_crit = self.basic_stat_crit + self.direct_crit_bonus + self.stat_agility // 20


    #      :
    def calculate_glancing_percent(self):
        self.stat_glancing_percent = 40 - self.stat_mastery * 4


الآن أنت بحاجة للتعامل مع المعدات. لفرز جميع الأشياء بشكل ملائم ، وإنشاء مجموعاتها ، قررت إنشاء قاموس منفصل لكل نوع من المعدات: RIGHT_HANDS ، LEFT_HANDS ، GLOVES ، HEADS ، CHESTS ، PANTS ، BOOTS. يتم تخزين الصفوف التالية كقيم في القواميس:

صورة

إنشاء ملف منفصل للقواميس مع المعدات. لدي العديد من هذه الملفات بمجموعات مختلفة.

معدات اختبار مجردة
#    ,     :
# 0 - , 1 - , 2 - , 3 - , 4 - , 5 - , 6 - 

EQUIPMENT_COLLECTION = 'custom'

RIGHT_HANDS = dict()
RIGHT_HANDS[1] = ('  ', 50, 3, 0, 0, 0, 0)
RIGHT_HANDS[2] = (' ', 40, 22, 0, 0, 0, 0)
RIGHT_HANDS[3] = (' ', 40, 0, 0, 3, 0, 0)
RIGHT_HANDS[4] = (' ', 40, 0, 0, 0, 0, 5)

LEFT_HANDS = dict()
LEFT_HANDS[1] = ('  ', 35, 3, 0, 0, 0, 0)
LEFT_HANDS[2] = (' ', 40, 22, 0, 0, 0, 0)
LEFT_HANDS[3] = (' ', 40, 0, 0, 3, 0, 0)
LEFT_HANDS[4] = (' ', 40, 0, 0, 0, 0, 5)

GLOVES = dict()
GLOVES[1] = (' ', 0, 12, 0, 2, 0, 0)
GLOVES[2] = (' ', 2, 2, 2, 1, 1, 0)

HEADS = dict()
HEADS[1] = (' ', 0, 22, 0, 0, 0, 0)
HEADS[2] = (' ', 0, 0, 0, 0, 2, 0)
HEADS[3] = (' ', 0, 0, 0, 2, 0, 0)

CHESTS = dict()
CHESTS[1] = (' ', 0, 30, 0, 0, 0, 0)
CHESTS[2] = (' ', 0, 0, 0, 0, 3, 0)
CHESTS[3] = (' ', 0, 0, 0, 3, 0, 0)

PANTS = dict()
PANTS[1] = (' ', 0, 24, 0, 0, 0, 0)
PANTS[2] = (' ', 0, 0, 0, 0, 2, 0)
PANTS[3] = (' ', 0, 0, 0, 2, 0, 0)

BOOTS = dict()
BOOTS[1] = ('  ', 14, 0, 5, 0, 1, 0)
BOOTS[2] = (' ', 0, 18, 0, 1, 0, 0)


الزي العالم من علب
#    ,     :
# 0 - , 1 - , 2 - , 3 - , 4 - , 5 - , 6 - 

EQUIPMENT_COLLECTION = "wow_classic_preraid"

RIGHT_HANDS = dict()
RIGHT_HANDS[1] = ('  \'', 81, 0, 4, 0, 1, 0)
RIGHT_HANDS[2] = (' ', 49, 0, 4, 0, 1, 0)
RIGHT_HANDS[3] = (' ', 57, 9, 9, 0, 0, 0)

LEFT_HANDS = dict()
LEFT_HANDS[1] = ('  \'', 52, 0, 0, 0, 0, 0)
LEFT_HANDS[2] = (' ', 49, 0, 4, 0, 1, 0)
LEFT_HANDS[3] = (' ', 57, 9, 9, 0, 0, 0)

GLOVES = dict()
GLOVES[1] = (' ', 28, 0, 0, 0, 1, 0)
GLOVES[2] = ('  ', 40, 0, 0, 0, 0, 0)

HEADS = dict()
HEADS[1] = (' ', 0, 0, 0, 2, 1, 0)
HEADS[2] = (' ', 0, 0, 13, 0, 2, 0)
HEADS[3] = (' ', 32, 0, 8, 0, 0, 0)
HEADS[4] = (' ', 0, 19, 12, 0, 0, 0)

CHESTS = dict()
CHESTS[1] = (' ', 60, 8, 8, 0, 0, 0)
CHESTS[2] = ('  ', 50, 5, 0, 0, 0, 0)
CHESTS[3] = (' ', 0, 11, 18, 0, 0, 0)

PANTS = dict()
PANTS[1] = (' ', 46, 0, 0, 0, 1, 0)
PANTS[2] = ('  ', 0, 5, 0, 1, 1, 0)

BOOTS = dict()
BOOTS[1] = (' ', 0, 21, 4, 0, 0, 0)
BOOTS[2] = ('  ', 40, 0, 0, 0, 0, 0)
BOOTS[3] = (' ', 0, 23, 0, 0, 0, 0)


إضافة سلسلة equipype إلى مُنشئ فئة Rogue
    ...
    #    ,    id  :
    # 0 -  , 1 -  , 2 - , 3 - , 4 - , 5 - , 6 - 
    self.equipment_slots = [0] * 7

    #    ,      :
    self.equipment_names = [''] * 7


نضيف أيضًا إلى صفنا ارتداء ( ارتداء الخصائص عند ارتداء الأشياء) وطرق unwear_all (إزالة كل الأشياء).

طرق الطبقة المسؤولة عن العمل مع المعدات
    ...
    #   "  ":
    def unwear_all(self):
        #  id      :
        for i in range(0, len(self.equipment_slots) ):
            self.equipment_slots[i] = 0
            self.equipment_names[i] = ''

        self.set_stats_without_equip()


    #    :
    def wear_item(self, slot, item_id, items_list):

        #      ,        ,   :
        if self.equipment_slots[slot] == 0:
            self.equipment_slots[slot] = item_id
            self.equipment_names[slot] = items_list[item_id][0]
            self.stat_agility += items_list[item_id][2]
            self.stat_power += items_list[item_id][3]
            #  ,            :
            self.stat_attackpower += items_list[item_id][1] + items_list[item_id][2] + items_list[item_id][3]
            self.stat_hit += items_list[item_id][4]
            self.direct_crit_bonus += items_list[item_id][5]
            self.stat_mastery += items_list[item_id][6]

            #         . ,   . :
            if items_list[item_id][2] != 0 or items_list[item_id][5] != 0:
                self.calculate_critical_percent()

            #    ,    :
            if items_list[item_id][6] != 0:
                self.calculate_glancing_percent()


أيضا ، حقيقة أن الجمع بين بعض الأشياء يعطي مكافآت إضافية (في "World of Warcraft" هذا يعرف باسم "المكافأة المحددة"). في مجموعتي المجردة ، يتم منح هذه المكافأة من وضع حارس اليد اليمنى للغابات وحارس اليد اليسرى للغابات في نفس الوقت. أضف هذا إلى كود طريقة ارتداء :

وضع مكافآت في طريقة ارتداء
    ...
    #      "custom":
            if EQUIPMENT_COLLECTION == 'custom':
                #      "  " (id 1   " "),     "  " (id 1   " "),   2  . :
                if slot == 1:
                    if self.equipment_slots[1] == 1 and self.equipment_slots[0] == 1:
                        self.direct_crit_bonus += 2
                        self.calculate_critical_percent()
                        print('  ...')


الآن يجب أن يعلمنا السارق كيفية القتال. سننظر في قتال سلسلة من 1000 هجوم على خصم يقف مع ظهره لنا ومشغول بشيء آخر (وضع نموذجي لـ World of Warcraft). كل مخالفة ، بصرف النظر عن المخالفات السابقة ، قد تكون:

  • عادي - ضرر قياسي ، في نموذجنا يعادل "قوة الهجوم" للشخصية
  • متحرك - 70٪ ضرر من المعتاد
  • حرجة - ضرر مزدوج من العادي
  • ملكة جمال - 0 ضرر

سيتم تحديد ذلك من خلال سلسلة من الفحوصات وفقًا لهذا المخطط:

صورة

وبالنسبة للسارق ذي القيم الأساسية ، يتخذ هذا المخطط الشكل: لنبرمج

صورة

هذه الميكانيكا عن طريق إضافة طريقة do_attack إلى رمز صفنا. سيعيد مجموعة من رقمين: (نتيجة الهجوم ، الضرر الذي حدث).

كود الهجوم
    ...
    #    :
    def do_attack(self):
        #   :
        event_hit = randint(1, 100)

        #  :
        if event_hit > self.stat_hit:
            return 0, 0

        #  :
        else:
            #   :
            event_glancing = randint(1, 100)

            #    ,    ,
            #      10  "",  stat_glancing_percent   0,
            #      
            if event_glancing <= self.stat_glancing_percent:
                damage = floor(self.stat_attackpower * 0.7)
                return 1, damage

            #    :
            else:
                #   :
                event_crit = randint(1, 100)

                #    :
                if event_crit > self.stat_crit:
                    damage = self.stat_attackpower
                    return 2, damage

                #   :
                else:
                    damage = self.stat_attackpower * 2
                    return 3, damage


سنحقق عرضًا مناسبًا للحالة الحالية للسارق ، بحيث يمكنك في أي وقت التحقق مما يحدث له:

إعادة تعريف طريقة __str__ السحرية
    ...
    #  " "     :
    def __str__(self):

        #      :
        using_equipment_names = ''
        for i in range(0, len(self.equipment_names) - 1 ):
            using_equipment_names += self.equipment_names[i] + '", "'
        using_equipment_names = '"' + using_equipment_names + self.equipment_names[-1] + '"'

        #  :
        description = ' 60 \n'
        description += using_equipment_names + '\n'
        description += ' : ' + str(self.stat_attackpower) + ' .\n'
        description += ': ' + str(self.stat_agility) + ' .\n'
        description += ': ' + str(self.stat_power) + ' .\n'
        description += ': ' + str(self.stat_hit) + '%\n'
        description += '. : ' + str(self.stat_crit) + '%\n'
        description += ': ' + str(self.stat_mastery) + ' .\n'
        description += ' . .: ' + str(self.stat_glancing_percent) + '%\n'
        return description


الخطوة 3 - التحضير للانطلاق


حان الوقت الآن لكتابة رمز سيوفر معارك لجميع المجموعات المحتملة من المعدات. للقيام بذلك ، أتصل بالوظائف بشكل تسلسلي وفقًا لهذا المخطط:

صورة

  1. run_session - يتم تنفيذ الحلقات المتداخلة هنا ، مع فرز جميع القواميس المطلوبة بالأشياء واستدعاء الوظيفة التالية لكل مجموعة ؛ في النهاية ، سيتم إنشاء نص التقرير وحفظه في سجل الجلسة
  2. test_combination - تتم إعادة تعيين جميع الأشياء التي تم ارتداؤها سابقًا ويتم استدعاء طريقة wear_item مرارًا وتكرارًا ، وتضميد الطابع في "زي" جديد ، وبعد ذلك يتم استدعاء الوظيفة التالية
  3. simulate_fight - تسمى طريقة do_attack نفسها 1000 مرة ، يتم الاحتفاظ بالبيانات المستلمة ، إذا لزم الأمر ، يتم الاحتفاظ بسجل مفصل لكل معركة

دالات الجري ، اختبار التجميع ، محاكاة_المحاربة
#     :
def run_session(SESSION_LOG):

    #  :
    fight_number = 1

    #    :
    all_fight_data = ''

    #      :
    for new_righthand_id in RIGHT_HANDS:
        #      :
        for new_lefthand_id in LEFT_HANDS:
            #   :
            for new_gloves_id in GLOVES:
                #   :
                for new_head_id in HEADS:
                    #   :
                    for new_chest_id in CHESTS:
                        #   :
                        for new_pants_id in PANTS:
                            #   :
                            for new_boots_id in BOOTS:

                                new_fight_data = test_combination(fight_number,
                                                                  new_righthand_id,
                                                                  new_lefthand_id,
                                                                  new_gloves_id,
                                                                  new_head_id,
                                                                  new_chest_id,
                                                                  new_pants_id,
                                                                  new_boots_id
                                                                  )

                                all_fight_data += new_fight_data
                                fight_number += 1

    #       :
    save_data_to_file(SESSION_LOG, all_fight_data)

#       :
def test_combination(fight_number, righthand_id, lefthand_id, gloves_id, head_id, chest_id, pants_id, boots_id):

    #   :
    my_rogue.unwear_all()

    #     :
    my_rogue.wear_item(0, righthand_id, RIGHT_HANDS)

    #     :
    my_rogue.wear_item(1, lefthand_id, LEFT_HANDS)

    #  :
    my_rogue.wear_item(2, gloves_id, GLOVES)

    #  :
    my_rogue.wear_item(3, head_id, HEADS)

    #  :
    my_rogue.wear_item(4, chest_id, CHESTS)

    #  :
    my_rogue.wear_item(5, pants_id, PANTS)

    #  :
    my_rogue.wear_item(6, boots_id, BOOTS)


    #    "" :
    equipment_profile = str(righthand_id) + ',' + str(lefthand_id) + ',' + str(gloves_id) + \
                            ',' + str(head_id) + ',' + str(chest_id) + ',' + str(pants_id) + \
                            ',' + str(boots_id)

    print(my_rogue)
    print('equipment_profile =', equipment_profile)

    #        :
    return simulate_fight(equipment_profile, fight_number)


#  ,    attacks_total   :
def simulate_fight(equipment_profile, fight_number):
    global LOG_EVERY_FIGHT

    #   :
    sum_of_attack_types = [0, 0, 0, 0]
    sum_of_damage = 0

    #  ,     :
    if LOG_EVERY_FIGHT:
        fight_log = ''
        verdicts = {
            0: '.',
            1: '.',
            2: '.',
            3: '.'
        }

    attacks = 0
    global ATTACKS_IN_FIGHT

    #  ,      :
    while attacks < ATTACKS_IN_FIGHT:
        #  - :
        damage_info = my_rogue.do_attack()

        #   :
        sum_of_damage += damage_info[1]

        #   :
        sum_of_attack_types[ damage_info[0] ] += 1

        attacks += 1

        #  ,   :
        if LOG_EVERY_FIGHT:
            fight_log += verdicts[ damage_info[0] ] + ' ' + str(damage_info[1]) + ' ' + str(sum_of_damage) + '\n'

    #  ,  :
    if LOG_EVERY_FIGHT:
        #  :
        filename = 'fight_logs/log ' + str(fight_number) + '.txt'
        save_data_to_file(filename, fight_log)

    #       :
    attacks_statistic = ','.join(map(str, sum_of_attack_types))
    fight_data = '#' + str(fight_number) + '/' + equipment_profile + '/' + str(sum_of_damage) + ',' + attacks_statistic + '\n'

    return fight_data



لحفظ السجلات ، أستخدم وظيفتين بسيطتين:

وظائف حفظ البيانات ، إضافة بيانات
#     :
def save_data_to_file(filename, data):
    with open(filename, 'w', encoding='utf8') as f:
        print(data, file=f)


#     :
def append_data_to_file(filename, data):
    with open(filename, 'a+', encoding='utf8') as f:
        print(data, file=f)


لذا ، يبقى الآن كتابة بضعة أسطر لبدء الجلسة وحفظ نتائجها. نقوم أيضًا باستيراد وحدات Python القياسية اللازمة. هذا هو المكان الذي يمكنك تحديد مجموعة المعدات التي سيتم اختبارها. لمحبي World of Warcraft ، التقطت معدات من هناك ، لكن تذكر أن هذا المشروع هو مجرد إعادة بناء تقريبي للميكانيكي من هناك.

كود البرنامج
#     :
from random import randint

#     :
from math import floor

#    :
from datetime import datetime
from time import time

#    :
from operations_with_files import *

#      :
from equipment_custom import *
#from equipment_wow_classic import *
#from equipment_obvious_strong import *
#from equipment_obvious_weak import *


# :
if __name__ == "__main__":

    #     :
    ATTACKS_IN_FIGHT = 1000

    #     :
    LOG_EVERY_FIGHT = False

    #     :
    SESSION_LOG = 'session_logs/for ' + EQUIPMENT_COLLECTION + ' results ' + datetime.strftime(datetime.now(), '%Y-%m-%d_%H-%M-%S') + '.txt'
    print('SESSION_LOG =', SESSION_LOG)

    #  :
    my_rogue = Rogue()

    #  :
    time_begin = time()

    #   :
    run_session(SESSION_LOG)

    #   :
    time_session = time() - time_begin
    duration_info = ' : ' + str( round(time_session, 2) ) + ' .'
    print('\n' + duration_info)
    append_data_to_file(SESSION_LOG, duration_info + '\n')

    #  ,   5    :
    top_sets_info = show_best_sets(SESSION_LOG, 5)

    #          :
    append_data_to_file(SESSION_LOG, top_sets_info)

else:
    print('__name__ is not "__main__".')


تستغرق جلسة 1728 معركة 5 ثوانٍ على حاسوبي المحمول. إذا قمت بتعيين LOG_EVERY_FIGHT = True ، فستظهر الملفات التي تحتوي على بيانات لكل قتال في مجلد fight_logs ، لكن الجلسة ستستغرق 9 ثوانٍ بالفعل. على أي حال ، سيظهر سجل الجلسة في مجلد session_logs:

أول 10 أسطر من السجل
#1/1,1,1,1,1,1,1/256932,170,324,346,160
#2/1,1,1,1,1,1,2/241339,186,350,331,133
#3/1,1,1,1,1,2,1/221632,191,325,355,129
#4/1,1,1,1,1,2,2/225359,183,320,361,136
#5/1,1,1,1,1,3,1/243872,122,344,384,150
#6/1,1,1,1,1,3,2/243398,114,348,394,144
#7/1,1,1,1,2,1,1/225342,170,336,349,145
#8/1,1,1,1,2,1,2/226414,173,346,322,159
#9/1,1,1,1,2,2,1/207862,172,322,348,158
#10/1,1,1,1,2,2,2/203492,186,335,319,160


كما ترى ، فإن مجرد عقد جلسة ليس كافيًا ؛ فأنت بحاجة إلى استخراج المعلومات من هذه المئات من الأسطر حول مجموعات تلك الأشياء التي أدت إلى أفضل النتائج. للقيام بذلك ، سنكتب وظيفتين إضافيتين. الفكرة العامة هي فتح السجل المستلم ، وإنشاء قائمة بمبالغ الضرر لكل معركة ، وفرزها ، على سبيل المثال ، كتابة أسماء الأشياء المستخدمة لأفضل 5 حالات.

وظائف لتحديد السرعة القصوى
#       :
def show_best_sets(SESSION_LOG, number_of_sets):

    #      :
    list_log = list()

    #   ,      list_log ,
    #          :
    with open(SESSION_LOG, 'r', encoding='utf8') as f:
        lines = f.readlines()
        for line in lines:
            try:
                list_line = line.split('/')
                list_fight = list_line[2].split(',')
                list_log.append( ( int(list_fight[0]), list_line[1].split(',') ) )
            except IndexError:
                break

    #  ,      :
    list_log.sort(reverse=True)

    #   ,  number_of_sets     :
    top_sets_info = ''
    for i in range(0, number_of_sets):
        current_case = list_log[i]

        #           :
        clear_report = ''
        equipment_names = ''
        equip_group = 1

        for equip_id in current_case[1]:
            equipment_names += '\n' + get_equip_name(equip_id, equip_group)
            equip_group += 1

        line_for_clear_report = '\n#' + str(i+1) + ' - ' + str(current_case[0]) + '   :' + equipment_names
        clear_report += line_for_clear_report

        print('\n', clear_report)
        top_sets_info += clear_report + '\r'

    return top_sets_info


#     id:
def get_equip_name(equip_id, equip_group):
    equip_id = int(equip_id)

    if equip_group == 1:
        return RIGHT_HANDS[equip_id][0]
    if equip_group == 2:
        return LEFT_HANDS[equip_id][0]
    if equip_group == 3:
        return GLOVES[equip_id][0]
    if equip_group == 4:
        return HEADS[equip_id][0]
    if equip_group == 5:
        return CHESTS[equip_id][0]
    if equip_group == 6:
        return PANTS[equip_id][0]
    if equip_group == 7:
        return BOOTS[equip_id][0]


يظهر الآن في نهاية أسطر السجل مع 5 تحديدات ، تعرض أفضل نتيجة:

أخيرا خطوط سجل للقراءة
 : 4.89 .

#1 - 293959   :
 
 
 
 
 
 
  

#2 - 293102   :
 
 
 
 
 
 
 

#3 - 290573   :
 
 
 
 
 
 
  

#4 - 287592   :
 
 
 
 
 
 
 

#5 - 284929   :
 
 
 
 
 
 
  


الخطوة 4 - تقييم استدامة النتائج


من المهم أن نتذكر أن هناك عناصر عشوائية في هذا المشروع: عند تحديد نوع السكتة الدماغية باستخدام وظيفة randint . أثناء إجراء الاختبارات بشكل متكرر ، لاحظت أنه عند تكرار الجلسات بنفس بيانات الإدخال ، قد تختلف أفضل 5 اختيارات. هذا ليس سعيدًا جدًا ، واستغرق حل المشكلة.

أولاً قمت بعمل مجموعة اختبار للمعدات "clear_strong" ، حيث حتى بدون اختبارات من الواضح أي مجموعات الأشياء هي الأفضل هنا:

شاهد مجموعة clear_strong
EQUIPMENT_COLLECTION = 'obvious_strong'

RIGHT_HANDS = dict()
RIGHT_HANDS[1] = (' ', 5000, 0, 0, 0, 0, 0)
RIGHT_HANDS[2] = (' ', 800, 0, 0, 0, 0, 0)
RIGHT_HANDS[3] = (' ', 20, 0, 0, 0, 0, 0)

LEFT_HANDS = dict()
LEFT_HANDS[1] = (' ', 4000, 0, 0, 0, 0, 0)
LEFT_HANDS[2] = (' ', 10, 0, 0, 0, 0, 0)

GLOVES = dict()
GLOVES[1] = (' ', 1, 0, 0, 0, 0, 0)

HEADS = dict()
HEADS[1] = (' ', 1, 0, 0, 0, 0, 0)

CHESTS = dict()
CHESTS[1] = (' ', 1, 0, 0, 0, 0, 0)

PANTS = dict()
PANTS[1] = (' ', 1, 0, 0, 0, 0, 0)

BOOTS = dict()
BOOTS[1] = (' ', 1, 0, 0, 0, 0, 0)


مع هذه المجموعة سيكون هناك 6 معارك (3 سيوف * 2 خنجر * 1 * 1 * 1 * 1 * 1). في أعلى 5 ، يجب عدم تضمين المعركة حيث يتم أخذ أسوأ سيف وأسوأ خنجر. حسنًا ، بالطبع ، يجب أن يكون المكان الأول اختيارًا مع أقوى شفرتين. إذا فكرت في الأمر ، فمن الواضح بالنسبة لكل مجموعة المكان الذي ستقع فيه. أجريت الاختبارات ، تم تلبية التوقعات.

في ما يلي تصور لنتائج أحد اختبارات هذه المجموعة:

صورة

بعد ذلك ، قمت بتقليص الفجوة إلى الحد الأدنى في مقدار المكافآت التي تمنحها هذه الشفرات من 5000 و 800 و 20 و 4000 و 10 إلى5 و 4 و 3 و 2 و 1 على التوالي (في المشروع تقع هذه المجموعة في ملف "equipment_oblear_weak.py"). وهنا فجأة ، جاء مزيج أقوى سيف وأسوأ خنجر في القمة. علاوة على ذلك ، في أحد الاختبارات ، جاء أفضل سلاحين فجأة في المركز الأخير:

صورة

كيف نفهم ذلك؟ ظلت التوقعات في الترتيب الصحيح الواضح للمجموعات دون تغيير ، ولكن تم تقليل درجة الفرق بينهما بشكل كبير . والآن ، اكتسبت الحوادث خلال المعارك (نسبة الضربات والضربات ، الضربات الحرجة وغير الحرجة ، إلخ) أهمية حاسمة.

دعونا نتحقق من عدد المرات التي لا يقع فيها "الثنائي من الشفرات العليا" في المقام الأول. لقد أجريت 100 عملية إطلاق من هذا القبيل (لهذا ، قمت بتغليف "خطوط إطلاق البرنامج" في دورة من 100 تكرار وبدأت في الاحتفاظ بسجل خاص لهذه "الجلسة الفائقة" بأكملها). هنا تصور النتائج:

صورة

لذا ، فإن النتائج في برنامجنا ليست مستقرة دائمًا (34٪ من النتائج "الصحيحة" مقابل 66٪ من "الخطأ"). يتناسب

استقرار النتائج بشكل مباشر مع الفرق في مكافآت العناصر المختبرة.

بالنظر إلى حقيقة أن الفرق في مقدار مكافآت الأشياء الجيدة التي تكون منطقية للاختبار غير محسوس بشكل ضعيف (كما هو الحال في "World of Warcraft") ، فإن نتائج هذه الاختبارات ستكون غير مستقرة نسبيًا (غير مستقرة ، غير مستقرة ، إلخ).

الخطوة 5 - زيادة المرونة


نحاول التفكير المنطقي.

نوجز معيار النجاح: يجب أن يكون "الثنائي من الشفرات العليا" في المرتبة الأولى في 99٪ من الحالات.

الوضع الحالي: 34٪ من هذه الحالات.

إذا لم تقم بتغيير النهج المعتمد من حيث المبدأ (الانتقال من محاكاة المعارك لجميع التحديدات إلى حساب بسيط للخصائص ، على سبيل المثال) ، يبقى تغيير بعض المعلمات الكمية لنموذجنا.

على سبيل المثال:

  • لإجراء معركة واحدة لكل مجموعة ، ولكن عدة ، ثم تسجيل المتوسط ​​الحسابي في السجل ، وتجاهل الأفضل والأسوأ ، وما إلى ذلك.
  • إجراء عدة جلسات اختبار بشكل عام ، ثم أخذ "المتوسط" أيضًا
  • لتمديد المعركة نفسها من 1000 ضربة إلى قيمة معينة ، والتي ستكون كافية لتكون النتائج عادلة للمجموعات القريبة جدًا من حيث إجمالي المكافأة

بادئ ذي بدء ، بدا لي فكرة ناجحة مع إطالة المعركة ، لأنه في هذا المكان يحدث كل شيء الذي تسبب في إطالة هذه المقالة. سأختبر

الفرضية القائلة بأن إطالة المعركة من 1000 إلى 10000 ضربة سيزيد من استقرار النتائج (لهذا تحتاج إلى تعيين ثابت ATTACKS_IN_FIGHT على 10000). وهذا هو ما يلي:

صورة

ثم قرر زيادة من 10000 إلى 100000 طلقة ، مما أدى إلى نجاح مائة بالمائة. بعد ذلك ، باستخدام طريقة البحث الثنائية ، بدأ في تحديد عدد النتائج التي ستمنح 99٪ من النجاحات للتخلص من الحسابات المفرطة. توقف عند 46875.

صورة

إذا كان تقديري لموثوقية 99 ٪ لنظام بطول المعركة هذا صحيحًا ، فإن اختبارين متتاليين يقللان من احتمال الخطأ إلى 0.01 * 0.01 = 0.0001 .

والآن ، إذا أجريت اختبارًا بمعركة من 46،875 سكتة دماغية لمجموعة من المعدات لـ 1،728 معركة ، فسوف يستغرق الأمر 233 ثانية ويلهم الثقة بأن قواعد "سيف السيد" تحكم:

نتائج 1728 معركة لـ46875 ضربة
 : 233.89 .

#1 - 13643508   :
 
 
 
 
 
 
 

#2 - 13581310   :
 
 
 
 
 
 
  

#3 - 13494544   :
 
 
 
 
 
 
 

#4 - 13473820   :
 
 
 
 
 
 
  

#5 - 13450956   :
 
 
 
 
 
 
 


ملاحظة: من السهل شرح ذلك: اثنان من "سيوف السيد" يسمحان لك بالحصول على 10 وحدات من المهارة ، والتي ، وفقًا للميكانيكا الكامنة ، تقضي على إمكانية السكتات الدماغية المنزلقة ، وهذا يضيف حوالي 40٪ من السكتات الدماغية عند تطبيق ضرر X أو 2X بدلاً من 0.7X.

نتيجة اختبار مماثل لمحبي WoW:

نتائج 1296 معركة بـ 46875 زيارة (نجاح باهر كلاسيكي)
 : 174.58 .


#1 - 19950930   :
  '
  '
  
 
 
 
  

#2 - 19830324   :
  '
  '
 
 
 
 
  

#3 - 19681971   :
  '
  '
  
 
 
 
  

#4 - 19614600   :
  '
  '
 
 
 
 
  

#5 - 19474463   :
  '
  '
  
 
 
 
 


ملخص


  1. — . , , 4 * 4 * 3 * 3 * 3 * 3 * 2 = 2592, .. 33% . .
  2. : , , , .
  3. , : , , , .

لقد نشرت كل كود المشروع على جيثب .

عزيزي المجتمع ، يسعدني تلقي تعليقات حول هذا الموضوع.

UPD من 04/08/2020:
بفضل التعليقاتDeerenaros، كنوتري و جريبوكسأدركت أنه بدلاً من محاكاة آلاف المعارك ، يمكنك حساب التوقع الرياضي لضربة واحدة ، وعلى هذا الأساس ، ترتيب المعدات. رميت كل ما يتعلق مشاجرات من القانون، بدلا من simulate_fight وظيفة أنا لم calculate_expectation . عند الإخراج ، أحصل على نفس النتائج. تمت إضافة الكود الناتج إلى المستودع .

All Articles