序言
最近女朋友在玩连连看,玩了一个星期了还没通关,真的是菜。
我实在是看不过去了,直接用python写了个脚本代码,一分钟一把游戏。
快是快,就是联网玩容易被骂,嘿嘿~
实现步骤
模块导入
1
2
3
4
5
6
7
8
|
import cv2 import numpy as np import win32api import win32gui import win32con from PIL import ImageGrab import time import random |
窗体标题 用于定位游戏窗体
1
|
WINDOW_TITLE = "连连看" |
时间间隔随机生成 [MIN,MAX]
1
2
|
TIME_INTERVAL_MAX = 0.06 TIME_INTERVAL_MIN = 0.1 |
游戏区域距离顶点的x偏移
1
|
MARGIN_LEFT = 10 |
游戏区域距离顶点的y偏移
1
|
MARGIN_HEIGHT = 180 |
横向的方块数量
1
|
H_NUM = 19 |
纵向的方块数量
1
|
V_NUM = 11 |
方块宽度
1
|
POINT_WIDTH = 31 |
方块高度
1
|
POINT_HEIGHT = 35 |
空图像编号
1
|
EMPTY_ID = 0 |
切片处理时候的左上、右下坐标:
1
2
3
4
|
SUB_LT_X = 8 SUB_LT_Y = 8 SUB_RB_X = 27 SUB_RB_Y = 27 |
游戏的最多消除次数
1
|
MAX_ROUND = 200 |
获取窗体坐标位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def getGameWindow(): # FindWindow(lpClassName=None, lpWindowName=None) 窗口类名 窗口标题名 window = win32gui.FindWindow( None , WINDOW_TITLE) # 没有定位到游戏窗体 while not window: print ( 'Failed to locate the game window , reposition the game window after 10 seconds...' ) time.sleep( 10 ) window = win32gui.FindWindow( None , WINDOW_TITLE) # 定位到游戏窗体 # 置顶游戏窗口 win32gui.SetForegroundWindow(window) pos = win32gui.GetWindowRect(window) print ( "Game windows at " + str (pos)) return (pos[ 0 ], pos[ 1 ]) |
获取屏幕截图
1
2
3
4
5
6
7
8
|
def getScreenImage(): print ( 'Shot screen...' ) # 获取屏幕截图 Image类型对象 scim = ImageGrab.grab() scim.save( 'screen.png' ) # 用opencv读取屏幕截图 # 获取ndarray return cv2.imread( "screen.png" ) |
从截图中分辨图片 处理成地图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
def getAllSquare(screen_image, game_pos): print ( 'Processing pictures...' ) # 通过游戏窗体定位 # 加上偏移量获取游戏区域 game_x = game_pos[ 0 ] + MARGIN_LEFT game_y = game_pos[ 1 ] + MARGIN_HEIGHT # 从游戏区域左上开始 # 把图像按照具体大小切割成相同的小块 # 切割标准是按照小块的横纵坐标 all_square = [] for x in range ( 0 , H_NUM): for y in range ( 0 , V_NUM): # ndarray的切片方法 : [纵坐标起始位置:纵坐标结束为止,横坐标起始位置:横坐标结束位置] square = screen_image[game_y + y * POINT_HEIGHT:game_y + (y + 1 ) * POINT_HEIGHT, game_x + x * POINT_WIDTH:game_x + (x + 1 ) * POINT_WIDTH] all_square.append(square) # 因为有些图片的边缘会造成干扰,所以统一把图片往内缩小一圈 # 对所有的方块进行处理 ,去掉边缘一圈后返回 finalresult = [] for square in all_square: s = square[SUB_LT_Y:SUB_RB_Y, SUB_LT_X:SUB_RB_X] finalresult.append(s) return finalresult |
判断列表中是否存在相同图形
存在返回进行判断图片所在的id
否则返回-1
1
2
3
4
5
6
7
8
9
10
|
def isImageExist(img, img_list): i = 0 for existed_img in img_list: # 两个图片进行比较 返回的是两个图片的标准差 b = np.subtract(existed_img, img) # 若标准差全为0 即两张图片没有区别 if not np. any (b): return i i = i + 1 return - 1 |
获取所有的方块类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
def getAllSquareTypes(all_square): print ( "Init pictures types..." ) types = [] # number列表用来记录每个id的出现次数 number = [] # 当前出现次数最多的方块 # 这里我们默认出现最多的方块应该是空白块 nowid = 0 ; for square in all_square: nid = isImageExist(square, types) # 如果这个图像不存在则插入列表 if nid = = - 1 : types.append(square) number.append( 1 ); else : # 若这个图像存在则给计数器 + 1 number[nid] = number[nid] + 1 if (number[nid] > number[nowid]): nowid = nid # 更新EMPTY_ID # 即判断在当前这张图中的空白块id global EMPTY_ID EMPTY_ID = nowid print ( 'EMPTY_ID = ' + str (EMPTY_ID)) return types |
将二维图片矩阵转换为二维数字矩阵
注意因为在上面对截屏切片时是以列为优先切片的
所以生成的record二维矩阵每行存放的其实是游戏屏幕中每列的编号
换个说法就是record其实是游戏屏幕中心对称后的列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
def getAllSquareRecord(all_square_list, types): print ( "Change map..." ) record = [] line = [] for square in all_square_list: num = 0 for type in types: res = cv2.subtract(square, type ) if not np. any (res): line.append(num) break num + = 1 # 每列的数量为V_NUM # 那么当当前的line列表中存在V_NUM个方块时我们认为本列处理完毕 if len (line) = = V_NUM: print (line); record.append(line) line = [] return record |
判断给出的两个图像能否消除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
def canConnect(x1, y1, x2, y2, r): result = r[:] # 如果两个图像中有一个为0 直接返回False if result[x1][y1] = = EMPTY_ID or result[x2][y2] = = EMPTY_ID: return False if x1 = = x2 and y1 = = y2: return False if result[x1][y1] ! = result[x2][y2]: return False # 判断横向连通 if horizontalCheck(x1, y1, x2, y2, result): return True # 判断纵向连通 if verticalCheck(x1, y1, x2, y2, result): return True # 判断一个拐点可连通 if turnOnceCheck(x1, y1, x2, y2, result): return True # 判断两个拐点可连通 if turnTwiceCheck(x1, y1, x2, y2, result): return True # 不可联通返回False return False |
判断横向联通
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
def horizontalCheck(x1, y1, x2, y2, result): if x1 = = x2 and y1 = = y2: return False if x1 ! = x2: return False startY = min (y1, y2) endY = max (y1, y2) # 判断两个方块是否相邻 if (endY - startY) = = 1 : return True # 判断两个方块通路上是否都是0,有一个不是,就说明不能联通,返回false for i in range (startY + 1 , endY): if result[x1][i] ! = EMPTY_ID: return False return True |
判断纵向联通
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def verticalCheck(x1, y1, x2, y2, result): if x1 = = x2 and y1 = = y2: return False if y1 ! = y2: return False startX = min (x1, x2) endX = max (x1, x2) # 判断两个方块是否相邻 if (endX - startX) = = 1 : return True # 判断两方块儿通路上是否可连。 for i in range (startX + 1 , endX): if result[i][y1] ! = EMPTY_ID: return False return True |
判断一个拐点可联通
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def turnOnceCheck(x1, y1, x2, y2, result): if x1 = = x2 or y1 = = y2: return False cx = x1 cy = y2 dx = x2 dy = y1 # 拐点为空,从第一个点到拐点并且从拐点到第二个点可通,则整条路可通。 if result[cx][cy] = = EMPTY_ID: if horizontalCheck(x1, y1, cx, cy, result) and verticalCheck(cx, cy, x2, y2, result): return True if result[dx][dy] = = EMPTY_ID: if verticalCheck(x1, y1, dx, dy, result) and horizontalCheck(dx, dy, x2, y2, result): return True return False |
判断两个拐点可联通
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
def turnTwiceCheck(x1, y1, x2, y2, result): if x1 = = x2 and y1 = = y2: return False # 遍历整个数组找合适的拐点 for i in range ( 0 , len (result)): for j in range ( 0 , len (result[ 1 ])): # 不为空不能作为拐点 if result[i][j] ! = EMPTY_ID: continue # 不和被选方块在同一行列的不能作为拐点 if i ! = x1 and i ! = x2 and j ! = y1 and j ! = y2: continue # 作为交点的方块不能作为拐点 if (i = = x1 and j = = y2) or (i = = x2 and j = = y1): continue if turnOnceCheck(x1, y1, i, j, result) and ( horizontalCheck(i, j, x2, y2, result) or verticalCheck(i, j, x2, y2, result)): return True if turnOnceCheck(i, j, x2, y2, result) and ( horizontalCheck(x1, y1, i, j, result) or verticalCheck(x1, y1, i, j, result)): return True return False |
自动消除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
def autoRelease(result, game_x, game_y): # 遍历地图 for i in range ( 0 , len (result)): for j in range ( 0 , len (result[ 0 ])): # 当前位置非空 if result[i][j] ! = EMPTY_ID: # 再次遍历地图 寻找另一个满足条件的图片 for m in range ( 0 , len (result)): for n in range ( 0 , len (result[ 0 ])): if result[m][n] ! = EMPTY_ID: # 若可以执行消除 if canConnect(i, j, m, n, result): # 消除的两个位置设置为空 result[i][j] = EMPTY_ID result[m][n] = EMPTY_ID print ( 'Remove :' + str (i + 1 ) + ',' + str (j + 1 ) + ' and ' + str (m + 1 ) + ',' + str ( n + 1 )) # 计算当前两个位置的图片在游戏中应该存在的位置 x1 = game_x + j * POINT_WIDTH y1 = game_y + i * POINT_HEIGHT x2 = game_x + n * POINT_WIDTH y2 = game_y + m * POINT_HEIGHT # 模拟鼠标点击第一个图片所在的位置 win32api.SetCursorPos((x1 + 15 , y1 + 18 )) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x1 + 15 , y1 + 18 , 0 , 0 ) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x1 + 15 , y1 + 18 , 0 , 0 ) # 等待随机时间 ,防止检测 time.sleep(random.uniform(TIME_INTERVAL_MIN, TIME_INTERVAL_MAX)) # 模拟鼠标点击第二个图片所在的位置 win32api.SetCursorPos((x2 + 15 , y2 + 18 )) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x2 + 15 , y2 + 18 , 0 , 0 ) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x2 + 15 , y2 + 18 , 0 , 0 ) time.sleep(random.uniform(TIME_INTERVAL_MIN, TIME_INTERVAL_MAX)) # 执行消除后返回True return True return False |
效果的话得上传视频,截图展现不出来效果,大家可以自行试试。
全部代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
|
# -*- coding:utf-8 -*- import cv2 import numpy as np import win32api import win32gui import win32con from PIL import ImageGrab import time import random # 窗体标题 用于定位游戏窗体 WINDOW_TITLE = "连连看" # 时间间隔随机生成 [MIN,MAX] TIME_INTERVAL_MAX = 0.06 TIME_INTERVAL_MIN = 0.1 # 游戏区域距离顶点的x偏移 MARGIN_LEFT = 10 # 游戏区域距离顶点的y偏移 MARGIN_HEIGHT = 180 # 横向的方块数量 H_NUM = 19 # 纵向的方块数量 V_NUM = 11 # 方块宽度 POINT_WIDTH = 31 # 方块高度 POINT_HEIGHT = 35 # 空图像编号 EMPTY_ID = 0 # 切片处理时候的左上、右下坐标: SUB_LT_X = 8 SUB_LT_Y = 8 SUB_RB_X = 27 SUB_RB_Y = 27 # 游戏的最多消除次数 MAX_ROUND = 200 def getGameWindow(): # FindWindow(lpClassName=None, lpWindowName=None) 窗口类名 窗口标题名 window = win32gui.FindWindow( None , WINDOW_TITLE) # 没有定位到游戏窗体 while not window: print ( 'Failed to locate the game window , reposition the game window after 10 seconds...' ) time.sleep( 10 ) window = win32gui.FindWindow( None , WINDOW_TITLE) # 定位到游戏窗体 # 置顶游戏窗口 win32gui.SetForegroundWindow(window) pos = win32gui.GetWindowRect(window) print ( "Game windows at " + str (pos)) return (pos[ 0 ], pos[ 1 ]) def getScreenImage(): print ( 'Shot screen...' ) # 获取屏幕截图 Image类型对象 scim = ImageGrab.grab() scim.save( 'screen.png' ) # 用opencv读取屏幕截图 # 获取ndarray return cv2.imread( "screen.png" ) def getAllSquare(screen_image, game_pos): print ( 'Processing pictures...' ) # 通过游戏窗体定位 # 加上偏移量获取游戏区域 game_x = game_pos[ 0 ] + MARGIN_LEFT game_y = game_pos[ 1 ] + MARGIN_HEIGHT # 从游戏区域左上开始 # 把图像按照具体大小切割成相同的小块 # 切割标准是按照小块的横纵坐标 all_square = [] for x in range ( 0 , H_NUM): for y in range ( 0 , V_NUM): # ndarray的切片方法 : [纵坐标起始位置:纵坐标结束为止,横坐标起始位置:横坐标结束位置] square = screen_image[game_y + y * POINT_HEIGHT:game_y + (y + 1 ) * POINT_HEIGHT, game_x + x * POINT_WIDTH:game_x + (x + 1 ) * POINT_WIDTH] all_square.append(square) # 因为有些图片的边缘会造成干扰,所以统一把图片往内缩小一圈 # 对所有的方块进行处理 ,去掉边缘一圈后返回 finalresult = [] for square in all_square: s = square[SUB_LT_Y:SUB_RB_Y, SUB_LT_X:SUB_RB_X] finalresult.append(s) return finalresult # 判断列表中是否存在相同图形 # 存在返回进行判断图片所在的id # 否则返回-1 def isImageExist(img, img_list): i = 0 for existed_img in img_list: # 两个图片进行比较 返回的是两个图片的标准差 b = np.subtract(existed_img, img) # 若标准差全为0 即两张图片没有区别 if not np. any (b): return i i = i + 1 return - 1 def getAllSquareTypes(all_square): print ( "Init pictures types..." ) types = [] # number列表用来记录每个id的出现次数 number = [] # 当前出现次数最多的方块 # 这里我们默认出现最多的方块应该是空白块 nowid = 0 ; for square in all_square: nid = isImageExist(square, types) # 如果这个图像不存在则插入列表 if nid = = - 1 : types.append(square) number.append( 1 ); else : # 若这个图像存在则给计数器 + 1 number[nid] = number[nid] + 1 if (number[nid] > number[nowid]): nowid = nid # 更新EMPTY_ID # 即判断在当前这张图中的空白块id global EMPTY_ID EMPTY_ID = nowid print ( 'EMPTY_ID = ' + str (EMPTY_ID)) return types # 将二维图片矩阵转换为二维数字矩阵 # 注意因为在上面对截屏切片时是以列为优先切片的 # 所以生成的record二维矩阵每行存放的其实是游戏屏幕中每列的编号 # 换个说法就是record其实是游戏屏幕中心对称后的列表 def getAllSquareRecord(all_square_list, types): print ( "Change map..." ) record = [] line = [] for square in all_square_list: num = 0 for type in types: res = cv2.subtract(square, type ) if not np. any (res): line.append(num) break num + = 1 # 每列的数量为V_NUM # 那么当当前的line列表中存在V_NUM个方块时我们认为本列处理完毕 if len (line) = = V_NUM: print (line); record.append(line) line = [] return record def canConnect(x1, y1, x2, y2, r): result = r[:] # 如果两个图像中有一个为0 直接返回False if result[x1][y1] = = EMPTY_ID or result[x2][y2] = = EMPTY_ID: return False if x1 = = x2 and y1 = = y2: return False if result[x1][y1] ! = result[x2][y2]: return False # 判断横向连通 if horizontalCheck(x1, y1, x2, y2, result): return True # 判断纵向连通 if verticalCheck(x1, y1, x2, y2, result): return True # 判断一个拐点可连通 if turnOnceCheck(x1, y1, x2, y2, result): return True # 判断两个拐点可连通 if turnTwiceCheck(x1, y1, x2, y2, result): return True # 不可联通返回False return False def horizontalCheck(x1, y1, x2, y2, result): if x1 = = x2 and y1 = = y2: return False if x1 ! = x2: return False startY = min (y1, y2) endY = max (y1, y2) # 判断两个方块是否相邻 if (endY - startY) = = 1 : return True # 判断两个方块通路上是否都是0,有一个不是,就说明不能联通,返回false for i in range (startY + 1 , endY): if result[x1][i] ! = EMPTY_ID: return False return True def verticalCheck(x1, y1, x2, y2, result): if x1 = = x2 and y1 = = y2: return False if y1 ! = y2: return False startX = min (x1, x2) endX = max (x1, x2) # 判断两个方块是否相邻 if (endX - startX) = = 1 : return True # 判断两方块儿通路上是否可连。 for i in range (startX + 1 , endX): if result[i][y1] ! = EMPTY_ID: return False return True def turnOnceCheck(x1, y1, x2, y2, result): if x1 = = x2 or y1 = = y2: return False cx = x1 cy = y2 dx = x2 dy = y1 # 拐点为空,从第一个点到拐点并且从拐点到第二个点可通,则整条路可通。 if result[cx][cy] = = EMPTY_ID: if horizontalCheck(x1, y1, cx, cy, result) and verticalCheck(cx, cy, x2, y2, result): return True if result[dx][dy] = = EMPTY_ID: if verticalCheck(x1, y1, dx, dy, result) and horizontalCheck(dx, dy, x2, y2, result): return True return False def turnTwiceCheck(x1, y1, x2, y2, result): if x1 = = x2 and y1 = = y2: return False # 遍历整个数组找合适的拐点 for i in range ( 0 , len (result)): for j in range ( 0 , len (result[ 1 ])): # 不为空不能作为拐点 if result[i][j] ! = EMPTY_ID: continue # 不和被选方块在同一行列的不能作为拐点 if i ! = x1 and i ! = x2 and j ! = y1 and j ! = y2: continue # 作为交点的方块不能作为拐点 if (i = = x1 and j = = y2) or (i = = x2 and j = = y1): continue if turnOnceCheck(x1, y1, i, j, result) and ( horizontalCheck(i, j, x2, y2, result) or verticalCheck(i, j, x2, y2, result)): return True if turnOnceCheck(i, j, x2, y2, result) and ( horizontalCheck(x1, y1, i, j, result) or verticalCheck(x1, y1, i, j, result)): return True return False def autoRelease(result, game_x, game_y): # 遍历地图 for i in range ( 0 , len (result)): for j in range ( 0 , len (result[ 0 ])): # 当前位置非空 if result[i][j] ! = EMPTY_ID: # 再次遍历地图 寻找另一个满足条件的图片 for m in range ( 0 , len (result)): for n in range ( 0 , len (result[ 0 ])): if result[m][n] ! = EMPTY_ID: # 若可以执行消除 if canConnect(i, j, m, n, result): # 消除的两个位置设置为空 result[i][j] = EMPTY_ID result[m][n] = EMPTY_ID print ( 'Remove :' + str (i + 1 ) + ',' + str (j + 1 ) + ' and ' + str (m + 1 ) + ',' + str ( n + 1 )) # 计算当前两个位置的图片在游戏中应该存在的位置 x1 = game_x + j * POINT_WIDTH y1 = game_y + i * POINT_HEIGHT x2 = game_x + n * POINT_WIDTH y2 = game_y + m * POINT_HEIGHT # 模拟鼠标点击第一个图片所在的位置 win32api.SetCursorPos((x1 + 15 , y1 + 18 )) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x1 + 15 , y1 + 18 , 0 , 0 ) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x1 + 15 , y1 + 18 , 0 , 0 ) # 等待随机时间 ,防止检测 time.sleep(random.uniform(TIME_INTERVAL_MIN, TIME_INTERVAL_MAX)) # 模拟鼠标点击第二个图片所在的位置 win32api.SetCursorPos((x2 + 15 , y2 + 18 )) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x2 + 15 , y2 + 18 , 0 , 0 ) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x2 + 15 , y2 + 18 , 0 , 0 ) time.sleep(random.uniform(TIME_INTERVAL_MIN, TIME_INTERVAL_MAX)) # 执行消除后返回True return True return False def autoRemove(squares, game_pos): game_x = game_pos[ 0 ] + MARGIN_LEFT game_y = game_pos[ 1 ] + MARGIN_HEIGHT # 重复一次消除直到到达最多消除次数 while True : if not autoRelease(squares, game_x, game_y): # 当不再有可消除的方块时结束 , 返回消除数量 return if __name__ = = '__main__' : random.seed() # i. 定位游戏窗体 game_pos = getGameWindow() time.sleep( 1 ) # ii. 获取屏幕截图 screen_image = getScreenImage() # iii. 对截图切片,形成一张二维地图 all_square_list = getAllSquare(screen_image, game_pos) # iv. 获取所有类型的图形,并编号 types = getAllSquareTypes(all_square_list) # v. 讲获取的图片地图转换成数字矩阵 result = np.transpose(getAllSquareRecord(all_square_list, types)) # vi. 执行消除 , 并输出消除数量 print ( 'The total elimination amount is ' + str (autoRemove(result, game_pos))) |
兄弟们快去试试吧
以上就是Python实现自动玩连连看的脚本分享的详细内容,更多关于Python连连看的资料请关注服务器之家其它相关文章!
原文链接:https://blog.csdn.net/fei347795790/article/details/123927228