用python實現自動玩21點小游戲


1. 背景

前段時間發現一個論壇上(https://npupt.com/blackjack.php)有21點小游戲。

這個21點小游戲的規則是每個人開局都會獲得隨機點數,如果覺得點數小,可以繼續摸牌。如果摸牌后點數大於21點,系統會自動結束摸牌,否則可以繼續摸牌。系統會自動對比2個參與者的點數,點數大的人獲勝。(大於21點會被視為1點)。

由於整個游戲過程都是通過鼠標點擊網頁來完成的,那么我們可以讓代碼幫我們完成自動玩這個小游戲。

2. 理清游戲邏輯

如上圖,點擊“開始游戲”后,系統會給我們隨機點數(如20點)。然后我們可以選擇“再抓一張”或者“不要抓了,結束”。但是在這個例子中,20點已經很大了,我們再抓牌,很容易就超過21點,那就得不償失了,故這里我選擇了“不要抓了,結束”。(如果選擇“再抓一張”,結果畫面和上圖的右側相同。)

如上圖,當選擇結束游戲后,系統就會等待對手完成。只有對手完成了,我們才能開始新的21點小游戲。

上述便是這個小游戲的玩法。

順便,總結下21點小游戲的用戶操作,主要有4個:

  1. 開始游戲;
  2. 再抓一張;
  3. 結束游戲;
  4. 等待對手完成。

3. 游戲邏輯轉換成腳本思路

既然我們知道了玩法,那么我們只需將這些玩法用計算機思維將其轉換成腳本可以執行的步驟就行。

上述玩法可以轉換成以下腳本思路:

  1. 等待開局。
  2. 開始21點。
  3. 判斷點數。
  4. 若點數大於20,回到第1步。
  5. 若點數大於17,則去到第8步。(17可變,只是我認為17點夠大了)
  6. 若點數小於等於17,則去到第7步。
  7. 繼續摸牌,回到第3步。
  8. 終止游戲,回到第1步。

4. 思考如何用python實現

對於接觸過html和學過計算機網絡的同學,可能第一時間就會知道關鍵在於“網絡請求”

一切的網頁操作都是需要和服務器交互的,所以只要捕獲到這些操作的“網絡請求”,我們就可以知道某個操作中瀏覽器請求的服務器,和請求的內容,以及服務器返回的內容。

而python具備向服務器發出請求的能力,如requests庫,我們只需用requests庫模擬用戶向服務器發出請求即可達到模擬用戶操作的目的。

故,用python實現自動玩這個小游戲,需要3大步驟:

  1. 使用抓包工具,監聽在“理清游戲邏輯”中提到的4大操作的網絡請求
  2. 然后用python模擬網絡請求。
  3. 用python實現游戲邏輯。

5. 准備工作

  • 安裝好Python 2.7。

    (Linux或Mac下的安裝教程https://www.cnblogs.com/toulanboy/p/7753502.html)

  • 通過pip安裝requests庫。

    pip install requests
    
  • 安裝谷歌瀏覽器 (可以用來抓包,捕獲網絡請求)

6. 捕獲網絡請求

谷歌瀏覽器有一個調試工具,這個調試工具可以監聽瀏覽器的一切行為,包括了我們最想知道的“網絡請求”。下面講述捕獲21點小游戲網絡請求的具體過程。

  1. 打開谷歌瀏覽器,輸入網址:https://npupt.com/blackjack.php。

  2. 然后對頁面右擊,選擇“檢查”,就會出現下圖的界面。左側是正常網頁,右側便是我們要用到的工具。

  3. 然后點擊“抓包工具”界面的“network”

  4. 點擊左側的“開始游戲”。會得到下圖界面。包含以下信息:

    • 左側網頁可以知道,我一開始是16點。
    • 右側網頁捕獲到了“開始游戲”這個操作的網絡請求。

  5. 那么,我們點擊上圖中“關鍵頁面”所標記的網頁,然后在最右側欄可以得到以下信息。

  6. 上述便是網絡請求。包含了請求信息/服務器返回信息。上面的信息太多,我提取了一下, 對我們最有用的是以下內容。

   Request URL(請求的服務器): https://npupt.com/blackjack.php
   Request Method(請求方式): POST
//請求參數 - toulanboy - http://www.cnblogs.com/toulanboy/
game: hit
start: yes

總結本節,通過這個過程,我們知道了“開始游戲”的請求內容,那么同理,“再抓一張”,“結束游戲”也可以得到相應內容。而,”等待對手結束“則需要檢查網頁源代碼,只需對網頁關鍵位置右鍵,選擇“檢查”,即可得到該位置的源代碼。這些都是為python腳本准備的關鍵素材。最后每個操作需要的關鍵信息如下:

1. 等待對手結束

若之前的21點還沒有結束(暫時沒有對手上線),那么不能開局,需等待之前的結束。若需等待,主頁面包含以下內容

<button type="submit" class="btn btn-default">刷新</button>

2. 開始游戲

若可以開始游戲,主頁面包含以下內容

<button type="submit" class="btn btn-primary">開始游戲!</button>

3. 開始21點小游戲

向主頁面(https://npupt.com/blackjack.php)post數據

game: hit,
start: yes

4. 判斷點數

判斷每次操作后,主頁面返回的網頁內容。點數的html樣式如下:

<b>點數 = 16</b>

5. 繼續摸牌

向主頁面post數據

game: hit

6. 結束游戲

向主頁面post數據

game: stop

7. 用python模擬網絡請求

模擬請求的思路是:通過Python的網絡庫requests,向指定服務器發送指定參數即可。具體實現如下:

模擬Get請求,判斷是否可以開始游戲

#function - Get網頁
#若網頁顯示之前的沒結束,則返回0
#若網頁顯示可以開始新的一局,則返回1
#其它情況返回-1
def getData(url):
    headers = {
        'User-Agent' : '-',#建議設置
        'cookie':'-'#我沒做登陸,所以手動弄cookie
        }
    try:
        response = requests.get(url, headers=headers)
        indexHtmlCode = response.text
        indexHtmlCode =  indexHtmlCode.encode('utf-8')
        
        #判斷是否有刷新按鈕,若有,說明上局沒結束
        freshRegex = r'刷新</button>'
        result = re.findall(freshRegex, indexHtmlCode)
        if result:
            return 0
# toulanboy - http://www.cnblogs.com/toulanboy/        
        #判斷是否有開始游戲按鈕,若有,說明可以開始游戲
        beginRegex = r'開始游戲!</button>'
        result = re.findall(beginRegex, indexHtmlCode)
        if result:
            return 1

		return -1

    except Exception as e:
        return -1

toulanboy - http://www.cnblogs.com/toulanboy/

模擬Post請求,實現開始/摸牌/停止的用戶操作**

### 不同動作需要的數據不一樣
# 開始游戲
startValues = {
    "game":"hit",
    "start":"yes"
}
# 繼續摸牌
hitValues = {
    "game":"hit"
}
# 停止摸牌
stopValues = {
    "game":"stop"
}

#function - Post網頁
#若是開始和摸牌,則返回點數
#頁面沒有點數(停止摸牌操作會出現),則返回0
#異常,返回-1
def postData(url, values):
    dd = urllib.urlencode(values)
    headers = {
            "Content-Length":str(len(dd)),
            "Content-Type":"application/x-www-form-urlencoded",
            'Cache-Control':'max-age=0',#上述三個參數其實應該不用手動添加,有可能request庫會幫我們添加。有待驗證。
            'User-Agent' : '-',#自己補充
            'cookie':'-'#自己補充
            }
    try:
        response = requests.post(url, data=dd,headers=headers)
        indexHtmlCode = response.text
        indexHtmlCode =  indexHtmlCode.encode('utf-8')
# toulanboy - http://www.cnblogs.com/toulanboy/
        # 查看返回的網頁的點數
        pointRegex = r'點數\s?=\s?(\d*)<'
        result = re.findall(pointRegex, indexHtmlCode)
        if result:
            return int(result[0])
        else:
            return 0

    except Exception as e:
        return -1

8. 用python實現游戲邏輯


url = 'https://npupt.com/blackjack.php' 

#不停地玩21點
while True:
    #先看之前的是否結束了
    result = getData(url)
    time.sleep(5) # toulanboy - http://www.cnblogs.com/toulanboy/
    if result == 0: #如果還沒結束,則繼續刷新
        print "之前的尚沒結束,等待中"
    elif result == 1:#如果結束了,則開始游戲
        point = postData(url, startValues)#發出“開始游戲”請求
        print "已開局,當前點數 = %d" % point
        #大於20點,系統會自動結束,故在這里我只需在小於21點的情況下摸牌
        while point <= 20:
            if point >= 17:#我認為只要大於17點我滿足了,所以大於17點就停止摸牌
                time.sleep(1)
                postData(url, stopValues)#發出“停止摸牌”請求
                print "停止摸牌了,當前點數 = %d" % point
                break
            else:#小於17點則繼續摸牌
                time.sleep(1)
                point = postData(url, hitValues)#發出“繼續摸牌”請求
                print "又摸牌了,當前點數 = %d" % point
             
        print "這局結束了,當前點數 = %d" % point
    else:#出現異常,則停止游戲。等待渣渣看日志看看哪里出問題了。
        sendEmail("xxx@qq.com", "Some errors occurred in python script for npubits", "Some errors occurred in python script for npubits")
        break

9. 完整代碼

為了便於維護,完整代碼中增加了日志記錄和郵件提醒

#!/usr/bin/python
# coding=utf-8
# 時間:2018-08-22
# 作者:toulanboy
# 緣由:想實現自動玩npubits的21點游戲

import requests
import re
import urllib
import time 
import logging
import smtplib
from email.mime.text import MIMEText

#配置日志
logging.basicConfig(filename='my.log',format='[%(asctime)s-%(filename)s-%(levelname)s:%(message)s]', level = logging.INFO,filemode='a',datefmt='%Y-%m-%d %I:%M:%S %p')

### 不同動作需要的數據不一樣
# 開始游戲
startValues = {
    "game":"hit",
    "start":"yes"
}
# 繼續摸牌
hitValues = {
    "game":"hit"
}
# 停止摸牌
stopValues = {
    "game":"stop"
}
# toulanboy - http://www.cnblogs.com/toulanboy/
#function - Post網頁
#若是開始和摸牌,則返回點數
#頁面沒有點數(停止摸牌操作會出現),則返回0
#異常,返回-1
def postData(url, values):
    dd = urllib.urlencode(values)
    headers = {
            "Content-Length":str(len(dd)),
            "Content-Type":"application/x-www-form-urlencoded",
            'Cache-Control':'max-age=0',#上述三個參數其實應該不用手動添加,有可能request庫會幫我們添加。有待驗證。
            'User-Agent' : '-',#自己補充
            'cookie':'-'#自己補充
            }
    try:
        response = requests.post(url, data=dd,headers=headers)
        indexHtmlCode = response.text
        indexHtmlCode =  indexHtmlCode.encode('utf-8')
        # 提取網頁主干,存入日志(方便后期的分析)
        body = re.findall(r'<div\s?id=\'main\'\s?class=\'well\s?no-border-radius\'>.*?</div>',indexHtmlCode, re.S)
        if body:
            logging.info(body[0])
        else:
            logging.info(indexHtmlCode)
        # 查看返回的網頁的點數
        pointRegex = r'點數\s?=\s?(\d*)<'
        result = re.findall(pointRegex, indexHtmlCode)
        if result:
            return int(result[0])
        else:
            return 0

    except Exception as e:
        logging.error(e)
        return -1

#function - Get網頁
#若網頁顯示之前的沒結束,則返回0
#若網頁顯示可以開始新的一局,則返回1
#其它情況返回-1
def getData(url):
    headers = {
        'User-Agent' : '-',#建議設置
        'cookie':'-'#我沒做登陸,所以手動弄cookie
        }
    try:
        response = requests.get(url, headers=headers)
        indexHtmlCode = response.text
        indexHtmlCode =  indexHtmlCode.encode('utf-8')
        
        #判斷是否有刷新按鈕,若有,說明上局沒結束
        freshRegex = r'刷新</button>'
        result = re.findall(freshRegex, indexHtmlCode)
        if result:
            return 0
        
        #判斷是否有開始游戲按鈕,若有,說明可以開始游戲
        beginRegex = r'開始游戲!</button>'
        result = re.findall(beginRegex, indexHtmlCode)
        if result:
            return 1

        # 若以上情況都不是,有可能是cookie過期了
        loginRegex = r'您尚未登錄</body>'
        result = re.findall(loginRegex, indexHtmlCode)
        if result:
            return 2
        
        # 如果不是cookie過期,則需打印當前錯誤信息(記錄返回的網頁源代碼),方便后面找錯
        logging.error(indexHtmlCode)

    except Exception as e:
        logging.error(e)
        return -1

#發郵件 (收件人 ,郵件主題 ,郵件正文)
def sendEmail(_to, subject, mainText):
    _user = "-"  #登錄的163郵箱
    _pwd = "-"   #163郵箱授權碼
    msg = MIMEText(mainText)    #郵件正文
    msg["Subject"] = subject    #郵件主題
    msg["From"]    = _user      #發件人
    msg["To"]      = _to        #收件人

    try:
        s = smtplib.SMTP_SSL("smtp.163.com", 465)
        s.login(_user, _pwd)#登錄
        s.sendmail(_user, _to, msg.as_string())#發送
        s.quit()#退出登錄
        logging.info("郵件發送成功!")
        print "郵件發送成功!"
    except smtplib.SMTPException,e:
        logging.info("郵件發送失敗,%s"%e[0])
        print "郵件發送失敗,%s"%e[0]


url = 'https://npupt.com/blackjack.php' 

#不停地玩21點
while True:
    #先看之前的是否結束了
    result = getData(url)
    time.sleep(5) 
    if result == 0: #如果還沒結束,則繼續刷新
        print "之前的尚沒結束,等待中"
    elif result == 1:#如果結束了,則開始游戲
        point = postData(url, startValues)#發出“開始游戲”請求
        logging.info("已開局,當前點數 = %d" % point)
        print "已開局,當前點數 = %d" % point
        #大於20點,系統會自動結束,故在這里我只需在小於21點的情況下摸牌
        while point <= 20:
            if point >= 17:#我認為只要大於17點我滿足了,所以大於17點就停止摸牌
                time.sleep(1)
                postData(url, stopValues)#發出“停止摸牌”請求
                logging.info("停止摸牌了,當前點數 = %d" % point)
                print "停止摸牌了,當前點數 = %d" % point
                break
            else:#小於17點則繼續摸牌
                time.sleep(1)
                point = postData(url, hitValues)#發出“繼續摸牌”請求
                logging.info("又摸牌了,當前點數 = %d" % point)
                print "又摸牌了,當前點數 = %d" % point
        
        logging.info("這局結束了,當前點數 = %d" % point)       
        print "這局結束了,當前點數 = %d" % point
    else:#出現異常,則停止游戲。等待渣渣看日志看看哪里出問題了。
        sendEmail("xxx@qq.com", "Some errors occurred in python script for npubits", "Some errors occurred in python script for npubits")
        break

toulanboy - http://www.cnblogs.com/toulanboy/

10 . 運行效果

從下圖可知,運行正常。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM