Python自动化控制微信小游戏《疯狂水世界》技术笔记

1. 概述

本文档整理了关于使用Python对微信小游戏《疯狂水世界》进行自动化操作的技术探讨,涵盖窗口控制、模拟点击、图像识别、抓包分析及虚拟环境使用等内容。


2. 环境配置

2.1 虚拟环境

虚拟环境是一个独立的Python运行环境,包含独立的Python解释器、pip工具及第三方库。

虚拟环境目录结构:

G:\ProgramFiles\pythonvenv\shuiyx\
├── Scripts\
│   ├── python.exe          # 虚拟环境的Python解释器
│   ├── pip.exe             # 虚拟环境的pip
│   └── activate.bat        # 激活脚本
└── Lib\
    └── site-packages\      # 所有安装的第三方库

核心逻辑:使用哪个路径的python.exe,就对应哪个环境。虚拟环境中的python.exe自动关联该环境Lib\site-packages下的所有库。

直接使用虚拟环境运行脚本

G:\ProgramFiles\pythonvenv\shuiyx\Scripts\python.exe s.py

2.2 依赖库安装

在虚拟环境中安装所需库:

pip install pywin32
pip install pyautogui
pip install pillow
pip install opencv-python
pip install pytesseract

注意:win32gui、win32con、win32api都是pywin32库的组成部分,无需单独安装。


3. 窗口控制

3.1 查找游戏窗口

import win32gui

def find_game_window():
    """查找疯狂水世界窗口"""
    def callback(hwnd, hwnds):
        if win32gui.IsWindowVisible(hwnd):
            title = win32gui.GetWindowText(hwnd)
            if "疯狂水世界" in title or "微信小游戏" in title:
                hwnds.append(hwnd)
        return True
    
    hwnds = []
    win32gui.EnumWindows(callback, hwnds)
    return hwnds[0] if hwnds else None

3.2 获取窗口位置和大小

def get_window_info():
    """获取当前窗口的位置和大小"""
    hwnd = find_game_window()
    if not hwnd:
        return None
    
    rect = win32gui.GetWindowRect(hwnd)
    x, y = rect[0], rect[1]
    width = rect[2] - rect[0]
    height = rect[3] - rect[1]
    
    print(f"窗口位置: ({x}, {y})")
    print(f"窗口大小: {width}x{height}")
    return {'x': x, 'y': y, 'width': width, 'height': height}

3.3 移动窗口并调整大小

import win32gui
import win32con
import win32api
import time

def move_and_resize_window(x, y, width, height):
    """移动窗口到指定位置并调整大小"""
    hwnd = find_game_window()
    if not hwnd:
        print("未找到窗口")
        return False
    
    # 如果窗口最小化,先恢复
    if win32gui.IsIconic(hwnd):
        win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
        time.sleep(0.3)
    
    # 将窗口置于前台
    try:
        win32gui.SetForegroundWindow(hwnd)
    except:
        pass
    
    # 执行移动和缩放
    win32gui.MoveWindow(hwnd, x, y, width, height, True)
    
    # 验证结果
    time.sleep(0.2)
    rect = win32gui.GetWindowRect(hwnd)
    actual_x, actual_y = rect[0], rect[1]
    actual_w = rect[2] - rect[0]
    actual_h = rect[3] - rect[1]
    
    print(f"窗口已移到 ({actual_x}, {actual_y}),大小 {actual_w}x{actual_h}")
    return True

3.4 保存和恢复窗口配置

使用JSON配置文件保存窗口位置,便于下次恢复:

import json
import os

CONFIG_FILE = "window_config.json"

def save_config(x, y, width, height):
    """保存窗口配置到文件"""
    config = {
        'x': x,
        'y': y,
        'width': width,
        'height': height,
        'saved_at': time.strftime('%Y-%m-%d %H:%M:%S')
    }
    with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
        json.dump(config, f, indent=2, ensure_ascii=False)

def load_config():
    """从文件加载窗口配置"""
    if os.path.exists(CONFIG_FILE):
        with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    return None

4. 模拟点击

4.1 基于屏幕坐标的点击

使用pyautogui库:

import pyautogui

# 点击指定坐标
pyautogui.click(x, y)

# 带随机延迟的点击(模拟人类操作)
import random
time.sleep(random.uniform(0.2, 0.5))
pyautogui.click(x, y)

4.2 后台发送点击消息

使用win32api向指定窗口发送鼠标消息,不依赖前台光标位置:

import win32api
import win32con

def send_click_to_window(hwnd, x, y):
    """
    向指定窗口发送点击消息
    x, y: 相对于窗口客户区的坐标
    """
    # 将坐标转换为lParam
    lParam = win32api.MAKELONG(x, y)
    
    # 发送鼠标按下消息
    win32api.SendMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lParam)
    time.sleep(0.05)
    # 发送鼠标弹起消息
    win32api.SendMessage(hwnd, win32con.WM_LBUTTONUP, 0, lParam)

5. 图像识别与数值判断

5.1 截取指定区域

import pyautogui
from PIL import Image

def capture_region(region):
    """
    截取屏幕指定区域
    region: (x, y, width, height)
    """
    screenshot = pyautogui.screenshot(region=region)
    return screenshot

5.2 OCR识别数字

使用pytesseract库识别图片中的数字:

import pytesseract
from PIL import Image

# 设置Tesseract路径(如果不在系统PATH中)
# pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

def get_number_from_region(region):
    """从指定区域识别数字"""
    screenshot = pyautogui.screenshot(region=region)
    screenshot = screenshot.convert('L')  # 转为灰度图
    
    # 只识别数字
    config = '--psm 8 --oem 3 -c tessedit_char_whitelist=0123456789.'
    text = pytesseract.image_to_string(screenshot, config=config)
    
    import re
    numbers = re.findall(r'\d+\.?\d*', text)
    if numbers:
        return float(numbers[0])
    return None

5.3 带图像预处理的识别(提高准确率)

import cv2
import numpy as np

def get_number_with_preprocessing(region):
    """带图像预处理的数字识别"""
    screenshot = pyautogui.screenshot(region=region)
    img = np.array(screenshot)
    
    # 转为灰度图
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    # 二值化
    _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
    # 放大图像
    resized = cv2.resize(binary, None, fx=2, fy=2, interpolation=cv2.INTER_LINEAR)
    
    text = pytesseract.image_to_string(
        resized, 
        config='--psm 8 -c tessedit_char_whitelist=0123456789.'
    )
    return text.strip()

5.4 条件判断与点击

def check_and_click(value_region, click_pos, threshold):
    """
    识别数值,如果小于阈值则点击
    """
    current_value = get_number_from_region(value_region)
    
    if current_value is not None:
        print(f"当前数值: {current_value}")
        if current_value < threshold:
            print(f"数值 {current_value} 小于阈值 {threshold},执行点击")
            pyautogui.click(click_pos[0], click_pos[1])
            return True
    return False

6. 大地图滚动

6.1 通过鼠标拖拽滚动地图

def scroll_map(dx, dy, duration=0.5):
    """
    通过鼠标拖拽滚动地图
    dx, dy: 拖拽偏移量
    """
    pyautogui.dragRel(dx, dy, duration=duration, button='left')
    time.sleep(0.5)  # 等待滚动完成

6.2 带工厂定位的滚动方案

如果多个工厂分布在地图不同位置,可以预先配置每个工厂的滚动偏移量:

FACTORY_SCROLL = {
    "工厂A": (0, 0),
    "工厂B": (-500, 0),
    "工厂C": (-1000, 0),
}

def scroll_to_factory(factory_name):
    """滚动到指定工厂位置"""
    if factory_name in FACTORY_SCROLL:
        dx, dy = FACTORY_SCROLL[factory_name]
        pyautogui.dragRel(dx, dy, duration=0.3, button='left')
        time.sleep(0.5)

7. 自动化逻辑架构

7.1 数据结构设计

使用嵌套字典定义工厂和产品位置:

GAME_LAYOUT = {
    "工厂A": {
        "products": [
            {"name": "产品1", "value_region": (100, 200, 50, 30), "click_pos": (120, 250)},
            {"name": "产品2", "value_region": (160, 200, 50, 30), "click_pos": (180, 250)},
            # ... 共7-8个产品
        ]
    },
    "工厂B": {
        "products": [
            # ...
        ]
    },
    # ... 共8-9个工厂
}

7.2 单个工厂处理逻辑

def process_factory_once(factory_name, factory_data, product_counts):
    """
    对一个工厂执行一轮操作:
    遍历所有产品,如果未满则点击一次
    返回是否所有产品都已满
    """
    print(f"进入 {factory_name}")
    
    # 滚动到该工厂
    scroll_to_factory(factory_name)
    
    all_full = True
    for product in factory_data["products"]:
        key = f"{factory_name}_{product['name']}"
        current_count = product_counts.get(key, 0)
        
        if current_count < 6:  # 最大队列6个
            # 识别数值并判断是否需要点击
            value = get_number_from_region(product["value_region"])
            if value is None or value < 55:
                pyautogui.click(product["click_pos"][0], product["click_pos"][1])
                product_counts[key] = current_count + 1
                all_full = False
                time.sleep(random.uniform(0.2, 0.5))
        else:
            print(f"产品 {product['name']} 已满")
    
    return all_full

7.3 多工厂并行轮询逻辑

核心思想是每次只处理最多3个工厂,每个工厂各点一轮产品,然后等待生产完成:

def main_loop():
    """主循环:轮询处理多个工厂"""
    MAX_PARALLEL = 3
    MAX_QUEUE = 6
    PRODUCE_INTERVAL = 15  # 每个产品生产时间,需实测
    
    product_counts = defaultdict(int)
    factory_list = list(FACTORIES.keys())
    round_num = 1
    
    while True:
        print(f"第 {round_num} 轮循环")
        
        # 选择本轮需要处理的工厂(最多3个且未满)
        factories_to_process = []
        for factory in factory_list:
            if len(factories_to_process) >= MAX_PARALLEL:
                break
            
            # 检查该工厂是否全部已满
            all_full = True
            for product in FACTORIES[factory]["products"]:
                key = f"{factory}_{product['name']}"
                if product_counts.get(key, 0) < MAX_QUEUE:
                    all_full = False
                    break
            
            if not all_full:
                factories_to_process.append(factory)
        
        if not factories_to_process:
            print("所有工厂已满")
            break
        
        # 逐个处理工厂
        for factory_name in factories_to_process:
            process_factory_once(factory_name, FACTORIES[factory_name], product_counts)
        
        # 等待生产
        wait_time = PRODUCE_INTERVAL + random.uniform(-2, 3)
        print(f"等待 {wait_time:.1f} 秒")
        time.sleep(max(1, wait_time))
        
        round_num += 1

8. 批处理运行

8.1 直接使用虚拟环境Python运行

创建run.bat文件:

@echo off
G:\ProgramFiles\pythonvenv\shuiyx\Scripts\python.exe s.py
pause

8.2 带菜单选择的批处理

@echo off
chcp 65001 >nul
title 疯狂水世界 - 窗口控制

echo ========================================
echo   疯狂水世界 - 窗口控制脚本
echo ========================================
echo.
echo [1] 保存当前窗口配置
echo [2] 恢复到保存的窗口配置
echo [3] 运行完整自动化
echo [0] 退出
echo.
set /p choice="请选择 (0-3): "

if "%choice%"=="1" goto save
if "%choice%"=="2" goto restore
if "%choice%"=="3" goto full
if "%choice%"=="0" exit

:save
G:\ProgramFiles\pythonvenv\shuiyx\Scripts\python.exe s.py save
pause
exit

:restore
G:\ProgramFiles\pythonvenv\shuiyx\Scripts\python.exe s.py
pause
exit

:full
G:\ProgramFiles\pythonvenv\shuiyx\Scripts\python.exe full_auto.py
pause
exit

9. 抓包分析方案

9.1 工具

  • Charles:中间人代理抓包工具
  • Burp Suite:请求重放与调试工具

9.2 基本流程

  1. 配置Charles代理(默认端口8888)
  2. 手机设置Wi-Fi代理为电脑IP和8888端口
  3. 安装并信任Charles SSL证书(用于解密HTTPS)
  4. 启用SSL代理(监听 *:443
  5. 在游戏中执行点击操作,捕获相关请求
  6. 分析请求URL、Headers和Body
  7. 使用Python的requests库重放请求

9.3 风险提示

  • 直接发送网络请求是游戏公司严厉打击的作弊行为
  • 封号风险极高
  • 如果请求参数包含加密签名(如sign),需要逆向分析生成规则

10. Cheat Engine内存修改方案

10.1 原理

Cheat Engine通过扫描和修改进程内存数据来实现修改。

10.2 局限性

  • 《疯狂水世界》是联网游戏,关键数据存储在服务器端
  • 本地内存修改后,服务器会下发正确数据覆盖
  • 微信小游戏平台有代码加固和动态反作弊机制

10.3 结论

对于《疯狂水世界》这类联网小游戏,内存修改方案基本无效且风险极高,不建议尝试。


11. 风险提示

所有自动化方案均存在以下风险:

  1. 违反微信小游戏用户协议
  2. 可能导致游戏账号被封禁
  3. 建议使用小号测试,不在主账号上尝试
  4. 操作频率加入随机延迟可降低被检测风险

12. 附录:常用函数速查

功能函数/方法
查找窗口win32gui.FindWindow() / win32gui.EnumWindows()
移动窗口win32gui.MoveWindow()
获取窗口位置win32gui.GetWindowRect()
恢复最小化窗口win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
屏幕截图pyautogui.screenshot()
模拟点击pyautogui.click(x, y)
鼠标拖拽pyautogui.dragRel(dx, dy)
OCR识别pytesseract.image_to_string()
发送后台点击win32api.SendMessage(hwnd, WM_LBUTTONDOWN, ...)
获取屏幕分辨率win32api.GetSystemMetrics(win32con.SM_CXSCREEN)

标签: none

添加新评论