preamble
Recently my girlfriend is playing connect the dots and hasn't cleared it after a week of playing, it's really a dish.
I just couldn't look at it anymore and just wrote a script code in python for a handful of games a minute.
It's fast, but it's easy to get yelled at for playing online, heh heh heh.
Implementation steps
Module Import
import cv2 import numpy as np import win32api import win32gui import win32con from PIL import ImageGrab import time import random
Form Title Used to position the game form
WINDOW_TITLE = "Back-to-back."
Time interval randomization [MIN,MAX]
TIME_INTERVAL_MAX = 0.06 TIME_INTERVAL_MIN = 0.1
The x offset of the game area from the vertices
MARGIN_LEFT = 10
The y offset of the game area from the vertices
MARGIN_HEIGHT = 180
Number of horizontal squares
H_NUM = 19
Number of vertical squares
V_NUM = 11
Square width
POINT_WIDTH = 31
Square Height
POINT_HEIGHT = 35
Empty image number
EMPTY_ID = 0
The upper left and lower right coordinates when slicing and processing:
SUB_LT_X = 8 SUB_LT_Y = 8 SUB_RB_X = 27 SUB_RB_Y = 27
Maximum number of eliminations in a game
MAX_ROUND = 200
Get the position of the form coordinates
def getGameWindow(): # FindWindow(lpClassName=None, lpWindowName=None) Window class name Window title name window = (None, WINDOW_TITLE) # No game form is positioned while not window: print('Failed to locate the game window , reposition the game window after 10 seconds...') (10) window = (None, WINDOW_TITLE) # Locate the game form # Topping the game window (window) pos = (window) print("Game windows at " + str(pos)) return (pos[0], pos[1])
Get Screenshot
def getScreenImage(): print('Shot screen...') # Get Screenshot Image type object scim = () ('') # Read screenshots with opencv # Get ndarray return ("")
Distinguish images from screenshots and process them into maps.
def getAllSquare(screen_image, game_pos): print('Processing pictures...') # Positioning through the game form # Add an offset to get the play area game_x = game_pos[0] + MARGIN_LEFT game_y = game_pos[1] + MARGIN_HEIGHT # From the top left of the playing area # Cut the image into identical chunks of a specific size # Cutting standards are based on the horizontal and vertical coordinates of the parcel all_square = [] for x in range(0, H_NUM): for y in range(0, V_NUM): # Slicing methods for ndarray: [vertical start position: vertical end position, horizontal start position: horizontal end position] 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) # Because the edges of some images can be intrusive, uniformly shrink the image inward by one turn # Process all the squares, remove a circle from the edge and return # finalresult = [] for square in all_square: s = square[SUB_LT_Y:SUB_RB_Y, SUB_LT_X:SUB_RB_X] (s) return finalresult
Determine whether the same figure exists in the list
Presence return to determine the id of the image
Otherwise return -1
def isImageExist(img, img_list): i = 0 for existed_img in img_list: # Two images are compared The standard deviation of the two images is returned b = (existed_img, img) # If the standard deviation is zero, then there is no difference between the two images. if not (b): return i i = i + 1 return -1
Get all cube types
def getAllSquareTypes(all_square): print("Init pictures types...") types = [] The # number list is used to record the number of occurrences of each id. number = [] # Currently the most frequently occurring square # Here we're defaulting to the most frequent square being the blank square # nowid = 0; for square in all_square: nid = isImageExist(square, types) # Insert list if this image does not exist if nid == -1: (square) (1); else: # If this image exists, give the counter + 1 number[nid] = number[nid] + 1 if (number[nid] > number[nowid]): nowid = nid # Update EMPTY_ID # i.e. determine the id of the blank block in the current diagram global EMPTY_ID EMPTY_ID = nowid print('EMPTY_ID = ' + str(EMPTY_ID)) return types
Converting a 2D picture matrix to a 2D numeric matrix
Note that because the screenshot is sliced above with the columns first
So each row of the generated record 2D matrix actually holds the number of each column in the game screen
To put it another way, the record is actually a list of what's in the center of the game screen after symmetry
def getAllSquareRecord(all_square_list, types): print("Change map...") record = [] line = [] for square in all_square_list: num = 0 for type in types: res = (square, type) if not (res): (num) break num += 1 # of each column V_NUM # Then we consider this column processed when there are V_NUM squares in the current line list. if len(line) == V_NUM: print(line); (line) line = [] return record
Determine whether the given two images can be eliminated
def canConnect(x1, y1, x2, y2, r): result = r[:] # If one of the two images is 0, return 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 # Judging lateral connectivity if horizontalCheck(x1, y1, x2, y2, result): return True # Judging vertical connectivity if verticalCheck(x1, y1, x2, y2, result): return True # Judging a point of inflection to be connectable if turnOnceCheck(x1, y1, x2, y2, result): return True # Judging two points of inflection to be connectable if turnTwiceCheck(x1, y1, x2, y2, result): return True # Unconnectable returns False return False
Judgement of horizontal connectivity
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) # Determine if two squares are adjacent if (endY - startY) == 1: return True # Determine whether the two square paths are both 0, one is not, it means that can not be connected, return false for i in range(startY + 1, endY): if result[x1][i] != EMPTY_ID: return False return True
Judging Vertical Connectivity
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) # Determine if two squares are adjacent if (endX - startX) == 1: return True # Determine whether two squares are connectable on the access road. for i in range(startX + 1, endX): if result[i][y1] != EMPTY_ID: return False return True
Judging an inflection point can be linked
def turnOnceCheck(x1, y1, x2, y2, result): if x1 == x2 or y1 == y2: return False cx = x1 cy = y2 dx = x2 dy = y1 # If the inflection point is empty and the whole path is passable from the first point to the inflection point and from the inflection point to the second point, then the whole path is passable. 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
Judging two inflection points can be linked
def turnTwiceCheck(x1, y1, x2, y2, result): if x1 == x2 and y1 == y2: return False # Iterate over the whole array to find the right inflection point # for i in range(0, len(result)): for j in range(0, len(result[1])): # Not being empty can't be an inflection point if result[i][j] != EMPTY_ID: continue # Not in the same row as the selected square cannot be used as an inflection point. if i != x1 and i != x2 and j != y1 and j != y2: continue # A square that's an intersection can't be an inflection point # 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
spontaneous elimination
def autoRelease(result, game_x, game_y): # Traversing the map for i in range(0, len(result)): for j in range(0, len(result[0])): # The current position is not empty if result[i][j] != EMPTY_ID: # Traversing the map again, looking for another image that meets the criteria # for m in range(0, len(result)): for n in range(0, len(result[0])): if result[m][n] != EMPTY_ID: # If elimination can be implemented if canConnect(i, j, m, n, result): # Eliminate two positions set to empty 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)) # Calculate where the current two positions of the image should exist in the game x1 = game_x + j * POINT_WIDTH y1 = game_y + i * POINT_HEIGHT x2 = game_x + n * POINT_WIDTH y2 = game_y + m * POINT_HEIGHT # Simulate a mouse click where the first image is located ((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) # Wait for a random time to prevent detection ((TIME_INTERVAL_MIN, TIME_INTERVAL_MAX)) # Simulate a mouse click where the second image is located ((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_INTERVAL_MIN, TIME_INTERVAL_MAX)) # Returns True when elimination is performed return True return False
You have to upload a video for the effect, but the screenshot doesn't show the effect, so you can try it on your own.
All Codes
# -*- coding:utf-8 -*- import cv2 import numpy as np import win32api import win32gui import win32con from PIL import ImageGrab import time import random # Form title Used to position the game form WINDOW_TITLE = "Back-to-back." # Interval randomization [MIN,MAX] TIME_INTERVAL_MAX = 0.06 TIME_INTERVAL_MIN = 0.1 # x offset of the game area from the vertices MARGIN_LEFT = 10 # y offset of the game area from the vertices MARGIN_HEIGHT = 180 # of horizontal squares H_NUM = 19 # of squares vertically V_NUM = 11 # Square width POINT_WIDTH = 31 # Height of the cube POINT_HEIGHT = 35 # Empty image number EMPTY_ID = 0 # The upper-left and lower-right coordinates at the time of the slice processing: SUB_LT_X = 8 SUB_LT_Y = 8 SUB_RB_X = 27 SUB_RB_Y = 27 # Maximum number of eliminations for the game MAX_ROUND = 200 def getGameWindow(): # FindWindow(lpClassName=None, lpWindowName=None) Window class name Window title name window = (None, WINDOW_TITLE) # No game form is positioned while not window: print('Failed to locate the game window , reposition the game window after 10 seconds...') (10) window = (None, WINDOW_TITLE) # Locate the game form # Topping the game window (window) pos = (window) print("Game windows at " + str(pos)) return (pos[0], pos[1]) def getScreenImage(): print('Shot screen...') # Get Screenshot Image type object scim = () ('') # Read screenshots with opencv # Get ndarray return ("") def getAllSquare(screen_image, game_pos): print('Processing pictures...') # Positioning through the game form # Add an offset to get the play area game_x = game_pos[0] + MARGIN_LEFT game_y = game_pos[1] + MARGIN_HEIGHT # From the top left of the playing area # Cut the image into identical chunks of a specific size # Cutting standards are based on the horizontal and vertical coordinates of the parcel all_square = [] for x in range(0, H_NUM): for y in range(0, V_NUM): # Slicing methods for ndarray: [vertical start position: vertical end position, horizontal start position: horizontal end position] 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) # Because the edges of some images can be intrusive, uniformly shrink the image inward by one turn # Process all the squares, remove a circle from the edge and return # finalresult = [] for square in all_square: s = square[SUB_LT_Y:SUB_RB_Y, SUB_LT_X:SUB_RB_X] (s) return finalresult # Determine if the same figure exists in the list # Presence return to determine the id of where the image is located # Otherwise return -1 def isImageExist(img, img_list): i = 0 for existed_img in img_list: # Two images are compared The standard deviation of the two images is returned b = (existed_img, img) # If the standard deviation is zero, then there is no difference between the two images. if not (b): return i i = i + 1 return -1 def getAllSquareTypes(all_square): print("Init pictures types...") types = [] The # number list is used to record the number of occurrences of each id. number = [] # Currently the most frequently occurring square # Here we're defaulting to the most frequent square being the blank square # nowid = 0; for square in all_square: nid = isImageExist(square, types) # Insert list if this image does not exist if nid == -1: (square) (1); else: # If this image exists, give the counter + 1 number[nid] = number[nid] + 1 if (number[nid] > number[nowid]): nowid = nid # Update EMPTY_ID # i.e. determine the id of the blank block in the current diagram global EMPTY_ID EMPTY_ID = nowid print('EMPTY_ID = ' + str(EMPTY_ID)) return types # Convert 2D picture matrices to 2D numeric matrices # Note that because when slicing the screenshot above it is sliced first as a column # So each row of the generated record 2D matrix actually holds the number of each column in the game screen # To put it another way, the record is actually a list of what's in the center of the game screen # def getAllSquareRecord(all_square_list, types): print("Change map...") record = [] line = [] for square in all_square_list: num = 0 for type in types: res = (square, type) if not (res): (num) break num += 1 # of each column V_NUM # Then we consider this column processed when there are V_NUM squares in the current line list. if len(line) == V_NUM: print(line); (line) line = [] return record def canConnect(x1, y1, x2, y2, r): result = r[:] # If one of the two images is 0, return 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 # Judging lateral connectivity if horizontalCheck(x1, y1, x2, y2, result): return True # Judging vertical connectivity if verticalCheck(x1, y1, x2, y2, result): return True # Judging a point of inflection to be connectable if turnOnceCheck(x1, y1, x2, y2, result): return True # Judging two points of inflection to be connectable if turnTwiceCheck(x1, y1, x2, y2, result): return True # Unconnectable returns 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) # Determine if two squares are adjacent if (endY - startY) == 1: return True # Determine whether the two square paths are both 0, one is not, it means that can not be connected, return 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) # Determine if two squares are adjacent if (endX - startX) == 1: return True # Determine whether two squares are connectable on the access road. 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 the inflection point is empty and the whole path is passable from the first point to the inflection point and from the inflection point to the second point, then the whole path is passable. 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 # Iterate over the whole array to find the right inflection point # for i in range(0, len(result)): for j in range(0, len(result[1])): # Not being empty can't be an inflection point if result[i][j] != EMPTY_ID: continue # Not in the same row as the selected square cannot be used as an inflection point. if i != x1 and i != x2 and j != y1 and j != y2: continue # A square that's an intersection can't be an inflection point # 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): # Traversing the map for i in range(0, len(result)): for j in range(0, len(result[0])): # The current position is not empty if result[i][j] != EMPTY_ID: # Traversing the map again, looking for another image that meets the criteria # for m in range(0, len(result)): for n in range(0, len(result[0])): if result[m][n] != EMPTY_ID: # If elimination can be implemented if canConnect(i, j, m, n, result): # Eliminate two positions set to empty 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)) # Calculate where the current two positions of the image should exist in the game x1 = game_x + j * POINT_WIDTH y1 = game_y + i * POINT_HEIGHT x2 = game_x + n * POINT_WIDTH y2 = game_y + m * POINT_HEIGHT # Simulate a mouse click where the first image is located ((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) # Wait for a random time to prevent detection ((TIME_INTERVAL_MIN, TIME_INTERVAL_MAX)) # Simulate a mouse click where the second image is located ((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_INTERVAL_MIN, TIME_INTERVAL_MAX)) # Returns True when elimination is performed return True return False def autoRemove(squares, game_pos): game_x = game_pos[0] + MARGIN_LEFT game_y = game_pos[1] + MARGIN_HEIGHT # Repeat an elimination until the maximum number of eliminations is reached while True: if not autoRelease(squares, game_x, game_y): # End when there are no more squares to eliminate, return the number of squares eliminated. return if __name__ == '__main__': () # i. Positioning the game form game_pos = getGameWindow() (1) # ii. Getting screenshots screen_image = getScreenImage() # iii. Slicing the screenshot to form a two-dimensional map all_square_list = getAllSquare(screen_image, game_pos) # iv. get all types of graphics and number them types = getAllSquareTypes(all_square_list) # v. Convert acquired image maps into digital matrices result = (getAllSquareRecord(all_square_list, types)) # vi. Perform the elimination and output the number of eliminations. print('The total elimination amount is ' + str(autoRemove(result, game_pos)))
Guys, go try it.
Above is Python to realize the automatic play serial script to share the details, more information about Python serial please pay attention to my other related articles!