用Python實現(xiàn)QQ游戲大家來找茬輔助工具
好久沒寫技術相關的文章,這次寫篇有意思的,關于一個有意思的游戲——QQ找茬,關于一種有意思的語言——Python,關于一個有意思的庫——Qt。
這是一個用于QQ大家來找茬(美女找茬)的輔助外掛,開發(fā)的原因是看到老爸天天在玩這個游戲,分數(shù)是慘不忍睹的負4000多。他玩游戲有他的樂趣,并不很在意輸贏,我做這個也只是自我娛樂,順便討他個好,畢竟我們搞編程的實在難有機會在父輩面前露露手。本來是想寫個很簡單的東西,但由于過程中老爸的多次嘲諷,逼得我不得不盡力完善,最后形成了一個小小的產(chǎn)品。
接觸Python是2010年,相見恨晚,去年拿它寫了些小玩意,離職前給前公司留下了一個Python+wxPython的工作工具,還挺受歡迎。換公司后努力學習C++&Qt,很后悔當初選擇了wxPython而不是PyQt,沒能一脈相承。使用Qt越久,不得不越來越喜歡,寫這個東西正好就用上了。
話不多說,進入正題。這不是一篇完整的代碼講解,只是過程中的一些技術做個分享,包括后來被放棄的一些技術點。當初搜索這些東西也挺費力的,在這做個筆記,后來者也許能搜到收益。
先上個圖:
話說這位是游戲中出鏡最多的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() # 分別裁剪左右內(nèi)容圖片 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()
上面用到的坐標都為為了演示代碼簡單填的,實際上使用了變量參數(shù),而且要區(qū)分分辨率什么的。
PIL是一個強大的Python圖形庫(使用文檔),待會的對比分析也須要用到。ImageGrab是PIL的一個模塊,用于圖像的抓取。不帶參數(shù)的ImageGrab.grab()進行全屏截屏,返回一個Image對象,也可使用一個元組作為參數(shù)指定要截取的范圍(左上與右下兩點的坐標),這兩種截屏都是不帶鼠標指針的,還有一個ImageGrab.grabclipboard()可從系統(tǒng)剪貼板采集圖像。
得到Image圖像后可用show()方法,使用系統(tǒng)默認的圖像查看工具打開,方便調(diào)試,也可以用save(filename)保存成文件,對應的可以Image.open(filename)打開獲得。
grab得到了一個包含左右圖片的Image對象后,用crop(box)方法可裁剪得到其中指定的區(qū)域,分別拿到左右兩個游戲圖片。
對比獲得兩圖內(nèi)容不同的區(qū)域
很自然想到把兩圖裁剪成N個小圖片分別對比,左右統(tǒng)一區(qū)域?qū)男D片不相等則為“茬”區(qū),唯一的問題是怎么判斷兩個圖片內(nèi)容不一致?
一開始以為很會有些麻煩,直到發(fā)現(xiàn)了Image.histogram()函數(shù),該函數(shù)用于得到圖像的顏色直方圖。我平常也愛好攝影,知道直方圖可以表示一張圖片中各種亮度(或顏色)的數(shù)量,兩張自然圖片的直方圖基本是不一樣的,除非兩圖對稱、顏色一致但排列不一,但就算如此,將兩圖繼續(xù)分割下去,其子圖的直方圖也會不一樣。直方圖就是一種圖形到數(shù)值的轉(zhuǎn)換,對比兩圖的顏色數(shù)值就可知是否存在差異。
一張用RBG顏色格式的圖像,histogram()函數(shù)將返回一個長度為768的數(shù)組,第0-255表示紅色的0-255,第256-511表色綠色的0-255,第512-767表色藍色的0-255,數(shù)值表示該顏色像素的個數(shù)。因此,histogram()列表所有成員之和等于改圖像的像素值 x 3。
寫了一個函數(shù),用來獲得兩圖比較的數(shù)值差:
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
將函數(shù)返回的紅綠藍差值相加,如果超過了預定定的閥值2000,則表示該區(qū)域不同。這個計算方式有點“土”,但對這次要解決的問題很有效,就沒再繼續(xù)改進。
將左右大圖裁剪成多個小圖分別進行對比 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的二位數(shù)組存儲結(jié)果,分別比較后將差值大于閥值的數(shù)組區(qū)域標記為1.
在游戲上標記兩邊不同的區(qū)域
最初我用了PyWin32的一些函數(shù),獲得游戲窗口句柄后直接在上面繪制,但我不太熟悉Windows編程,不知道如何解決游戲自身重繪后將我的標記擦除的問題,然后搬來了Qt。用Qt創(chuàng)建了一個和游戲大小一樣透明的QWidget窗口,疊加在游戲窗口上,用遮罩來繪制標記。標記數(shù)據(jù)已記錄在result數(shù)組中,在指定的位置繪制一個方格則表示該區(qū)域左右不同,要注意兩個方格間的邊界不要繪制,避免格子太多干擾了游戲。除標記外,還繪制了兩個按鈕來觸發(fā)對比與擦除。
ef paintEvent(self, event): # 重置遮罩圖像 self.pixmap.fill() # 創(chuàng)建繪制用的QPainter,筆畫粗細為2像素 # 事先已經(jīng)在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: # 定一個基點,避免算數(shù)太難看 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) # 在遮罩上繪制按鈕區(qū)域,避免按鈕被遮罩擋住看不見 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程序不出現(xiàn)在任務欄,構(gòu)造QWidget設置了這些屬性
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Popup | Qt.Tool)
讓PyQt程序加入系統(tǒng)托盤、資源文件使用
PyQt添加托盤菜單非常容易,幾行代碼就可以
創(chuàng)建托盤 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找茬助手已經(jīng)待命,進入游戲即可激活") # 托盤菜單 self.action = QAction(u"退出QQ找茬助手", self, triggered = sys.exit) # 觸發(fā)點擊后調(diào)用sys.exit()命令,即退出 self.menu = QMenu(self) self.menu.addAction(self.action) self.trayIcon.setContextMenu(self.menu)
最初我是用的托盤圖標是一個.ico文件,執(zhí)行腳本可以正常顯示,但打包成exe后執(zhí)行在托盤上顯示為一個空白圖標,用Python的idle工具編譯運行也是空白。嘗試多次后發(fā)現(xiàn):PyQt的托盤圖標不能使用.ico文件,否則會顯示空白,換成png格式素材就沒問題!
PyQt資源文件打包
Qt使用一個.qrc格式的xml文件管理素材,代碼用可用:\xxx\xxx.png的方式引用資源文件中的素材,這在PyQt中同樣支持。
這里我創(chuàng)建了一個resources.qrc文件
<!DOCTYPE RCC> <RCC version="1.0"> <qresource> <file>icon.png</file> </qresource> </RCC>
然后用
pyrcc4 resources.qrc > resources.py
命令,將資源文件轉(zhuǎn)成一個python模塊,在代碼中import resources,則可以用這樣的方式使用圖像素材
self.icon = QIcon(":\icon.png")
打包成可執(zhí)行程序
這個工具是給別人用的,肯定不能以py腳本的形式發(fā)布,我使用了cx_Freeze來打包為可執(zhí)行程序。
為此要寫一個打包命令腳本convert2exe.py
#!Python #coding=gbk # python轉(zhuǎn)exe腳本 # # 安裝cx_Freeze # 執(zhí)行 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")])
最后執(zhí)行一個命令
python convert2exe.py build
則會在當前路徑下創(chuàng)建個build目錄,打包的程序就在其中一個exe.win-amd64-2.7的目錄中,運行exe即可執(zhí)行,與Python無二??上н@個包太大了一些,整個目錄達到了30M。
為了讓exe程序也有一個好看的圖標,在最后一行中的executables參數(shù)中指定了icon = "icon.ico",這個圖標就最好使用多頁的.ico格式(16x16,32x32,48x48...),讓程序在各種顯示環(huán)境下(桌面、文件夾)都有原生的顯示。
如果打包的時候必須使用獨立的資源,可在buildOptions字典參數(shù)中增加一條include_files = ['xxx.dat']配置,這樣在打包時會將python腳本目錄中的xxx.dat文件拷貝到exe目錄中,不寫的話就得人工拷貝了。
小技巧:Python獲得自己的絕對路徑
Python中有個魔術變量可以得到腳本自身的名稱,但轉(zhuǎn)換成exe后該變量失效,這時得改用sys.executable獲得可執(zhí)行程序的名稱,可用hasattr(sys, "frozen")判斷自己是否已被打包,下面是一個方便取絕對路徑的函數(shù):
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())))
結(jié)束語
Python可能是程序員最好的玩具,什么都能粘起來,日常寫點小工具再合適不過了。
文中的第三方模塊都可以Google獲得下載地址,有些庫沒有Win7 64位的原始版本(比如PIL),但可到
http://www.lfd.uci.edu/~gohlke/pythonlibs/
下載別人編譯好的,也很方便。
- Python計算斗牛游戲概率算法實例分析
- Python實現(xiàn)的破解字符串找茬游戲算法示例
- Python實現(xiàn)破解猜數(shù)游戲算法示例
- python實現(xiàn)的生成隨機迷宮算法核心代碼分享(含游戲完整代碼)
- Python寫的貪吃蛇游戲例子
- python開發(fā)的小球完全彈性碰撞游戲代碼
- 基于Python實現(xiàn)的掃雷游戲?qū)嵗a
- Python新手實現(xiàn)2048小游戲
- python基礎教程之實現(xiàn)石頭剪刀布游戲示例
- python實現(xiàn)猜數(shù)字游戲(無重復數(shù)字)示例分享
- Python版的文曲星猜數(shù)字游戲代碼
- Python基于分水嶺算法解決走迷宮游戲示例
相關文章
pyecharts繪制各種數(shù)據(jù)可視化圖表案例附效果+代碼
這篇文章主要介紹了pyecharts繪制各種數(shù)據(jù)可視化圖表案例并附效果和代碼,文章圍繞主題展開詳細的內(nèi)容介紹,感興趣的小伙伴可以參考一下2022-06-06使用Python中的Argparse實現(xiàn)將列表作為命令行參數(shù)傳遞
Argparse?是一個?Python?庫,用于以用戶友好的方式解析命令行參數(shù),本文我們將討論如何使用?Python?中的?Argparse?庫將列表作為命令行參數(shù)傳遞,感興趣的可以了解下2023-08-08Python實現(xiàn)繪制3D地球旋轉(zhuǎn)效果
這篇文章主要為大家詳細介紹了如何利用Python實現(xiàn)繪制出3D地球旋轉(zhuǎn)的效果,文中的示例代碼講解詳細,具有一定的借鑒價值,需要的可以參考一下2023-02-02python中讀取txt文件時split()函數(shù)的妙用
這篇文章主要介紹了python中讀取txt文件時split()函數(shù)的妙用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11