• python 玩连一连


    平时偶尔在微信上玩一些小游戏,某天发现一款称之为《最强连一连》的益智游戏,具体玩法概括就是"一笔画"的问题。玩了几关之后随着游戏的格子数量的增加,感觉自己的算力不够用了【汗】于是打算写个脚本用于辅助。

    一、开发环境配置

    1. windows环境下搭建好python编程环境,本人使用python3.8.3版本
    2. 安装adb(添加到系统环境变量)
    3. 安卓手机打开usb调试模式,本人手机:小米note3分辨率为1920*1080
    4. pip 安装好python opencv库

    二、分析游戏通关流程

    通过对游戏界面截图分析,发现游戏通关过程其实就是从起始点格子(内外层颜色不同的格子)、访问完全部的空白格子。看到这里很容易联想到把所有的格子抽象成N×M的二维数组,我们把不可访问的格子抽象成二维数组中的‘1’,可访问的格子抽象成‘0’,起始点格子抽象成‘S’,然后问题就变成了从二维数组中的‘S’点不重复访问数组中所有的‘0’点。然后通过深度优先算法暴力进行求解、并记录访问顺序。我们把格子相应的坐标与二维数组中的点进行映射,通过遍历保存的路径,然后在屏幕上点击相对应的坐标那么问题就解决了。

    游戏运行界面:

     

    三、使用opencv对图像进行操作

    经过上一步的分析我们需要获得游戏截图中所有的格子的坐标,提起图像处理那么现在该轮到opencv上场了,大致思路为:
    5. 对游戏截图进行裁剪后,预处理灰度变换、滤波去噪点、二值化、膨胀处理
    6. 使用findContours函数提取格子外轮廓,去掉边太短、两边差值过大的轮廓,选择相似面积最多的轮廓
    7. 根据找到的轮廓按内颜色值不同,确定起始点格子
    8. 找到格子轮廓的长与宽,以及轮廓之间的间距
    9. 确定轮廓的左上顶点、然后枚举轮廓顶点坐标开始建图

    图像预处理之后:

    四、操作流程

    1. 使用adb screencap命令对游戏界面进行截图
    2. 根据截图构建对应的二维数组
    3. 使用搜索算法求解、记录路径
    4. 遍历路径,使用adb tap或者touchscreen swipe命令点击相应的屏幕坐标点
    5. 游戏每次通关或通过累计5关后会弹出一个界面,使用adb tap命令点击相应坐标进入下一关

    五、具体实现

    建图

    """
    ===================================
        -*- coding:utf-8 -*-
        Author     :GadyPu
        E_mail     :Gadypy@gmail.com
        Time       :2020/9/20 0016 上午 11:44
        FileName   :create_map.py
    ===================================
    """
    import cv2
    import numpy as np
    from find_path import FindPath
    
    class CreateMap(object):
    
        def __init__(self, img_path: str = '', cut_size: tuple = (320, 1600)):
            self.img_path = img_path
            self.cut_size = cut_size
            self.points = []
            self.start_ptn = None
            self.x_min = 0
            self.y_min = 0
    
        def get_rect_area(self, point: tuple):
            return abs(point[0] - point[2]) * abs(point[1] - point[3])
    
        def img_process(self):
            img = cv2.imdecode(np.fromfile(self.img_path, dtype = np.uint8), cv2.IMREAD_COLOR)
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            gray = gray[self.cut_size[0]: self.cut_size[1], :]
            media_blur = cv2.medianBlur(gray, 3)
            thres = cv2.threshold(media_blur, 180, 255, cv2.THRESH_BINARY)[1]
            dila = cv2.dilate(thres, (3, 3))
            contours = cv2.findContours(dila, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]
            for c in contours:
                #cv2.drawContours(img, [c], -1, (0, 0, 255), 2)
                x, _x, y, _y = -1, 9999, -1, 9999
                for [i] in c:
                    x = max(x, i[0])
                    _x = min(_x, i[0])
                    y = max(y, i[1])
                    _y = min(_y, i[1])
                # 边太短的矩形丢弃
                if abs(_x - x) < 40 or abs(_y - y) < 40:
                    continue
                # 两边差值的绝对值控制在5以内
                if abs(abs(_x - x) - abs(_y - y)) < 5:
                    self.points.append((_x, _y + self.cut_size[0], x, y + self.cut_size[0]))
    
            # 寻找相似面积最多的轮廓
            freq, freq_area = -1, -1
            for i in range(len(self.points)):
                area = self.get_rect_area(self.points[i])
                count = 1
                for j in range(len(self.points)):
                    if j == i:
                        continue
                    _area = self.get_rect_area(self.points[j])
                    if abs(area - _area) < 500:
                        count += 1
                if count > freq:
                    freq = count
                    freq_area = area
    
            point_temp = []
            for i in self.points:
                area = self.get_rect_area(i)
                if abs(freq_area - area) < 500:
                    point_temp.append(i)
    
            self.points.clear()
            self.points = point_temp
            self.points.reverse()
    
           # 寻找起始点(内外颜色值不同)
            count = { }
            temp = None
            for i in self.points:
                x, y = (i[0] + i[2]) // 2, (i[1] + i[3]) // 2
                d = tuple(img[y][x])
                if d in count.keys():
                    count.pop(d)
                    temp = d
                elif d != temp:
                    count[d] = i
    
            for i in count:
                self.start_ptn = count[i]
    
            # cv2.imshow('', img)
            # cv2.waitKey(0)
            # cv2.destroyAllWindows()
    
        def get_map_size(self):
    
            x, _x, y, _y = 9999, -1, 9999, -1
            for i in self.points:
                x = min(x, i[0])
                y = min(y, i[1])
                _x = max(_x, i[2])
                _y = max(_y, i[3])
                self.x_min = x
                self.y_min = y
    
            dif_x, dif_y = 0, 0
    
            H, W = self.points[0][3] - self.points[0][1], self.points[0][2] - self.points[0][0]
            n = (_x - x) // W
            m = (_y - y) // H
    
            ptn = self.points[0]
    
            for i in range(1, len(self.points)):
                if abs(ptn[1] - self.points[i][1]) < 3:
                    dif_x = self.points[i][0] - ptn[2]
                    if dif_x > W:
                        continue
                    else:
                        break
                else:
                    ptn = self.points[i]
    
            dif_y = dif_x
    
            if n * W + (n - 1) * dif_x > _x - x + n * 2:
                n -= 1
            if m * H + (m - 1) * dif_y > _y - y + m * 2:
                m -= 1
            return m, n, dif_x, dif_y, H, W
    
        def creat_grid(self):
            m, n, dif_x, dif_y, H, W = self.get_map_size()
            grid = [[('0', (0, 0)) for _ in range(n)] for _ in range(m)]
    
            for i in range(m):
                for j in range(n):
                    _find = False
    
                    p1 = self.x_min + (dif_x + W) * j
                    p2 = self.y_min + (dif_y + H) * i
    
                    center_point = (None, None)
                    for ptn in self.points:
                        if abs(p1 - ptn[0]) < 5 and 
                                abs(p2 - ptn[1]) < 5:
                            _find = True
                            center_point = (ptn[0] + ptn[2]) // 2, (ptn[1] + ptn[3]) // 2
                            if ptn == self.start_ptn:
                                grid[i][j] = ('S', center_point)
                            break
                    if grid[i][j][1] == (0, 0):
                        grid[i][j] = ('0' if _find else '1', center_point)
            #print(grid)
            return grid
    
        def build_map(self, img_path):
            self.img_path = img_path
            self.points = []
            self.start_ptn = None
            self.x_min = 0
            self.y_min = 0
            self.img_process()
            return self.creat_grid()
    
    if __name__ == '__main__':
    
        import time
        t = time.time()
        d = CreateMap()
        grid = d.build_map('01.png')
        p = FindPath()
        print(p.find_path(grid))
        print(time.time() - t)

    寻找路径

    """
    ===================================
        -*- coding:utf-8 -*-
        Author     :GadyPu
        E_mail     :Gadypy@gmail.com
        Time       :2020/9/20 0013 下午 07:59
        FileName   :find_path.py
    ===================================
    """
    import copy
    
    class FindPath(object):
    
        def __init__(self):
            self.dx = [0, 0, -1, 1]
            self.dy = [-1, 1, 0, 0]
            self.ret_path = []
            self.blank = 0
            self.start_x = -1
            self.start_y = -1
    
        def find_path(self, grid: list):
            H, W = len(grid), len(grid[0])
            vis = [[False for _ in range(W)] for _ in range(H)]
            self.blank = 0
            self.ret_path = []
            for i in range(H):
                for j in range(W):
                    if grid[i][j][0] == 'S':
                        self.start_x, self.start_y = i, j
                        vis[i][j] = True
                    elif grid[i][j][0] == '0':
                        self.blank += 1
    
            self.dfs(self.start_x, self.start_y, grid, vis, H, W, 0, self.blank, [])
            self.ret_path.insert(0, grid[self.start_x][self.start_y][1])
            return self.ret_path
    
        def dfs(self, x: int, y: int, grid: list, vis: list, H :int, W: int, step: int, tot: int, res: list):
            if step == tot:
                self.ret_path = copy.deepcopy(res)
                print('find a answer!')
                return None
            for i in range(4):
                _x, _y = x + self.dx[i], y + self.dy[i]
                if _x < 0 or _y < 0 or _x >= H or _y >= W:
                    continue
                if grid[_x][_y][0] == '0' and not vis[_x][_y]:
                    vis[_x][_y] = True
                    res.append(grid[_x][_y][1])
                    self.dfs(_x, _y, grid, vis, H, W, step + 1, tot, res)
                    vis[_x][_y] = False
                    res.pop()
    
    if __name__ == '__main__':
    
        pass

    主程序

    """
    ===================================
        -*- coding:utf-8 -*-
        Author     :GadyPu
        E_mail     :Gadypy@gmail.com
        Time       :2020/9/20 0016 上午 11:51
        FileName   :AutoRun.py
    ===================================
    """
    import time
    import signal
    import os
    from create_map import CreateMap
    from find_path import FindPath
    g_exit = True
    
    class AutoRun():
    
        def __init__(self):
            self.get_map = CreateMap()
            self.get_path = FindPath()
            self.next_level_point = (536, 1417)
            self.ad_point = (930, 660)
    
        def handler(self, signum, frame):
            global g_exit
            g_exit = False
            print('接收到ctrl c信号程序退出')
    
        def run(self):
            signal.signal(signal.SIGINT, self.handler)
            signal.signal(signal.SIGTERM, self.handler)
            level = 1
            while g_exit:
    
                os.system('adb shell screencap -p /sdcard/Download/01.png')
                os.system('adb pull /sdcard/Download/01.png')
    
                time.sleep(1)
    
                grid = self.get_map.build_map('01.png')
                path = self.get_path.find_path(grid)
    
                print(path)
                if not path or len(path) == 1:
                    time.sleep(0.2)
                    continue
    
                for i in range(len(path) - 1):
                    x0, y0, x1, y1, t = path[i][0], path[i][1], path[i + 1][0], path[i + 1][1], 50
                    os.system("adb shell input touchscreen swipe %d %d %d %d %d" % (x0, y0, x1, y1, t))
    
                time.sleep(1.5)
                if level % 5 == 0:
                    os.system(f'adb shell input tap {self.ad_point[0]} {self.ad_point[1]}')
                    time.sleep(0.5)
    
                os.system(f'adb shell input tap {self.next_level_point[0]} {self.next_level_point[1]}')
                level += 1
                os.system(f'adb shell input tap 0 0')
                print('程序运行中...')
    
    if __name__ == '__main__':
    
        d = AutoRun()
        d.run()

    效果图

    六、其它

    每台手机分辨率不一样,若要在其他机型上运行需要修改相应的坐标点。

    adb tap命令执行太慢了每通关一关差不多要20s左右,到现在也没有啥好的办法。

  • 相关阅读:
    Android Wifi简单管理与操作
    Android 语音识别(其它资料里面的代码)
    Android 读取资源文件下面的文件
    slice,substr和substring的区别
    !important minheight
    Jquery之美中不足之三delegate的缺憾
    事件切片
    QWrap简介之:apps果实篇之:定制
    QWrap简介之:apps果实篇之:小结
    QWrap简介之:结语
  • 原文地址:https://www.cnblogs.com/GadyPu/p/13705577.html
Copyright © 2020-2023  润新知