亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

用Python實現QQ游戲大家來找茬輔助工具

系統 2015 0

好久沒寫技術相關的文章,這次寫篇有意思的,關于一個有意思的游戲――QQ找茬,關于一種有意思的語言――Python,關于一個有意思的庫――Qt。

這是一個用于QQ大家來找茬(美女找茬)的輔助外掛,開發的原因是看到老爸天天在玩這個游戲,分數是慘不忍睹的負4000多。他玩游戲有他的樂趣,并不很在意輸贏,我做這個也只是自我娛樂,順便討他個好,畢竟我們搞編程的實在難有機會在父輩面前露露手。本來是想寫個很簡單的東西,但由于過程中老爸的多次嘲諷,逼得我不得不盡力完善,最后形成了一個小小的產品。

接觸Python是2010年,相見恨晚,去年拿它寫了些小玩意,離職前給前公司留下了一個Python+wxPython的工作工具,還挺受歡迎。換公司后努力學習C++&Qt,很后悔當初選擇了wxPython而不是PyQt,沒能一脈相承。使用Qt越久,不得不越來越喜歡,寫這個東西正好就用上了。

話不多說,進入正題。這不是一篇完整的代碼講解,只是過程中的一些技術做個分享,包括后來被放棄的一些技術點。當初搜索這些東西也挺費力的,在這做個筆記,后來者也許能搜到收益。
先上個圖:

用Python實現QQ游戲大家來找茬輔助工具_第1張圖片

話說這位是游戲中出鏡最多的MM,和QQ什么關系啊?
輔助工具在游戲中增加了兩個按鈕,點擊“對比”則自動找“茬”,用藍色小框標識,點擊“擦除”清除標識。

游戲窗口探查
這得用PyWin32庫,它是對windows接口的Python封裝,VC能做的它基本都行。
下載地址:http://sourceforge.net/projects/pywin32/,但不能直接點Download圖標,不然下下來是一個Readme.txt,點“Browse All Files”尋找需要的版本。

            
#coding=gbk  import win32gui
 
game_hwnd = win32gui.FindWindow("#32770", "大家來找茬") print game_hwnd
          


QQ找茬是個對話框窗口,Class是“#32770”,這種窗口桌面上有很多,所以還配合了標題“大家來找茬”匹配,又因為是中文,所以第一行指定了使用gbk編碼,否則要么找不到,要么運行出錯。

游戲圖片提取
提取圖片采用了截屏的方式,找到窗口后將窗口提到最前,再作窗口截屏。截屏使用了大名鼎鼎的Python Imaging Library (PIL)庫。

            
import ImageGrab import win32con
 
win32gui.ShowWindow(game_hwnd, win32con.SW_RESTORE) # 強行顯示界面后才好截圖 win32gui.SetForegroundWindow(game_hwnd) # 將游戲窗口提到最前  # 裁剪得到全圖 game_rect = win32gui.GetWindowRect(game_hwnd)
src_image = ImageGrab.grab((game_rect[0] + 9, game_rect[1] + 190, game_rect[2] - 9, game_rect[1] + 190 + 450)) # src_image.show()  # 分別裁剪左右內容圖片 left_box = (9, 0, 500, 450)
right_box = (517, 0, 517 + 500, 450)
image_left = src_image.crop(left_box)
image_right = src_image.crop(right_box) # image_left.show() # image_right.show()
          

上面用到的坐標都為為了演示代碼簡單填的,實際上使用了變量參數,而且要區分分辨率什么的。
PIL是一個強大的Python圖形庫(使用文檔),待會的對比分析也須要用到。ImageGrab是PIL的一個模塊,用于圖像的抓取。不帶參數的ImageGrab.grab()進行全屏截屏,返回一個Image對象,也可使用一個元組作為參數指定要截取的范圍(左上與右下兩點的坐標),這兩種截屏都是不帶鼠標指針的,還有一個ImageGrab.grabclipboard()可從系統剪貼板采集圖像。
得到Image圖像后可用show()方法,使用系統默認的圖像查看工具打開,方便調試,也可以用save(filename)保存成文件,對應的可以Image.open(filename)打開獲得。

grab得到了一個包含左右圖片的Image對象后,用crop(box)方法可裁剪得到其中指定的區域,分別拿到左右兩個游戲圖片。

對比獲得兩圖內容不同的區域
很自然想到把兩圖裁剪成N個小圖片分別對比,左右統一區域對應的小圖片不相等則為“茬”區,唯一的問題是怎么判斷兩個圖片內容不一致?

一開始以為很會有些麻煩,直到發現了Image.histogram()函數,該函數用于得到圖像的顏色直方圖。我平常也愛好攝影,知道直方圖可以表示一張圖片中各種亮度(或顏色)的數量,兩張自然圖片的直方圖基本是不一樣的,除非兩圖對稱、顏色一致但排列不一,但就算如此,將兩圖繼續分割下去,其子圖的直方圖也會不一樣。直方圖就是一種圖形到數值的轉換,對比兩圖的顏色數值就可知是否存在差異。

一張用RBG顏色格式的圖像,histogram()函數將返回一個長度為768的數組,第0-255表示紅色的0-255,第256-511表色綠色的0-255,第512-767表色藍色的0-255,數值表示該顏色像素的個數。因此,histogram()列表所有成員之和等于改圖像的像素值 x 3。

寫了一個函數,用來獲得兩圖比較的數值差:

            
ef compare(image_a, image_b): '''返回兩圖的差異值
  返回兩圖紅綠藍差值萬分比之和''' histogram_a = image_a.histogram()
  histogram_b = image_b.histogram() if len(histogram_a) != 768 or len(histogram_b) != 768: return None 
  red_a = 0 red_b = 0 for i in xrange(0, 256):
    red_a += histogram_a[i + 0] * i
    red_b += histogram_b[i + 0] * i
  diff_red = 0 if red_a + red_b > 0:
    diff_red = abs(red_a - red_b) * 10000 / max(red_a, red_b)
 
  green_a = 0 green_b = 0 for i in xrange(0, 256):
    green_a += histogram_a[i + 256] * i
    green_b += histogram_b[i + 256] * i
  diff_green = 0 if green_a + green_b > 0:
    diff_green = abs(green_a - green_b) * 10000 / max(green_a, green_b)
 
  blue_a = 0 blue_b = 0 for i in xrange(0, 256):
    blue_a += histogram_a[i + 512] * i
    blue_b += histogram_b[i + 512] * i
  diff_blue = 0 if blue_a + blue_b > 0:
    diff_blue = abs(blue_a - blue_b) * 10000 / max(blue_a, blue_b)
 return diff_red, diff_green, diff_blue
          

將函數返回的紅綠藍差值相加,如果超過了預定定的閥值2000,則表示該區域不同。這個計算方式有點“土”,但對這次要解決的問題很有效,就沒再繼續改進。

            
將左右大圖裁剪成多個小圖分別進行對比 result = [[0 for a in xrange(0, 50)] for b in xrange(0, 45)] for col in xrange(0, 50):
  for row in xrange(0, 45):
    clip_box = (col * 10, row * 10, (col + 1) * 10, (row + 1) * 10)
    clip_image_left = image_left.crop(clip_box)
    clip_image_right = image_right.crop(clip_box)
    clip_diff = self.compare(clip_image_left, clip_image_right)
 
    if sum(clip_diff) > 2000:
      result[row][col] = 1
          


大圖是500x450,分隔成10x10的小塊,定義一個50x45的二位數組存儲結果,分別比較后將差值大于閥值的數組區域標記為1.
在游戲上標記兩邊不同的區域

最初我用了PyWin32的一些函數,獲得游戲窗口句柄后直接在上面繪制,但我不太熟悉Windows編程,不知道如何解決游戲自身重繪后將我的標記擦除的問題,然后搬來了Qt。用Qt創建了一個和游戲大小一樣透明的QWidget窗口,疊加在游戲窗口上,用遮罩來繪制標記。標記數據已記錄在result數組中,在指定的位置繪制一個方格則表示該區域左右不同,要注意兩個方格間的邊界不要繪制,避免格子太多干擾了游戲。除標記外,還繪制了兩個按鈕來觸發對比與擦除。

            
ef paintEvent(self, event): # 重置遮罩圖像 self.pixmap.fill()
 # 創建繪制用的QPainter,筆畫粗細為2像素 # 事先已經在Qt窗體上鋪了一個藍色的背景圖片,因此投過遮罩圖案看下去標記線條是藍色的 p = QPainter(self.pixmap)
  p.setPen(QPen(QBrush(QColor(0, 0, 0)), 2))
 for row in xrange(len(self.result)): for col in xrange(len(self.result[0])): if self.result[row][col] != 0: # 定一個基點,避免算數太難看 base_l_x = self.ANCHOR_LEFT_X + self.CLIP_WIDTH * col
        base_r_x = self.ANCHOR_RIGHT_X + self.CLIP_WIDTH * col
        base_y = self.ANCHOR_Y + self.CLIP_HEIGHT * row
 if row == 0 or self.result[row - 1][col] == 0: # 如果是第一行,或者上面的格子為空,畫一條上邊 p.drawLine(base_l_x, base_y, base_l_x + self.CLIP_WIDTH, base_y)
          p.drawLine(base_r_x, base_y, base_r_x + self.CLIP_WIDTH, base_y) if row == len(self.result) - 1 or self.result[row + 1][col] == 0: # 如果是最后一行,或者下面的格子為空,畫一條下邊 p.drawLine(base_l_x, base_y + self.CLIP_HEIGHT, base_l_x + self.CLIP_WIDTH, base_y + self.CLIP_HEIGHT)
          p.drawLine(base_r_x, base_y + self.CLIP_HEIGHT, base_r_x + self.CLIP_WIDTH, base_y + self.CLIP_HEIGHT) if col == 0 or self.result[row][col - 1] == 0: # 如果是第一列,或者左邊的格子為空,畫一條左邊 p.drawLine(base_l_x, base_y, base_l_x, base_y + self.CLIP_HEIGHT)
          p.drawLine(base_r_x, base_y, base_r_x, base_y + self.CLIP_HEIGHT) if col == len(self.result[0]) - 1 or self.result[row][col + 1] == 0: # 如果是第一列,或者右邊的格子為空,畫一條右邊 p.drawLine(base_l_x + self.CLIP_WIDTH, base_y, base_l_x + self.CLIP_WIDTH, base_y + self.CLIP_HEIGHT)
          p.drawLine(base_r_x + self.CLIP_WIDTH, base_y, base_r_x + self.CLIP_WIDTH, base_y + self.CLIP_HEIGHT)
 # 在遮罩上繪制按鈕區域,避免按鈕被遮罩擋住看不見 p.fillRect(self.btn_compare.geometry(), QBrush(QColor(0, 0, 0)))
  p.fillRect(self.btn_toggle.geometry(), QBrush(QColor(0, 0, 0)))
 # 將遮罩圖像作為遮罩 self.setMask(QBitmap(self.pixmap))
          


這里我沒有替換變量,太麻煩了,能看清楚算法就行。
讓PyQt程序在任務欄隱藏
為了讓PyQt程序不出現在任務欄,構造QWidget設置了這些屬性

            
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Popup | Qt.Tool)
          


讓PyQt程序加入系統托盤、資源文件使用
PyQt添加托盤菜單非常容易,幾行代碼就可以

            
創建托盤 self.icon = QIcon(":\icon.png")
 self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setIcon(self.icon) self.trayIcon.setToolTip(u"QQ找茬助手") self.trayIcon.show()
 # 托盤氣泡消息 self.trayIcon.showMessage(u"QQ找茬助手", u"QQ找茬助手已經待命,進入游戲即可激活")
 # 托盤菜單 self.action = QAction(u"退出QQ找茬助手", self, triggered = sys.exit) # 觸發點擊后調用sys.exit()命令,即退出 self.menu = QMenu(self) self.menu.addAction(self.action) self.trayIcon.setContextMenu(self.menu)

          

用Python實現QQ游戲大家來找茬輔助工具_第2張圖片

最初我是用的托盤圖標是一個.ico文件,執行腳本可以正常顯示,但打包成exe后執行在托盤上顯示為一個空白圖標,用Python的idle工具編譯運行也是空白。嘗試多次后發現:PyQt的托盤圖標不能使用.ico文件,否則會顯示空白,換成png格式素材就沒問題!
PyQt資源文件打包
Qt使用一個.qrc格式的xml文件管理素材,代碼用可用:\xxx\xxx.png的方式引用資源文件中的素材,這在PyQt中同樣支持。
這里我創建了一個resources.qrc文件

            
              
                
                  icon.png
                
              
            
          


然后用

            
pyrcc4 resources.qrc > resources.py
          


命令,將資源文件轉成一個python模塊,在代碼中import resources,則可以用這樣的方式使用圖像素材

            
self.icon = QIcon(":\icon.png")
          


打包成可執行程序
這個工具是給別人用的,肯定不能以py腳本的形式發布,我使用了cx_Freeze來打包為可執行程序。
為此要寫一個打包命令腳本convert2exe.py

            
#!Python #coding=gbk  # python轉exe腳本 # # 安裝cx_Freeze # 執行 python convert2exe.py build # 將自動生成build目錄, 其下所有文件都必須打包 #  import sys from cx_Freeze import setup, Executable
 
base = None if sys.platform == "win32":
  base = "Win32GUI" 
buildOptions = dict(
  compressed = True)
 
setup(
    name = "ZhaoChaAssistant", version = "1.0", description = "ZhaoChaAssistant", options = dict(build_exe = buildOptions), executables = [Executable("zhaochaassistant.py", base = base, icon = "icon.ico")])

          


最后執行一個命令

            
python convert2exe.py build

          

則會在當前路徑下創建個build目錄,打包的程序就在其中一個exe.win-amd64-2.7的目錄中,運行exe即可執行,與Python無二。可惜這個包太大了一些,整個目錄達到了30M。

為了讓exe程序也有一個好看的圖標,在最后一行中的executables參數中指定了icon = "icon.ico",這個圖標就最好使用多頁的.ico格式(16x16,32x32,48x48...),讓程序在各種顯示環境下(桌面、文件夾)都有原生的顯示。

如果打包的時候必須使用獨立的資源,可在buildOptions字典參數中增加一條include_files = ['xxx.dat']配置,這樣在打包時會將python腳本目錄中的xxx.dat文件拷貝到exe目錄中,不寫的話就得人工拷貝了。

小技巧:Python獲得自己的絕對路徑
Python中有個魔術變量可以得到腳本自身的名稱,但轉換成exe后該變量失效,這時得改用sys.executable獲得可執行程序的名稱,可用hasattr(sys, "frozen")判斷自己是否已被打包,下面是一個方便取絕對路徑的函數:
import sys?? def module_path(): if hasattr(sys, "frozen"): return os.path.dirname(os.path.abspath(unicode(sys.executable, sys.getfilesystemencoding()))) return os.path.dirname(os.path.abspath(unicode(__file__, sys.getfilesystemencoding())))

結束語

Python可能是程序員最好的玩具,什么都能粘起來,日常寫點小工具再合適不過了。
文中的第三方模塊都可以Google獲得下載地址,有些庫沒有Win7 64位的原始版本(比如PIL),但可到
http://www.lfd.uci.edu/~gohlke/pythonlibs/
下載別人編譯好的,也很方便。


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 欧美日韩国产高清视频 | 欧洲色网 | 精品日韩在线视频 | 成年免费网站 | 国产99视频精品一区 | 久久99九九99九九99精品 | 青青热久久国产久精品秒播 | 国产福利91 | 99日精品欧美国产 | 精品视频 久久久 | 亚洲日韩中文字幕一区 | 成人午夜毛片在线看 | 欧美成人久久久 | 日韩免费成人 | 日本一区二 | 中文字幕一区二区日产乱码 | 国产日韩欧美亚洲综合在线 | 亚洲天堂久久精品成人 | 国产在线原创剧情麻豆 | 男人天堂2021| 亚洲欧美第一 | 成人ab片 | 天天操夜夜操美女 | 色婷婷激婷婷深爱五月小蛇 | 97理论三级九七午夜在线观看 | 婷婷色婷婷 | 玖玖爱精品 | 国产精品中文字幕在线 | 久久国产精品女 | 亚洲第一综合网站 | 福利一区在线观看 | 欧美日韩精品一区三区 | 日韩一区精品 | 国产大陆亚洲精品国产 | 精品久久国产 | 特黄aaaaaaaaa及毛片 | 国产亚洲福利精品一区 | 日本一区色 | 欧美xxx精品 | 久久黄色一级视频 | 久久久综合久久 |