Game Development 16 min read

Python Script for Automating the 4399 Mini‑Game “Pet Link Classic 2” Using Win32 GUI, PIL, Numpy, and PyMouse

This tutorial explains how to build a Python automation script for the 4399 mini‑game “Pet Link Classic 2”, covering environment setup, window handling, screenshot processing, image comparison with Hamming distance, path‑finding algorithms, and mouse‑click simulation to clear matching icons.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Python Script for Automating the 4399 Mini‑Game “Pet Link Classic 2” Using Win32 GUI, PIL, Numpy, and PyMouse

The article demonstrates how to create a Python script that automates the 4399 mini‑game “Pet Link Classic 2” by recognizing small icons, simulating mouse clicks, and quickly clearing matching pairs, providing a practical example for learning game‑script development.

It runs on Windows 10 with Python 3.5 and relies on the modules win32gui (window operations), PIL (screen capture), numpy (matrix handling), operator (value comparison) and pymouse (mouse simulation).

The author briefly discusses two categories of game assistants—memory‑reading hacks and user‑behavior simulation—explaining the choice of the latter for this tutorial and noting its relevance for hobbyist programmers.

The development workflow is outlined as: capture the game window, split the screenshot into individual icons, compare each icon using a Hamming‑distance based hash, store unique icons in a list, build a numeric matrix, search the matrix for connectable pairs, and finally simulate mouse clicks to remove matched icons.

The core algorithm section details how the numeric matrix is constructed, how the getIndex function uses Hamming distance (threshold 10) to identify identical icons, and how the isReachable , isRowConnect , and isColConnect functions determine whether two icons can be linked according to the game’s rules.

In the summary, the author reflects on the educational value of such a project, suggests possible extensions (e.g., keyboard input, other automation tasks), and encourages further exploration of similar scripts.

Below is the complete Python source code used in the tutorial:

# -*- 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):
        """初始化"""
        # 取得窗口句柄
        self.hwnd = win32gui.FindWindow(0, wdname)
        if not self.hwnd:
            print("窗口找不到,请确认窗口句柄名称:【%s】" % wdname )
            exit()
        # 窗口显示最前面
        win32gui.SetForegroundWindow(self.hwnd)
        # 小图标编号矩阵
        self.im2num_arr = []
        # 主截图的左上角坐标和右下角坐标
        self.scree_left_and_right_point = (299, 251, 768, 564)
        # 小图标宽高
        self.im_width = 39
        # PyMouse对象,鼠标点击
        self.mouse = PyMouse()

    def screenshot(self):
        """屏幕截图"""
        # 1、用grab函数截图,参数为左上角和右下角左标
        image = ImageGrab.grab(self.scree_left_and_right_point)
        # 2、分切小图
        image_list = {}
        offset = self.im_width # 39
        # 8行12列
        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):
        """将图标矩阵转换成数字矩阵"""
        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("图标数:", 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):
        # (299, 251, 768, 564)
        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("消除:(%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()
algorithmimage processingscriptingGame AutomationPyMouseWin32
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.