请不要转载,如有疑问请私,如侵权,请私可删.
联系方式 Q 537406976
目录
需要的库
random库 是实现引入随机位置.
os库 实现关闭炉石传说进程.
multiprocessing库 实现多进程.
cv2库 实现图片模板匹配.
pyautogui 实现模拟鼠标键盘.
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
截图与查找
为实现相关功能,需实现截屏与图像查找.
截屏
get_screen(img2,region) img2为截屏后保存的地方,region为截屏区域.
# 类 Screen 属性
def get_screen(self,img2='scree.jpg',region=(0,0,1920,1080)):
'''
截屏
----------
img2 : 图片存储路径. 默认'scree.jpg'.
region : (x1,y1,width,hight) 截取矩形左顶点(x1,y1) 与矩形width,hight
'''
if region=='default':
region = (0,0,1920,1080)
elif len(region)<4:
raise ValueError('region值错误 截取矩形左顶点(x1,y1) 与矩形width,hight')
pyautogui.screenshot(img2,region=region)
图像匹配
模板指在图片中要查找的图像.
is_sim :
K1 不为True时,表示输出匹配值.
N 不为True时,表示不截屏,这时需要img2参数表示图片.
T 表示利用cv2.imread()读取时灰度读取或彩色读取.
thresh 不为None,表示二值化匹配,经过数千次试验后,仅用于识别手牌费用.
mul 匹配多模板,比如用于识别费用,在不使用机器学习的情况下,必须匹配多模板,然后返回匹配值大的,同时该最大值还是要大于一定值
mul_I 即为多个模板.
# 类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):
'''
判断img2里是否有img1
进行多图像匹配时,需要参数mul,img2,mul_I,region
----------
img1 : The default is ''.
img2 : 默认为'img1_1',格式与img1相同.
s : 匹配最小值. The default is 0.75.
K1 : 用于匹配值较小时使用,仅在测试时设置为False.
N : 不截屏,而是使用已有图像,默认截屏.
region : 传给get_screen.
mul : 截图,匹配多图像,返回每个对象匹配值;
若thresh不为None,则先二值化再匹配.
mul_I : 图像中含有对象,list.
T : 0表示灰度读取,-1表示彩色读取.传给cv2.imread().
thresh : 不为None,则表示二值化后匹配,目前用于识别费用.不为None则T应该为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
获取模板的位置
用于实现之后的点击.
一般使用只需img1参数.这时会在没有识别该模板时,等待直到识别到该模板.
M 比如由于识别嘲讽时不一定场上有嘲讽,返回100,100.
# 类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):
'''
多图像查找位置时,不支持等待.
----------
img1 : 路径名,需要在屏幕上查找的图片
img2 : 截图时临时存放位置. 默认为'img1_1',格式与img1相同.
s : 不大于1的正数,传递给is_sim函数.
M : BOOL, optional
为False时,未查找到返回(100,100)点. The default is True.
K1 : False 取消阈值 默认True.
N : False表示不截图,直接进行图像对比, 默认True.
region : 传给get_screen.
T : 0表示灰度读取,-1表示彩色读取.传给cv2.imread().
thresh : 不为None,则表示二值化后匹配,目前用于识别费用.
mul : 截图,匹配多图像,返回每个对象匹配值.
mul_I : 图像中含有对象,list.
Returns
-------
(X,Y) 图像中心点坐标
'''
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' # 左上角
if a==M[-1] or a==M[-2]:
T = 'right' # 右上角
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' # 左上角
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)
实现鼠标与键盘模拟
优化pyautogui内的功能,同时出牌时是drag,一个位置托到另一个位置.
# 类Screen
def click(self,x,y,t=20,button='primary'):
'''
点击给定点附近,所有非强制要求点应使用该方法
-------
x,y : 位置(x,y)
t : 默认20
button : 默认'primary',可设为'left'/'right'/'middle'
'''
pyautogui.click(x+randint(-t, t),y+randint(-t,t),button=button)
def move(self,x,y,t=20):
'''
移到给定点附近
'''
pyautogui.moveTo(x+randint(-t, t),y+randint(-t, t))
def drag(self,x0,y0,x1,y1,t=5):
'''
从当前位置(x0,y0)拖拽到指定位置(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前面并没有空格
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)}
重连
由于潜在的代码异常可能使得游戏以及掉线,这时应先关闭游戏再重连.假设执行该函数chl时,游戏进程已结束.
先回到桌面 : 按’windows’键加’d’
再识别暴雪战网的位置.默认点击后会自动登录账号.
再识别进入游戏的位置,这时注意请确保进入后的是炉石窗口
之后再识别炉石传说的对战模式.
Sc = Screen()
# 函数
def chl():
time.sleep(10) # 处理其它进程
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'): # 避免重连,实际用处几乎不大
return 'chl'
x,y = Sc.get_position('duizhan.jpg')
pyautogui.doubleClick(x,y)
time.sleep(3)
从这之后是另一个类GameAssist
它继承上一个类Screen.
初始化
X_D是相应手牌数各手牌对应位置.
fy是模板费用列表
T是特殊牌(及不是直接托到敌方英雄处就能释放的牌,以及使用后需点击其它位置(比如有发现一张牌等)建议不要携带特殊牌.
class GameAssist(Screen):
def __init__(self,classname,wdname):
"""初始化"""
super().__init__()
# 获取窗口句柄
self.hwnd = win32gui.FindWindow(classname,wdname)
if not self.hwnd:
self.Chonglian()
#窗口显示最前化
self.SetAsForegroundWindow()
# 使得分辨率为1920x1080
self.get_screen()
# 以下为一些参数信息,之后优化
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 = {} # 特殊牌
重连机制
通过查找窗口句柄来确定是否需要重连
# 重连机制,通过查找窗口句柄确认
# 类GameAssist
def Chonglian(self,q=Queue()):
'''
重连机制,找不到窗口则回到桌面,打开应用
-------
q, 如果未找到窗口,不为空
'''
self.hwnd = win32gui.FindWindow(classname,wdname)
if not self.hwnd:
q.put('error')
A = chl() # 重连
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()
窗口置前
# 类GameAssist
def SetAsForegroundWindow(self):
win32gui.SetForegroundWindow(self.hwnd)
表示已进入对局
def start(self,Q):
'''
Q : 用来将数据传出到其它进程
'''
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):
'''
使用cmd命令结束进程
'''
os.system("taskkill /f /t /im Hearthstone.exe")
技能
def jineng(self,mode=None):
'''
使用技能,每回合必进行一次,避免浪费费用.
mode : None,仅仅的点击技能即可.此类适用于(猎人,战士,萨满,圣骑士)
'fashi' : 法师技能.对敌方英雄使用.
'mushi' : 牧师技能,对自己英雄使用.
'shushi' : 术士技能,永不使用.
'other' : 潜行者,德鲁伊,恶魔猎手技能,使用无嘲讽则攻击敌方英雄
'''
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 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 {}
认输
主动的认输更能使得在一定时间内达到更多的胜利场数.
# 认输
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):
'''
返回手牌最小费用
----------
SP : 手牌费用.
mode : 默认'low'表示从低费开始
若为高费,需要fee参数,用来返回不大于fee
的最大费用
返回
-------
去除0费后最小费用
'''
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):
'''
查找手牌费用,起始时根据num查找所有手牌费用,之后
指定位置,查找获得手牌费用.
实现为仅在第一回合获得手牌费用,这得于对出牌函数优化引入重读取手牌费用步骤
实现对新牌费用查找
获得牌费用,考虑到有时费用仅靠灰度识别误差较大,更改为二值化后再识别.
------
num : 手牌数,由幸运币判断起始手牌数self.N
之后根据出牌与获得牌更新,为1表示查找某处费用.
position : 默认不需要,指定表示仅查找该处牌费用.
G_F : 不为None,表示更新手牌费用列表.
------
返回 手牌从左到右的费用
'''
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): # 该部分是之后用于确定优先出牌使用
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
敌方嘲讽随从
mul_I 中为嘲讽随从模板,几乎满足要求.
def chaofeng(self):
'''
已实现对嘲讽的快速查找
------
修改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):
'''
用来获得关卡界面,与gongji函数一起实现推测
己方随从位置.
同时判断是否含有幸运币.
进而获取此时所有手牌费用
'''
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={}):
'''
说明 : 目前属于设想阶段,将记录特殊的牌.
为实现此应优化feiyong函数,实现对特殊牌的识别.
-----
对于识别,由于要识别的特殊牌目前来说终究是较小的,
考虑到费用的不确定性,目前暂未确定检索方式.
-----
设想 :
position :
None : 表示出牌位置为敌方英雄处,所有随从\武器\以及一些可以打脸的法术.
'debuff' : 表示对敌方随从使用的法术.
'buff' : 表示对己方随从使用的法术.
'huixue' : 表示回血法术.
position_ :
None : 表示该牌打出后无需其它操作.
'huixue' : 表示回血.
'buff' : 表示对己方随从使用.
'debuff' : 表示对敌方随从使用.
'debuff_2' : 表示对敌方英雄使用.
'choice' : 表示选择.
------------------
参数x : 出牌位置x,是由相关函数获得.
position : 已实现对默认值与'huixue'的操作.
position_ : 已实现对默认值,'huixue','debuff_2'的操作.
'''
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) # 出牌
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': #暂时没用到,故还未优化
self.move(1000,500)
time.sleep(0.5)
L = self.gongji()
if len(L)==0:
print('这里需要实现优化,在自己场上没有随从时不使用该法术')
time.sleep(0.5)
攻击
返回己方随从位置列表
def gongji(self):
'''
通过确定己方场上有几个随从来判断随从位置
'''
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]
执行回合
炉石传说进入游戏不过是你一回合我一回合.
in_game()表示回合开始执行
判断出什么牌
优先出恰好的费用,及四费出一张四费卡,无若大于4费从高费出起,否则从低费出起.
def func(fee,mode='low'):
'''
判断手牌中哪些牌可以出
-------
fee : 接受目前可出费用.
mode : 接受出牌模式.
-------
return None,None,{}
第一个参数表示要出牌在手牌中的排序,None表示无牌可出
第二个参数表示是否可继续,None表示无需继续,True表示可继续
第三个参数不为空时表示需传给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':
# 从低费出起
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:
# 从高费开始
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 : 实际上是回合数.
返回 : 手牌费用.
----------
进入自己回合执行,获得新手牌费用,增加到F中
如果费用大于4就从高费出起,否则从低费出起
出完后更新F,与可使用费用,
当无牌可出时结束
'''
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('异常,执行出牌时间过长')
break
if self.F.count(0)!=0: # 幸运币机制,直接跳费使用
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,'错误')
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 : 回合数.
F : 手牌费用.
结束本回合,
对方一个回合用时大于3*60 则直接退出
--------
返回 : 对手用时,手牌费用
'''
pos1 = {1:(1468,154),2:(489,130),3:(472,883)} # 对手回合鼠标位置
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():
'''
不再随机攻击,而是自两端向中间攻击
'''
y1 = 590
L = self.gongji()
L1 = L[:]
t0 = time.time()
j = 0
while len(L)>0:
if time.time()-t0>20: # 引入计时机制
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') # 右击取消选定,避免一个错误使得其它随从无法选定其它随从
time.sleep(1)
j += 1
if len(L1)!=len(self.gongji()):
L = self.gongji()
L1 = L[:]
己方回合
先出牌,在释放技能,在攻击,最后结束.
不引入烧绳机制.
def mine(g):
'''
自己回合,
执行完便回合结束,不再引入拖时间机制
'''
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') # 技能释放改为出完牌后,随从攻击前
time.sleep(1)
gongji()
整个in_game(代码)
def in_game(self,Q):
def func(fee,mode='low'):
'''
判断手牌中哪些牌可以出
-------
F : 接受手牌费用.
fee : 接受目前可出费用.
mode : 接受出牌模式.
-------
return None,None,{}
第一个参数表示要出牌在手牌中的排序,None表示无牌可出
第二个参数表示是否可继续,None表示无需继续,True表示可继续
第三个参数不为空时表示需传给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':
# 从低费出起
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:
# 从高费开始
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 : 手牌费用.
fee : 实际上是回合数.
返回 : 手牌费用.
----------
进入自己回合执行,获得新手牌费用,增加到F中
如果费用大于4就从高费出起,否则从低费出起
出完后更新F,与可使用费用,
当无牌可出时结束
'''
# 暂时不引入使用幸运币机制
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('异常,执行出牌时间过长')
break
if self.F.count(0)!=0: # 幸运币机制,直接跳费使用
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,'错误')
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 : 回合数.
F : 手牌费用.
结束本回合,
再自己结束第一个回合后开始返回手牌费用.
对方一个回合用时大于3*60 则直接退出
--------
返回 : 对手用时,手牌费用
'''
pos1 = {1:(1468,154),2:(489,130),3:(472,883)} # 对手回合鼠标位置
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():
'''
不再随机攻击,而是自两端向中间攻击
'''
y1 = 590
L = self.gongji()
L1 = L[:]
t0 = time.time()
j = 0
while len(L)>0:
if time.time()-t0>20: # 引入计时机制
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') # 右击取消选定,避免一个错误使得其它随从无法选定其它随从
time.sleep(1)
j += 1
if len(L1)!=len(self.gongji()):
L = self.gongji()
L1 = L[:]
def mine(g):
'''
自己回合,
执行完便回合结束,不再引入拖时间机制
'''
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') # 技能释放改为出完牌后,随从攻击前
time.sleep(1)
gongji()
# return self.F
time.sleep(4)
g = 1
self.F = [] # 起始置空,实际上在第二回合结束由jieshu获取
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()):
'''
根据返回值传递信息:
返回 'error' ,表示未找到游戏窗口,这时应只有重连操作进行,
其余相关进程应结束;
返回 'in' , 表示这时进入开局,这时由进程"over"开始对局;
返回 'start' , 表示这时开始对局,进行进程"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=[]):
'''
根据name决定进程,实现多进程
----------
name : 目前有'over' : 用来确定一局结束,进行下一局;
'start' : 用来开始对局,进行对局,并运行对局中的脚本;
'error' : 监测窗口句柄,窗口句柄未找到则进行运行游戏相关操作
.
q : Queue(), 当未找到窗口句柄时,不为空,并在运行Break 返回'error'时,重新为空.
q1 : Queue(), 不为空表示一局游戏结束,因终止'over'.
q2 : Queue(), 接受信息有'in','start'.先由over输入'in'后,'start'接受'in'并输入'start'
'in'表示已进入关内,'main'表示开始可进入关,'finishi'已完成关卡.
List : 用来存储各相关进程所需分别参数.
'''
while True:
if name=='over': #一把游戏结束
if Break(q,q1,q2)=='error':
break
if time.time()-List[0]>=20*60: # 20分钟后直接退出并结束该进程
print('该局时间大于20分钟',f'现在时间 {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 = '胜利'
elif L[1]>0.6:
end = '失败'
else:
end = '未知'
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,'错误')
break
except Exception as E:
print(E,'错误')
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': # 开始游戏
# 此进程一次结束,并在游戏结束
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'表示与旅店老板战斗,进行优化
if T=='maoxian': # 旅馆进行优化
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' : # 每月20号之后不再自动投降
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': # 异常
demo.Chonglian(q)
demo.yichang()
if not q.empty(): # 执行完重连后将q复位
q.get()
else:
print('未设置该',name,'函数')
剩下的部分
这是窗口句柄的获取
classname = "UnityWndClass"
wdname = "炉石传说"
demo = GameAssist(classname,wdname)
这是重要的
由于是多进程运行,写入到__main__内是重要的,同时需在Shell中运行.
if __name__=='__main__' and 1==1:
J = 0
while True:
q,q1,q2,Q = Queue(),Queue(),Queue(),Queue()
# pro进程,一直进行,实时查找游戏窗口
pro = Process(target=process_job,args=('error',q,q1,q2,Q))
pro.daemon = True
pro.start()
while True: #该循环不能退出,否则会增加pro进程
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()
整个代码
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 18 18:52:06 2021
@author: 537406976(这时本人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)):
'''
截屏
----------
img2 : 图片存储路径. 默认'scree.jpg'.
region : (x1,y1,width,hight) 截取矩形左顶点(x1,y1) 与矩形width,hight
'''
if region=='default':
region = (0,0,1920,1080)
elif len(region)<4:
raise ValueError('region值错误 截取矩形左顶点(x1,y1) 与矩形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):
'''
判断img2里是否有img1
进行多图像匹配时,需要参数mul,img2,mul_I,region
----------
img1 : The default is ''.
img2 : 默认为'img1_1',格式与img1相同.
s : 匹配最小值. The default is 0.75.
K1 : 用于匹配值较小时使用,仅在测试时设置为False.
N : 不截屏,而是使用已有图像,默认截屏.
region : 传给get_screen.
mul : 截图,匹配多图像,返回每个对象匹配值;
若thresh不为None,则先二值化再匹配.
mul_I : 图像中含有对象,list.
T : 0表示灰度读取,-1表示彩色读取.传给cv2.imread().
thresh : 不为None,则表示二值化后匹配,目前用于识别费用.不为None则T应该为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):
'''
建议设置img2参数,避免多进程抢资源.
多图像查找位置时,不支持等待.
----------
img1 : 路径名,需要在屏幕上查找的图片
DESCRIPTION.
img2 : 截图时临时存放位置. 默认为'img1_1',格式与img1相同.
s : 不大于1的正数,传递给is_sim函数.
M : BOOL, optional
为False时,未查找到返回(100,100)点. The default is True.
K1 : False 取消阈值 默认True.
N : False表示不截图,直接进行图像对比, 默认True.
region : 传给get_screen.
T : 0表示灰度读取,-1表示彩色读取.传给cv2.imread().
thresh : 不为None,则表示二值化后匹配,目前用于识别费用.
mul : 截图,匹配多图像,返回每个对象匹配值.
mul_I : 图像中含有对象,list.
Returns
-------
(X,Y) 图像中心点坐标
'''
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' # 左上角
if a==M[-1] or a==M[-2]:
T = 'right' # 右上角
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' # 左上角
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'):
'''
点击给定点附近,所有非强制要求点应使用该方法
-------
x,y : 位置(x,y)
t : 默认20
button : 默认'primary',可设为'left'/'right'/'middle'
'''
pyautogui.click(x+randint(-t, t),y+randint(-t,t),button=button)
def move(self,x,y,t=20):
'''
移到给定点附近
'''
pyautogui.moveTo(x+randint(-t, t),y+randint(-t, t))
def drag(self,x0,y0,x1,y1,t=5):
'''
从当前位置(x0,y0)拖拽到指定位置(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) # 处理其它进程
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):
"""初始化"""
# 获取窗口句柄
super().__init__()
self.hwnd = win32gui.FindWindow(classname,wdname)
if not self.hwnd:
self.Chonglian()
#窗口显示最前化
self.SetAsForegroundWindow()
# win32gui.SetActiveWindow(self.hwnd)
# 使得分辨率为1920x1080
self.get_screen()
# 以下为一些参数信息,之后优化
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 = {} # 特殊牌
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!='未知':
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=='胜利':
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)
# 重连机制,通过查找窗口句柄确认
def Chonglian(self,q=Queue()):
'''
重连机制,找不到窗口则回到桌面,打开应用
-------
q, 如果未找到窗口,不为空
返回
-------
如果找到窗口就返回'in'
'''
self.hwnd = win32gui.FindWindow(classname,wdname)
if not self.hwnd:
q.put('error')
A = chl() # 重连
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 : 用来将数据传出到其它进程
'''
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):
'''
使用cmd命令结束进程
'''
os.system("taskkill /f /t /im Hearthstone.exe")
def jineng(self,mode=None):
'''
使用技能,每回合必进行一次,避免浪费费用.
mode : None,仅仅的点击技能即可.此类适用于(猎人,战士,萨满,圣骑士)
'fashi' : 法师技能.对敌方英雄使用.
'mushi' : 牧师技能,对自己英雄使用.
'shushi' : 术士技能,永不使用.
'other' : 潜行者,德鲁伊,恶魔猎手技能,使用无嘲讽则攻击敌方英雄
'''
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):
# 已取消该机制
'''
交流
i : 表示发出语言
1 : 感谢 2 : 惊叹
3 : 称赞 4 : 失误
5 : 问候 6 : 威胁
'''
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 {}
# 重连后直接认输
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):
'''
返回手牌最小费用
----------
SP : 手牌费用.
mode : 默认'low'表示从低费开始
若为高费,需要fee参数,用来返回不大于fee
的最大费用
返回
-------
去除0费后最小费用
'''
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):
'''
查找手牌费用,起始时根据num查找所有手牌费用,之后
指定位置,查找获得手牌费用.
实现为仅在第一回合获得手牌费用,这得于对出牌函数优化引入重读取手牌费用步骤
实现对新牌费用查找
获得牌费用,考虑到有时费用仅靠灰度识别误差较大,更改为二值化后再识别.
------
num : 手牌数,由幸运币判断起始手牌数self.N
之后根据出牌与获得牌更新,为1表示查找某处费用.
position : 默认不需要,指定表示仅查找该处牌费用.
G_F : 不为None,表示更新手牌费用列表.
------
返回 手牌从左到右的费用
'''
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): # 该部分是之后用于确定优先出牌使用
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):
'''
已实现对嘲讽的快速查找
------
修改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):
'''
用来获得关卡界面,与gongji函数一起实现推测
己方随从位置.
同时判断是否含有幸运币.
进而获取此时所有手牌费用
'''
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={}):
'''
说明 : 目前属于设想阶段,将记录特殊的牌.
为实现此应优化feiyong函数,实现对特殊牌的识别.
-----
对于识别,由于要识别的特殊牌目前来说终究是较小的,
考虑到费用的不确定性,目前暂未确定检索方式.
-----
设想 :
position :
None : 表示出牌位置为敌方英雄处,所有随从\武器\以及一些可以打脸的法术.
'debuff' : 表示对敌方随从使用的法术.
'buff' : 表示对己方随从使用的法术.
'huixue' : 表示回血法术.
position_ :
None : 表示该牌打出后无需其它操作.
'huixue' : 表示回血.
'buff' : 表示对己方随从使用.
'debuff' : 表示对敌方随从使用.
'debuff_2' : 表示对敌方英雄使用.
'choice' : 表示选择.
------------------
参数x : 出牌位置x,是由相关函数获得.
position : 已实现对默认值与'huixue'的操作.
position_ : 已实现对默认值,'huixue','debuff_2'的操作.
'''
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) # 出牌
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': #暂时没用到,故还未优化
self.move(1000,500)
time.sleep(0.5)
L = self.gongji()
if len(L)==0:
print('这里需要实现优化,在自己场上没有随从时不使用该法术')
time.sleep(0.5)
def gongji(self):
'''
通过确定己方场上有几个随从来判断随从位置
'''
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'):
'''
判断手牌中哪些牌可以出
-------
F : 接受手牌费用.
fee : 接受目前可出费用.
mode : 接受出牌模式.
-------
return None,None,{}
第一个参数表示要出牌在手牌中的排序,None表示无牌可出
第二个参数表示是否可继续,None表示无需继续,True表示可继续
第三个参数不为空时表示需传给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':
# 从低费出起
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:
# 从高费开始
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 : 实际上是回合数.
返回 : 手牌费用.
----------
进入自己回合执行,获得新手牌费用,增加到F中
如果费用大于4就从高费出起,否则从低费出起
出完后更新F,与可使用费用,
当无牌可出时结束
'''
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('异常,执行出牌时间过长')
break
if self.F.count(0)!=0: # 幸运币机制,直接跳费使用
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,'错误')
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 : 回合数.
F : 手牌费用.
结束本回合,
再自己结束第一个回合后开始返回手牌费用.
对方一个回合用时大于3*60 则直接退出
--------
返回 : 对手用时,手牌费用
'''
pos1 = {1:(1468,154),2:(489,130),3:(472,883)} # 对手回合鼠标位置
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():
'''
不再随机攻击,而是自两端向中间攻击
'''
y1 = 590
L = self.gongji()
L1 = L[:]
t0 = time.time()
j = 0
while len(L)>0:
if time.time()-t0>20: # 引入计时机制
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') # 右击取消选定,避免一个错误使得其它随从无法选定其它随从
time.sleep(1)
j += 1
if len(L1)!=len(self.gongji()):
L = self.gongji()
L1 = L[:]
def mine(g):
'''
自己回合,
执行完便回合结束,不再引入拖时间机制
'''
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') # 技能释放改为出完牌后,随从攻击前
time.sleep(1)
gongji()
# return self.F
time.sleep(4)
g = 1
self.F = [] # 起始置空,实际上在第二回合结束由jieshu获取
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()):
'''
根据返回值传递信息:
返回 'error' ,表示未找到游戏窗口,这时应只有重连操作进行,
其余相关进程应结束;
返回 'in' , 表示这时进入开局,这时由进程"over"开始对局;
返回 'start' , 表示这时开始对局,进行进程"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=[]):
'''
根据name决定进程,实现多进程
----------
name : 目前有'over' : 用来确定一局结束,进行下一局;
'start' : 用来开始对局,进行对局,并运行对局中的脚本;
'error' : 监测窗口句柄,窗口句柄未找到则进行运行游戏相关操作
.
q : Queue(), 当未找到窗口句柄时,不为空,并在运行Break 返回'error'时,重新为空.
q1 : Queue(), 不为空表示一局游戏结束,因终止'over'.
q2 : Queue(), 接受信息有'in','start'.先由over输入'in'后,'start'接受'in'并输入'start'
'in'表示已进入关内,'main'表示开始可进入关,'finishi'已完成关卡.
List : 用来存储各相关进程所需分别参数.
'''
while True:
if name=='over': #一把游戏结束
if Break(q,q1,q2)=='error':
break
if time.time()-List[0]>=20*60: # 20分钟后直接退出并结束该进程
print('该局时间大于20分钟',f'现在时间 {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 = '胜利'
elif L[1]>0.6:
end = '失败'
else:
end = '未知'
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,'错误')
break
except Exception as E:
print(E,'错误')
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': # 开始游戏
# 此进程一次结束,并在游戏结束
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'表示与旅店老板战斗,进行优化
if T=='maoxian': # 旅馆进行优化
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' : # 每月20号之后不再自动投降
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': # 异常
demo.Chonglian(q)
demo.yichang()
if not q.empty(): # 执行完重连后将q复位
q.get()
else:
print('未设置该',name,'函数')
classname = "UnityWndClass"
wdname = "炉石传说"
demo = GameAssist(classname,wdname)
'''
猎人1000胜已完成,
现在是术士 : 无特殊牌,不使用技能
'''
if __name__=='__main__' and 1==1:
J = 0
while True:
q,q1,q2,Q = Queue(),Queue(),Queue(),Queue()
# pro进程,一直进行,实时查找游戏窗口
pro = Process(target=process_job,args=('error',q,q1,q2,Q))
pro.daemon = True
pro.start()
while True: #该循环不能退出,否则会增加pro进程
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()
最后的说明
请在有一定的编程能力与我交流,伸手党勿向本人要相关模板图像.
请勿转载!!请勿用于商业用途!!
仅用于将编程推广
如有疑问,请私(Q537406976,请备注 编程询问)
一些相关的模板图片由于可能侵权,请私我,以及运行出现异常请在评论留下或私我