期末的时候看到一篇博客,写的宠物连连看的辅助脚本,感觉很有意思,就自己跟着博客自己实现了一遍,开发过程中遇到了一些问题,也体会到了解决问题的乐趣,遂在此记录一下。
先放一下博客的链接:https://www.cnblogs.com/reader/p/10111777.html
这篇博客给出了完整版的代码,大体上我是根据他的思路实现的,只有部分细节按照我自己的想法做了修改。
具体流程如下:
窗口句柄: 简单理解就是窗口的id,可以根据这个id识别已经打开的窗口
在Windows中,句柄是一个系统内部数据结构的引用。例如当你操作一个窗口,或说是一个Delphi窗体时,系统会给你一个该窗口的句柄,系统会通知你:你正在操作142号窗口,就此你的应用程序就能要求系统对142号窗口进行操作——移动窗口、改变窗口大小、把窗口最小化等等。
–《百度百科》
可以使用winspy,spy++ 等工具获取某个窗口的句柄。
使用360浏览器打开宠物连连看小游戏后,我用winspy获取到的句柄是"宠物连连看经典版2小游戏,在线玩,4399小游戏 - 360安全浏览器 10.0"
获取到窗口的句柄之后,就可以使用python的库操纵窗口了,使用的库是win32gui
# 获取窗口的句柄
self.hwnd = win32gui.FindWindow(0, wdname)
if self.hwnd == 0:
print('no such hwnd')
exit(1)
# 将该窗口显示在最前面
win32gui.SetForegroundWindow(self.hwnd)
执行之后,连连看的窗口就显示到最前面了,下一步就可以截图了
主要使用到的库是PIL的ImageGrab,PIL现在官网上不去,pillow好像和PIL功能是差不多的,文档可以参考pillow的。
这一步的重点是要计算出每个图标的左上角、右下角的坐标,准确将其从截图中分割出来。
'''
获取屏幕截图,并将图标分割
'''
def screen_grab(self):
# 获取整个屏幕截图
image_grab = ImageGrab.grab()
# 获取截图中间所有动物图标的截图
box = (399, 305, 1247, 873)
animals_iamge = image_grab.crop(box)
# 将每个动物图像分割,得到图像矩阵
images_list = []
offset = 71 # 将截图用windows自带的画图打开,就可以查看某个点的位置,
# 计算出每个图标的大小大约是71px,不是特别准确,但是基本可以分割出每个图标了
x0 = 0
y0 = 0
for i in range(8):
images_row = []
for j in range(12):
# 小图标左上角的坐标
x1 = x0 + j*offset
y1 = y0 + i*offset
# 小图标右下角的坐标
x2 = x1 + offset
y2 = y1 + offset
# 5px的偏移是为了去掉小图标周围,只保留中间,这样区分不同的图片更容易
images_row.append(animals_iamge.crop((x1+5, y1+5, x2-5, y2-5)))
images_list.append(images_row)
return images_list
这一步是比较复杂的,主要的目标是将每个图标转换成一个数字,要求相同的图标数字相同。
这里分为两步:
首先将每个图标转成一个灰度图标,然后将这个灰度图标转换成01字符串。
然后比较两个字符串各个位置0、1的区别,记录不同的个数,然后设定一个阈值,不同的个数如果低于这个阈值(即两个图标相差不多),可以认为它们是同一种图标,否者不是。
比较麻烦的是阈值的确定,只能将两个图片的灰度值慢慢比较,找到一个threshold,高于这个threshold能区分为两个不同的图片;低于这threshold保证两个图标相同。
我用到的一个技巧就是,在创建图标矩阵的时候(上一步),每个图标截取的时候往中间多收缩了5px,这样就可以去掉截取的图标周围的一些"杂质",更容易确定阈值。
def get_index(self, str01, threshold, str01_list):
for i in range(len(str01_list)):
diff = sum(map(operator.ne, str01, str01_list[i]))
if diff < threshold:
return i
return -1
'''
将每个图片转换成一个数字,相同的图标数字相同
'''
def image2num(self, animal_images):
# 将每个图标转换成灰度图标,然后再将灰度图标转换成一个01字符串
num_str01_matrix = []
for i in range(8):
num_row = []
for j in range(12):
im = animal_images[i][j]
im_L = im.convert("L")
# im_L.show()
pixels = list(im_L.getdata()) # 每个点的像素值
avg_pixel = sum(pixels) / len(pixels)
str01 = "".join(map(lambda x: "1" if x > avg_pixel else "0", pixels))
num_row.append(str01)
num_str01_matrix.append(num_row)
# 将每个01字符串转换成一个数字
threshold = 800 # 低于这个阈值认为是同一种图片
image_type_list = [] # 所有图标的类型
num_matrix = np.zeros((8,12), dtype=np.uint32) #创建一个全0矩阵
for i in range(8):
for j in range(12):
index = self.get_index(num_str01_matrix[i][j], threshold, image_type_list)
if index < 0:
image_type_list.append(num_str01_matrix[i][j])
num_matrix[i][j] = len(image_type_list)
else:
num_matrix[i][j] = index+1
return num_matrix
得到的一个矩阵实例:
[[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 1 2 2 3 2 4 5 6 7 7 1 8 0]
[ 0 9 4 9 10 8 3 1 1 2 3 11 5 0]
[ 0 6 12 6 3 1 2 9 4 12 8 11 3 0]
[ 0 3 12 10 11 12 3 8 5 9 6 6 10 0]
[ 0 7 4 7 11 4 8 12 5 12 7 5 2 0]
[ 0 10 1 4 2 11 7 6 6 11 5 5 3 0]
[ 0 11 9 5 8 4 2 10 9 10 11 9 9 0]
[ 0 10 12 7 1 12 10 7 6 4 1 8 8 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]
根据游戏规则,对一个点(x1, y1) 得到它可以直接到达的点的集合list1,所谓直接到达,指的是从(x1, y1)出发上下左右连续为0的点;对于(x2, y2)得到list2,然后判断list1中每个点和list2中每个点有没有可以直接到达(即两个点在同一行或同一列,且中间都是0),如果存在这样地点,就说明(x1, y1) 、(x2, y2)可以到达。
def is_row_connected(self, x, y1, y2):
if y1 > y2:
tmp = y1
y1 = y2
y2 = tmp
if y2 - y1 == 1:
return True
for i in range(y1+1, y2):
if self.map_matrix[x][i] != 0:
return False
return True
def is_col_connected(self, x1, x2, y):
if x1 > x2:
tmp = x1
x1 = x2
x2 = tmp
if x2 - x1 == 1:
return True
for i in range(x1+1, x2):
if self.map_matrix[i][y] != 0:
return False
return True
def get_direct_connected(self, x, y):
# 同一行直接相连的点
ans_list = []
row = x - 1
while row >= 0:
if self.map_matrix[row][y] == 0:
ans_list.append([row, y])
else:
break
row = row - 1
row = x + 1
while row < self.map_matrix.shape[0]:
if self.map_matrix[row][y] == 0:
ans_list.append([row, y])
else:
break
row = row + 1
col = y - 1
while col >= 0:
if self.map_matrix[x][col] == 0:
ans_list.append([x, col])
else:
break
col = col - 1
col = y + 1
while col < self.map_matrix.shape[1]:
if self.map_matrix[x][col] == 0:
ans_list.append([x, col])
else:
break
col = col + 1
return ans_list
def is_reachable(self, x1, y1, x2, y2):
# 如果数字不相同,直接返回不可到达
if self.map_matrix[x1][y1] != self.map_matrix[x2][y2]:
return False
list1 = self.get_direct_connected(x1, y1)
list2 = self.get_direct_connected(x2, y2)
for x1,y1 in list1:
for x2,y2 in list2:
if x1 == x2:
if self.is_row_connected(x1, y1, y2):
return True
elif y1 == y2:
if self.is_col_connected(x1, x2, y1):
return True
return False
由于数量比较少,可以直接枚举消除;
pymouse的使用环境主要需要**PyHook(下载对应python版本的)和PyUserInput(pip install PyUserInput即可)**两个,而且需要先下载PyHook再下载PyUserInut。安装博客: https://blog.csdn.net/dianmomanxue/article/details/95044676
PyHook和PyUserInput都下载好了后:
pip install python-xlib(安装pymouse必须要xlib的支持)
pip install pym
'''
依次点击(x1, y1) (x2, y2), 并且将这两个位置的数字变成0
'''
def click_and_set0(self, x1, y1, x2, y2):
# 确定需要点击的坐标的中心位置
c_x1 = int(self.base_x + (y1 - 1)*self.width + self.width/2)
c_y1 = int(self.base_y + (x1 - 1)*self.width + self.width/2)
c_x2 = int(self.base_x + (y2 - 1)*self.width + self.width/2)
c_y2 = int(self.base_y + (x2 - 1)*self.width + self.width/2)
time.sleep(self.click_time)
self.mouse.click(c_x1, c_y1)
time.sleep(self.click_time)
self.mouse.click(c_x2, c_y2)
# 矩阵中设为0
self.map_matrix[x1][y1] = 0
self.map_matrix[x2][y2] = 0
'''
扫描整个矩阵,并点击相消
'''
def scan_game(self):
row_num = self.map_matrix.shape[0]
col_num = self.map_matrix.shape[1]
# self.click_and_set0(1,5,1,7)
print(row_num)
print(col_num)
for i in range(1, row_num):
for j in range(1, col_num):
if self.map_matrix[i][j] == 0:
continue
for l in range(1, row_num):
for k in range(1, col_num):
if i == l and j == k:
continue
if self.map_matrix[l][k] == 0:
continue
if self.is_reachable(i, j, l, k):
self.click_and_set0(i, j, l, k)
# 开始游戏!
def start(self):
# 获取图像矩阵
animal_images = self.screen_grab()
# 获取图标的数字矩阵
self.num_matrix = self.image2num(animal_images)
# 四周添加上0,做成地图矩阵
self.map_matrix = np.zeros((self.num_matrix.shape[0] + 2, self.num_matrix.shape[1] + 2), dtype=np.uint32)
# print(map_matrix.shape)
self.map_matrix[1:9, 1:13] = self.num_matrix
print(self.map_matrix)
self.scan_game()
self.scan_game() # 很不优雅地扫描两遍,不过数据量小,没有关系
https://github.com/ScorpioWZ/4399game