[Python] Python YoGiOh Masterduel how do I efficiently find out the chance for developing into...

Discussão em 'Python' iniciado por Stack, Outubro 5, 2024.

  1. Stack

    Stack Membro Participativo

    in the game of YoGiOh, the player start with drawing 5 cards from a deck with 40 to 60 cards, then the player can using starters out of those 5 cards to develop an end board, that is hard for the opponent to break. Not every starting hand is good. Some starters need 1 or more other specific card in hand to develop. It is hard to optimize a deck for 2-card combinations (combos).

    I am writing a program to evaluate my chance to have combos. It works, but 40 seconds for a small deck with 42 cards is not very efficient. I am looking for optimization.

    Input:

    Card Code Card Name Count Combo
    0 Effect Veiler 3
    1 Tenyi Spirit - Adhara 2 1D
    2 Maxx "C" 3
    3 Ghost Ogre & Snow Rabbit 1
    4 Ash Blossom & Joyous Spring 3
    5 Incredible Ecclesia, the Virtuous 3 51 56 57 58 5C 5D 5E 5G 5J
    6 Swordsoul of Mo Ye 3 61 66 67 68 6C 6D 6E 6G 6J
    7 Swordsoul of Taia 2 71 7C 7D
    8 Swordsoul Strategist Longyuan 3 81 87 8C 8D 8E 8G 8J
    9 Bystial Druiswurm 2
    A Bystial Magnamhut 1
    B Bystial Baldrake 1
    C Tenyi Spirit - Vishuda 1
    D Tenyi Spirit - Ashuna 3
    E Archnemeses Protos 1
    F Pot of Desires 1
    G Swordsoul Emergence 3 G G1 G7 GC GD GE GG GJ
    H Called by the Grave 2
    I Infinite Impermanence 3
    J Swordsoul Blackout 1

    This is a 42 card deck with "Swordsoul Tenyi" cards. Combo "1D" means if the player has "Tenyi Spirit - Adhara" with the code 1 and "Tenyi Spirit - Ashuna" with the code D, the hand is playable.

    The deck can be coded as 0001122234445556667788899ABCDDDEFGGGHHIIIJ .Explanation: "000" because we use 3 copies of the card "Effect veiler" with code 0.

    Hand with combos contains characters in the combo section. 04568 contains 56 and 68: that means 2 different combos are playable.


    from itertools import combinations
    from collections import Counter
    from math import comb
    import functools
    import operator
    import time
    import multiprocessing

    import concurrent.futures
    #multi thread

    number_threads = 10
    number_process = 6

    #cut list into equal size
    def chunkify(iterable,n):
    lst = list(iterable)
    return [lst[i::n] for i in range(n)]
    #multi thread stuff END

    ######INPUT
    deck_input = '0001122234445556667788899ABCDDDEFGGGHHIIIJ'
    success_input= '1D 51 56 57 58 5C 5D 5E 5G 5J 61 66 67 68 6C 6D 6E 6G 6J 71 7C 7D 81 87 8C 8D 8E 8G 8J G1 G7 GC GD GE GG GJ'

    number_card_in_hand = 5
    ######INPUT END

    #process inputs
    deck_counter = Counter(deck_input)
    all_possible_hand_combination = list(combinations(deck_input, number_card_in_hand))

    successes_list_of_counter = []
    for combo in success_input.split(' '):
    successes_list_of_counter.append(Counter(combo))
    successes_set_of_counter = []

    #remove duplicates of counter objects
    for i in successes_list_of_counter:
    if i not in successes_set_of_counter:
    successes_set_of_counter.append(i)
    #process inputs END


    #test if any pattern of success is included in hand, give score depends on the amount of different starters in hand
    def is_success_with_score(hand, successes_set_of_counter = successes_set_of_counter):
    counter_hand = Counter(hand)
    res = 0
    for s in successes_set_of_counter:
    if counter_hand >= s:
    res+=1
    res = min (res,len(hand))
    return res

    def countingWorker(possible_hand_chunk):
    return count_successes(hand_combination = possible_hand_chunk)

    def count_success_thread_starter():
    with concurrent.futures.ThreadPoolExecutor(max_workers=number_threads) as executor:
    worker_input = chunkify(all_possible_hand_combination,number_threads)
    futures = [executor.submit(countingWorker, chunk) for chunk in worker_input]
    res={"hand_with_starter":0, "total_number_starter":0}
    for future in futures:
    fr = future.result() # block until a result is returned
    res["hand_with_starter"] += fr["hand_with_starter"]
    res["total_number_starter"] += fr["total_number_starter"]
    return res

    def count_success_process_starter():
    pool = multiprocessing.Pool(processes=number_process)
    worker_inputs = chunkify(all_possible_hand_combination,number_threads)
    res={"hand_with_starter":0, "total_number_starter":0}
    for worker_result in pool.map(countingWorker, worker_inputs):
    res["hand_with_starter"] += worker_result["hand_with_starter"]
    res["total_number_starter"] += worker_result["total_number_starter"]
    return res


    def count_successes(hand_combination = all_possible_hand_combination, deck = deck_input, number_card_in_hand = number_card_in_hand, successes_set_of_counter = successes_set_of_counter):
    res={"hand_with_starter":0, "total_number_starter":0}
    for hand_tuple in hand_combination:
    success_score = is_success_with_score(hand_tuple)
    if success_score > 0:
    res["hand_with_starter"]+=1
    res["total_number_starter"] += success_score
    return res

    def total_hands(deck = deck_input, number_card_in_hand = number_card_in_hand):
    return comb(len(deck), number_card_in_hand)



    print('Deck:',deck_input)
    print('Deck Counter:',deck_counter)
    print('Deck Size:',len(deck_input))
    print('Hand Size:',number_card_in_hand)


    all_possible_hand_combination = list(combinations(deck_input, number_card_in_hand))

    time_start = time.time()
    c = count_success_thread_starter()
    print('Chance to have at least 1 starter',c["hand_with_starter"]/total_hands())
    print('average amount of different starters in hand',c["total_number_starter"]/total_hands())
    time_end = time.time()
    print('time passed:', time_end-time_start)



    code output:

    Deck: 0001122234445556667788899ABCDDDEFGGGHHIIIJ
    Deck Size: 42
    Hand Size: 5
    Chance to have at least 1 starter 0.8039293825558267
    average amount of different starters in hand 1.921834370165564
    time passed: 38.807377576828


    profiler
    195255471 function calls (195254994 primitive calls) in 47.338 seconds

    Ordered by: cumulative time

    ncalls tottime percall cumtime percall filename:lineno(function)
    24/1 0.000 0.000 47.338 47.338 {built-in method builtins.exec}
    1 0.045 0.045 47.338 47.338 2card combo.py:1(<module>)
    1 0.349 0.349 47.261 47.261 2card combo.py:200(count_successes)
    658008 3.395 0.000 46.912 0.000 2card combo.py:165(is_success_with_score)
    12502152 8.150 0.000 42.003 0.000 __init__.py:802(__ge__)
    12502323 6.879 0.000 32.231 0.000 {built-in method builtins.all}
    87423969 22.410 0.000 26.229 0.000 __init__.py:806(<genexpr>)


    currently the solution with thread and without thread are equally slow. The solution with process is never endingly slow.

    I thought the combinations generator is slow, but it only took 0.04 second to cache the list. I tried multithreading, multiprocessing. The results are the same or worse.

    Should I use numpy and multiply tensors? :(

    Continue reading...

Compartilhe esta Página