How to Build a Python Bot to Auto‑Play 4399’s “Pet Link‑Match Classic”
This tutorial walks through creating a Windows‑10 Python script that captures the 4399 mini‑game “Pet Link‑Match Classic 2”, identifies icons via image processing, builds a matrix, finds connectable pairs with a path‑finding algorithm, and simulates mouse clicks to automatically clear the board.
1. Introduction
This article uses the 4399 mini‑game “Pet Link‑Match Classic 2” as a test case to demonstrate how to recognize small icons, simulate mouse clicks, and quickly complete matches. It is useful for anyone interested in learning game‑scripting techniques.
2. Development Outlook
Game‑assistant scripts are popular for automating repetitive actions. Two main approaches exist: reading and modifying in‑memory game data (similar to cheats) or simulating user behavior such as mouse clicks and keyboard input. This example follows the latter, focusing on user‑behavior simulation.
3. Development Process
3.1 Obtain window handle and bring to front
Using the win32gui module, FindWindow() retrieves the window handle (passing 0 for the parent and the full Chrome title as the window name). SetForegroundWindow() then brings the game window to the foreground.
3.2 Capture game screen and split icons
The script captures the game area with ImageGrab.grab() using the top‑left and bottom‑right coordinates of the game board. The captured image is divided into 8 rows × 12 columns of 39‑pixel squares, each stored in an image_list dictionary.
Each icon is then compared to previously seen icons. If an icon is new, it is added to image_type_list and assigned a new numeric ID; otherwise the existing ID is reused. The comparison uses a Hamming‑distance‑based hash: icons are resized to 20×20, converted to grayscale, turned into binary strings, and the number of differing bits is counted. A threshold of 10 treats the icons as identical.
The resulting numeric matrix ( self.im2num_arr) represents the board, with zeros for empty cells.
4. Core Icon‑Connection Algorithm (Path Finding)
The algorithm checks whether two cells with the same numeric ID can be connected according to the game rules (8 rows × 12 columns, with a surrounding border of zeros that allows paths to wrap around). The steps are:
Gather the set of horizontally and vertically reachable empty cells for each coordinate ( getDirectConnectList).
Compare the two sets; if any pair of reachable cells are also directly connectable, the original cells are considered connectable.
Key helper functions: isReachable(x1, y1, x2, y2) – verifies identical IDs and then checks connectivity. isDirectConnect(x1, y1, x2, y2) – determines if two cells are on the same row or column and have a clear line of zeros between them. isRowConnect and isColConnect – test adjacency or a continuous zero line.
When a connectable pair is found, clickAndSetZero simulates mouse clicks on the two icons (calculating screen coordinates from the board indices) and sets the corresponding matrix entries to zero.
5. Development Summary
Building a game‑assistant script like this helps cultivate programming interest and can be extended beyond games to automate repetitive desktop tasks such as sending messages, scrolling, or keyboard input.
6. Source Code
Note: the source code is provided for learning purposes only.
# -*- coding:utf-8 -*-
import win32gui
import time
from PIL import ImageGrab, Image
import numpy as np
import operator
from pymouse import PyMouse
class GameAssist:
def __init__(self, wdname):
"""Initialize"""
# Get window handle
self.hwnd = win32gui.FindWindow(0, wdname)
if not self.hwnd:
print("Window not found, please verify the handle name: %s" % wdname)
exit()
# Bring window to front
win32gui.SetForegroundWindow(self.hwnd)
# Icon‑ID matrix
self.im2num_arr = []
# Screenshot coordinates (left, top, right, bottom)
self.scree_left_and_right_point = (299, 251, 768, 564)
# Icon size
self.im_width = 39
# Mouse controller
self.mouse = PyMouse()
def screenshot(self):
"""Capture the screen"""
image = ImageGrab.grab(self.scree_left_and_right_point)
image_list = {}
offset = self.im_width
for x in range(8):
image_list[x] = {}
for y in range(12):
top = x * offset
left = y * offset
right = (y + 1) * offset
bottom = (x + 1) * offset
im = image.crop((left, top, right, bottom))
image_list[x][y] = im
return image_list
def image2num(self, image_list):
"""Convert icon matrix to numeric matrix"""
arr = np.zeros((10, 14), dtype=np.int32)
image_type_list = []
for i in range(len(image_list)):
for j in range(len(image_list[0])):
im = image_list[i][j]
index = self.getIndex(im, image_type_list)
if index < 0:
image_type_list.append(im)
arr[i + 1][j + 1] = len(image_type_list)
else:
arr[i + 1][j + 1] = index + 1
print("Icon count:", len(image_type_list))
self.im2num_arr = arr
return arr
def getIndex(self, im, im_list):
for i in range(len(im_list)):
if self.isMatch(im, im_list[i]):
return i
return -1
def isMatch(self, im1, im2):
image1 = im1.resize((20, 20), Image.ANTIALIAS).convert("L")
image2 = im2.resize((20, 20), Image.ANTIALIAS).convert("L")
pixels1 = list(image1.getdata())
pixels2 = list(image2.getdata())
avg1 = sum(pixels1) / len(pixels1)
avg2 = sum(pixels2) / len(pixels2)
hash1 = "".join(map(lambda p: "1" if p > avg1 else "0", pixels1))
hash2 = "".join(map(lambda p: "1" if p > avg2 else "0", pixels2))
match = sum(map(operator.ne, hash1, hash2))
return match < 10
def isAllZero(self, arr):
for i in range(1, 9):
for j in range(1, 13):
if arr[i][j] != 0:
return False
return True
def isReachable(self, x1, y1, x2, y2):
if self.im2num_arr[x1][y1] != self.im2num_arr[x2][y2]:
return False
list1 = self.getDirectConnectList(x1, y1)
list2 = self.getDirectConnectList(x2, y2)
for x1_, y1_ in list1:
for x2_, y2_ in list2:
if self.isDirectConnect(x1_, y1_, x2_, y2_):
return True
return False
def getDirectConnectList(self, x, y):
plist = []
for px in range(0, 10):
for py in range(0, 14):
if self.im2num_arr[px][py] == 0 and self.isDirectConnect(x, y, px, py):
plist.append([px, py])
return plist
def isDirectConnect(self, x1, y1, x2, y2):
if x1 == x2 and y1 == y2:
return False
if x1 != x2 and y1 != y2:
return False
if x1 == x2 and self.isRowConnect(x1, y1, y2):
return True
if y1 == y2 and self.isColConnect(y1, x1, x2):
return True
return False
def isRowConnect(self, x, y1, y2):
minY = min(y1, y2)
maxY = max(y1, y2)
if maxY - minY == 1:
return True
for y0 in range(minY + 1, maxY):
if self.im2num_arr[x][y0] != 0:
return False
return True
def isColConnect(self, y, x1, x2):
minX = min(x1, x2)
maxX = max(x1, x2)
if maxX - minX == 1:
return True
for x0 in range(minX + 1, maxX):
if self.im2num_arr[x0][y] != 0:
return False
return True
def clickAndSetZero(self, x1, y1, x2, y2):
p1_x = int(self.scree_left_and_right_point[0] + (y1 - 1) * self.im_width + (self.im_width / 2))
p1_y = int(self.scree_left_and_right_point[1] + (x1 - 1) * self.im_width + (self.im_width / 2))
p2_x = int(self.scree_left_and_right_point[0] + (y2 - 1) * self.im_width + (self.im_width / 2))
p2_y = int(self.scree_left_and_right_point[1] + (x2 - 1) * self.im_width + (self.im_width / 2))
time.sleep(0.2)
self.mouse.click(p1_x, p1_y)
time.sleep(0.2)
self.mouse.click(p2_x, p2_y)
self.im2num_arr[x1][y1] = 0
self.im2num_arr[x2][y2] = 0
print("Eliminated: (%d, %d) (%d, %d)" % (x1, y1, x2, y2))
def start(self):
image_list = self.screenshot()
self.image2num(image_list)
print(self.im2num_arr)
while not self.isAllZero(self.im2num_arr):
for x1 in range(1, 9):
for y1 in range(1, 13):
if self.im2num_arr[x1][y1] == 0:
continue
for x2 in range(1, 9):
for y2 in range(1, 13):
if self.im2num_arr[x2][y2] == 0 or (x1 == x2 and y1 == y2):
continue
if self.isReachable(x1, y1, x2, y2):
self.clickAndSetZero(x1, y1, x2, y2)
if __name__ == "__main__":
wdname = u'宠物连连看经典版2,宠物连连看经典版2小游戏,4399小游戏 www.4399.com - Google Chrome'
demo = GameAssist(wdname)
demo.start()Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
