Writing hearthstone legend script based on Anaconda

Posted by tbobker on Thu, 10 Feb 2022 20:55:52 +0100

Please don't reprint it. If you have any questions, please delete it
Contact information: Q 537 four 0 six 976

Required Library

Random library is to introduce random location
os library to close the furnace stone legend process
The multiprocessing library implements multiprocessing
cv two library realizes image template matching
Py autogui realizes the simulation of mouse and keyboard

import win32gui,time,pyautogui,cv2
import os
from PIL import Image
import numpy as np
from openpyxl import load_workbook
from multiprocessing import Process,Queue
from random import randint,choice

Screenshot and search

In order to realize relevant functions, screen capture and image search need to be realized

Screenshot

get_screen(img2,region) img2 is the place where the screenshot is saved, and region is the screenshot area

# Class Screen attribute
def get_screen(self,img2='scree.jpg',region=(0,0,1920,1080)):
        '''
        Screenshot
        ----------
        img2 : Image storage path. default'scree.jpg'.
        region : (x1,y1,width,hight) Intercept the left vertex of the rectangle(x1,y1) And rectangle width,hight
        '''
        if region=='default':
            region = (0,0,1920,1080)
        elif len(region)<4:
            raise ValueError('region Rectangle vertex truncation error(x1,y1) And rectangle width,hight')
        pyautogui.screenshot(img2,region=region)

image matching

Template refers to the image to be found in the picture

is_sim :
When K1 is not True, it indicates that the matching value is output
When N is not True, it means no screen capture. In this case, the img2 parameter is required to represent the picture
T means using CV2 Imread() reads grayscale or color
thresh is not None, which means binary matching. After thousands of tests, it is only used to identify the hand cost
mul matches multiple templates, for example, to identify costs. Without using machine learning, you must match multiple templates, and then return the one with a large matching value. At the same time, the maximum value must be greater than a certain value
mul_I is multiple templates

# Class Screen
def is_sim(self,img1='',img2='default',s=0.75,K1=True,N=True,region='default',mul=False,mul_I=[],thresh=None,T=-1):
        '''

        judge img2 Is there any in the img1
        When performing multi image matching,require parameters mul,img2,mul_I,region
        ----------
        img1 :  The default is ''.
        img2 :  Default to'img1_1',Format and img1 identical.
        s : Match minimum. The default is 0.75.
        K1 : Used when the matching value is small,Set to on test only False.
        N : No screenshots,Instead, use existing images,Default screenshot.
        region : Pass to get_screen.
        mul : screenshot,Matching multiple images,Returns the matching value of each object;
                if thresh Not for None,Binarization before matching.
        mul_I : The image contains objects,list.
        T : 0 Indicates grayscale reading,-1 Indicates color reading.Pass to cv2.imread().
        thresh : Not for None,It means matching after binarization,Currently used to identify costs.Not for None be T Expected 0.
        
        '''
        if mul:
            if N:
                self.get_screen(img2,region=region)
            im2 = cv2.imread(img2,T)
            if thresh!=None:
                assert T==0
                im2 = cv2.adaptiveThreshold(im2,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
            M = []
            for i in mul_I:
                im1 = cv2.imread(i,T)
                if thresh!=None:
                    im1 = cv2.adaptiveThreshold(im1,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
                res = cv2.matchTemplate(im2,im1,cv2.TM_CCOEFF_NORMED)
                M.append(res.max())
            return M
        im1 = cv2.imread(img1,T)
        if img2=='default':
            img2 = img1[:img1.index('.')]+'_1'+img1[img1.index('.'):]
        if N:
            self.get_screen(img2,region=region)
        im2 = cv2.imread(img2,T)
        res = cv2.matchTemplate(im2,im1,cv2.TM_CCOEFF_NORMED)
        if not K1:
            return res.max()
        if res.max()<s:
            return False
        return True

Get the location of the template

Used for clicking after implementation
Generally, only img1 parameter is needed When the template is not recognized, wait until the template is recognized
M for example, since there may not be ridicule on the field when recognizing ridicule, 100100 is returned

# Class Screen
def get_position(self,img1='',img2='default',s=0.75,M=True,K1=True,N=True,region='default',mul=False,mul_I=[],T=-1,thresh=None):
        '''
        When multiple images find location,Waiting is not supported.
        ----------
        img1 : Pathname,Pictures to find on the screen
        img2 : Temporary storage location for screenshots. Default to'img1_1',Format and img1 identical.
        s : Positive number not greater than 1,Pass to is_sim function.
        M : BOOL, optional
            by False Time,Return not found(100,100)spot. The default is True.
        K1 : False Cancel threshold default True.
        N  :    False Indicates no screenshot,Direct image comparison, default True.
        region : Pass to get_screen.
        T : 0 Indicates grayscale reading,-1 Indicates color reading.Pass to cv2.imread().
        thresh : Not for None,It means matching after binarization,Currently used to identify costs.
        mul : screenshot,Matching multiple images,Returns the matching value of each object.
        mul_I : The image contains objects,list.
        Returns
        -------
        (X,Y) Image center coordinates
        '''
        if mul:
            M = self.is_sim(mul=mul,mul_I=mul_I,img2=img2,region=region,T=T,thresh=thresh)
            M1 = [M[0],M[1],M[3],M[4]]
            a = max(M1)
            if a>s:
                img1 = mul_I[M.index(a)]
                d = Image.open(img1).size
                im = cv2.imread(img1,T)
                
                im2 = cv2.imread(img2,T)
                
                res = cv2.matchTemplate(im2,im,cv2.TM_CCOEFF_NORMED)
                loc = np.where(res==res.max())
        
                X = int(loc[1]+d[0]/2)
                Y = int(loc[0]+d[1]/2)
                T = 'left' # top left corner
                if a==M[-1] or a==M[-2]:
                    T = 'right' # Upper right corner
                if region != 'default':
                    X += region[0]
                    Y += region[1]
                    return (X,Y,T)
            if M[2]>0.85:
                img1 = mul_I[2]
                d = Image.open(img1).size
                im = cv2.imread(img1,T)
                im2 = cv2.imread(img2,T)
                res = cv2.matchTemplate(im2,im,cv2.TM_CCOEFF_NORMED)
                loc = np.where(res==res.max())
        
                X = int(loc[1]+d[0]/2)
                Y = int(loc[0]+d[1]/2)
                T = 'left' # top left corner
                if region != 'default':
                    X += region[0]
                    Y += region[1]
                    return (X,Y,T)
            return 100,100,None
        if img2=='default':
            img2 = img1[:img1.index('.')]+'_1'+img1[img1.index('.'):]
        while K1 and self.is_sim(img1=img1,img2=img2,s=s,K1=K1,N=N,region=region,T=T,thresh=thresh) is not True:
            if M is True :
                continue
            else:
                return (100,100)
        if N:
            self.get_screen(img2,region)
        d = Image.open(img1).size

        im = cv2.imread(img1,T)
        if thresh!=None:
            im = cv2.adaptiveThreshold(im,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
        im2 = cv2.imread(img2,T)
        if thresh!=None:
            im2 = cv2.adaptiveThreshold(im2,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
        res = cv2.matchTemplate(im2,im,cv2.TM_CCOEFF_NORMED)
        loc = np.where(res==res.max())
        
        X = int(loc[1]+d[0]/2)
        Y = int(loc[0]+d[1]/2)
        if region != 'default':
            X += region[0]
            Y += region[1]
        return (X,Y)

Realize mouse and keyboard simulation

Optimize the functions in pyautogui. At the same time, when playing cards, it is drag, and one position is entrusted to another position

# Class Screen
def click(self,x,y,t=20,button='primary'):
        '''
        Click near a given point,This method shall be used for all non mandatory points
        -------
        x,y : position(x,y)
        t : Default 20
        button : default'primary',Can be set to'left'/'right'/'middle'
        '''
        pyautogui.click(x+randint(-t, t),y+randint(-t,t),button=button)
    
    def move(self,x,y,t=20):
        '''
        Move near a given point
        '''
        pyautogui.moveTo(x+randint(-t, t),y+randint(-t, t))
    
    def drag(self,x0,y0,x1,y1,t=5):
        '''
        From current location(x0,y0)Drag to the specified location(x1,y1)
        '''
        def mousedown(x,y,t):
            pyautogui.mouseDown(x+randint(-t, t),y+randint(-t, t))
        def mouseup(x,y,t):
            pyautogui.mouseUp(x+randint(-t, t),y+randint(-t, t))

        mousedown(x0, y0,t)
        self.move(x1,y1,t)
        time.sleep(0.5)
        mouseup(x1, y1,t)

Specialization of the game

The above is needed by almost all games, and the following is the optimization code and process based on the game

Position of your entourage

# Global variables, and D are not preceded by spaces
D = {'one':(926,560,71,86),'two':(830,560,40,88),
     'three':(760,560,40,81),'four':(670,560,45,73),
     'five':(610,560,40,75),'six':(535,560,40,78),
     'seven':(465,560,40,73)}

Reconnection

Due to potential code exceptions, the game and may be disconnected. At this time, the game should be closed first and then reconnected Suppose that when the function chl is executed, the game process has ended
Go back to the desktop first: press the 'windows' key plus'd'
Then identify the location of Blizzard battle net By default, the account will be automatically logged in after clicking
Then identify the position of entering the game. At this time, please make sure that the furnace stone window is after entering
Then identify the battle mode of hearthstone legend

Sc = Screen()

# function
def chl():
    time.sleep(10) # Handle other processes
    pyautogui.hotkey('winleft','d')
    time.sleep(1)
    x,y = Sc.get_position('baoxue.jpg')
    time.sleep(1)
    pyautogui.doubleClick(x,y)
    time.sleep(5)     
    x,y = Sc.get_position('jinru.jpg')
    time.sleep(1)
    pyautogui.doubleClick(x,y)     
    time.sleep(10)
    while True:
        if Sc.is_sim('duizhan.jpg'):
            break
        if Sc.is_sim('chlchg.jpg'): # Avoiding reconnection is of little practical use
            return 'chl'
    x,y = Sc.get_position('duizhan.jpg')  
    pyautogui.doubleClick(x,y) 
    time.sleep(3)

After that is another class, GameAssist
It inherits the previous class Screen

initialization

X_D is the number of corresponding hands and the corresponding position of each hand
fy is the template expense list
T is a special card (and a card that can not be released by directly holding it to the enemy hero, and you need to click other positions after use (such as finding a card, etc.). It is recommended not to carry a special card

class GameAssist(Screen):   
    def __init__(self,classname,wdname):
        """initialization"""
        super().__init__()
        # Get window handle
        self.hwnd = win32gui.FindWindow(classname,wdname)
        if not self.hwnd:
            self.Chonglian()
        #Window display frontmost
        self.SetAsForegroundWindow()
        # Make the resolution 1920x1080
        self.get_screen()
        
        # The following is some parameter information, which will be optimized later
        self.X_D = {8:[667,719,769,825,902,997,1059,1150],
                    7:[650,750,809,874,958,1038,1119],
                    6:[675,794,856,924,1022,1124],
                    5:[673,825,928,1041,1110],
                    4:[732,849,996,1108],
                    3:[805,897,1097],
                    2:[867,978],1:[889],0:[]}
        self.fy = []
        
        for m in range(11):
            img = f'shoupai\\{m}.jpg'
            self.fy.append(img)
        self.T = {} # Special card

Reconnection mechanism

Determine whether the window needs to be reconnected through the handle

# Reconnection mechanism, which is confirmed by looking up the window handle
# Class GameAssist
    def Chonglian(self,q=Queue()):
        '''
        Reconnection mechanism,If the window cannot be found, return to the desktop,Open app
        -------
        q, If the window is not found,Not empty
        '''
        self.hwnd = win32gui.FindWindow(classname,wdname)
        if not self.hwnd:
            q.put('error')
            A = chl() # Reconnection
            if A=='chl': # Indicates return to match (thousands of runs, very low probability of discovery)
                self.renshu()
            self.hwnd = win32gui.FindWindow(classname,wdname)
        else:
            A = self.get_position('ljzhd.jpg',M=False)
            # Sometimes the connection is interrupted, and you need to exit
            if A[0]==100:
                pass
            else:
                self.tuichu()
            

Window front

# Class GameAssist
def SetAsForegroundWindow(self):
        win32gui.SetForegroundWindow(self.hwnd)

Indicates that the match has been entered

def start(self,Q):
        '''
        Q : Used to transfer data out to other processes
        '''
        Q.put({'num_h':0,'start_T':time.time()}) # Number of rounds, and start time
        pyautogui.moveTo(800,200)
        while self.is_sim(img1='in_game\\duishouxuanp.jpg'):
            time.sleep(0.5) # Opponent selection
        pyautogui.moveTo(700,400)
        time.sleep(5)
        self.in_game(Q) # Start the game

sign out

def tuichu(self):
        '''
        use cmd Command end process
        '''
        os.system("taskkill /f /t /im Hearthstone.exe")

skill

def jineng(self,mode=None):
        '''
        Use skills,Once per round,Avoid wasting expenses.
        mode : None,Just click skills.This applies to(hunter,warrior,Shaman,The paladin)
                'fashi' : Mage skills.Use against enemy heroes.
                'mushi' : Priest skills,Use it on your hero.
                'shushi' : Warlock skill,Never use.
                'other' : Stalker,Druid ,Demon hunter skill,Use no irony to attack enemy heroes

        '''
        if mode==None:
            self.click(1140, 810)
        elif mode=='fashi':
            self.drag(1140,810,961,201)
        elif mode=='mushi':
            self.drag(1140,810,961,821)
        elif mode=='shushi':
            pass
        elif mode=='other':
            self.click(1140,810)
            x,y = self.chaofeng()
            if x==950:
                self.drag(961,821,x,y)

Possible exceptions

def yichang(self):
        x,y = self.get_position(M=False,img1='renwu.jpg')
        # This part is a task that will appear in the early morning of each day. You can continue the game only after clicking
        while x!=100:
            time.sleep(1)
            self.click(x,y,0)
            time.sleep(1)
            x,y = self.get_position(M=False,img1='renwu.jpg')
        x,y = self.get_position(M=False,img1='yichang.jpg') # The exception is an OK button
        if x==100:
            x,y = self.get_position(N=False,img1='chxlj.jpg',img2='yichang_1.jpg',M=False) # The exception is the reconnect button
            if x==100:
                return None
        self.click(x,y,0)

special

Please don't use special cards unless you know the script and optimize it

def teshu(self):
        for j in self.T:
            if self.is_sim(N=False,img1=j,img2='shoupai\\new_big.jpg',K1=False)>0.8:
                return self.T[j]
        return {}

Admit defeat

The initiative to admit defeat can make more victories in a certain period of time

# Admit defeat
    def renshu(self):
        pyautogui.press('esc')
        
        time.sleep(1)
        x,y = self.get_position('renshu.jpg',M=False)
        if x==100:
            self.tuichu()
        else:
            self.click(x,y,0)

Return the cost of playing cards

If you need to optimize yourself, this will be used to determine the card in your turn

def min_SP(self,SP=[],mode='low',fee=None):
        '''
        Minimum cost of return hand
        ----------
        SP : Hand fee.
        mode : default'low'It means to start with low fees
                In case of high cost,need fee parameter,Used to return no greater than fee
                Maximum cost of
        return
        -------
        Minimum cost after removing 0 cost
        '''
        if mode=='low':
            if min(SP)==0:
                for i in range(1,10):
                    if SP.count(i)!=0:
                        return i
            return min(SP)
        else:
            if max(SP)>fee:
                for i in range(fee,0,-1):
                    if SP.count(i)!=0:
                        return i
            return max(SP)

Card acquisition fee

This part has been proved to be feasible without optimization. It's best not to change the code here unless there is a better improvement in efficiency

def feiyong(self,num=4,position=None,M=[],G_F=None): 
        '''
        Find hand fee,Initial basis num Find all hand charges,after
        Specify location,Find the cost of getting a hand.
        Realize to obtain the hand fee only in the first round,This is due to the introduction of the reread hand cost step in the optimization of the card playing function
        Realize the search of new card expenses
        Licensing fee,Considering that sometimes the cost depends only on the gray level, the recognition error is large,Change to binarization before recognition.
        ------
        num : Number of cards in hand,Judging the starting number of cards by lucky coin self.N
            Then update according to the cards played and obtained,A value of 1 indicates the cost of finding somewhere.
        position : Not required by default,Specifies that only the card fee is found.
        G_F : Not for None,Indicates that the hand fee list is updated.
        ------
        Returns the cost of a hand from left to right
        '''
        if position==None or G_F!=None:
            if position==None:
                X = self.X_D[num]
            else:
                X = position
            num = len(X)
            if num==0:
                X = self.X_D[1]
            L,L_ = [],[]
            M = []
            for j in range(num): # This part is later used to determine the priority of playing cards
                img = f'shoupai\\in_{j}.jpg'
                L.append(img)
                self.move(X[j],1000,0)
                time.sleep(0.2)
                self.get_screen(img,region=(501,552,880,125))
                img_ = f'shoupai\\in_{j}_big.jpg'
                L_.append(img_)
                self.get_screen(img_,region=(X[j]-270,552,620,580))
                
            for i in L:
                F = self.is_sim(mul=True,mul_I=self.fy,N=False,img2=i,thresh=True,T=0)
                if max(F)>0.65:
                    M.append(F.index(max(F)))
            self.move(200,500,100)
            return M
        else:
            self.move(position[0],position[1],0)
            time.sleep(0.2)
            self.get_screen('shoupai\\new.jpg',region=(501,552,880,105))
            self.get_screen('shoupai\\new_big.jpg',region=(position[0]-270,552,620,580))
            
            Ex = self.is_sim(mul=True,mul_I=self.fy,N=False,img2='shoupai\\new.jpg',thresh=True,T=0)
            
           
            j = max(Ex)
            i = Ex.index(max(Ex))
            if j>0.65:
                M.append(i)
        if num==1:
            return i
        self.move(200,500,100)
        return M

The enemy mocked his entourage

mul_I is a mockery follower template, which almost meets the requirements

def chaofeng(self):
        '''
        Quick search for irony has been realized
        ------
        modify mul_I
        '''
        mul_I=['in_game\\chaof1.jpg','in_game\\chaof2.jpg',
               'in_game\\chaof3.jpg','in_game\\chaof4.jpg',
               'in_game\\chaof5.jpg']
        region=(471,293,1007,199)
        x0,y0,T = self.get_position(mul=True,mul_I=mul_I,img2='chaofeng.jpg',region=region,s=0.75)

        if x0==100:
            return 950,190
        if T=='left':
            x0 += 50
            y0 += 20
        elif T=='right':
            x0 -= 50
            y0 += 20
        return x0,y0

Attack assist

The modified part helps to identify the number of your followers At the same time, determine whether it is the first

def init(self):
        '''
        Used to obtain the level interface,And gongji Function to implement speculation
        Position of your entourage.
        At the same time, judge whether it contains lucky coins.
        Then you can get all the hand fees at this time
        '''
        self.move(1700,77,10)
        time.sleep(2.5)
        self.get_screen('in_game\\beijing.jpg',region=D['one'])
        self.get_screen('in_game\\bj_1.jpg',region=D['two'])
        self.get_screen('in_game\\bj_2.jpg',region=D['three'])
        self.get_screen('in_game\\bj_3.jpg',region=D['four'])
        self.get_screen('in_game\\bj_4.jpg',region=D['five'])
        self.get_screen('in_game\\bj_5.jpg',region=D['six'])
        self.get_screen('in_game\\bj_6.jpg',region=D['seven'])
        if self.is_sim(img1='xyb.jpg',region=(982,924,132,137)):
            self.N = 6
        else:
            self.N = 4

Playing cards (not perfect, but available)

Note: if there are special cards, please optimize here!!!

def chupai(self,x,position=None,position_=None,D={}):
        '''
        explain : At present, it is in the tentative stage,Special cards will be recorded.
              To achieve this, it should be optimized feiyong function,Realize the recognition of special cards.
              
              -----
              For identification,Due to the special card to be identified, it is small at present,
              Considering the uncertainty of cost,The retrieval method has not been determined yet.
              -----
              
        imagine : 
              position :
                  None : Indicates that the card is played at the enemy hero's place,All attendants\arms\And some spells that can hit the face.
                  'debuff' : Indicates a spell used against an enemy follower.
                  'buff' : Indicates a spell used against your followers.
                  'huixue' : Indicates a blood return spell.
            
              position_ :
                  None : Indicates that no other operation is required after the card is played.
                  'huixue' : Indicates blood return.
                  'buff' : It means to use it for your entourage.
                  'debuff' : Indicates the use of the enemy's entourage.
                  'debuff_2' : Indicates that it is used against enemy heroes.
                  'choice' : Indicates selection.
                  
        ------------------
        parameter x : Playing position x,Is obtained from the correlation function.
        position : Comparison between default value and'huixue'Operation of.
        position_ : The default value has been implemented,'huixue','debuff_2'Operation of.
        

        '''
        position,position_ = None,None
        if len(D)==0:
            position,position_ = None,None
        else:
            for i in D:
                if i=='position':
                    position = D[i]
                if i=='position_':
                    position_ = D[i]
        if position==None:
            self.drag(x, 1000, 963, 190) # play 
            if position_==None:
                time.sleep(0.7)
            elif position_=='huixue':
                time.sleep(0.3)
                self.click(941,829,5)

            elif position_=='debuff_2':
                time.sleep(0.3)
                self.click(963,190,5)

        elif position=='huixue':
            self.drag(x,1000,941,829)
        
        elif position=='buff': #It has not been used for the time being, so it has not been optimized
            self.move(1000,500)
            time.sleep(0.5)
            L = self.gongji()
            if len(L)==0:
                print('Optimization is needed here,Do not use this spell when you have no followers in your field')
        
        time.sleep(0.5)

attack

Return to your entourage location list

def gongji(self):
        '''
        Determine the follower position by determining how many followers are on your field
        '''
        s = 0.7
        if self.is_sim('in_game\\beijing.jpg','beijing.jpg',s,region=D['one']):
            return []
        if self.is_sim('in_game\\bj_1.jpg','bj_1.jpg',s,region=D['two']):
            return [956]    
        if self.is_sim('in_game\\bj_2.jpg','bj_2.jpg',s,region=D['three']):
            return [900,1024]
        if self.is_sim('in_game\\bj_3.jpg','bj_3.jpg',s,region=D['four']):
            return [817,956,1104]
        if self.is_sim('in_game\\bj_4.jpg','bj_4.jpg',s,region=D['five']):
            return [735,900,1024,1170]
        if self.is_sim('in_game\\bj_5.jpg','bj_5.jpg',s,region=D['six']):
            return [680,817,956,1104,1237]
        if self.is_sim('in_game\\bj_6.jpg','bj_6.jpg',s,region=D['seven']):
            return [614,755,900,1024,1170,1306]
        return [539,680,817,956,1104,1237,1388]

Execution round

The legend of hearthstone enters the game only one round for you and one round for me
in_game() indicates that the round starts to execute

Judge what card

Give priority to the right fees and a four fee card for four fees. If there is no more than four fees, start from the high fees, otherwise start from the low fees

def func(fee,mode='low'):
            '''
            Determine which cards in your hand can be played
            -------
            fee : Accept the current available expenses.
            mode : Accept play mode.
            -------
            return None,None,{}
            The first parameter indicates the order of cards to be played in the hand,None Indicates that there is no card available
            The second parameter indicates whether to continue,None Indicates that there is no need to continue,True Indicates that you can continue
            When the third parameter is not empty, it means that it needs to be passed to self.chupai.
            '''
            F_ = self.F[:]
            Err = False
            Err1 = False
            while not Err1:
                if Err:
                    Err1 = True
                po,T,D = None,None,{}
                if len(F_)==0:
                    return None,None,{}
                if len(F_)==1:
                    if F_[0]<=fee:
                        po = 0
                        M = F_[0]
                
                elif F_.count(fee)!=0:
                    po = F_.index(fee)
                    M = fee
    
                elif mode=='low':
                    # From low cost
                    M = self.min_SP(F_,mode,fee=fee)
                    if M<=fee:
                        po = F_.index(M)
                        fee = fee-M
                        F_.remove(M)
                        M2 = self.min_SP(F_,mode,fee=fee)
                        if M2<=fee:
                            T = True
                        
                else:
                    # Start with high fees
                    M = self.min_SP(F_,mode='h',fee=fee)
                    po = F_.index(M)
                    fee = fee-M
                    F_.remove(M)
                    M2 = self.min_SP(F_,mode='low',fee=fee)
                    if M2<=fee:
                        T = True
                if po==None:
                    return None,None,{}
                x = self.X_D[len(self.F)][po]
                N = self.feiyong(num=1,position=(x,1000))
                D = self.teshu()
                if N==M or N==0:
                    return po,T,D
                else:
                    self.F = self.feiyong(num=len(self.F),position=None)
                    F_ = self.F[:]
                    fee = fee+M
                    Err = True
                    continue
            return None,None,{}

play

Get the cost of novice cards, and re read the cost when the cost of cards to be played is different from that during execution

def chupai(fee=2):
            '''
            fee : It's actually the number of rounds.
            return : Hand fee.
            ----------
            Enter your own turn to execute,Cost of obtaining novice card,increase to F in
            If the cost is more than 4, start from the high cost,Otherwise, from the low cost
            Update after exit F,And available expenses,
            End when there is no card to play
            '''
            position = self.X_D[len(self.F)+1][-1]
            self.F = self.feiyong(position=(position,1000),M=self.F)
            
            T = True
            t = time.time()
            while T:
                if time.time()-t>25:
                    print('abnormal,Too long playing time')
                    break
                if self.F.count(0)!=0: # Lucky coin mechanism, direct fee hopping
                    if self.F.count(fee+1)!=0:
                        x = self.X_D[len(self.F)][self.F.index(0)]
                        if self.feiyong(num=1,position=(x,1000))==0:
                            self.chupai(x)
                            self.F.remove(0)
                            po,T,D = func(fee+1)
                            x = self.X_D[len(self.F)][po]
                            self.chupai(x,D=D)
                            f = self.F.pop(po)
                            fee = fee+1-f
                        else:
                            self.F = self.feiyong(num=len(self.F),position=None)
                            continue
                if fee>4:
                    mode='h'
                else:
                    mode='low'
                po,T,D = func(fee,mode)
                if po==None:
                    break
                x = self.X_D[len(self.F)][po]
                try:
                    self.chupai(x,D=D)
                except IndexError:
                    print(po,x,self.F,'error')
                    raise IndexError
                f = self.F.pop(po)
                fee = fee-f
                if len(self.F)==0:
                    self.F = self.feiyong(G_F='need',position=[889])

End Turn

At the end of the turn, the opponent's turn is represented by the opponent's turn template. When it is not matched and matched to the end of the turn, it indicates that it has reached its own turn again (note that there are two ways to end the turn)

def jieshu(g):
            '''
            g : Rounds .
            F : Hand fee.
            End this round,
            The opponent spends more than 3 hours in one round*60 Then exit directly
            --------
            return : Opponent time,Hand fee
            '''
            
            pos1 = {1:(1468,154),2:(489,130),3:(472,883)} # Mouse position of opponent's turn
            
            pyautogui.click(1561,496)
            x,y = pos1[randint(1, 3)] 
            self.move(x, y, t=50)
            time.sleep(0.3)
            t0 = time.time()
            time.sleep(2)
                   
            while self.is_sim('in_game\\duishou.jpg',K1=False)>=0.55:
                if int(time.time()-t0)>3*60:
                    self.tuichu()
                continue

attack

Obtain the number of your followers, find the invincible Party's mocking followers, and hit the face or the enemy's mocking followers After mocking each follower of the enemy, renew the number of followers
When the number of your followers changes, continue to start with your followers on both sides The rest mechanism for the attendant is not realized
The timing mechanism is introduced to prevent the exception from causing the execution all the time and finally causing the exception of the whole program

def gongji():
            '''
            No more random attacks,But attack from both ends to the middle
            '''
            y1 = 590
            L = self.gongji()
            L1 = L[:]
            t0 = time.time()
            j = 0
            while len(L)>0:
                if time.time()-t0>20: # Introduce timing mechanism
                    break
                x0,y0 = self.chaofeng()
                x = L[0] if j%2==0 else L[-1]
                L.remove(x)
                self.drag(x, y1, x0, y0)
                time.sleep(0.8)
                self.click(1750,300,50,'right') # Right click to cancel the selection to avoid an error that makes other followers unable to select other followers
                time.sleep(1) 
                j += 1
                if len(L1)!=len(self.gongji()):
                    L = self.gongji()
                    L1 = L[:]

Friendly round

Play cards first, release skills, attack and end at last
No rope burning mechanism is introduced

def mine(g):
            '''
            Own round,
            After execution, the turn ends,No longer introduce the delay mechanism
            '''
            mul_I = ['in_game\\jieshu.jpg',
                     'in_game\\jieshu_2.jpg']
            L = self.is_sim(mul=True,mul_I=mul_I,img2='huihe.jpg')
            while max(L)<0.7:
                time.sleep(0.5)
                L = self.is_sim(mul=True,mul_I=mul_I,img2='huihe.jpg')
           
            self.move(1780,200)
            if g==1:
                self.init()
                time.sleep(0.5)
                self.F = self.feiyong(G_F='need',num=self.N)
                return None
             
            time.sleep(0.5)
            chupai(g)
            time.sleep(1.5)
            self.jineng('shushi')  # The skill release is changed to after playing cards and before the follower attacks
            time.sleep(1)
            gongji()

Whole in_ Game (code)

def in_game(self,Q):
        def func(fee,mode='low'):
            '''
            Determine which cards in your hand can be played
            -------
            F : Accept hand fee.
            fee : Accept the current available expenses.
            mode : Accept play mode.
            -------
            return None,None,{}
            The first parameter indicates the order of cards to be played in the hand,None Indicates that there is no card available
            The second parameter indicates whether to continue,None Indicates that there is no need to continue,True Indicates that you can continue
            When the third parameter is not empty, it means that it needs to be passed to self.chupai.
            '''
            F_ = self.F[:]
            Err = False
            Err1 = False
            while not Err1:
                if Err:
                    Err1 = True
                po,T,D = None,None,{}
                if len(F_)==0:
                    return None,None,{}
                if len(F_)==1:
                    if F_[0]<=fee:
                        po = 0
                        M = F_[0]
                
                elif F_.count(fee)!=0:
                    po = F_.index(fee)
                    M = fee
    
                elif mode=='low':
                    # From low cost
                    M = self.min_SP(F_,mode,fee=fee)
                    if M<=fee:
                        po = F_.index(M)
                        fee = fee-M
                        F_.remove(M)
                        M2 = self.min_SP(F_,mode,fee=fee)
                        if M2<=fee:
                            T = True
                        
                else:
                    # Start with high fees
                    M = self.min_SP(F_,mode='h',fee=fee)
                    po = F_.index(M)
                    fee = fee-M
                    F_.remove(M)
                    M2 = self.min_SP(F_,mode='low',fee=fee)
                    if M2<=fee:
                        T = True
                if po==None:
                    return None,None,{}
                x = self.X_D[len(self.F)][po]
                N = self.feiyong(num=1,position=(x,1000))
                D = self.teshu()
                if N==M or N==0:
                    return po,T,D
                else:
                    self.F = self.feiyong(num=len(self.F),position=None)
                    F_ = self.F[:]
                    fee = fee+M
                    Err = True
                    continue
            return None,None,{}
                
        def chupai(fee=2):
            '''
            F : Hand fee.
            fee : It's actually the number of rounds.
            return : Hand fee.
            ----------
            Enter your own turn to execute,Cost of obtaining novice card,increase to F in
            If the cost is more than 4, start from the high cost,Otherwise, from the low cost
            Update after exit F,And available expenses,
            End when there is no card to play
            '''

            # The lucky coin mechanism will not be introduced for the time being

            position = self.X_D[len(self.F)+1][-1]
            self.F = self.feiyong(position=(position,1000),M=self.F)
            
            T = True
            t = time.time()
            while T:
                if time.time()-t>25:
                    print('abnormal,Too long playing time')
                    break
                if self.F.count(0)!=0: # Lucky coin mechanism, direct fee hopping
                    if self.F.count(fee+1)!=0:
                        x = self.X_D[len(self.F)][self.F.index(0)]
                        if self.feiyong(num=1,position=(x,1000))==0:
                            self.chupai(x)
                            self.F.remove(0)
                            po,T,D = func(fee+1)
                            x = self.X_D[len(self.F)][po]
                            self.chupai(x,D=D)
                            f = self.F.pop(po)
                            fee = fee+1-f
                        else:
                            self.F = self.feiyong(num=len(self.F),position=None)
                            continue
                if fee>4:
                    mode='h'
                else:
                    mode='low'
                po,T,D = func(fee,mode)
                if po==None:
                    break
                x = self.X_D[len(self.F)][po]
                try:
                    self.chupai(x,D=D)
                except IndexError:
                    print(po,x,self.F,'error')
                    raise IndexError
                f = self.F.pop(po)
                fee = fee-f
                if len(self.F)==0:
                    self.F = self.feiyong(G_F='need',position=[889])
                
        def jieshu(g):
            '''
            g : Rounds .
            F : Hand fee.
            End this round,
            Return to the hand fee after finishing the first round.
            
            The opponent spends more than 3 hours in one round*60 Then exit directly
            --------
            return : Opponent time,Hand fee
            '''
            
            pos1 = {1:(1468,154),2:(489,130),3:(472,883)} # Mouse position of opponent's turn
            
            pyautogui.click(1561,496)
            x,y = pos1[randint(1, 3)] 
            self.move(x, y, t=50)
            time.sleep(0.3)
            t0 = time.time()
            time.sleep(2)
                   
            while self.is_sim('in_game\\duishou.jpg',K1=False)>=0.55:
                if int(time.time()-t0)>3*60:
                    self.tuichu()
                continue
            # return self.F
        
        def gongji():
            '''
            No more random attacks,But attack from both ends to the middle

            
            '''
            y1 = 590
            L = self.gongji()
            L1 = L[:]
            t0 = time.time()
            j = 0
            while len(L)>0:
                if time.time()-t0>20: # Introduce timing mechanism
                    break
                x0,y0 = self.chaofeng()
                x = L[0] if j%2==0 else L[-1]
                L.remove(x)
                self.drag(x, y1, x0, y0)
                time.sleep(0.8)
                self.click(1750,300,50,'right') # Right click to cancel the selection to avoid an error that makes other followers unable to select other followers
                time.sleep(1) 
                j += 1
                if len(L1)!=len(self.gongji()):
                    L = self.gongji()
                    L1 = L[:]
                
        def mine(g):
            '''
            Own round,
            After execution, the turn ends,No longer introduce the delay mechanism

            '''
            mul_I = ['in_game\\jieshu.jpg',
                     'in_game\\jieshu_2.jpg']
            L = self.is_sim(mul=True,mul_I=mul_I,img2='huihe.jpg')
            while max(L)<0.7:
                time.sleep(0.5)
                L = self.is_sim(mul=True,mul_I=mul_I,img2='huihe.jpg')
           
            self.move(1780,200)

        
            if g==1:
                self.init()
                time.sleep(0.5)
                self.F = self.feiyong(G_F='need',num=self.N)
                return None
             
            time.sleep(0.5)
            chupai(g)
            time.sleep(1.5)
            self.jineng('shushi')  # The skill release is changed to after playing cards and before the follower attacks
            time.sleep(1)
            gongji()
            # return self.F
        
        time.sleep(4)
        g = 1
        self.F = [] # The start is set to null, which is actually obtained by jieshu at the end of the second round
        
        while True:
            A = Q.get()
            A['num_h'] = g
            Q.put(A)
            mine(g)#
            time.sleep(0.5)
            jieshu(g)#
            g = g+1

Next is the code that can be optimized to simplify, but I lose motivation. They are important but cumbersome

For communication between multiple processes

def Break(q=Queue(),q1=Queue(),q2=Queue()):
    '''
        Pass information according to the return value:
        return 'error' ,Indicates that the game window was not found,At this time, only reconnection operation should be carried out,
        The remaining relevant processes should be completed;
        return 'in' , It means that we enter the beginning at this time,At this time, the process"over"Start the game;
        return 'start' , It means that the game begins at this time,Progress"start".
    '''
    if not q.empty():
        return 'error'
    if q2.empty():
        return None
    order = q2.get()
    q2.put(order)
    if q1.empty() is False:
        return True
    elif order=='start':
        return 'start'
    elif order=='in':
        return 'in'

Implement multi process

def process_job(name,q=Queue(),q1=Queue(),q2=Queue(),Q=Queue(),List=[]):
    '''
    according to name Decision process,Implement multi process
    ----------
    name : At present'over' : Used to determine the end of a game,Proceed to the next round;
                'start' : To start the game,Match up,And run the script in the game;
                'error' : Monitor window handle,If the window handle is not found, the operation related to running the game will be carried out
                .
    q : Queue(), When the window handle is not found,Not empty,And running Break return'error'Time,Re empty.
    q1 : Queue(), If it is not empty, it indicates the end of a game,Termination due to'over'.
    q2 : Queue(), The information received is'in','start'.First cause over input'in'after,'start'accept'in'And enter'start'
        'in'Indicates that it has entered the customs,'main'Indicates that you can enter the off at the beginning,'finishi'Completed level.
    List : It is used to store the parameters required by each related process.
    '''
    
    while True:
        if name=='over': #End the game
            if Break(q,q1,q2)=='error':
                break
            if time.time()-List[0]>=20*60: # Exit directly after 20 minutes and end the process
                print('The time of the bureau is more than 20 minutes',f'present time {time.localtime()[3:5]}')
                # demo.renshu()
                demo.tuichu()
                break
            L = demo.is_sim(img2='jieshu.jpg',mul=True,mul_I=['shengli.jpg','shibai.jpg','in_game\\djjx.jpg'])
            if max(L)>0.6:
                q1.put(None)
                T = ''
                for i in time.localtime()[1:5]:
                    T = T+str(i)+'|'
                T = T[:-1]
                if L[0]>0.6:
                    end = 'victory'
                elif L[1]>0.6:
                    end = 'fail'
                else:
                    end = 'unknown'
                if Q.empty() is True:
                    break
                A = Q.get()
                try:
                    num_h = A['num_h']
                    game_time = int(time.time()-A['start_T'])
                    game_time = f'{game_time//60}m {game_time%60}s'
                except KeyError:
                    print(A,'error')
                    break
                except Exception as E:
                    print(E,'error')
                    break
                
                if A['num_h']>3:
                    demo.write(game_endtime=T,end=end,num_h=num_h,game_time=game_time)
                break
            
            if demo.is_sim('start.jpg'):
                if Break(q,q1,q2)!='in':
                    
                    q2.put('start')
                    time.sleep(3)

        elif name=='start': # Start the game
            # This process ends once and at the end of the game
            if Break(q,q1,q2)=='start':
                q2.get()
                q2.put('in')
                x,y = demo.get_position('start.jpg')
                demo.click(x,y)
                time.sleep(1)
                while demo.is_sim('start.jpg'):
                    x,y = demo.get_position('start.jpg')
                    demo.click(x,y)
                    time.sleep(1)
                    
                T = '' # T = 'maoxian' means fighting with the hotel owner for optimization
                
                if T=='maoxian': # Hotel optimization
                    x = 1400
                    y = choice([116,199,256,319,396,442,517,576,650,718])
                    demo.click(x,y,0)
                    x,y = demo.get_position('start.jpg')
                    demo.click(x,y)
                    time.sleep(1)
                    while demo.is_sim('start.jpg'):
                        x,y = demo.get_position('start.jpg')
                        demo.click(x,y)
                        time.sleep(1)
                t = time.time()
                
                
                
            if Break(q,q1,q2)=='in':
                while not demo.is_sim(img1='in_game\\qr.jpg',s=0.5):
                    if time.time()-t>5*60:
                        demo.tuichu()
                        break
                x,y = demo.get_position(img1='in_game\\qr.jpg',s=0.5)
                
                
                if  time.localtime()[2]<=20 and List[0]%2==0 and T!='maoxian' : # No automatic surrender after the 20th of each month
                    demo.renshu()
                    break
                
                time.sleep(1)
                t = 3.5
                if not demo.is_sim(img1='fee\\fee_2.jpg',region=(452,328,1026,95)):
                    X = [600,900,1300]
                    y0 = 538
                    for x0 in X:
                        demo.click(x0,y0)
                    t = 7.5
                demo.click(x,y)
                time.sleep(t)
                demo.start(Q)
    
        elif name=='error': # abnormal    
            demo.Chonglian(q)
            demo.yichang()
            if not q.empty(): # Reset q after reconnection
                q.get()
   
        else:
            print('The is not set',name,'function')

The rest

This is the acquisition of the window handle

classname = "UnityWndClass"
wdname = "Hearth Stone"
demo = GameAssist(classname,wdname)

This is important
Since it is a multi process operation, write to__ main__ Internal is important and needs to be run in Shell

if __name__=='__main__' and 1==1:
    J = 0
    while True:
        q,q1,q2,Q = Queue(),Queue(),Queue(),Queue()
        
        # pro process, always in progress, find the game window in real time
        pro = Process(target=process_job,args=('error',q,q1,q2,Q))
        pro.daemon = True
        pro.start()
        
        while True: #The loop cannot exit, otherwise the pro process will be increased
            while not q.empty():
                time.sleep(5)
                continue
            t0 = time.time()
            pr1 = Process(target=process_job,args=('over',q,q1,q2,Q,[t0],))
            pr1.start()
            
            pr2 = Process(target=process_job,args=('start',q,q1,q2,Q,[J]))
            J = J+1
            pr2.start()
            
            while win32gui.FindWindow(classname,wdname):
                if not pr1.is_alive():
                    if not pr2.is_alive():
                        pr2.terminate()
                    if not q1.empty():
                        t = time.time()
                        time.sleep(1)
                        while not demo.is_sim(img1='start.jpg'):
                            demo.click(700,900,50)
                            time.sleep(1.5)
                            if time.time()-t>3*60:
                                demo.tuichu()
                                time.sleep(1)
                                break
                    break
            if pr1.is_alive():
                pr1.terminate()
            if pr2.is_alive():
                pr2.terminate()
            time.sleep(5)
            q1,q2,Q = Queue(),Queue(),Queue()

Entire code

# -*- coding: utf-8 -*-
"""
Created on Thu Mar 18 18:52:06 2021

@author: 537406976(Then I QQ)
"""




import win32gui,time,pyautogui,cv2
import os
from PIL import Image
import numpy as np
from openpyxl import load_workbook
from multiprocessing import Process,Queue
from random import randint,choice

os.chdir(r'F:\Python\game')

class Screen():
    def get_screen(self,img2='scree.jpg',region=(0,0,1920,1080)):
        '''
        Screenshot
        ----------
        img2 : Image storage path. default'scree.jpg'.
        region : (x1,y1,width,hight) Intercept the left vertex of the rectangle(x1,y1) And rectangle width,hight
        '''
        if region=='default':
            region = (0,0,1920,1080)
        elif len(region)<4:
            raise ValueError('region Rectangle vertex truncation error(x1,y1) And rectangle width,hight')
        pyautogui.screenshot(img2,region=region)
        
    def is_sim(self,img1='',img2='default',s=0.75,K1=True,N=True,region='default',mul=False,mul_I=[],thresh=None,T=-1):
        '''

        judge img2 Is there any in the img1
        When performing multi image matching,require parameters mul,img2,mul_I,region
        ----------
        img1 :  The default is ''.
        img2 :  Default to'img1_1',Format and img1 identical.
        s : Match minimum. The default is 0.75.
        K1 : Used when the matching value is small,Set to on test only False.
        N : No screenshots,Instead, use existing images,Default screenshot.
        region : Pass to get_screen.
        mul : screenshot,Matching multiple images,Returns the matching value of each object;
                if thresh Not for None,Binarization before matching.
        mul_I : The image contains objects,list.
        T : 0 Indicates grayscale reading,-1 Indicates color reading.Pass to cv2.imread().
        thresh : Not for None,It means matching after binarization,Currently used to identify costs.Not for None be T Expected 0.
        
        '''
        if mul:
            if N:
                self.get_screen(img2,region=region)
            im2 = cv2.imread(img2,T)
            if thresh!=None:
                assert T==0
                im2 = cv2.adaptiveThreshold(im2,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
            M = []
            for i in mul_I:
                im1 = cv2.imread(i,T)
                if thresh!=None:
                    im1 = cv2.adaptiveThreshold(im1,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
                res = cv2.matchTemplate(im2,im1,cv2.TM_CCOEFF_NORMED)
                M.append(res.max())
            return M
        im1 = cv2.imread(img1,T)
        if img2=='default':
            img2 = img1[:img1.index('.')]+'_1'+img1[img1.index('.'):]
        if N:
            self.get_screen(img2,region=region)
        im2 = cv2.imread(img2,T)
        res = cv2.matchTemplate(im2,im1,cv2.TM_CCOEFF_NORMED)
        if not K1:
            return res.max()
        if res.max()<s:
            return False
        return True
    
    def get_position(self,img1='',img2='default',s=0.75,M=True,K1=True,N=True,region='default',mul=False,mul_I=[],T=-1,thresh=None):
        '''
        Recommended settings img2 parameter,Avoid multi process resource grabbing.
        When multiple images find location,Waiting is not supported.
        ----------
        img1 : Pathname,Pictures to find on the screen
            DESCRIPTION.
        img2 : Temporary storage location for screenshots. Default to'img1_1',Format and img1 identical.
        s : Positive number not greater than 1,Pass to is_sim function.
        M : BOOL, optional
            by False Time,Return not found(100,100)spot. The default is True.
        K1 : False Cancel threshold default True.
        N  :    False Indicates no screenshot,Direct image comparison, default True.
        region : Pass to get_screen.
        T : 0 Indicates grayscale reading,-1 Indicates color reading.Pass to cv2.imread().
        thresh : Not for None,It means matching after binarization,Currently used to identify costs.
        mul : screenshot,Matching multiple images,Returns the matching value of each object.
        mul_I : The image contains objects,list.
        Returns
        -------
        (X,Y) Image center coordinates
        '''
        if mul:
            M = self.is_sim(mul=mul,mul_I=mul_I,img2=img2,region=region,T=T,thresh=thresh)
            M1 = [M[0],M[1],M[3],M[4]]
            a = max(M1)
            if a>s:
                img1 = mul_I[M.index(a)]
                d = Image.open(img1).size
                im = cv2.imread(img1,T)
                
                im2 = cv2.imread(img2,T)
                
                res = cv2.matchTemplate(im2,im,cv2.TM_CCOEFF_NORMED)
                loc = np.where(res==res.max())
        
                X = int(loc[1]+d[0]/2)
                Y = int(loc[0]+d[1]/2)
                T = 'left' # top left corner
                if a==M[-1] or a==M[-2]:
                    T = 'right' # Upper right corner
                if region != 'default':
                    X += region[0]
                    Y += region[1]
                    return (X,Y,T)
            if M[2]>0.85:
                img1 = mul_I[2]
                d = Image.open(img1).size
                im = cv2.imread(img1,T)
                im2 = cv2.imread(img2,T)
                res = cv2.matchTemplate(im2,im,cv2.TM_CCOEFF_NORMED)
                loc = np.where(res==res.max())
        
                X = int(loc[1]+d[0]/2)
                Y = int(loc[0]+d[1]/2)
                T = 'left' # top left corner
                if region != 'default':
                    X += region[0]
                    Y += region[1]
                    return (X,Y,T)
            return 100,100,None
        if img2=='default':
            img2 = img1[:img1.index('.')]+'_1'+img1[img1.index('.'):]
        while K1 and self.is_sim(img1=img1,img2=img2,s=s,K1=K1,N=N,region=region,T=T,thresh=thresh) is not True:
            if M is True :
                continue
            else:
                return (100,100)
        if N:
            self.get_screen(img2,region)
        d = Image.open(img1).size

        im = cv2.imread(img1,T)
        if thresh!=None:
            im = cv2.adaptiveThreshold(im,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
        im2 = cv2.imread(img2,T)
        if thresh!=None:
            im2 = cv2.adaptiveThreshold(im2,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
        res = cv2.matchTemplate(im2,im,cv2.TM_CCOEFF_NORMED)
        loc = np.where(res==res.max())
        
        X = int(loc[1]+d[0]/2)
        Y = int(loc[0]+d[1]/2)
        if region != 'default':
            X += region[0]
            Y += region[1]
        return (X,Y)
    
    def click(self,x,y,t=20,button='primary'):
        '''
        Click near a given point,This method shall be used for all non mandatory points
        -------
        x,y : position(x,y)
        t : Default 20
        button : default'primary',Can be set to'left'/'right'/'middle'
        '''
        pyautogui.click(x+randint(-t, t),y+randint(-t,t),button=button)
    
    def move(self,x,y,t=20):
        '''
        Move near a given point
        '''
        pyautogui.moveTo(x+randint(-t, t),y+randint(-t, t))
    
    def drag(self,x0,y0,x1,y1,t=5):
        '''
        From current location(x0,y0)Drag to the specified location(x1,y1)
        '''
        def mousedown(x,y,t):
            pyautogui.mouseDown(x+randint(-t, t),y+randint(-t, t))
        def mouseup(x,y,t):
            pyautogui.mouseUp(x+randint(-t, t),y+randint(-t, t))

        mousedown(x0, y0,t)
        self.move(x1,y1,t)
        time.sleep(0.5)
        mouseup(x1, y1,t)
   
    

D = {'one':(926,560,71,86),'two':(830,560,40,88),
     'three':(760,560,40,81),'four':(670,560,45,73),
     'five':(610,560,40,75),'six':(535,560,40,78),
     'seven':(465,560,40,73)}
Sc = Screen()

def chl():
    time.sleep(10) # Handle other processes
    pyautogui.hotkey('winleft','d')
    time.sleep(1)
    x,y = Sc.get_position('baoxue.jpg')
    time.sleep(1)
    pyautogui.doubleClick(x,y)
    time.sleep(5)     
    x,y = Sc.get_position('jinru.jpg')
    time.sleep(1)
    pyautogui.doubleClick(259,181)
    time.sleep(1)
    pyautogui.doubleClick(x,y)     
    time.sleep(10)
    while True:
        if Sc.is_sim('duizhan.jpg'):
            break
        if Sc.is_sim('chlchg.jpg'):
            return 'chl'
    x,y = Sc.get_position('duizhan.jpg')  
    pyautogui.doubleClick(x,y) 
    time.sleep(3)
    
    
class GameAssist(Screen):   
    def __init__(self,classname,wdname):
        """initialization"""
        # Get window handle
        super().__init__()
        self.hwnd = win32gui.FindWindow(classname,wdname)
        if not self.hwnd:
            self.Chonglian()
        #Window display frontmost
        self.SetAsForegroundWindow()
        # win32gui.SetActiveWindow(self.hwnd)
        # Make the resolution 1920x1080
        self.get_screen()
        
        # The following is some parameter information, which will be optimized later
        self.X_D = {8:[667,719,769,825,902,997,1059,1150],
                    7:[650,750,809,874,958,1038,1119],
                    6:[675,794,856,924,1022,1124],
                    5:[673,825,928,1041,1110],
                    4:[732,849,996,1108],
                    3:[805,897,1097],
                    2:[867,978],1:[889],0:[]}
        self.fy = []
        
        for m in range(11):
            img = f'shoupai\\{m}.jpg'
            self.fy.append(img)
        self.T = {} # Special card
           
    
    def write(self,file1='game_X.xlsx',file2='game_J.xlsx',game_endtime='',game_time='',end='',num_h=''):
        wb = load_workbook(filename=file1)
        ws = wb.active
        ws.append([game_endtime,end,num_h,game_time])
        wb.save(file1)
        if end!='unknown':
            wb = load_workbook(filename=file2)
            ws = wb.active
            end_ = int(ws.cell(row=2,column=1).value)

            ws.cell(row=2,column=1).value = end_+1
            if end=='victory':
                end_ = int(ws.cell(row=2,column=2).value)
                ws.cell(row=2,column=2).value = end_+1
            else:
                end_ = int(ws.cell(row=2,column=3).value)
                ws.cell(row=2,column=3).value = end_+1
            ws.cell(row=2,column=4).value = f'{int(ws.cell(row=2,column=2).value)/int(ws.cell(row=2,column=1).value)*100:.2f}%'
            wb.save(file2)
        
    # Reconnection mechanism, which is confirmed by looking up the window handle
    def Chonglian(self,q=Queue()):
        '''
        Reconnection mechanism,If the window cannot be found, return to the desktop,Open app
        -------
        q, If the window is not found,Not empty

        return
        -------
        Return if the window is found'in'

        '''
        self.hwnd = win32gui.FindWindow(classname,wdname)
        if not self.hwnd:
            q.put('error')
            A = chl() # Reconnection
            if A=='chl':
                self.renshu()
            self.hwnd = win32gui.FindWindow(classname,wdname)
        else:
            A = self.get_position('ljzhd.jpg',M=False)
            if A[0]==100:
                pass
            else:
                self.tuichu()
            A = self.get_position('chonglian.jpg',M=False)
            if A[0]==100:
                pass
            else:
                pyautogui.click(500,500,10)
    
    def SetAsForegroundWindow(self):
        win32gui.SetForegroundWindow(self.hwnd)



    def start(self,Q):
        '''
        Q : Used to transfer data out to other processes
        '''
        Q.put({'num_h':0,'start_T':time.time()})

        pyautogui.moveTo(800,200)
        while self.is_sim(img1='in_game\\duishouxuanp.jpg'):
            time.sleep(0.5)
        pyautogui.moveTo(700,400)
        time.sleep(5)
        self.in_game(Q)
        
    def tuichu(self):
        '''
        use cmd Command end process
        '''
        os.system("taskkill /f /t /im Hearthstone.exe")

    def jineng(self,mode=None):
        '''
        Use skills,Once per round,Avoid wasting expenses.
        mode : None,Just click skills.This applies to(hunter,warrior,Shaman,The paladin)
                'fashi' : Mage skills.Use against enemy heroes.
                'mushi' : Priest skills,Use it on your hero.
                'shushi' : Warlock skill,Never use.
                'other' : Stalker,Druid ,Demon hunter skill,Use no irony to attack enemy heroes

        '''
        if mode==None:
            self.click(1140, 810)
        elif mode=='fashi':
            self.drag(1140,810,961,201)
        elif mode=='mushi':
            self.drag(1140,810,961,821)
        elif mode=='shushi':
            pass
        elif mode=='other':
            self.click(1140,810)
            x,y = self.chaofeng()
            if x==950:
                self.drag(961,821,x,y)
        
    def jiaoliu(self,i=0):
        # The mechanism has been cancelled
        '''
        communication
        
        i : Indicates the emitting language
        1 : thank      2 : wonder
        3 : praise      4 : an error
        5 : to greet      6 : threaten
        '''
        pyautogui.rightClick(976,867)
        chat = {1:(813,687),
         2:(1109,690),
         3:(758,778),
         4:(1155,772),
         5:(760,854),
         6:(1151,871)}
        if i in chat.keys():
            pass
        else:
            i = randint(1, 6)
        x,y= chat[i]
        time.sleep(1.5)
        self.click(x,y,5)
        self.move(1756,77)
        
        pass
    
    
    def yichang(self):
        x,y = self.get_position(M=False,img1='renwu.jpg')
        while x!=100:
            time.sleep(1)
            self.click(x,y,0)
            time.sleep(1)
            x,y = self.get_position(M=False,img1='renwu.jpg')
        x,y = self.get_position(M=False,img1='yichang.jpg')
        if x==100:
            x,y = self.get_position(N=False,img1='chxlj.jpg',img2='yichang_1.jpg',M=False)
            if x==100:
                return None
        self.click(x,y,0)
    
    def teshu(self):
        for j in self.T:
            if self.is_sim(N=False,img1=j,img2='shoupai\\new_big.jpg',K1=False)>0.8:
                return self.T[j]
        return {}
    

        
    # Admit defeat directly after reconnection
    def renshu(self):
        pyautogui.press('esc')
        
        time.sleep(1)
        x,y = self.get_position('renshu.jpg',M=False)
        if x==100:
            self.tuichu()
        else:
            self.click(x,y,0)
        
    def min_SP(self,SP=[],mode='low',fee=None):
        '''
        Minimum cost of return hand
        ----------
        SP : Hand fee.
        mode : default'low'It means to start with low fees
                In case of high cost,need fee parameter,Used to return no greater than fee
                Maximum cost of
        return
        -------
        Minimum cost after removing 0 cost

        '''
                    

        if mode=='low':
            if min(SP)==0:
                for i in range(1,10):
                    if SP.count(i)!=0:
                        return i
            return min(SP)
        else:
            if max(SP)>fee:
                for i in range(fee,0,-1):
                    if SP.count(i)!=0:
                        return i
            return max(SP)

    def feiyong(self,num=4,position=None,M=[],G_F=None): 
        '''
        Find hand fee,Initial basis num Find all hand charges,after
        Specify location,Find the cost of getting a hand.
        Realize to obtain the hand fee only in the first round,This is due to the introduction of the reread hand cost step in the optimization of the card playing function
        Realize the search of new card expenses
        Licensing fee,Considering that sometimes the cost depends only on the gray level, the recognition error is large,Change to binarization before recognition.
        ------
        num : Number of cards in hand,Judging the starting number of cards by lucky coin self.N
            Then update according to the cards played and obtained,A value of 1 indicates the cost of finding somewhere.
        position : Not required by default,Specifies that only the card fee is found.
        G_F : Not for None,Indicates that the hand fee list is updated.
        ------
        Returns the cost of a hand from left to right
        '''
        if position==None or G_F!=None:
            if position==None:
                X = self.X_D[num]
            else:
                X = position
            num = len(X)
            if num==0:
                X = self.X_D[1]
            L,L_ = [],[]
            M = []
            for j in range(num): # This part is later used to determine the priority of playing cards
                img = f'shoupai\\in_{j}.jpg'
                L.append(img)
                self.move(X[j],1000,0)
                time.sleep(0.2)
                self.get_screen(img,region=(501,552,880,125))
                img_ = f'shoupai\\in_{j}_big.jpg'
                L_.append(img_)
                self.get_screen(img_,region=(X[j]-270,552,620,580))
                
            for i in L:
                F = self.is_sim(mul=True,mul_I=self.fy,N=False,img2=i,thresh=True,T=0)
                if max(F)>0.65:
                    M.append(F.index(max(F)))
            self.move(200,500,100)
            return M
        else:
            self.move(position[0],position[1],0)
            time.sleep(0.2)
            self.get_screen('shoupai\\new.jpg',region=(501,552,880,105))
            self.get_screen('shoupai\\new_big.jpg',region=(position[0]-270,552,620,580))
            
            Ex = self.is_sim(mul=True,mul_I=self.fy,N=False,img2='shoupai\\new.jpg',thresh=True,T=0)
            
           
            j = max(Ex)
            i = Ex.index(max(Ex))
            if j>0.65:
                M.append(i)
        if num==1:
            return i
        self.move(200,500,100)
        return M

    def chaofeng(self):
        '''
        Quick search for irony has been realized
        ------
        modify mul_I
        '''
        mul_I=['in_game\\chaof1.jpg','in_game\\chaof2.jpg',
               'in_game\\chaof3.jpg','in_game\\chaof4.jpg',
               'in_game\\chaof5.jpg']
        region=(471,293,1007,199)
        x0,y0,T = self.get_position(mul=True,mul_I=mul_I,img2='chaofeng.jpg',region=region,s=0.75)

        if x0==100:
            return 950,190
        if T=='left':
            x0 += 50
            y0 += 20
        elif T=='right':
            x0 -= 50
            y0 += 20
        return x0,y0
    
    def init(self):
        '''
        Used to obtain the level interface,And gongji Function to implement speculation
        Position of your entourage.
        At the same time, judge whether it contains lucky coins.
        Then you can get all the hand fees at this time
        '''
        self.move(1700,77,10)
        time.sleep(2.5)
        self.get_screen('in_game\\beijing.jpg',region=D['one'])
        self.get_screen('in_game\\bj_1.jpg',region=D['two'])
        self.get_screen('in_game\\bj_2.jpg',region=D['three'])
        self.get_screen('in_game\\bj_3.jpg',region=D['four'])
        self.get_screen('in_game\\bj_4.jpg',region=D['five'])
        self.get_screen('in_game\\bj_5.jpg',region=D['six'])
        self.get_screen('in_game\\bj_6.jpg',region=D['seven'])
        if self.is_sim(img1='xyb.jpg',region=(982,924,132,137)):
            self.N = 6
        else:
            self.N = 4
            
    def chupai(self,x,position=None,position_=None,D={}):
        '''
        explain : At present, it is in the tentative stage,Special cards will be recorded.
              To achieve this, it should be optimized feiyong function,Realize the recognition of special cards.
              
              -----
              For identification,Due to the special card to be identified, it is small at present,
              Considering the uncertainty of cost,The retrieval method has not been determined yet.
              -----
              
        imagine : 
              position :
                  None : Indicates that the card is played at the enemy hero's place,All attendants\arms\And some spells that can hit the face.
                  'debuff' : Indicates a spell used against an enemy follower.
                  'buff' : Indicates a spell used against your followers.
                  'huixue' : Indicates a blood return spell.
            
              position_ :
                  None : Indicates that no other operation is required after the card is played.
                  'huixue' : Indicates blood return.
                  'buff' : It means to use it for your entourage.
                  'debuff' : Indicates the use of the enemy's entourage.
                  'debuff_2' : Indicates that it is used against enemy heroes.
                  'choice' : Indicates selection.
                  
        ------------------
        parameter x : Playing position x,Is obtained from the correlation function.
        position : Comparison between default value and'huixue'Operation of.
        position_ : The default value has been implemented,'huixue','debuff_2'Operation of.
        

        '''
        position,position_ = None,None
        if len(D)==0:
            position,position_ = None,None
        else:
            for i in D:
                if i=='position':
                    position = D[i]
                if i=='position_':
                    position_ = D[i]
        if position==None:
            self.drag(x, 1000, 963, 190) # play 
            if position_==None:
                time.sleep(0.7)
            elif position_=='huixue':
                time.sleep(0.3)
                self.click(941,829,5)

            elif position_=='debuff_2':
                time.sleep(0.3)
                self.click(963,190,5)

        elif position=='huixue':
            self.drag(x,1000,941,829)
        
        elif position=='buff': #It has not been used for the time being, so it has not been optimized
            self.move(1000,500)
            time.sleep(0.5)
            L = self.gongji()
            if len(L)==0:
                print('Optimization is needed here,Do not use this spell when you have no followers in your field')
        
        time.sleep(0.5)
        
            
        
    def gongji(self):
        '''
        Determine the follower position by determining how many followers are on your field
        '''
        s = 0.7
        if self.is_sim('in_game\\beijing.jpg','beijing.jpg',s,region=D['one']):
            return []
        if self.is_sim('in_game\\bj_1.jpg','bj_1.jpg',s,region=D['two']):
            return [956]    
        if self.is_sim('in_game\\bj_2.jpg','bj_2.jpg',s,region=D['three']):
            return [900,1024]
        if self.is_sim('in_game\\bj_3.jpg','bj_3.jpg',s,region=D['four']):
            return [817,956,1104]
        if self.is_sim('in_game\\bj_4.jpg','bj_4.jpg',s,region=D['five']):
            return [735,900,1024,1170]
        if self.is_sim('in_game\\bj_5.jpg','bj_5.jpg',s,region=D['six']):
            return [680,817,956,1104,1237]
        if self.is_sim('in_game\\bj_6.jpg','bj_6.jpg',s,region=D['seven']):
            return [614,755,900,1024,1170,1306]
        return [539,680,817,956,1104,1237,1388]
    
    def in_game(self,Q):
        def func(fee,mode='low'):
            '''
            Determine which cards in your hand can be played
            -------
            F : Accept hand fee.
            fee : Accept the current available expenses.
            mode : Accept play mode.
            -------
            return None,None,{}
            The first parameter indicates the order of cards to be played in the hand,None Indicates that there is no card available
            The second parameter indicates whether to continue,None Indicates that there is no need to continue,True Indicates that you can continue
            When the third parameter is not empty, it means that it needs to be passed to self.chupai.
            '''
            F_ = self.F[:]
            Err = False
            Err1 = False
            while not Err1:
                if Err:
                    Err1 = True
                po,T,D = None,None,{}
                if len(F_)==0:
                    return None,None,{}
                if len(F_)==1:
                    if F_[0]<=fee:
                        po = 0
                        M = F_[0]
                
                elif F_.count(fee)!=0:
                    po = F_.index(fee)
                    M = fee
    
                elif mode=='low':
                    # From low cost
                    M = self.min_SP(F_,mode,fee=fee)
                    if M<=fee:
                        po = F_.index(M)
                        fee = fee-M
                        F_.remove(M)
                        M2 = self.min_SP(F_,mode,fee=fee)
                        if M2<=fee:
                            T = True
                        
                else:
                    # Start with high fees
                    M = self.min_SP(F_,mode='h',fee=fee)
                    po = F_.index(M)
                    fee = fee-M
                    F_.remove(M)
                    M2 = self.min_SP(F_,mode='low',fee=fee)
                    if M2<=fee:
                        T = True
                if po==None:
                    return None,None,{}
                x = self.X_D[len(self.F)][po]
                N = self.feiyong(num=1,position=(x,1000))
                D = self.teshu()
                if N==M or N==0:
                    return po,T,D
                else:
                    self.F = self.feiyong(num=len(self.F),position=None)
                    F_ = self.F[:]
                    fee = fee+M
                    Err = True
                    continue
            return None,None,{}
                
        def chupai(fee=2):
            '''
            fee : It's actually the number of rounds.
            return : Hand fee.
            ----------
            Enter your own turn to execute,Cost of obtaining novice card,increase to F in
            If the cost is more than 4, start from the high cost,Otherwise, from the low cost
            Update after exit F,And available expenses,
            End when there is no card to play
            '''
            position = self.X_D[len(self.F)+1][-1]
            self.F = self.feiyong(position=(position,1000),M=self.F)
            
            T = True
            t = time.time()
            while T:
                if time.time()-t>25:
                    print('abnormal,Too long playing time')
                    break
                if self.F.count(0)!=0: # Lucky coin mechanism, direct fee hopping
                    if self.F.count(fee+1)!=0:
                        x = self.X_D[len(self.F)][self.F.index(0)]
                        if self.feiyong(num=1,position=(x,1000))==0:
                            self.chupai(x)
                            self.F.remove(0)
                            po,T,D = func(fee+1)
                            x = self.X_D[len(self.F)][po]
                            self.chupai(x,D=D)
                            f = self.F.pop(po)
                            fee = fee+1-f
                        else:
                            self.F = self.feiyong(num=len(self.F),position=None)
                            continue
                if fee>4:
                    mode='h'
                else:
                    mode='low'
                po,T,D = func(fee,mode)
                if po==None:
                    break
                x = self.X_D[len(self.F)][po]
                try:
                    self.chupai(x,D=D)
                except IndexError:
                    print(po,x,self.F,'error')
                    raise IndexError
                f = self.F.pop(po)
                fee = fee-f
                if len(self.F)==0:
                    self.F = self.feiyong(G_F='need',position=[889])
                
        def jieshu(g):
            '''
            g : Rounds .
            F : Hand fee.
            End this round,
            Return to the hand fee after finishing the first round.
            
            The opponent spends more than 3 hours in one round*60 Then exit directly
            --------
            return : Opponent time,Hand fee
            '''
            
            pos1 = {1:(1468,154),2:(489,130),3:(472,883)} # Mouse position of opponent's turn
            
            pyautogui.click(1561,496)
            x,y = pos1[randint(1, 3)] 
            self.move(x, y, t=50)
            time.sleep(0.3)
            t0 = time.time()
            time.sleep(2)
                   
            while self.is_sim('in_game\\duishou.jpg',K1=False)>=0.55:
                if int(time.time()-t0)>3*60:
                    self.tuichu()
                continue
        
        def gongji():
            '''
            No more random attacks,But attack from both ends to the middle
            '''
            y1 = 590
            L = self.gongji()
            L1 = L[:]
            t0 = time.time()
            j = 0
            while len(L)>0:
                if time.time()-t0>20: # Introduce timing mechanism
                    break
                x0,y0 = self.chaofeng()
                x = L[0] if j%2==0 else L[-1]
                L.remove(x)
                self.drag(x, y1, x0, y0)
                time.sleep(0.8)
                self.click(1750,300,50,'right') # Right click to cancel the selection to avoid an error that makes other followers unable to select other followers
                time.sleep(1) 
                j += 1
                if len(L1)!=len(self.gongji()):
                    L = self.gongji()
                    L1 = L[:]
                
        def mine(g):
            '''
            Own round,
            After execution, the turn ends,No longer introduce the delay mechanism

            '''
            mul_I = ['in_game\\jieshu.jpg',
                     'in_game\\jieshu_2.jpg']
            L = self.is_sim(mul=True,mul_I=mul_I,img2='huihe.jpg')
            while max(L)<0.7:
                time.sleep(0.5)
                L = self.is_sim(mul=True,mul_I=mul_I,img2='huihe.jpg')
           
            self.move(1780,200)

        
            if g==1:
                self.init()
                time.sleep(0.5)
                self.F = self.feiyong(G_F='need',num=self.N)
                return None
             
            time.sleep(0.5)
            chupai(g)
            time.sleep(1.5)
            self.jineng('shushi')  # The skill release is changed to after playing cards and before the follower attacks
            time.sleep(1)
            gongji()
            # return self.F
        
        time.sleep(4)
        g = 1
        self.F = [] # The start is set to null, which is actually obtained by jieshu at the end of the second round
        
        while True:
            A = Q.get()
            A['num_h'] = g
            Q.put(A)
            mine(g)#
            time.sleep(0.5)
            jieshu(g)#
            g = g+1
            
 

       
def Break(q=Queue(),q1=Queue(),q2=Queue()):
    '''
        Pass information according to the return value:
        return 'error' ,Indicates that the game window was not found,At this time, only reconnection operation should be carried out,
        The remaining relevant processes should be completed;
        return 'in' , It means that we enter the beginning at this time,At this time, the process"over"Start the game;
        return 'start' , It means that the game begins at this time,Progress"start".
    '''
    if not q.empty():
        return 'error'
    if q2.empty():
        return None
    order = q2.get()
    q2.put(order)
    if q1.empty() is False:
        return True
    elif order=='start':
        return 'start'
    elif order=='in':
        return 'in'

def process_job(name,q=Queue(),q1=Queue(),q2=Queue(),Q=Queue(),List=[]):
    '''
    according to name Decision process,Implement multi process
    ----------
    name : At present'over' : Used to determine the end of a game,Proceed to the next round;
                'start' : To start the game,Match up,And run the script in the game;
                'error' : Monitor window handle,If the window handle is not found, the operation related to running the game will be carried out
                .
    q : Queue(), When the window handle is not found,Not empty,And running Break return'error'Time,Re empty.
    q1 : Queue(), If it is not empty, it indicates the end of a game,Termination due to'over'.
    q2 : Queue(), The information received is'in','start'.First cause over input'in'after,'start'accept'in'And enter'start'
        'in'Indicates that it has entered the customs,'main'Indicates that you can enter the off at the beginning,'finishi'Completed level.
    List : It is used to store the parameters required by each related process.
    '''
    
    while True:
        if name=='over': #End the game
            if Break(q,q1,q2)=='error':
                break
            if time.time()-List[0]>=20*60: # Exit directly after 20 minutes and end the process
                print('The time of the bureau is more than 20 minutes',f'present time {time.localtime()[3:5]}')
                # demo.renshu()
                demo.tuichu()
                break
            L = demo.is_sim(img2='jieshu.jpg',mul=True,mul_I=['shengli.jpg','shibai.jpg','in_game\\djjx.jpg'])
            if max(L)>0.6:
                q1.put(None)
                T = ''
                for i in time.localtime()[1:5]:
                    T = T+str(i)+'|'
                T = T[:-1]
                if L[0]>0.6:
                    end = 'victory'
                elif L[1]>0.6:
                    end = 'fail'
                else:
                    end = 'unknown'
                if Q.empty() is True:
                    break
                A = Q.get()
                try:
                    num_h = A['num_h']
                    game_time = int(time.time()-A['start_T'])
                    game_time = f'{game_time//60}m {game_time%60}s'
                except KeyError:
                    print(A,'error')
                    break
                except Exception as E:
                    print(E,'error')
                    break
                
                if A['num_h']>3:
                    demo.write(game_endtime=T,end=end,num_h=num_h,game_time=game_time)
                break
            
            if demo.is_sim('start.jpg'):
                if Break(q,q1,q2)!='in':
                    
                    q2.put('start')
                    time.sleep(3)

        elif name=='start': # Start the game
            # This process ends once and at the end of the game
            if Break(q,q1,q2)=='start':
                q2.get()
                q2.put('in')
                x,y = demo.get_position('start.jpg')
                demo.click(x,y)
                time.sleep(1)
                while demo.is_sim('start.jpg'):
                    x,y = demo.get_position('start.jpg')
                    demo.click(x,y)
                    time.sleep(1)
                    
                T = '' # T = 'maoxian' means fighting with the hotel owner for optimization
                
                if T=='maoxian': # Hotel optimization
                    x = 1400
                    y = choice([116,199,256,319,396,442,517,576,650,718])
                    demo.click(x,y,0)
                    x,y = demo.get_position('start.jpg')
                    demo.click(x,y)
                    time.sleep(1)
                    while demo.is_sim('start.jpg'):
                        x,y = demo.get_position('start.jpg')
                        demo.click(x,y)
                        time.sleep(1)
                t = time.time()
                
                
                
            if Break(q,q1,q2)=='in':
                while not demo.is_sim(img1='in_game\\qr.jpg',s=0.5):
                    if time.time()-t>5*60:
                        demo.tuichu()
                        break
                x,y = demo.get_position(img1='in_game\\qr.jpg',s=0.5)
                
                
                if  time.localtime()[2]<=20 and List[0]%2==0 and T!='maoxian' : # No automatic surrender after the 20th of each month
                    demo.renshu()
                    break
                
                time.sleep(1)
                t = 3.5
                if not demo.is_sim(img1='fee\\fee_2.jpg',region=(452,328,1026,95)):
                    X = [600,900,1300]
                    y0 = 538
                    for x0 in X:
                        demo.click(x0,y0)
                    t = 7.5
                demo.click(x,y)
                time.sleep(t)
                demo.start(Q)
    
        elif name=='error': # abnormal    
            demo.Chonglian(q)
            demo.yichang()
            if not q.empty(): # Reset q after reconnection
                q.get()
   
        else:
            print('The is not set',name,'function')


classname = "UnityWndClass"
wdname = "Hearth Stone"
demo = GameAssist(classname,wdname)


'''
Hunter 1000 wins completed,
Now it's a warlock : No special card,Do not use skills
'''
if __name__=='__main__' and 1==1:
    J = 0
    while True:
        q,q1,q2,Q = Queue(),Queue(),Queue(),Queue()
        
        # pro process, always in progress, find the game window in real time
        pro = Process(target=process_job,args=('error',q,q1,q2,Q))
        pro.daemon = True
        pro.start()
        
        while True: #The loop cannot exit, otherwise the pro process will be increased
            while not q.empty():
                time.sleep(5)
                continue
            t0 = time.time()
            pr1 = Process(target=process_job,args=('over',q,q1,q2,Q,[t0],))
            pr1.start()
            
            pr2 = Process(target=process_job,args=('start',q,q1,q2,Q,[J]))
            J = J+1
            pr2.start()
            
            while win32gui.FindWindow(classname,wdname):
                if not pr1.is_alive():
                    if not pr2.is_alive():
                        pr2.terminate()
                    if not q1.empty():
                        t = time.time()
                        time.sleep(1)
                        while not demo.is_sim(img1='start.jpg'):
                            demo.click(700,900,50)
                            time.sleep(1.5)
                            if time.time()-t>3*60:
                                demo.tuichu()
                                time.sleep(1)
                                break
                    break
            if pr1.is_alive():
                pr1.terminate()
            if pr2.is_alive():
                pr2.terminate()
            time.sleep(5)
            
            
            q1,q2,Q = Queue(),Queue(),Queue()

Final description

Please communicate with me when you have certain programming ability. Don't ask me for relevant template images
Please do not reprint!! Do not use for commercial purposes!!
It is only used to popularize programming
If you have any questions, please contact us privately (Q537406976, please note programming inquiry)
Some related template pictures may be infringed, please keep it private, and if there are exceptions in the operation, please leave it in the comments or keep it private

Topics: Python Anaconda multiple processes