歡樂五子棋輔助

  • 2020 年 3 月 29 日
  • 筆記

苦練技術是不可能的,這輩子不可能好好練習。學技術又學不進去,就只有靠輔助才能維持的生活這樣子。

簡述

最近玩微信小程式 – 歡樂五子棋,結果老是被虐,好氣啊。偶然想到了前段時間網上很火爆的跳一跳輔助。簡單想了一想輔助實現的思路,發現目前所需的工具已經足夠。

需要的工具主要分為以下三類:

  1. Yixin 奕心引擎,這個引擎是國人所作,可以說是非商用版裡面最強的五子棋AI

    這個是官網 https://www.aiexp.info/pages/yixin-cn.html

    一開始我想使用奕心的介面+引擎那款,因為可訂製性足夠強,結果發現Python 不好與帶介面的程式進行交互,所以就選擇了引擎。然而尷尬的一點是,我把官方文檔翻了個遍都沒有找到引擎的使用方法。不過後來在世界五子棋錦標賽 https://gomocup.org/ 找到了參加比賽的AI 必備的兩種介面。

    http://petr.lastovicka.sweb.cz/protocl2en.htm 使用輸入輸出流,本文選擇的就是這種方式

    http://petr.lastovicka.sweb.cz/protocl1en.htm 使用文件

  2. Python 簡單圖片處理

  3. Python adb 操作手機

思路

整個思路如下圖,我們按照上面的順序從簡到繁,開始介紹每個模組。整個思路順下來程式其實是比較好寫的,就是前期需要手動的截取匹配圖片,查看相應的像素點位置

實現

前期準備

下面的區域參數以我的手機1920*1080為例,不同螢幕請自行適配

class mVars:      address='C:/Users/EA/Desktop/yixin/' # 使用到的文件所存放地址      boradOne = 67 #一個相鄰落子點的像素間隔      borad = (65,480) #用來將圖片像素坐標和棋盤坐標互轉      confirmBW = (820,1590,820+45,1590+60)#用來確定己方是黑棋還是白棋的區域      confirmWin = (660,1780,660+46,1780+46)#用來確定是否勝利的區域  

這些是前期需要準備好的圖片,至於怎麼獲取這些圖片,等後面有時間會在補充在文末,事實上,做好這些準備工作,整個進度就完成了30%

image-20200329105705796

Yixin 引擎

從官網上可以下載這個Yixin 引擎 http://gomocup.org/static/download-ai/YIXIN18.zip

結合 http://petr.lastovicka.sweb.cz/protocl2en.htm 可以知道如何與引擎交互

我使用 subprocess 這個模組讓python 與 引擎交互

import subprocess as sub    class YiXin:      mYixin = sub.Popen(mVars.address+"Yixin.exe", stdin=sub.PIPE, stdout=sub.PIPE, stderr=sub.PIPE)        def __init__(self):          self.input('START 15')          self.input('INFO timeout_match 300000')          self.input('INFO timeout_turn 10000')            self.output()          print("YiXin ready!!")        def input(self,str): #向Yixin 輸入對手落子指令          print('Human: '+str)          self.mYixin.stdin.write((str+'n').encode())          self.mYixin.stdin.flush()        def output(self):   #獲取Yixin 的輸出          #一直獲取Yixin 輸出,直到落子的指令或其它          while True:              str = bytes.decode(self.mYixin.stdout.readline())              print('YiXin: '+ str,end='')              if ((',' in str) or ('OK' in str)):                  break;          self.mYixin.stdout.flush()          if(',' in str):              return str        def restart(self):          self.input('RESTART 15')          self.output()  
圖片處理

這個模組需要做的事就是處理跟圖片相關的,包括比較圖片,轉換坐標等

起初有兩種思路,1.每隔一段時間獲取截一張圖,對比兩張圖不同的地方,從而獲取對手落點位置。2.沒隔一段時間截一張圖,識別圖上的所有有棋的位置並保存,然後通過比較,得到對手落子位置。出於效率考慮,我選擇了第一種方法。

class ImageProcess:      #如果匹配成功,則返回中心像素點      def matchImg(self,imgsrc,imgobj,confidence=0.8):          coord = None          res = ac.find_template(imgsrc,imgobj,confidence)          if res != None:              coord = (int(res['result'][0]),int(res['result'][1]))          return coord        #將像素坐標轉化為棋盤坐標      def transformBoard(self,coord):          x = coord[0]          y = coord[1]          xcoord = ycoord = 0          while x>=mVars.borad[0]:              x-=mVars.boradOne              xcoord+=1          while y>=mVars.borad[1]:              y-=mVars.boradOne              ycoord+=1            return xcoord-1,ycoord-1        #將棋盤坐標轉化為像素坐標      def transfromScreen(self,coord):          return (coord[0]*mVars.boradOne+mVars.borad[0],coord[1]*mVars.boradOne+mVars.borad[1])        #對比兩張圖片的差異      def difference(self,img1,img2):          return img1-img2    
ADB 模組

這個模組與手機交互,這塊我用的是無線ADB連接,當然也可以有線連接,關於ADB連接教程見https://github.com/mzlogin/awesome-adb

import os  import time  class Adb:      #無線連接手機      def __init__(self):          os.system('adb connect 1.1.1.1:5555')#ip 示例          os.system('adb devices')      #捕獲截圖      def capture(self):          os.system('adb exec-out screencap -p > '+mVars.address+'sc.jpg')          return ac.imread(mVars.address+'sc.jpg')      #點擊特定位置      def click(self,piexl):          os.system('adb shell input tap %d %d'%(piexl[0],piexl[1]))          time.sleep(0.1)          os.system('adb shell input tap %d %d'%(piexl[0],piexl[1]))  
遊戲系統模組
class System:      Yixin = YiXin()      ImageP = ImageProcess()      Adb = Adb()      imgobj = None #用來檢測對手落子的圖片      certain = 0 #1表示己方為白,2表示己方為黑        #確認是否勝利      def confirmWin(self,imgsrc):          x0,y0,x1,y1 = mVars.confirmWin          imgsrc = imgsrc[y0:y1,x0:x1]          imgobj = ac.imread(mVars.address+'confirmwin.jpg')          return self.ImageP.matchImg(imgsrc,imgobj,0.9)        #確認己方是黑棋還是白棋      def confirmBW(self,imgsrc):          x0,y0,x1,y1 = mVars.confirmBW          imgsrc = imgsrc[y0:y1,x0:x1]            imgobjw = ac.imread(mVars.address+'confirmw.jpg')          imgobjb = ac.imread(mVars.address+'confirmb.jpg')            if (self.ImageP.matchImg(imgsrc,imgobjw,0.8) != None):              self.certain = 1              self.imgobj=ac.imread(mVars.address+'objb.jpg')          elif (self.ImageP.matchImg(imgsrc,imgobjb,0.8)!= None):              self.certain = 2              self.imgobj=ac.imread(mVars.address+'objw.jpg')        #做好比賽前準備,      def ready(self):          while True:              imgsrc = self.Adb.capture()              self.confirmBW(imgsrc)              if(self.certain != 0):                  break;              print('UnCertain')              time.sleep(1)            if self.certain == 2:              self.runCommand('BEGIN')              return imgsrc          elif self.certain == 1:              return ac.imread(mVars.address+'None.jpg')        #向Yixin 輸入對方落點,獲得Yixin 落點並點擊螢幕      def runCommand(self,COMMAND):          self.Yixin.input(COMMAND)          str = self.Yixin.output()          a = str.find(',')          b = str.find('r')            piexl = self.ImageP.transfromScreen((int(str[0:a]),int(str[a+1:b])))          # print(piexl)          self.Adb.click(piexl)        #開始遊戲      def play(self,imgsrc):          flag=False          imagep = self.ImageP          oldimg = newimg = imgsrc          while self.confirmWin(newimg) == None:              imgdif = imagep.difference(oldimg,newimg)              ac.cv2.imwrite(mVars.address+'diff.jpg',imgdif)              coord = imagep.matchImg(imgdif,self.imgobj)              # print(coord)              if(coord != None):                  x, y = imagep.transformBoard(coord)                  COMMAND = "TURN %d,%d"%(x,y)                  self.runCommand(COMMAND)                oldimg,newimg = newimg,self.Adb.capture()              time.sleep(0.8)        #新一輪遊戲      def newGame(self):          time.sleep(4)          os.system('cls')          os.system('adb shell input tap %d %d'%(100,1820))          time.sleep(0.5)          os.system('adb shell input tap %d %d'%(540,940))            self.Yixin.restart()          self.certain = 0          self.imgobj = None    
主函數
msys = System()  # n = input("請輸入你想玩的局數:")  # for i in range(1,int(n)+1):  while True:      imgBegin = msys.ready()      msys.play(imgBegin)      print("You Win !! Next Game Will Begin After 4sec")      msys.newGame()  

後記

所有程式碼到這裡就結束了,後面將用到的資源整理一下再上傳。
已經成功的上到最高級,勝率不是100%,因為歡樂五子棋沒有禁手,所以執白時偶爾會輸,但執黑必勝