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

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

صورة

هذه المقالة هي تعميق الموضوع السابق ، الذي يصف بالفعل تفاصيل تنفيذ الشخصية وتأثير المعدات.

الغرض: إظهار المهتمين بكيفية إنشاء مجموعة افتراضية من وكلاء الألعاب بأنفسهم ، ودفعهم معًا ، وتنفيذ خوارزمية جينية بسيطة (مع الطفرات) وتصور كل هذا باستخدام HTML و CSS و Javascript

ما تحتاجه:

1. Python 3 + تثبيت وحدة matplotlib . IDE (لدي PyCharm) ؛
2. إذا كنت ترغب في تحرير قالب تقرير تفاعلي ، عندها فهم أساسي لـ HTML ، CSS ، JavaScript (jQuery).

الخطوة 1 - حدد هدفًا ، تعامل مع OOP


لذا ، فإن الهدف النهائي لترميزنا هو جعل من الممكن الحصول على أفضل زي مع محاكاة للوراثة / التطور / الانتقاء الطبيعي. والحصول على فرصة لتتبع هذا المسار بصريا.

نحول هدفنا إلى مهام محددة:

  1. التفكير في الآلية الجينية وتنفيذها على المستوى الفردي (يجب أن تؤثر "جينات" السارق بشكل مباشر على اختيار المعدات)
  2. تنفيذ آليات ولادة الأحفاد ، ونقل الجينات إليهم ، وكذلك الطفرات البسيطة العشوائية ، لضمان التباين وإمكانية العثور على الأفضل
  3. لتحقيق "التنافس" الوراثي ، واختيار الجينات (لدفع حامليها معًا ، بحيث تؤدي نتيجة التصادم إلى الإطاحة بالجينات "السيئة" وترفع الجينات "الجيدة")
  4. تجميع كل شيء في نظام متماسك حيث يمكن أن يتطور اللصوص إلى مقاتلين مثاليين
  5. لجمع وتصور البيانات بحيث يكون هناك شيء معجب به (وإلا فهو مثير للاهتمام!)
  6. تقييم فائدة هذه الأنشطة

هنا سوف يساعدنا OOP ، سننشئ 4 فئات أساسية:

صورة

الخطوة 2 - شحذ الطبقة المارقة للتكاثر


الاتفاق على شروط على الشاطئ


في هذه المقالة سوف أقوم بجزء من المصطلحين "gene" (في الشفرة - gene ) و "genotype" (في code - genotype ). سيكون هناك أيضا "الطفرات" (في التعليمات البرمجية - تنضج ، طفرة )، مما يعني تغييرا في واحد أو أكثر من الجينات من معناها. سيحدث التكاثر ببساطة من خلال "التبرعم" من سلالة الوالد ، لذلك لن يكون هناك عبور ومضاعفات أخرى مماثلة. النمط الجيني للسارق هو قائمة من 7 أرقام هي جيناته:

صورة

لذا ، فإن فئة Rogue تنمو بشكل كبير مقارنة بالوقت الأخير .

1.يتلقى السارق الجينات بإحدى الطرق الممكنة (تم إنشاؤه في اليوم الأول أو موروث من "الوالد" مع أو بدون طفرة). الطرق generate_random_genes ، mutate_genes ، mutate_gene هي المسؤولة عن هذا :

طرق تشكيل الجينات البرمجية
    #     ():
    def generate_random_genes(self):
        dbg = DBG_rogue_generate_random_genes

        self.my_genes[0] = randrange(0, len(RIGHT_HANDS))    # <--   :
        self.my_genes[1] = randrange(0, len(LEFT_HANDS))     # <--   :
        self.my_genes[2] = randrange(0, len(GLOVES))         # <--  :
        self.my_genes[3] = randrange(0, len(HEADS))          # <--  :
        self.my_genes[4] = randrange(0, len(CHESTS))         # <--  :
        self.my_genes[5] = randrange(0, len(PANTS))          # <--  :
        self.my_genes[6] = randrange(0, len(BOOTS))          # <--  :

        if dbg:  #  :
            print('\nf "generate_random_genes":' + '\n\tgenes generated:\n\t', end='')
            print(self.my_genes)


    #      :
    def mutate_genes(self, parent_genes):
        dbg = DBG_rogue_mutate_genes

        #     :
        self.my_genes = parent_genes.copy()

        #    :
        event_mutation = randint(1, 10)

        #     ,    :
        if event_mutation == 10:
            if dbg:  #  :
                print('\nf "mutate_genes"   :' + '\n\t  \n\told genes: ', end='')
                print(parent_genes)
                print('\tnew genes: ', end='')
                print(self.my_genes)
            return 0

        #    :
        else:
            #  ""  =  ,   :
            mutation_volume = randint(0, 30)
            mutation_iters = 1
            if 22 <= mutation_volume <= 28:
                mutation_iters = 2
            elif 29 <= mutation_volume <= 30:
                mutation_iters = 3

            if dbg:  #  :
                print('\nf "mutate_genes" :' + '\n\t : ' + str(mutation_iters))

            #  ,   :
            genes_available = [0, 1, 2, 3, 4, 5, 6]

            #   :
            genes_mutated = []

            current_iter = 0
            while current_iter < mutation_iters:
                if dbg:  #  :
                    print('\tw1')

                #     :
                gene_with_forced_mutation = choice(genes_available)

                #      :
                if gene_with_forced_mutation not in genes_mutated:
                    self.mutate_gene(gene_with_forced_mutation)
                    genes_mutated.append(gene_with_forced_mutation)
                    current_iter += 1
                    if dbg:  #  :
                        print('\tcurrent_iter =', current_iter)
                else:
                    if dbg:  #  :
                        print('\telse, because ' + str(gene_with_forced_mutation) + ' already in genes_mutated')

        if dbg:  #  :
            genes_mutated_str = ''
            if len(genes_mutated) > 1:
                for x in genes_mutated:
                    genes_mutated_str += str(x) + ', '
            else:
                genes_mutated_str = str(genes_mutated[0])
            print('\nf "mutate_genes" :' + '\n\told genes: ', end='')
            print(parent_genes)
            print('\tgenes_mutated: ' + genes_mutated_str)
            print('\tnew genes: ', end='')
            print(self.my_genes)


    #     ,      :
    def mutate_gene(self, gene_id):
        dbg = DBG_rogue_mutate_gene

        current_value = self.my_genes[gene_id]
        new_value = current_value

        if dbg:  #  :
            print('\nf "mutate_gene":' + '\n\tgene_id: ' + str(gene_id) + '\n\told gene value: ' + str(current_value))

        tries = 0
        while new_value == current_value:
            if dbg and tries > 0:  #  :
                print('\tw2, because ' + str(new_value) + ' = ' + str(current_value) )
            new_value = randrange(0, len(LINKS_TO_EQUIP_DICTS[gene_id]))
            self.my_genes[gene_id] = new_value
            tries += 1

        if dbg:  #  :
            print('\tnew gene value: ' + str(new_value) + '\n\ttries: ' + str(tries))


2. الجينات (الوراثي) تحدد التي ارتداء معدات السارق (مباشرة في ولادة الكائن التهيئة). للقيام بذلك ، تسمى طريقة application_genes :

تطبيق_الجينات
    # ""  ()     :
    def apply_genes(self):
        dbg = DBG_rogue_apply_genes

        pointer = 0
        for item_id in self.my_genes:
            self.wear_item(pointer, item_id, LINKS_TO_EQUIP_DICTS[pointer])
            pointer += 1

        if dbg:  #  :
            print('\nf "apply_genes":' + '\n\t.')
            print(self)


3. ستحدد المعدات القيمة النهائية لـ "تصنيف" السارق. للقيام بذلك ، سيتم استدعاء طريقة calculate_rate ، التي تحسب التوقع الرياضي للضرر:

احسب
    #     :
    def calculate_rate(self):
        dbg = DBG_rogue_calculate_rate

        #   :
        p_hit = self.stat_hit / 100

        #      :
        p_glancing = self.stat_glancing_percent / 100
        not_p_glancing = 1 - self.stat_glancing_percent / 100

        #      :
        p_crit = self.stat_crit / 100
        not_p_crit = 1 - self.stat_crit / 100

        #   :
        expectation_modificator = p_hit * (p_glancing * 0.7 + not_p_glancing * (p_crit * 2 + not_p_crit))

        #      :
        expectation_damage = expectation_modificator * self.stat_attackpower
        expectation_damage = round(expectation_damage, 3)

        if dbg:
            print('\t  =', expectation_modificator)
            print('\t  =', expectation_damage)

        return expectation_damage


4. سيكون تصنيف السارق هو العامل الحاسم الذي يؤدي إلى التكاثر أو الموت المخجل. لهذا ، تم تقديم مفهومي "النصر" (طريقة embody_win ) و "هزيمة" (طريقة embody_defeat ):

embody_win و embody_deat
    #    :
    def embody_win(self):
        dbg = DBG_rogue_embody_win

        self.my_wins += 1
        stats.genes_add_win(self.my_genes)

        #    :
        if self.my_wins % population.wins_to_reproduce == 0:

            #    :
            total_borns = choice(population.possible_birth_quantities)
            if dbg:
                print('  ' + str(total_borns))

            for x in range(0, total_borns):
                if dbg:
                    print(self.name + '  ...')

                #  -:
                new_rogue = Rogue(self.my_genes, self.my_generation, from_parent=True)
                ROGUES_LIST.append(new_rogue)

            Population.day_of_last_changes = current_day

        #         :
        if self.my_wins > Population.record_max_wins:
            Population.record_max_wins = self.my_wins
            Population.max_winner_name = self.name
            Population.max_winner_genes = self.my_genes


    #    :
    def embody_defeat(self):
        dbg = DBG_rogue_embody_defeat

        self.my_defeats += 1

        #    :
        if self.my_defeats == population.defeats_to_die:
            self.alive = False
            Population.how_many_rogues_alive -= 1
            Population.day_of_last_changes = current_day

            if dbg:
                print(self.name + ' ...')


حسنًا ، تم إعادة تصميم مُنشئ الصف وفقًا لذلك لجعل هذه السلسلة "جينات - معدات - تصنيف" تعمل:

منشئ الطبقة المارقة
    def __init__(self, genes_list_inherited, parent_generation, from_parent=True, genes_can_mutate=True):

        #    ,    id  :
        # 0 -  , 1 -  , 2 - , 3 - , 4 - , 5 - , 6 - 
        self.equipment_slots = [0] * 7

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

        #    ( -     ):
        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()

        #  :
        Population.how_many_rogues += 1
        Population.how_many_rogues_alive += 1

        #  :
        self.my_generation = parent_generation + 1
        if self.my_generation > Population.generations:
            Population.generations = self.my_generation

        # "" :
        self.name = '"' + str(Population.how_many_rogues) + '-,   ' + str(parent_generation + 1) + '"'

        #  :
        self.alive = True

        #    :
        self.my_wins = 0
        self.my_defeats = 0

        #   :
        self.my_genes = [0] * 7

        if genes_can_mutate:
            #      ,     :
            if from_parent:
                self.mutate_genes(genes_list_inherited)
            else:
                self.generate_random_genes()
        else:
            self.my_genes = genes_list_inherited

        #     :
        stats.genes_add_presence(self.my_genes, self.my_generation)

        #     :
        self.apply_genes()


لتلخيص منطق الصف مع صورة:

صورة

المرحلة 3 - السكان والإحصائيات وفئات التحدي


فئة السكان مطلوبة فقط لثلاثة أشياء:

  1. إنشاء مجموعة من عدد محدد من اللصوص بجينات عشوائية ومعلمات بيولوجية ستظل دائمًا دون تغيير ( wins_to_reproduce - كم عدد الانتصارات التي يحتاج إليها السارق من أجل التكاثر ، هزائم_إلى - عدد الهزائم التي ستؤدي إلى موت الفرد) ؛
  2. "إعادة تعيين" السكان ، أي عندما ينتهي اليوم الأخير من المرحلة الحالية ، يتم تدمير كل لصوص ويتم إنشاء لصوص مع أفضل الأنماط الجينية (انظر طريقة إعادة التحميل ) ؛
  3. تخزين بعض البيانات السكانية للإحصاءات.

سكان الطبقة
class Population():
    """     ."""

    how_many_rogues = 0 # <--   
    how_many_rogues_alive = 0 # <--   
    how_many_battles = 0 # <--  
    how_many_ties = 0 # <--  
    generations = 0 # <--  
    day_of_last_changes = 0  # <--       
    max_days_without_changes = 0  # <--    

    #        ,      :
    record_max_wins = 0
    max_winner_name = 'none'
    max_winner_genes = 'none'


    #       :
    def __init__(self, total, possible_birth_quantities, wins_to_reproduce, defeats_to_die):

        #   :
        self.initial_size = total

        #   ,        :
        self.wins_to_reproduce = wins_to_reproduce
        self.defeats_to_die = defeats_to_die
        self.possible_birth_quantities = possible_birth_quantities

        while total > 0:

            #   ,  " "    ,     :
            new_rogue = Rogue('', 0, from_parent=False)

            #   :
            ROGUES_LIST.append(new_rogue)

            total -= 1


    #     :
    def __str__(self):
        text = ':\n'
        text += ': ' + str(Population.generations) + '\n'
        text += ' : ' + str(Population.how_many_rogues) + '\n'
        text += ' : ' + str(Population.how_many_rogues_alive) + '\n'
        text += ' : ' + str(Population.record_max_wins) + '\n'
        text += ' : ' + str(Population.max_winner_name) + '\n'
        text += ' : ' + str(Population.max_winner_genes) + '\n'
        return text


    #  ,    how_many_to_save     :
    def reload(self, how_many_to_save):

        #  - :
        Population.how_many_rogues_alive = 0

        # ""   :
        for x in ROGUES_LIST:
            x.alive = False

        #        :
        list_genotypes_top = stats.get_ordered_list_from_dict(LIST_FOR_DICTS_GENOTYPES[current_stage], inner_index=2)

        #      :
        for x in range(0, how_many_to_save):

            #      :
            genotype_in_str = list_genotypes_top[x][0]
            genotype_in_list = []
            for char in genotype_in_str:
                if char != '-':
                    genotype_in_list.append( int(char) )

            #   ,      ,     :
            new_rogue = Rogue(genotype_in_list, 0, from_parent=True, genes_can_mutate=False)

            #   :
            ROGUES_LIST.append(new_rogue)


ينفذ الفصل الإحصائي مهمتين رئيسيتين:

  1. جمع البيانات أثناء المحاكاة (على سبيل المثال ، مؤشرات كل يوم: عدد اللصوص الأحياء ، عدد الأنماط الجينية الفريدة ، وما إلى ذلك)
  2. ( matplotlib, HTML-, ).

في المستقبل ، سأظهر كيف سيبدو هذا التقرير:

صورة

كود قالب HTML للتقرير متاح على github .

من الأفضل أيضًا عرض فئة Stats على github (وإلا سيكون هناك العديد من الخطوط هنا).

إن فئة Challenger مسؤولة عن محاكاة التصادمات بين اللصوص الذين يتم اختيارهم عشوائيًا. كل يوم ، تُسمى الطريقة Perform_battles ، والتي تتكون من اللصوص الأحياء الذين يتنافسون على الزوج ويواجههم بطريقة Perform_battle ، وفي النهاية لكل سارق يحدث إما الطريقة embody_win ، أي طريقة embody_deat. بالمناسبة ، إذا اتضح أن التعادل قد حدث (الأنماط الجينية ، وبالتالي فإن تقييمات اللصوص هي نفسها) ، فإنها تتباعد دون عواقب:

المنافس الطبقي
class Challenger():
    """      ."""

    #   :
    def perform_battles(self):
        dbg = DBG_challenger_perform_battles

        #    :
        rogues_alive = []
        for x in ROGUES_LIST:
            if x.alive:
                rogues_alive.append(x)

        #  :
        shuffle(rogues_alive)

        #       :
        pairs_total = int(len(rogues_alive) // 2)

        if dbg:
            print('pairs_total =', pairs_total)

        #       :
        counter = 0
        pointer = 0
        while counter < pairs_total:
            a_1 = rogues_alive[pointer]
            a_2 = rogues_alive[pointer + 1]
            self.perform_battle(a_1, a_2)
            counter += 1
            pointer += 2


    #     :
    def perform_battle(self, rogue_1, rogue_2):
        dbg = DBG_challenger_perform_battle

        if dbg:
            print('\n  :', rogue_1.name, '', rogue_2.name)

        #     (        ):
        rating_1 = rogue_1.calculate_rate()
        rating_2 = rogue_2.calculate_rate()

        #  :
        Population.how_many_battles += 1

        if dbg:
            print('\t :', rating_1, '', rating_2, '.')

        #      :
        if rating_1 > rating_2:
            rogue_1.embody_win()
            rogue_2.embody_defeat()
        elif rating_1 < rating_2:
            rogue_1.embody_defeat()
            rogue_2.embody_win()
        else:
            Population.how_many_ties += 1
            if dbg:
                print('\t !  !')


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

كود لتشغيل المحاكاة
# :
MAX_STAGES = 8 # <--      
MAX_DAYS_AT_STAGE = 20 # <--        
SLIDING_FREQUENCY = 10 # <--     HTML-    (1 =   , 10 =   10 )
ROGUES_AT_BEGIN = 10 # <--    (  )
WINS_TO_REPRODUCE = 2 # <--     ,  
DEFEATS_TO_DIE = 2 # <--      
POSSIBLE_BIRTH_QUANTITIES = [1] # <--   ,       , :
# [1, 2] ,   50%-    1,  1 
# [1, 1, 2] ,   66%-   1 

HTML_GSQUARE_SIDE = 10 # <--   ,     
HTML_GSQUARE_MARGIN = 3 # <--   ,     

#     :
LINKS_TO_EQUIP_DICTS = [RIGHT_HANDS, LEFT_HANDS, GLOVES, HEADS, CHESTS, PANTS, BOOTS]

#         (    ):
LIST_FOR_DICTS_GENOTYPES = [{}] * MAX_STAGES

#        :
DICT_UNIQUE_GENOTYPES = {}

#      :
DICT_DAYS = {}

# ,       -  :
ROGUES_LIST = list()

#       (     )
current_stage = 0

#     "" (    /):
DBG_rogue_mutate_genes = False
DBG_rogue_generate_random_genes = False
DBG_rogue_apply_genes = False
DBG_rogue_calculate_rate = False
DBG_rogue_mutate_gene = False
DBG_rogue_embody_win = False
DBG_rogue_embody_defeat = False
DBG_rogue_wear_item = False
DBG_challenger_perform_battles = False
DBG_challenger_perform_battle = False
DBG_days_report = False  # <--     



# :
if __name__ == '__main__':

    #  :
    time_begin = time()

    #        :
    max_days_for_current_stage = 0
    current_day = 1
    while current_stage < MAX_STAGES:

        #    :
        if current_stage == 0:

            #   :
            stats = Stats()

            #          ,      :
            population = Population(ROGUES_AT_BEGIN, POSSIBLE_BIRTH_QUANTITIES, wins_to_reproduce=WINS_TO_REPRODUCE, defeats_to_die=DEFEATS_TO_DIE)

            #     :
            challenger = Challenger()

            # "" :
            print(population)

        #        :
        max_days_for_current_stage += MAX_DAYS_AT_STAGE

        # /      (1  = 1    (*) )
        # * -     -    , -           
        while current_day <= max_days_for_current_stage:
            print('st ' + str(current_stage) + ', day ' + str(current_day))
            if DBG_days_report:
                print('\n\n/DAY', current_day)

            #   :
            challenger.perform_battles()

            if DBG_days_report:
                print('\n', current_day, '.')
                print(population)

            #    :
            stats.add_new_day(current_day)

            #        :
            if current_day - Population.day_of_last_changes > Population.max_days_without_changes:
                Population.max_days_without_changes = current_day - Population.day_of_last_changes

            #    SLIDING_FREQUENCY  (     )     :
            if current_day % SLIDING_FREQUENCY == 0 or current_day == 1 or current_day == MAX_DAYS_AT_STAGE * MAX_STAGES:
                stats.draw_genes_distribution(current_day)

            #    SLIDING_FREQUENCY  (     )     :
            if current_day % SLIDING_FREQUENCY == 0 or current_day == 1 or current_day == MAX_DAYS_AT_STAGE * MAX_STAGES:
                stats.draw_genes_wins(current_day)

            current_day += 1

        #      ,  ,     -  :
        if current_stage < MAX_STAGES - 1:
            population.reload(ROGUES_AT_BEGIN)

        current_stage += 1


    #      ,      LIST_FOR_DICTS_GENOTYPES:
    current_stage -= 1

    #   :
    print('\n:')
    print(DICT_DAYS)

    #       :
    stats.draw_and_put_line_chart_to_file(DICT_DAYS, 1, ' ', '', '', 'charts/chart_population_demography.png')

    #       -  :
    stats.draw_and_put_line_chart_to_file(DICT_DAYS, 0, ' ', '', '', 'charts/chart_population_total.png')

    #      :
    stats.draw_and_put_line_chart_to_file(DICT_DAYS, 2, ' ', '', ' ', 'charts/chart_genotypes_variety.png')

    #      (=   ):
    stats.draw_and_put_line_chart_to_file(DICT_DAYS, 3, ' ', '', '', 'charts/chart_genotypes_ties.png')

    #   HTML   :
    stats.create_index_html()

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

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


في نهاية المحاكاة ، يتم إنشاء ملف باسم نوع "تقرير 2020-04-25_10-33-54.html" في مجلد البرنامج. المزيد عنه في الجزء التالي من المقالة.

الخطوة 4 - تصور البيانات


لمزيد من التوضيحات ، سأستخدم هذه المجموعة من المعدات:

Evolution_equipment_oblear_strong.py
#       .
#    ,     :
# 0 - , 1 - , 2 - , 3 - , 4 - , 5 - , 6 - 

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] = (' ', 300, 0, 0, 0, 0, 0)
GLOVES[2] = (' ', 1, 0, 0, 0, 0, 0)

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

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

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

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


إليك المجموعات الممكنة من 192 مجموعة (3 * 2 * 2 * 2 * 2 * 2 * 2). وبناءً على ذلك ، سيكون هناك 192 من الأنماط الجينية المحتملة .

الفكرة الرئيسية للتصور : لتمثيل المساحة الكاملة للأنماط الجينية المحتملة كحقل مستطيل ، حيث يمثل كل مربع نمطًا جينيًا منفصلاً. تجد خوارزمية بسيطة قواطع وسط للرقم 192 ، هما 16 و 12:

كود هذه الخوارزمية من مُنشئ فئة Stats
        #      - :
        list_divisors = list()
        current_number = int(self.genotypes_total // 2)
        while current_number >= 2:
            if self.genotypes_total % current_number == 0:
                list_divisors.append(current_number)
            current_number -= 1
        print('list_divisors:', list_divisors)

        #        :
        somewhere_in_the_middle = len(list_divisors) // 2

        #     :
        side_1 = list_divisors[somewhere_in_the_middle]
        side_2 = self.genotypes_total / side_1

        # ,   ,     ""  :
        self.side_x = int(side_1 if side_1 >= side_2 else side_2)
        self.side_y = int(self.genotypes_total / self.side_x)
        print('side_x =', self.side_x, 'side_y =', self.side_y)


نحصل على منطقة 16x12: يتم

صورة

إنشاء قائمة الأنماط الجينية المحتملة بواسطة هذا الرمز:

        #    :
        self.list_of_possible_genotypes = list()

        #      :
        for g1 in RIGHT_HANDS:
            #      :
            for g2 in LEFT_HANDS:
                #   :
                for g3 in GLOVES:
                    #   :
                    for g4 in HEADS:
                        #   :
                        for g5 in CHESTS:
                            #   :
                            for g6 in PANTS:
                                #   :
                                for g7 in BOOTS:
                                    current_genotype = str(g1-1)+'-'+str(g2-1)+'-'+str(g3-1)+'-'+str(g4-1)+'-'+str(g5-1)+'-'+str(g6-1)+'-'+str(g7-1)
                                    self.list_of_possible_genotypes.append(current_genotype)

لذلك ، تمثل المربعات في الحقل الأنماط الجينية في هذا الترتيب:

صورة

هذه ميزة مهمة ، لأن ، على سبيل المثال ، الأنماط الجينية التي تحتوي على "جين السيف القوي" ( الزاوية الزرقاء ) في الجزء العلوي من الحقل ، وتلك التي تحتوي أيضًا على "جين الخنجر القوي" ( الزاوية الزرقاء ) - تحتل منطقة أعلى:

صورة

وبالتالي ، فإن أقوى نمط وراثي ( 0-0-0-0-0-0-0-0 ) في الزاوية اليسرى العليا ، والأضعف ( 2-1-1-1- 1-1-1 ) - بالعكس. سيساعدنا هذا في مراقبة ديناميكيات الوضع ، لأننا سنعرف أنه خلال المحاكاة يجب أن "يتحول" تجمع الجينات المسيطر "إلى الزاوية اليسرى العليا من المجال:

صورة

الآن قم بتشغيل المحاكاة بالمعلمات التالية:

MAX_STAGES = 5
MAX_DAYS_AT_STAGE = 40
SLIDING_FREQUENCY = 1
ROGUES_AT_BEGIN = 8
WINS_TO_REPRODUCE = 2
DEFEATS_TO_DIE = 2
POSSIBLE_BIRTH_QUANTITIES = [1]

HTML_GSQUARE_SIDE = 16
HTML_GSQUARE_MARGIN = 3

أي ، 8 لصوص في البداية ، <div> - يتم إنشاء حاويات تحتوي على حقول (يشار إليها فيما يلي بالشرائح ) لكل يوم ، 5 مراحل من 40 يومًا لكل منها. ونتيجة لذلك ، نحصل على مجموعتين من الشرائح المكونة من 200 قطعة لكل منهما: " انتشار الأنماط الجينية " (الألوان الزرقاء والخضراء) و " انتصار الأنماط الجينية " (الألوان الحمراء والخضراء). يعتمد تشبع لون المربعات على القيم.

نفتح تقرير HTML الذي تم إنشاؤه ونرى هذه الصورة لليوم الأول والعاشر:

صورة

لذا ، في اليوم الأول ، تم إنشاء 8 لصوص ، ستلقي مربعاتهم بظلالها حتى نهاية المحاكاة. علاوة على ذلك ، نرى أنهم بدأوا في القتال ، مما يعني ظهور الانتصارات الأولى ، مما أدى إلى التكاثر. يرتبط التكاثر مع احتمال كبير بالطفرات ، لذلك هناك المزيد من المربعات الملونة.

نلقي نظرة في الأيام التالية. في اليوم 44 ، ظهر النمط الجيني " 0-0-0-0-0-0-0-0 " (انظر خلف السهم الأزرق). في اليوم 59 ، حقق تقدمًا بالفعل في الانتصارات (انظر خلف السهم الأحمر).

صورة

في اليوم 137 ، يمكن ملاحظة أن " 0-0-0-0-0-0-0-0 " تعرض للضرب في الصدارة بعدد مرات الظهور بين السكان (انظر خلف السهم الأزرق). وعلى شريحة اليوم الأخير كان له ظل ذهبي ، حيث احتل المركز الأول من حيث عدد الانتصارات.

صورة

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

صورة

حسنًا ، الحكم في التقرير صحيح: النمط الجيني الفائز هو 0-0-0-0-0-0-0-0 :

صورة

ستساعد الرسوم البيانية التي تم الحصول عليها باستخدام matplotlib في تقييم ما يحدث:

1. يوضح الرسم البياني " السكان الحيون " الديناميكيات التغييرات في عدد اللصوص الذين يعيشون في وقت واحد ؛
2. إن الرسم البياني لـ " ولد كل شيء " يقترب في الغالب من خط مستقيم ، إذا لم تبدأ اللعب مع المعلمات الديموغرافية:

صورة

3. الرسم البياني لـ " تنوع الأنماط الجينية " - عادة ما يكون النمو السريع في البداية ، ثم يتباطأ ، ويقترب تدريجياً من الحد الأقصى ؛
4. جدول " رسم الديناميكيات"- يوضح كيف يتغير عدد السحوبات بمرور الوقت (هذه معارك عندما يصطدم نوعان وراثيان متطابقان في قتال لا معنى له):

صورة

يحدث أن" الجمود "يركد عندما يبقى لصوص بنفس الأنماط الجينية فيه (تظهر الرسوم البيانية: زيادة حادة في عدد رسم ، ثبات . من حيث عدد السكان وعدد الأنماط الجينية الفريدة)

ركود السكان يعني أن شرائح الحساب والرسم تذهب عبثًا لتقليل فترات الركود ، فمن الضروري تقليل القيمة الثابتة MAX_DAYS_AT_STAGE ، وسيتبين أن الازدحام قد انخفض ، وفي بعض الأماكن اختفى تمامًا:

صورة

الخطوة 5 - تجربة مساحات كبيرة من النمط الجيني


الآن دعنا نحاول تشغيل محاكاة مجموعة معدات التطور_المعدات_المخصصة التي نوقشت في المقالة الأخيرة مع معلمات مختلفة قليلاً. هنا القيمة POSSIBLE_BIRTH_QUANTITIES = [1 ، 2] تعني أنه مع فعل التكاثر باحتمال 50٪ ، سيظهر إما 1 أو 2 من نسل. سيؤدي ذلك إلى تعزيز ديناميات ما يحدث:

MAX_STAGES = 20
MAX_DAYS_AT_STAGE = 50
SLIDING_FREQUENCY = 10
ROGUES_AT_BEGIN = 8 
WINS_TO_REPRODUCE = 2
DEFEATS_TO_DIE = 2
POSSIBLE_BIRTH_QUANTITIES = [1, 2]

HTML_GSQUARE_SIDE = 10
HTML_GSQUARE_MARGIN = 3

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

صورة

بالمناسبة ، حددت هذه الخوارزمية بشكل واضح المجموعة الأعلى من المعدات ( 3-3-0-0-0-0-1-1 ) ، والتي تتطابق مع تلك التي حددتها الخوارزمية التوافقية من المقالة السابقة :

صورة

وأخيرًا ، أجريت الاختبار للمجموعة الموسعة ، والتي تعطي 18432 تركيبات:

مع هذه المعلمات
MAX_STAGES = 30
MAX_DAYS_AT_STAGE = 50
SLIDING_FREQUENCY = 100
ROGUES_AT_BEGIN = 20
WINS_TO_REPRODUCE = 2
DEFEATS_TO_DIE = 2
POSSIBLE_BIRTH_QUANTITIES = [1, 2]

HTML_GSQUARE_SIDE = 4
HTML_GSQUARE_MARGIN = 1


صورة

بشكل عام ، يمكنك الاستمرار في تسليتك مع هذا ، لكن الاتجاه لا يزال دون تغيير: أثناء المحاكاة ، تبدأ الأنماط الجينية في التراكم بسرعة في "مراكز القوة" هذه. والنمط الجيني السائد في واحدة من "البؤر" اللافتة للنظر.

الخطوة 6 - هل يعقل كل ذلك؟


إذا انتقلنا الآن إلى الجانب العملي من السؤال ، فنحن بحاجة إلى فهم ما إذا كانت هذه الخوارزمية الجينية قادرة على العثور على الإجابة الصحيحة بتكلفة معارك أقل من البحث التوافقي البسيط لجميع الأنماط الجينية. الجواب: نعم قادر . دليل تحت القطع:

إثبات فعالية الخوارزمية الجينية
1728 .

, .. 1728 .

, 1728 . «» — 2 ( ). , , 1728 / 2 = 864. .

:

MAX_STAGES = 8
MAX_DAYS_AT_STAGE = 20
SLIDING_FREQUENCY = 10
ROGUES_AT_BEGIN = 10
WINS_TO_REPRODUCE = 2
DEFEATS_TO_DIE = 2
POSSIBLE_BIRTH_QUANTITIES = [1]

HTML_GSQUARE_SIDE = 10
HTML_GSQUARE_MARGIN = 3

, 3-3-0-0-0-0-1:

image

:

image

, 3-3-0-0-0-0-1 547 . 87 (), 16% .

.

ملاحظة : من الممكن الحصول على الإجابة الصحيحة حتى مع ROGUES_AT_BEGIN = 2 و POSSIBLE_BIRTH_QUANTITIES = [1] . يبدو هذا مفاجئًا ، لأن السكان لم يرتفعوا أبدًا فوق لصين. لأن أحدهما يخسر ، والآخر يفوز ، ويموت الأول ، والثاني يلد سليل واحد. ويبدأ الوالد في التنافس مع هذا السليل. السليل إما أقوى أو أضعف. وهكذا تتحرك عجلة الاختيار القاسية بين الوالد ونسله حتى يصل إلى نقطة أفضل (والتي يمكنه تحقيقها في الوقت المخصص ، لذلك ليس هذا هو الأفضل دائمًا).

ملخص


  1. يتم حل المشكلة باستخدام الخوارزميات الجينية.
  2. «» .
  3. , , ( calculate_rate Rogue, ).
  4. بالطبع ، في هذا البرنامج لا يزال بإمكانك التجربة والتجربة ، وتحسين الكفاءة. على سبيل المثال ، في مرحلة ما ، ابدأ في "حظر" فقدان الأنماط الجينية بشكل واضح ، وعدم السماح لأصحابها بالقتال أو حتى الظهور. وهكذا ، سيضيق قمع "القادة" ، حيث يجب أن يحددوا بدقة فيما بينهم من "الأقوى".

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

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

All Articles