用python实现自动玩21点小游戏
1. 背景
前段时间发现一个论坛上(https://npupt.com/blackjack.php)有21点小游戏。
这个21点小游戏的规则是每个人开局都会获得随机点数,如果觉得点数小,可以继续摸牌。如果摸牌后点数大于21点,系统会自动结束摸牌,否则可以继续摸牌。系统会自动对比2个参与者的点数,点数大的人获胜。(大于21点会被视为1点)。
由于整个游戏过程都是通过鼠标点击网页来完成的,那么我们可以让代码帮我们完成自动玩这个小游戏。
2. 理清游戏逻辑

如上图,点击“开始游戏”后,系统会给我们随机点数(如20点)。然后我们可以选择“再抓一张”或者“不要抓了,结束”。但是在这个例子中,20点已经很大了,我们再抓牌,很容易就超过21点,那就得不偿失了,故这里我选择了“不要抓了,结束”。(如果选择“再抓一张”,结果画面和上图的右侧相同。)

如上图,当选择结束游戏后,系统就会等待对手完成。只有对手完成了,我们才能开始新的21点小游戏。
上述便是这个小游戏的玩法。
顺便,总结下21点小游戏的用户操作,主要有4个:
- 开始游戏;
- 再抓一张;
- 结束游戏;
- 等待对手完成。
3. 游戏逻辑转换成脚本思路
既然我们知道了玩法,那么我们只需将这些玩法用计算机思维将其转换成脚本可以执行的步骤就行。
上述玩法可以转换成以下脚本思路:
- 等待开局。
- 开始21点。
- 判断点数。
- 若点数大于20,回到第1步。
- 若点数大于17,则去到第8步。(17可变,只是我认为17点够大了)
- 若点数小于等于17,则去到第7步。
- 继续摸牌,回到第3步。
- 终止游戏,回到第1步。
4. 思考如何用python实现
对于接触过html和学过计算机网络的同学,可能第一时间就会知道关键在于“网络请求”。
一切的网页操作都是需要和服务器交互的,所以只要捕获到这些操作的“网络请求”,我们就可以知道某个操作中浏览器请求的服务器,和请求的内容,以及服务器返回的内容。
而python具备向服务器发出请求的能力,如requests库,我们只需用requests库模拟用户向服务器发出请求即可达到模拟用户操作的目的。
故,用python实现自动玩这个小游戏,需要3大步骤:
- 使用抓包工具,监听在“理清游戏逻辑”中提到的4大操作的网络请求
- 然后用python模拟网络请求。
- 用python实现游戏逻辑。
5. 准备工作
安装好Python 2.7。
(Linux或Mac下的安装教程https://www.cnblogs.com/toulanboy/p/7753502.html)
通过pip安装requests库。
pip install requests
安装谷歌浏览器 (可以用来抓包,捕获网络请求)
6. 捕获网络请求
谷歌浏览器有一个调试工具,这个调试工具可以监听浏览器的一切行为,包括了我们最想知道的“网络请求”。下面讲述捕获21点小游戏网络请求的具体过程。
打开谷歌浏览器,输入网址:https://npupt.com/blackjack.php。
然后对页面右击,选择“检查”,就会出现下图的界面。左侧是正常网页,右侧便是我们要用到的工具。

然后点击“抓包工具”界面的“network”。
点击左侧的“开始游戏”。会得到下图界面。包含以下信息:
- 左侧网页可以知道,我一开始是16点。
- 右侧网页捕获到了“开始游戏”这个操作的网络请求。

那么,我们点击上图中“关键页面”所标记的网页,然后在最右侧栏可以得到以下信息。

上述便是网络请求。包含了请求信息/服务器返回信息。上面的信息太多,我提取了一下, 对我们最有用的是以下内容。
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 . 运行效果
从下图可知,运行正常。

用python实现自动玩21点小游戏的更多相关文章
- 用Python实现童年的21款小游戏,有你玩过的吗?(不要错过哦)
Python为什么能这么火热,Python相对于其他语言来说比较简单,即使是零基础的普通人也能很快的掌握,在其他方面比如,处于灰色界的爬虫,要VIP的视频,小说,歌,没有爬虫解决不了的:数据挖掘及分析 ...
- 如何手动写一个Python脚本自动爬取Bilibili小视频
如何手动写一个Python脚本自动爬取Bilibili小视频 国庆结束之余,某个不务正业的码农不好好干活,在B站瞎逛着,毕竟国庆嘛,还让不让人休息了诶-- 我身边的很多小伙伴们在朋友圈里面晒着出去游玩 ...
- Python基础入门-实现猜数字小游戏
今天呢,我们来通过前面学过的一些知识点来完成一个猜数字大小的游戏程序设计.那么呢,一般人写代码直接上来就干,没有分析,这样的做法是没有产出的,除非你是大牛,今天呢,我会把我学习编程的思路分享给大家,我 ...
- 用python来自动玩类似跳一跳的小游戏
最近春节,qq上出了一个叫穿越福城的小游戏.游戏的玩法类似挑一挑,也是通过一个个木桩.只不过把跳的过程变成了搭梯子.按的时间越长,梯子越长.梯子过长或者过短小企鹅都会掉下去,游戏失败.我的目的是用py ...
- 零基础python教程-用Python设计你的第一个小游戏
学以致用,既然学习了python就要让它来实现我们想做的东西,这次咱就用python来做个简单小游戏,在实践中不断成长. 1.游戏代码: 输入数字,来猜测原作者心中所想的数字,猜中夸你,猜不中不夸你, ...
- python 基础(实例1——登陆小游戏)
一个简单的登陆小游戏,输入用户名和密码,如果和user_passwd.txt中内容匹配,则打印“welcome to login...”,如果三次输入错误则将该用户列入黑名单,无法再用该用户名登陆. ...
- 用 Python 写个消消乐小游戏
提到开心消消乐这款小游戏,相信大家都不陌生,其曾在 2015 年获得过玩家最喜爱的移动单机游戏奖,受欢迎程度可见一斑,本文我们使用 Python 来做个简单的消消乐小游戏. 很多人学习python,不 ...
- 初学python写个自娱自乐的小游戏
一.摘要 当编写完后的代码执行第一次后达到了目标的预期效果,内心有些许满足,但是当突发情况产生后,程序便不能正常运行,于是准备从简单的版本开始出发,综合考虑使用者的需求,和使用过程中会遇到的问题,一步 ...
- 初识python: while循环 猜年龄小游戏
知识点: 1.python注释方法: 单行注释: # 多行注释: '''注释内容 ''' (单引号或双引号都可以),亦可打印多行 例: #此处是单行注释信息 print('这里是打印内容') #这里 ...
随机推荐
- VC++6.0下新建工程中有17个选项,都是做什么用的?
要理解每种工程的作用需要很多基础知识,只能简要的和你讲一下: 1.ATL COM AppWizard 用来新建一个COM组件的向导,比如WORD里用的公式编辑器就是一个COM组件. 2.Cluster ...
- JavaScript 对象的原型扩展(JS面向对象中的继承)
<script type="text/javascript"> function person(name, age) { this._name = name; this ...
- JQuery | trigger() 方法
trigger() 方法触发被选元素的指定事件类型. 语法格式: trigger(type,[data]) type:触发事件类型 [data]:可选项,表示在触发事件时传递给函数的附加参数. 实例: ...
- centos 无界面 服务器 安装chrome部署chromedriver
转:https://blog.csdn.net/u013849486/article/details/79466359 基本 做完了,要弄进docker里面去了的时候,才搜到 docker-chrom ...
- JAVA常用知识总结(十二)——数据库(二)
MySQL主从热备份工作原理 简单的说:就是主服务器上执行过的sql语句会保存在binLog里面,别的从服务器把他同步过来,然后重复执行一遍,那么它们就能一直同步啦. 整体上来说,复制有3个步骤: 作 ...
- Linux--NiaoGe-Service-06
Linux网络排错 思路: 硬件问题: 首先排除硬件故障,包括网线.Hub.Switch.Router.网卡.设备配置规则等等. 软件问题: 1.网卡的IP/Netmask设置错误 IP.Netmas ...
- Azkaban是什么?(一)
不多说,直接上干货! http://www.cnblogs.com/zlslch/category/938837.html Azkaban是什么? Azkaban是一套简单的任务调度服务,整体包括三 ...
- Spark Mllib里如何将数据集按比例随机地分成trainData、testData和validationData数据集(图文详解)
不多说,直接上干货! 具体详情见 Hadoop+Spark大数据巨量分析与机器学习整合开发实战的第11章 电影推荐引擎
- 牛客网Java刷题知识点之同步方法和同步代码块的区别(用synchronized关键字修饰)
不多说,直接上干货! 扩展博客 牛客网Java刷题知识点之多线程同步的实现方法有哪些 为何要使用同步? java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查 ...
- 物体检测丨Faster R-CNN详解
这篇文章把Faster R-CNN的原理和实现阐述得非常清楚,于是我在读的时候顺便把他翻译成了中文,如果有错误的地方请大家指出. 原文:http://www.telesens.co/2018/03/1 ...