【五子棋实战】第3章 算法包装成第三方接口
- 使用Flask开放接口
- ## 定义接口输入
- ## 开放接口、跨域配置、数据解析
- 数据预处理
- ## 数据检查与异常捕获
- ## 预处理数据
- ## 定义接口输出
- 开启接口
- 继续学习下一篇实战!
我们在上一章实现了博弈树负值极大alpha-beta剪枝算法,即ai()
函数,拿到了四个返回值:x、y坐标,搜索次数、是否赢了。
现在我们需要把这个ai()
函数再套两层壳:
1、第一层是预处理的壳。对于接口传入的参数,我们需要预处理成为ai()
函数需要的数据结构;还需要检查输入对不对,如果输入的棋盘不是正方形的,或者输入的棋盘值除了1、0、-1还有其他值等等等等,我们要加以限制。
2、第二层是套python的Flask库,把我们的函数开放为接口。包括一些请求的跨域配置、request请求数据的处理都在这完成。
使用Flask开放接口
## 定义接口输入
接口输入的原则是,参数尽量少,要能够有通用性,比如对棋盘的输入,采取如下的形式:
[
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,1,-1,0,0,0,0],
[0,0,0,0,-1,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
]
直接把二维棋盘转化为二维数组就行,这样方便大家自定义自己的前端页面。
所以综上原则,我们的接口有四个参数:
ratio
:ai的攻击系数。
depth
:算法遍历的深度。
length
:棋盘边长长度。
board
:从表单中获取的变量,二维数组,表示棋盘状态,通过逗号分隔的字符串形式表示。通过使用split
函数将字符串拆分为整数列表。
## 开放接口、跨域配置、数据解析
app = Flask(__name__)
CORS(app, supports_credentials=True)
@app.route('/api/next_step', methods=['POST'])
@cross_origin(supports_credentials=True)
def api():
try:
ratio = int(request.form.get('ratio'))
depth = int(request.form.get('depth'))
length = int(request.form.get('length'))
board = [int(_) for _ in request.form.get('board').split(',')]
board = np.array(board).reshape(-1, length)
return next_step(board, length, depth, ratio ) # 返回接口响应
except Exception as e:
return {
'code': 300,
'msg': str(e)
}
if __name__ == '__main__':
app.run()
上面的代码是一个基于Flask框架的API接口,创建了路由 /api/next_step
,该接口接受POST请求,并处理包含 ratio、depth、length 和 board 参数的表单数据。在请求中,它首先从表单数据中获取这些参数的值,并将 board 转换为二维数组。然后,它调用 next_step 函数来处理请求,并将返回的结果作为接口的响应返回。如果在处理过程中发生异常,它会返回一个包含错误信息的字典。最后,通过运行应用程序来启动服务。
如果不加跨域的话,我们在前端侧访问接口要加一些配置,比较麻烦。
对于前端侧传过来的二维数组,它其实是转化成了字符串形式的,所以接收之后,我们要手动转化成二维数组。然后就可以去交给预处理去执行了。
数据预处理
## 数据检查与异常捕获
对于接口传过来的值,我们关心这样几个问题:
1、棋盘边长会不会太小了
2、棋盘是否不是正方形的
3、遍历的深度要控制在1,2,3,4以内,不然太大了,进程一下就阻塞了,要是部署在服务器上,可能一下服务器就瘫痪了
4、棋盘值会不会有超出1、0、-1以外的值
5、棋盘会不会满了
# 统计白子数
white_count = np.count_nonzero(board == 1)
# 统计黑子数
black_count = np.count_nonzero(board == -1)
# 统计空子数
empty_count = np.count_nonzero(board == 0)
"""输入检查"""
# TODO 修改最低大小
if length < 12:
raise ValueError(f"棋盘边长最低为12.")
if board.shape[1] != length:
raise ValueError(f"输入棋盘不是正方形.")
if depth not in [1,2,3,4]:
raise ValueError(f"输入的遍历深度有误, 应该为1,2,3或4.")
assert (white_count + black_count + empty_count) == board.size,\
"输入的棋盘数据有误.只能为0,1,-1.其中1代表白棋,-1代表黑棋,0代表空棋."
if empty_count == 0:
raise ValueError(f"输入的棋盘已满, 无法下棋.")
统计白子数:
使用 np.count_nonzero()
函数统计棋盘中值为1的元素个数,并将结果赋值给变量 white_count
。
统计黑子数:
使用 np.count_nonzero()
函数统计棋盘中值为-1的元素个数,并将结果赋值给变量 black_count
。
统计空子数:
使用 np.count_nonzero()
函数统计棋盘中值为0的元素个数,并将结果赋值给变量 empty_count
。
输入检查:
- 检查棋盘边长是否小于12,如果是,则抛出 ValueError
异常,提示棋盘边长最低为12。
- 检查输入的棋盘是否为正方形,如果不是,则抛出 ValueError
异常,提示输入棋盘不是正方形。
- 检查遍历深度是否为1、2、3或4,如果不是,则抛出 ValueError
异常,提示输入的遍历深度有误,应为1、2、3或4。
- 使用断言语句检查棋盘中的元素总数是否与棋盘大小相等,如果不相等,则抛出 AssertionError
异常,提示输入的棋盘数据有误,只能为0、1、-1,其中1代表白棋,-1代表黑棋,0代表空棋。
- 检查棋盘中空子的个数是否为0,如果是,则抛出 ValueError
异常,提示输入的棋盘已满,无法下棋。
## 预处理数据
# 当前玩家
player = 'black' if black_count == white_count else 'white'
# 当前步数
step = black_count + white_count + 1
# 开始计时
start_time = time.time()
# 电脑计算
x, y, search_count, flag = ai(player, ratio, length, board, depth)
# 结束计时
end_time = time.time()
根据当前的黑子和白子个数,确定当前的玩家。并计算回溯算法执行时间。
## 定义接口输出
定义的输出如下:
return {
'code': 200,
'msg': '成功',
'data': {
'x': x,
'y': y,
'time': end_time - start_time,
'step': step,
'player': player,
'length': length,
'search_count': search_count,
'flag': flag,
'info': "【默认黑子先行】"
"【黑子存-1,白子存1,空子存0】"
"【x:横坐标,从0开始,length结束】"
"【y:纵坐标,从0开始,length结束】"
"【time:搜索时间】"
"【step:这是第几步棋】"
"【player:当前下棋的玩家'black'/'white'】"
"【length:棋盘边长,线的个数,即格子的个数+1】"
"【search_count:搜索次数】"
"【flag:是否赢了】"
"【info:参数介绍】"
}
}
上述代码是一个返回结果的字典,包含以下键值对:
- 'code'
:表示返回的状态码,这里设置为 200 表示成功。
- 'msg'
:表示返回的消息,这里设置为 ‘成功’。
- 'data'
:表示返回的数据,是一个包含多个键值对的字典。
- 'x'
:表示横坐标。
- 'y'
:表示纵坐标。
- 'time'
:表示搜索时间,是结束时间减去开始时间的差值。
- 'step'
:表示当前步数。
- 'player'
:表示当前下棋的玩家,可以是 ‘black’ 或 ‘white’。
- 'length'
:表示棋盘边长,即格子的个数加1。
- 'search_count'
:表示搜索次数。
- 'flag'
:表示是否赢了。
- 'info'
:表示参数介绍的字符串,包含一些说明信息。
这段代码用于构建一个包含返回结果的字典,并将其作为函数的返回值。返回结果中包含了搜索结果的相关信息。
开启接口
此时去运行编写的接口的py文件:
如上图,表示开启成功,然后去写post请求调用就可以了。