先上知乎上大神所寫:html
而後別急着看blog,玩一把遊戲再說!python
看知乎評論,有人說他用了一個下午時間就寫出來了. wo kao!!! 我斷斷續續寫了一週的下午才搞定,而後又用了4個小時將近一個下午纔將代碼搬到博客園. 要說這個自動鏈接連連看說簡單也簡單,說不簡單也不簡單.反正是沒你想的那麼簡單也沒你想的那麼複雜(喜歡說廢話的人).作這個小程序遇到了好幾個問題,一個一個搞定還蠻有成就感.web
我也先放一個視頻來看看程序跑起來的效果,(話說博客園不能上傳視頻,只能上傳swf) mp4 轉swf 失真的太厲害啦! 就忍着這樣看吧.......算法
連連看顧名思義, 將相同的圖案鏈接起來,規則是什麼呢? 很簡單, 在三條線之內完成鏈接便可,換句話說,在小於等於兩次拐角的狀況下能把圖案相同的鏈接起來就是可聯通點.因此思路很清晰了,只要知足條件就能聯通,3條線之內可分三種狀況.chrome
狀況一:直線鏈接, 也就是兩個點同在X軸 或同在Y軸, 這種狀況下,咱們先判斷是在X軸仍是在Y軸, 而後計算出同一個軸上的最大值與最小值,最大值MAX減去最小值Min就是兩個點之間的距離,而後在判斷兩點之間的距離是否爲空,爲空即爲可聯通的點,不爲空表示之間有其餘不一樣的圖案點.json
狀況二:一次拐角鏈接, 一次拐角就至關因而用了兩條線進行聯通,也就是兩個點確定不在同一個X軸或確定不在同一個Y軸,那兩個點的沿(長)線上確定會有交集, 並且確定是兩個交集, 一個是點1的X軸與點2的Y軸交集 暫叫該點爲點C, 另一個是點1的Y軸與點2的X軸 暫叫該點爲點D,那問題就很簡單了,一次拐角的鏈接其實就是兩個直線的鏈接, 只是這個拐角的鏈接點變成了兩點直接的兩個交集, 只要計算兩點與交集是否能同時聯通, 同時聯通就認爲一次拐角可聯通.好比:點1與C可聯通,但點C與點2不可聯通,視爲不可聯通, 若是點1與點D可聯通,且點D與點2可聯通,即視爲點1與點2一次拐角可聯通.小程序
狀況三:兩次拐角鏈接,剛纔說過一次拐角鏈接的狀況其實就是兩個直線鏈接的and狀況, 那兩次拐角鏈接是否是三個直線的and狀況呢, 對的,事實就是這樣的,只不過一次拐角鏈接的狀況咱們能知道兩點的交集點,也就是上面剛纔所說的點C與點D,兩次拐角的狀況咱們就不知道這個點在哪了,因此咱們就要先選擇一個點, 就它了---->點1, 遍歷點1在一條直線上的點,這條直線多是在點1的X軸上,有可能在點1的Y軸上 ,無論如何,遍歷出的這個點確定在點1的X軸或Y軸上,不然與點1就不是一條直線了,假設咱們遍歷點1直線上的點如今取到了該點爲點E, 問題就變成點1與點E是否直線可聯通, 點E與點2是否一次拐角可聯通,點E與點2是否一次拐角可聯通能夠直接用狀況二的算法. 當知足一個直線,一個拐角 and (且) 狀況下即爲聯通,不然繼續遍歷尋找點E,直到知足條件. 瀏覽器
再來講說實現的過程app
網頁flash遊戲來自: 工具
這種方案是由於哥用的Ubuntu,應用商店幾乎沒有好玩點的連連看,H5, flash確定是首選,那經過PC玩遊戲更是首選.
參考:
利用selenium實現驗證碼獲取並驗證 https://zhuanlan.zhihu.com/p/25171554
selenium+python實現1688網站驗證碼圖片的截取 http://www.javashuo.com/article/p-gqjeabvi-bq.html
selenium獲取位置,經過PIL保存截圖,分析相同圖像,在經過click事件觸發自動完成連連看, OK,上代碼:
# 前提 程序啓動時 對瀏覽器窗口大小 設置 ''' https://zhuanlan.zhihu.com/p/25171554 https://blog.csdn.net/zwq912318834/article/details/78605486 ''' from selenium import webdriver import time from PIL import Image chromeOpitons = webdriver.ChromeOptions() prefs = { "profile.managed_default_content_settings.images": 1, "profile.content_settings.plugin_whitelist.adobe-flash-player": 1, "profile.content_settings.exceptions.plugins.*,*.per_resource.adobe-flash-player": 1, } chromeOpitons.add_experimental_option('prefs', prefs) time.sleep(5) # 打開 driver = webdriver.Chrome() url = "http://www.4399.com/flash/24238_2.htm" driver.set_window_size(1200, 800) driver.get(url) time.sleep(15) # 獲取截圖 driver.get_screenshot_as_file('screenshot.png') # 獲取指定元素位置 element = driver.find_element_by_id('swfdiv') left = int(element.location['x']) top = int(element.location['y']) print(left, top) print(element.size['width'], element.size['height']) right = int(element.location['x'] + element.size['width']) bottom = int(element.location['y'] + element.size['height']) # 經過Image處理圖像 im = Image.open('screenshot.png') im = im.crop((left, top, right, bottom)) im.save('flash_game.png')
問題來了,chrome瀏覽器支持flash,可是當webdriver 控制瀏覽器的時候,flash死活不能啓用! Firefox也是同樣!
後來當我寫這個blog的時候又search 查緣由,嘗試了下, 竟然能夠了!不過已經用本地player實現了, 有時間了在寫這個webdriver 控制瀏覽器截圖+自動click
說一下當時爲何flash在瀏覽器中能夠啓用, 在webdriver控制中就不能使用, 一行代碼解決,自行體會
sudo apt-get install pepperflashplugin-nonfree
當時放棄了selenium 控制瀏覽器的方法後,就把flash.swf下載下來,經過 gnash flash播放器打開swf文件,PIL截圖,切圖,對比圖片,pyautogui模擬鼠標點擊.......的思路完成自動連連看.思路有了,就一步一步去實現.
查找類似的圖片就是把每一個小方塊都切割出來進行對比,PIL和openCV均可以作到,這裏只提PIL.
PIL截圖很容易Image.save 方法就是保存操做,切圖使用Image.crop方法,crop有四個元組的參數表示爲座標 (left, upper, right, lower), right-left 與lower-upper 計算的大小就是圖片的像素大小.
例以下面這張圖片,切成4X4的小圖片 (源圖片的大小爲 840X1280)
from PIL import Image img = Image.open('resource.jpg') # 固定圖片的大小爲 840X1280 region = (210, 0, 420, 320) # rop((x0,y0,x1,y1)) 圖片裁切 for i in range(0, 4): # width長 for j in range(0, 4): # height高 r = (i * 210, j * 320, 210 + 210 * i, 320 + 320 * j) # 裁切圖片 cropImg = img.crop(r) # 保存裁切後的圖片 cropImg.save('crop' + str(i) + str(j) + '.jpg') print(r)
切割後就生成了12個小圖片,如下是12個小圖片拼接的效果
其實,在這個程序中,也能夠不用保存小方塊, 直接將切好的圖片對象放在list中,用的時候直接從內存讀取. 在程序中生成是便於理解.ok,知道了PIL如何截圖和切圖,咱們如今模擬打開flash播放器,截取屏幕中的遊戲區域與連連看的小方塊區域,並生成小方塊.
在這以前,咱們要知道flash的大小,遊戲區域與屏幕距離大小, 連連看的小方塊區域與flash播放器的高度與左側空間區域大小,每一個小方塊像素大小.
上代碼:
import pyscreenshot as ImageGrab import os import time from PIL import Image os.popen('gnash a.swf -j750 -k450 -X100 -Y100') print('打開gnash swf 完成') time.sleep(5) im = ImageGrab.grab([100, 100, 850, 580]) # X1,Y1,X2,Y2 (X2-X1=750,Y2-Y1=450) # 經過截圖工具查看爲750*480 im.save("1.png") # 切片 # 經過Image處理圖像 im = Image.open('1.png') left = 80 top = 145 im = im.crop((left, top, 560 + left, 320 + top)) im.save('2.png') # 貼片成小方塊 img = Image.open('2.png') for y in range(0, 8): # height高 for x in range(0, 14): # width長 r = (x * 40, y * 40, 40 + 40 * x, 40 + 40 * y) cropImg = img.crop(r) x_play = x + 1 y_play = abs(8 - y) cropImg.save('./openpic_o/x' + str(x_play) + "y" + str(y_play) + '.png') cropImg = cropImg.crop((5, 5, 35, 35)) # 生成的40*40 左右上下裁剪5px cropImg.save('./openpic/x' + str(x_play) + "y" + str(y_play) + '.png') time.sleep(0.01) print(r)
打印的內容:
打開gnash swf 完成 (0, 0, 40, 40) (40, 0, 80, 40) (80, 0, 120, 40) (120, 0, 160, 40) (160, 0, 200, 40) (200, 0, 240, 40) (240, 0, 280, 40) (280, 0, 320, 40) (320, 0, 360, 40) (360, 0, 400, 40) (400, 0, 440, 40) (440, 0, 480, 40) (480, 0, 520, 40) (520, 0, 560, 40) (0, 40, 40, 80) (40, 40, 80, 80) (80, 40, 120, 80) (120, 40, 160, 80) (160, 40, 200, 80) (200, 40, 240, 80) (240, 40, 280, 80) (280, 40, 320, 80) (320, 40, 360, 80) (360, 40, 400, 80) (400, 40, 440, 80) (440, 40, 480, 80) (480, 40, 520, 80) (520, 40, 560, 80) (0, 80, 40, 120) (40, 80, 80, 120) (80, 80, 120, 120) (120, 80, 160, 120) (160, 80, 200, 120) (200, 80, 240, 120) (240, 80, 280, 120) (280, 80, 320, 120) (320, 80, 360, 120) (360, 80, 400, 120) (400, 80, 440, 120) (440, 80, 480, 120) (480, 80, 520, 120) (520, 80, 560, 120) (0, 120, 40, 160) (40, 120, 80, 160) (80, 120, 120, 160) (120, 120, 160, 160) (160, 120, 200, 160) (200, 120, 240, 160) (240, 120, 280, 160) (280, 120, 320, 160) (320, 120, 360, 160) (360, 120, 400, 160) (400, 120, 440, 160) (440, 120, 480, 160) (480, 120, 520, 160) (520, 120, 560, 160) (0, 160, 40, 200) (40, 160, 80, 200) (80, 160, 120, 200) (120, 160, 160, 200) (160, 160, 200, 200) (200, 160, 240, 200) (240, 160, 280, 200) (280, 160, 320, 200) (320, 160, 360, 200) (360, 160, 400, 200) (400, 160, 440, 200) (440, 160, 480, 200) (480, 160, 520, 200) (520, 160, 560, 200) (0, 200, 40, 240) (40, 200, 80, 240) (80, 200, 120, 240) (120, 200, 160, 240) (160, 200, 200, 240) (200, 200, 240, 240) (240, 200, 280, 240) (280, 200, 320, 240) (320, 200, 360, 240) (360, 200, 400, 240) (400, 200, 440, 240) (440, 200, 480, 240) (480, 200, 520, 240) (520, 200, 560, 240) (0, 240, 40, 280) (40, 240, 80, 280) (80, 240, 120, 280) (120, 240, 160, 280) (160, 240, 200, 280) (200, 240, 240, 280) (240, 240, 280, 280) (280, 240, 320, 280) (320, 240, 360, 280) (360, 240, 400, 280) (400, 240, 440, 280) (440, 240, 480, 280) (480, 240, 520, 280) (520, 240, 560, 280) (0, 280, 40, 320) (40, 280, 80, 320) (80, 280, 120, 320) (120, 280, 160, 320) (160, 280, 200, 320) (200, 280, 240, 320) (240, 280, 280, 320) (280, 280, 320, 320) (320, 280, 360, 320) (360, 280, 400, 320) (400, 280, 440, 320) (440, 280, 480, 320) (480, 280, 520, 320) (520, 280, 560, 320)
再看同級目錄下有兩張圖片,1.png與2.png
在openpic_o目錄,就能夠看到裁剪後的小方塊,方塊大小就是原小方塊大小 40*40像素的.
在openpic目錄, 看到的是在小方塊的基礎之上,又裁剪了一次,把小方塊的上 下 左 右 各邊框都裁剪了5個像素,爲何要這樣操做呢? 由於40*40px的圖片即便flash引用的相同圖片,但因分辨率問題,PIL裁剪後也有可能出現毛邊,爲了排除這個干擾才又裁剪了一次.以下圖
有沒有發現上面代碼中有這麼兩行代碼
x_play = x + 1
y_play = abs(8 - y)
爲何是這樣呢?咱們先來看下圖, 下圖是咱們平時用的平面二位直角座標,是從中心點(0,0)開始, 橫向左爲負數,橫向右爲正數,縱軸下爲負數,縱軸上爲正數,可是電腦上的座標都是左上角(0,0)爲座標系,爲了便於理解,咱們就把Y軸進倒置,由於高度有8個小方塊,因此就是8-y的絕對值.至於x軸,由於從左到右也是依次增大,so,X軸不變.
但爲何又是 x_play = x + 1 先看生成的小方塊,小方塊的名稱是從 x1y1, x1y2,x1y3....x2y1...... 也就是從1開始,若是(1,1)就是起始的座標點的話,四個邊框的點座標就只能直線鏈接或從裏面消除點後再經過裏面點進行迂迴鏈接,這樣便一開始就出問題了,由於四個邊框的點是能夠經過兩個拐角鏈接的,換句話說,四個邊框的外圍還有一圈空的點,這些空的點座標是能夠聯通的,因此才能構成邊框上的點聯通.也就有了(0,1),(1,0),(0,2),(0,3)...等座標了,這裏的x+1,也就是直接初試從(1,1)開始,給空的座標點作預留.看下圖.
咱們定義link_ponts 爲可聯通的點座標
link_points = [] # 可聯通點座標 for x in range(0, 14 + 2): # 網格個數+2 能夠認爲網格小方塊的外圍有一圈空的方塊 for y in range(0, 8 + 2): # 網格個數+2 load_name = "x" + str(x) + "y" + str(y) if not os.path.exists("./openpic/" + load_name + '.png'): link_points.append(load_name) print(link_points)
打印link_points
['x0y0', 'x0y1', 'x0y2', 'x0y3', 'x0y4', 'x0y5', 'x0y6', 'x0y7', 'x0y8', 'x0y9', 'x1y0', 'x1y9', 'x2y0', 'x2y9', 'x3y0', 'x3y9', 'x4y0', 'x4y9', 'x5y0', 'x5y9', 'x6y0', 'x6y9', 'x7y0', 'x7y9', 'x8y0', 'x8y9', 'x9y0', 'x9y9', 'x10y0', 'x10y9', 'x11y0', 'x11y9', 'x12y0', 'x12y9', 'x13y0', 'x13y9', 'x14y0', 'x14y9', 'x15y0', 'x15y1', 'x15y2', 'x15y3', 'x15y4', 'x15y5', 'x15y6', 'x15y7', 'x15y8', 'x15y9']
這一步驟, 就要提到昨天寫的blog
圖片的類似度無論知乎大神用的是
cv2.subtract
仍是
numpy.subtract
我這裏處理的小方塊都有問題,很簡單的(x1y2.png),(x2y4.png), (x2y6.png)識別就有問題.說x1y2.png與x2y6.png不相等,我就笑了.
(x1y2.png) (x2y4.png)
(x2y6.png)
後來我就用了openCV圖像的類似度對比,哈希算法也行,灰度RGB通道直方圖算法匹配也行, 可是感受通道直方圖計算出來的值更準確, 只能說沒有更好的,只有更適合的.
可是有個問題,通道直方圖計算匹配的時候賊慢賊慢的,個人電腦計算的時候執行了8秒多.
def has_group_list(list_group, m2): ret = False if list_group: for klist in list_group: if m2 in klist: ret = True break return ret # 經過獲得每一個通道的直方圖來計算類似度 def classify_hist_with_split(image1, image2, size=(256, 256)): # 將圖像resize後,分離爲三個通道,再計算每一個通道的類似值 image1 = cv2.resize(image1, size) image2 = cv2.resize(image2, size) sub_image1 = cv2.split(image1) sub_image2 = cv2.split(image2) sub_data = 0 for im1, im2 in zip(sub_image1, sub_image2): sub_data += calculate(im1, im2) sub_data = sub_data / 3 return sub_data # 計算單通道的直方圖的類似值 def calculate(image1, image2): hist1 = cv2.calcHist([image1], [0], None, [256], [0.0, 255.0]) hist2 = cv2.calcHist([image2], [0], None, [256], [0.0, 255.0]) # 計算直方圖的重合度 degree = 0 for i in range(len(hist1)): if hist1[i] != hist2[i]: degree = degree + (1 - abs(hist1[i] - hist2[i]) / max(hist1[i], hist2[i])) else: degree = degree + 1 degree = degree / len(hist1) return degree list_group = [] def get_list_group(): tmpgroup = [] tmpIJ = '' for i in range(1, 15): for j in range(1, 9): # ij 座標1 m1 = 'x' + str(i) + 'y' + str(j) for n in range(1, 15): for m in range(1, 9): # nm 座標2 m2 = 'x' + str(n) + 'y' + str(m) img1 = cv2.imread('openpic/' + m1 + ".png") img2 = cv2.imread('openpic/' + m2 + ".png") hn = classify_hist_with_split(img1, img2) if hn > 0.80: if tmpIJ != m1: if tmpgroup: list_group.append(tmpgroup) tmpgroup = [] print(tmpIJ) print(list_group) tmpIJ = m1 else: if not has_group_list(list_group, m2): if m1 not in tmpgroup: tmpgroup.append(m1) tmpgroup.append(m2) get_list_group() print(list_group)
打印list_group
[['x1y1', 'x1y3', 'x5y6', 'x8y4', 'x13y8', 'x14y8'], ['x1y2', 'x1y8', 'x2y4', 'x3y8', 'x6y8', 'x7y2'], ['x1y4', 'x11y1'], ['x1y5', 'x4y7', 'x6y6', 'x11y3', 'x11y5', 'x12y8'], ['x1y6', 'x3y2', 'x4y8', 'x8y1', 'x9y7', 'x10y4', 'x11y4', 'x14y3'], ['x1y7', 'x2y1', 'x3y3', 'x4y3', 'x6y4', 'x8y5'], ['x2y2', 'x2y3', 'x7y8', 'x12y2', 'x13y1', 'x14y5'], ['x2y5', 'x6y3', 'x6y5', 'x8y8'], ['x2y6', 'x6y2'], ['x2y7', 'x5y5'], ['x2y8', 'x3y6', 'x12y5', 'x14y4'], ['x3y1', 'x5y1', 'x9y1', 'x10y3', 'x13y2', 'x13y5'], ['x3y4', 'x7y3', 'x7y4', 'x10y2', 'x11y8', 'x12y6', 'x13y4', 'x13y7', 'x14y2', 'x14y6'], ['x3y5', 'x4y4', 'x7y6', 'x9y8'], ['x3y7', 'x14y1'], ['x4y1', 'x9y2', 'x10y8', 'x11y2'], ['x4y2', 'x4y6', 'x12y3', 'x12y4'], ['x4y5', 'x5y7', 'x6y1', 'x9y5'], ['x5y2', 'x13y6'], ['x5y3', 'x7y1'], ['x5y4', 'x8y2'], ['x5y8', 'x9y3', 'x10y5', 'x11y7', 'x12y7', 'x13y3'], ['x6y7', 'x9y6'], ['x7y5', 'x8y3', 'x9y4', 'x10y7'], ['x7y7', 'x11y6'], ['x8y6', 'x12y1'], ['x8y7', 'x10y1'], ['x10y6', 'x14y7']]
問我爲何視頻中沒有等待8秒時間, 笑笑笑.....
看代碼:
""" list_group 寫入文件 """ fileObject = open('data_list_group.txt', 'w') ret = json.dump(link_points,fileObject) fileObject.close() """ 讀取 list_group """ f= open('data_list_group.txt','r') list_group=[] for i in f.readlines(): list_group.append(i.strip('\n')) print(list_group)
如今算法有了, 小方塊座標有了, 可聯通的link_points列表有了,類似圖片分組list_group有了,就差模擬鼠標點擊了,pyautogui登場
畫個方形我也會:
import pyautogui for i in range(3): pyautogui.moveTo(300, 300, duration=0.25) pyautogui.moveTo(400, 300, duration=0.25) pyautogui.moveTo(400, 400, duration=0.25) pyautogui.moveTo(300, 400, duration=0.25)
OK! 例子在手, 說走就走!
config.py
# -*- coding:utf-8 -*- """常量配置文件""" PLAYER = 'gnash' # player SWF_PATH = 'play.swf' GAME_Width = 750 # flash 實際 750*450 GAME_HEIGHT = 450 PLAYER_JK = '-j' + str(GAME_Width) + ' -k' + str(GAME_HEIGHT) # jk 窗口寬高,參考 gnash -h PLAYER_X = 100 # XY 窗口 距離(0.0)座標的位置長度與高度 PLAYER_Y = 100 # XY 窗口 距離(0.0)座標的位置長度與高度 GRID_SIZE = 40 # 網格大小 GRID_X_NUM = 14 # 網格X軸個數 GRID_Y_NUM = 8 # 網格Y軸個數 X11_HEIGHT = 30 # Ubuntu X11 窗體 title欄高度 PLAY_BTN = (315, 380) # player flash 開始遊戲按鈕座標 CENTER_LEFT = 80 # 中心網格左側距離 CENTER_TOP = 145 # 中心網格頂部高度距離 GRID_DIR = "img/" # 網格小方塊目錄 GRID_MIC_DIR = 'img_micro/' # 裁剪後小方塊保存目錄 GRID_ZOOM = 5 # 裁剪小方塊 上 下 左 右 5px
play.swf
回到本頁頂部,本身手動玩哈.
screenshot.py
# -*- coding:utf-8 -*- import pyscreenshot as ImageGrab from PIL import Image import time, os from config import * class screenshot_class: """ 截取 player 窗體圖片在init中 """ def __init__(self): im = ImageGrab.grab([PLAYER_X, PLAYER_Y, PLAYER_X + GAME_Width, PLAYER_Y + GAME_HEIGHT + X11_HEIGHT]) # X1,Y1,X2,Y2 (X2-X1=750,Y2-Y1=450+30) im.save("1.png") print("截取player圖片完成") time.sleep(1) im = Image.open('1.png') im = im.crop((CENTER_LEFT, CENTER_TOP, GRID_X_NUM * GRID_SIZE + CENTER_LEFT, GRID_Y_NUM * GRID_SIZE + CENTER_TOP)) # 80,145,40*14+80,40*8+155 im.save('2.png') print("裁剪截圖處理完成") def save_grid(self): """ 保存小方塊 """ print("cut cut cut .......") if not os.path.exists(GRID_DIR): os.mkdir(GRID_DIR) # 小方塊存放目錄 if not os.path.exists(GRID_MIC_DIR): os.mkdir(GRID_MIC_DIR) # 裁剪後的小方塊存放目錄 img = Image.open('2.png') for y in range(0, GRID_Y_NUM): # height高 for x in range(0, GRID_X_NUM): # width長 r = (x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE + GRID_SIZE * x, GRID_SIZE + GRID_SIZE * y) crop_img = img.crop(r) x_play = x + 1 y_play = abs(GRID_Y_NUM - y) # 原座標是從左上開始計算Y軸,將其轉換爲從(0,0) 開始 crop_img.save(GRID_DIR + "x" + str(x_play) + "y" + str(y_play) + '.png') crop_img = crop_img.crop((GRID_ZOOM, GRID_ZOOM, GRID_SIZE - GRID_ZOOM, GRID_SIZE - GRID_ZOOM)) # 生成的40*40 左右上下裁剪5px (5, 5, 35, 35) crop_img.save(GRID_MIC_DIR + "x" + str(x_play) + "y" + str(y_play) + '.png') time.sleep(1) print("cut 小方塊完成") if __name__ == '__main__': print("from img") screenshot_class()
autolinlink.py
# -*- coding:utf-8 -*- import pyautogui from itertools import combinations import cv2 import os, time from config import * import screenshot class autolinlink: """autolinlink""" @staticmethod def open_swf(): """打開player""" os.popen(PLAYER + " " + SWF_PATH + " " + PLAYER_JK + " " + "-X" + str(PLAYER_X) + " -Y" + str(PLAYER_Y)) print("打開player完成") @staticmethod def play_btn(): """play btn 開始按鈕""" pyautogui.moveTo(PLAYER_X + PLAY_BTN[0], PLAYER_Y + PLAY_BTN[1], duration=3.6) # 移動鼠標至play位置 pyautogui.click() print("已進入遊戲") def screen_shot(self): time.sleep(0.2) # 休眠0.2s 等待background後的網格小方塊加載 screenshot.screenshot_class() screenshot.screenshot_class.save_grid(self) def get_points(self): """ 獲取 可聯通點 座標 :return: list_points """ print("獲取可聯通點座標") link_points = [] # 可聯通點座標 for x in range(0, GRID_X_NUM + 2): # 網格個數+2 能夠認爲網格小方塊的外圍有一圈空的方塊 for y in range(0, GRID_Y_NUM + 2): # 網格個數+2 load_name = "x" + str(x) + "y" + str(y) if not os.path.exists(GRID_MIC_DIR + load_name + '.png'): link_points.append(load_name) return link_points def get_group(self): """ 獲取 類似圖片group :return: list_group """ print("計算圖片類似度,大概持續8秒左右.....") list_group = [] # f = open('data_list_group.txt') # list_group = json.load(f) # return list_group tmp_group = [] # 每一個相同小方塊的group分組 tmpIJ = '' # 每一個相同座標分組中,第一個座標1,以後循環與該點匹配判斷是否在同一個分組 for i in range(1, GRID_X_NUM + 1): # GRID_X_NUM+1 [1-14]區間 for j in range(1, GRID_Y_NUM + 1): # ij 座標1 m1 = 'x' + str(i) + 'y' + str(j) for n in range(1, GRID_X_NUM + 1): for m in range(1, GRID_Y_NUM + 1): # nm 座標2 m2 = 'x' + str(n) + 'y' + str(m) img1 = cv2.imread("./" + GRID_MIC_DIR + m1 + ".png") img2 = cv2.imread(GRID_MIC_DIR + m2 + ".png") hn = self.classify_hist_with_split(img1, img2) if hn > 0.80: # 大於0.8認爲小方塊相同 if tmpIJ != m1: if tmp_group: list_group.append(tmp_group) tmp_group = [] tmpIJ = m1 else: if not self.has_group_list(list_group, m2): if m1 not in tmp_group: tmp_group.append(m1) tmp_group.append(m2) return list_group def classify_hist_with_split(self, image1, image2, size=(256, 256)): """經過獲得每一個通道的直方圖來計算類似度""" # 將圖像resize後,分離爲三個通道,再計算每一個通道的類似值 image1 = cv2.resize(image1, size) image2 = cv2.resize(image2, size) sub_image1 = cv2.split(image1) sub_image2 = cv2.split(image2) sub_data = 0 for im1, im2 in zip(sub_image1, sub_image2): sub_data += self.calculate(im1, im2) sub_data = sub_data / 3 return sub_data def calculate(self, image1, image2): """計算單通道的直方圖的類似值""" hist1 = cv2.calcHist([image1], [0], None, [256], [0.0, 255.0]) hist2 = cv2.calcHist([image2], [0], None, [256], [0.0, 255.0]) # 計算直方圖的重合度 degree = 0 for i in range(len(hist1)): if hist1[i] != hist2[i]: degree = degree + (1 - abs(hist1[i] - hist2[i]) / max(hist1[i], hist2[i])) else: degree = degree + 1 degree = degree / len(hist1) return degree def has_group_list(self, list_group, p): """判斷當前點座標在不在group中""" ret = False if list_group: for klist in list_group: if p in klist: ret = True break return ret def run(self, list_group, link_points, tmp_points_list=[]): """執行操做""" total = (GRID_X_NUM + 2) * (GRID_Y_NUM + 2) sum_link_points = len(link_points) haslink = False if link_points != total: for k in list_group: # k=(x2y1,x2y4,x12y12,x90y90) for i in combinations(k, 2): # permutations 排列重複組合,combinations 組合不重複 if i[0] not in link_points and i[1] not in link_points: # 排除 當前k () 已在link_points中 llk = self.linlink(i, link_points) if llk: tmp_points_list.append(i[0]) tmp_points_list.append(i[1]) if i == ('x5y4', 'x8y2'): time.sleep(1) # flash "加時"魔法干擾,臨時sleep 1s time.sleep(0.5) self.click(i[0], i[1]) haslink = True break if sum_link_points != total and haslink == False: print('剩餘點沒法消除') elif sum_link_points == total: print('完成') else: print('再來一次') list_group, link_points, tmp_points_list = self.update_group(list_group, link_points, tmp_points_list) return self.run(list_group, link_points, tmp_points_list) def update_group(self, list_group, link_points, tmp_points_list): link_points += tmp_points_list # 刪除list_group 已鏈接數據 for i in list_group: for t in tmp_points_list: if t in i: i.remove(t) tmp_points_list = [] return list_group, link_points, tmp_points_list def linlink(self, xy, link_points): ret = False # 判斷點是否能聯通 x1 = xy[0].split('y')[0] y1 = 'y' + xy[0].split('y')[1] x2 = xy[1].split('y')[0] y2 = 'y' + xy[1].split('y')[1] result = self.lineCase(xy, link_points, x1, y1, x2, y2) if result: print(xy, '直線鏈接可聯通') ret = True else: result = self.onceCorner(xy, link_points, x1, y1, x2, y2) # 一次拐角 if result: print(xy, '一次拐角可聯通') ret = True else: result = self.doubleCorner(xy, link_points, x1, y1, x2, y2) # 兩個拐角 if result: print(xy, '兩次拐角可聯通') ret = True else: # print(xy, '兩次拐角貌似不可聯通') pass return ret # 兩次拐角 def doubleCorner(self, xy, link_points, x1, y1, x2, y2): ret = False first_match = 1 for px in range(GRID_X_NUM + 2): # (GRID_X_NUM + 2) * (GRID_Y_NUM + 2) for py in range(GRID_Y_NUM + 2): tmpx = 'x' + str(px) tmpy = 'y' + str(py) # 以第一個點座標尋找拐點, if (tmpx == x1 and tmpy != y1) or (tmpy == y1 and tmpx != x1): # 第一個點座標四個方向但除了本身 # 任意拐點,(px,py)與第一個點座標爲直線狀況,與第二個點爲一次拐角狀況 lineC = self.lineCase(xy, link_points, x1, y1, tmpx, tmpy, corner=True) onceC = self.onceCorner(xy, link_points, tmpx, tmpy, x2, y2) if lineC and onceC and first_match == 1: first_match += 1 ret = True break return ret # 一次拐角 def onceCorner(self, xy, link_points, x1, y1, x2, y2): ret = False C = (x2, y1) # 用 第一個的Y,第二個X # C,D 爲中間(p,Tmp) 的過渡點 D = (x1, y2) # 用 第一個X,第二個Y # C點分別與第一個點 與第二點 聯通 ClineCaseY = self.lineCase(xy, link_points, x1, y1, C[0], C[1], True) # 即 (x1,y1,x2,y1) ClineCaseX = self.lineCase(xy, link_points, x2, y2, C[0], C[1], True) # 即 (x2,y2,x2,y1) if ClineCaseX and ClineCaseY: # C點與第一個點的X軸聯通,與第二個點的Y軸聯通 ret = True DlineCaseX = self.lineCase(xy, link_points, x1, y1, D[0], D[1], True) # 即 (x1,y1,x1,y2) DlineCaseY = self.lineCase(xy, link_points, x2, y2, D[0], D[1], True) # 即 (x2,y2,x1,y2) if DlineCaseX and DlineCaseY: # D點與第一個點的X軸聯通,與第二個點的Y軸聯通 ret = True return ret def lineCase(self, xy, link_points, x1, y1, x2, y2, corner=False): ret = False if x1 == x2: # x縱軸 相同 xmin = min(int(y1[1:]), int(y2[1:])) # 字符串不能進行max min比較 int(max(x1,x2)[1:]) # x1=9 x2=13 xmax = max(int(y1[1:]), int(y2[1:])) point_num = xmax - xmin - 1 if point_num == 0 and corner == False: ret = True elif point_num == 0 and corner: # 相連 and 來自拐角 if x2 + y2 in link_points: # 判斷後(xy,tmp) tmp中間點 點是否爲空 ret = True else: point_num_able = 0 for i in range(xmin + 1, xmax): if x1 + "y" + str(i) in link_points: point_num_able += 1 if point_num == point_num_able and point_num > 0 and ((x2 + y2 in link_points and corner) or corner == False): # 可聯通點的個數等於同軸points點的個數 ret = True if y1 == y2: ymin = min(int(x1[1:]), int(x2[1:])) # 字符串不能進行max min比較 int(max(x1,x2)[1:]) # x1=9 x2=13 ymax = max(int(x1[1:]), int(x2[1:])) point_num = ymax - ymin - 1 if point_num == 0 and corner == False: ret = True elif point_num == 0 and corner: if x2 + y2 in link_points: ret = True else: point_num_able = 0 for i in range(ymin + 1, ymax): if "x" + str(i) + y1 in link_points: point_num_able += 1 if (point_num == point_num_able) and point_num > 0 and (( x2 + y2 in link_points and corner) or corner == False): # 可聯通點的個數等於同軸points點的個數 # x2+y2 in link_points and corner做爲拐角的點也必須爲空 ret = True return ret def click(self, p1, p2): """模擬鼠標點擊""" # ('x4y8', 'x8y1') # 設置x(0,0),y(0,0)座標 init_pint = (PLAYER_X + CENTER_LEFT - GRID_SIZE + 20, CENTER_TOP + X11_HEIGHT + PLAYER_Y + ((GRID_Y_NUM) * GRID_SIZE) - 20) # x=80-40 , y= 145+30+9*40 x+20 與y-20可讓鼠標移動到小方塊的中心 # x軸 相加, Y軸 相減 pX1 = init_pint[0] + int(p1.split('y')[0][1:]) * GRID_SIZE pY1 = init_pint[1] - int(p1.split('y')[1]) * GRID_SIZE pX2 = init_pint[0] + int(p2.split('y')[0][1:]) * GRID_SIZE pY2 = init_pint[1] - int(p2.split('y')[1]) * GRID_SIZE pyautogui.moveTo(pX1, pY1, duration=0.05) pyautogui.click() pyautogui.moveTo(pX2, pY2, duration=0.05) pyautogui.click() if __name__ == '__main__': app = autolinlink() app.open_swf() app.play_btn() app.screen_shot() link_points = app.get_points() list_group = app.get_group() if link_points and list_group: app.run(list_group, link_points)
源文件:
http://u.163.com/Bhfyp9nm 提取碼: LkYikiOz