Python 机器学习实战(二):三个人也能联机下五子棋?怎么赢?
0 写在前面
人工智能被广泛用于棋类对弈的主要原因是:
-
棋类对弈自古以来就被认为是人类智力活动的象征,若人工智能成功达到、甚至高于人类水平,则就代表AI的发展潜力,从而吸引更多研究者关注并投身其中;
-
棋类很适合作为新AI算法的标杆。棋类游戏规则简洁、输赢都在盘面,适合计算机求解。理论上只要在计算能力和算法上有新的突破,任何新的棋类游戏都有可能得到攻克。而在棋类游戏上的表现也可以直观体现出AI之间计算能力与算法的高低,是促进AI算法发展的有效途径。
就本五子棋智能对弈系统 而言,其服务对象为同局域网下的多个终端,讲求联机互动
、互相限制
、互相博弈
,打破了传统二人对弈五子棋规则中,“若无禁手,先手易胜;若为后手,十堵九输”的说法,是AI算法设计、网络通信、数据库等技术的综合应用。
1 效果展示
2 项目需求与技术选型
- 系统可以正确判定胜负——以率先连续五子连成一条线的玩家为胜
- 支持三名玩家参与游戏且具有合理的UI界面,三人各执黑、白、黄三色棋子
- 系统包含对战服务平台,用以管理玩家对战、判定胜负、记录走子轨迹等
- 支持三个相同或有所不同的智能对弈Agent
- 能够在服务端记录并保存所有玩家历史走子记录或棋盘状态变化历史,并能够在客户端中回放
- 能在游戏对弈过程中根据走子数据动态输出对每个玩家水平的战斗力评级,以及每个玩家对自身当前态势即输赢概率的估计
- 对战服务平台可以将状态、评级等数据实时发送给客户端
总结起来,最终的效果就是:局域网内三个玩家联机,由对战服务器协调游戏进程,三个玩家可以是真人也可以是AI。
技术选型方面,前端使用PyQt
,后端使用Flask
,数据库使用MySQL
3 主要接口设计
上图所示是本项目在对弈过程中用到的主要接口。
上图所示是本项目在玩家管理中用到的主要接口。
4 项目流程
4.1 登录注册
其中选子界面
如图所示,用户可自主选择喜欢的颜色。客户端选子界面与服务器通过接口 /rest/player
产生交互,选择的颜色若未被其他玩家选择,服务器会返回一条成功信息给用户,并提交颜色注册信息到数据库;若该颜色已被其他玩家选择,服务器在提交用户注册信息到数据库时,会返回核验失败信息,客户端相应弹出“颜色已被选择”提示。
模式选择界面如图所示,用户可以自主选择“真人对战”或“AI对战”。选择后客户端记录相应模式。
等待界面如图所示,本系统支持3人对战,在玩家人数未达到要求时,所有玩家都将进入等待界面——类似于“游戏大厅”。此时界面通过 /rest/play
接口与服务器发生交互,服务器不断审查数据库的玩家注册信息,直至人数达标。基于PyQT封装的定时器属性Timer,当轮询到数据库返回成功消息后,Timer激活相应的槽函数,进入“中断服务”,在“中断服务”内使能“完成”按钮,并设置进度条达到100%
4.2 智能走子
下棋权限即是棋局状态State,由客户端与服务器通过 /rest/play/state
接口交互产生。
当State为1时即表示“轮到己方”,此时根据前设的模式决定不同的响应事件:“真人对战”——鼠标点击事件、“AI对战”——Agent走子事件,完成后再通过 /rest/board
接口将走子信息送达服务器记入数据库,并广播给其他客户端。
当State为0时即表示“轮到他方”,此时通过接口 /rest/play/state
获取其他客户端广播的走子信息,实现对战情况的实时更新。
5 项目实现
前端业务代码不一一展示,主要列出后端数据处理的逻辑。只要运行本文实现的后端服务器,采取任意方式的前端页面访问接口都能实现相应功能,包括但不限于PyQt
、C#
、MFC
、Vue
等方案。
5.1 用户管理
# 针对所有的用户的操作
class UserResource(Resource):
# get 请求的处理
# marshal 维持秩序,可以定制显示哪些数据(是有序的字典)
@marshal_with(user_fields)
def get(self):
users = User.query.all()
return users # 也可以users[0]
@marshal_with(user_fields)
def post(self):
# 获取数据
args = parser.parse_args() # 把验证通过的所有数据都放到一个字典里
username = args.get('username')
password = args.get('password')
# 创建user对象
user = User()
user.username = username
user.password = password
db.session.add(user)
db.session.commit()
return user # 返回创建成功的对象
# put
def put(self):
return {'msg': '------>put'}
# delete
def delete(self):
return {'msg': '------>delete'}
5.2 选子与游戏大厅玩家等待
# url:/rest/play
# 玩家在选完子之后向服务端不断发起提问,直至收到1
class RestPlayResource(Resource):
# get 请求的处理
def get(self):
result1 = Player.query.filter_by(ID=1).first()
if result1 is None:
playstate1 = 0 # 执黑棋的人
else:
playstate1 = 1
result2 = Player.query.filter_by(ID=2).first()
if result2 is None:
playstate2 = 0
else:
playstate2 = 1
result3 = Player.query.filter_by(ID=3).first()
if result3 is None:
playstate3 = 0
else:
playstate3 = 1
if playstate1 and playstate2 and playstate3:
playstate = 1
else:
playstate = 0
return {"PlayState": playstate}
# url:/rest/player
# 判断玩家是否可以选择这个颜色的棋子,选子成功则注册玩家信息
class RestPlayerResource(Resource):
# post 请求的处理
def post(self):
# 获取数据
# global CountPlayers
args = parser.parse_args() # 把验证通过的所有数据都放到一个字典里
id = args.get('id')
color = args.get('color')
# 查询数据库中有没有重复的id
result = Player.query.filter_by(ID=id).first()
if result is None:
# 创建player对象,并加入数据库
player = Player()
player.ID = id
player.Player = color
player.Enable = 0
db.session.add(player)
db.session.commit()
if id == 1:
msg = "您当前执棋为黑棋"
config.add_value("CountPlayers", 1)
# CountPlayers = CountPlayers + 1
elif id == 2:
msg = "您当前执棋为白棋"
config.add_value("CountPlayers", 1)
# CountPlayers = CountPlayers + 1
else:
msg = "您当前执棋为黄棋"
config.add_value("CountPlayers", 1)
# CountPlayers = CountPlayers + 1
if config.get_value("CountPlayers") == 3:
# if CountPlayers == 3: 如果三个人都到齐了 删掉上一次的走子信息表
Pieces.query.delete()
db.session.commit()
config.init_board() # 清全局变量
return {"succ": 1, "msg": msg}
else:
msg = "该颜色已被选择!请重新选择。"
return {"succ": 0, "msg": msg}
5.3 AI智能走子
def judge(JudgeId, x, y):
# global WinnerId, WinX1, WinX2, WinY1, WinY2, BoardSize
# 新建函数内变量 修改完成后提交全局变量
boardsizetemp = config.get_value("BoardSize")
checkerboardtemp = config.get_value("checkerboard")
winx1temp = config.get_value("WinX1")
winx2temp = config.get_value("WinX2")
winy1temp = config.get_value("WinY1")
winy2temp = config.get_value("WinY2")
winneridtemp = config.get_value("WinnerId")
# 判断算法
# 判断最后一子竖排
if y - 4 >= 0 and y <= boardsizetemp - 1:
if checkerboardtemp[x][y - 1] == JudgeId \
and checkerboardtemp[x][y - 2] == JudgeId \
and checkerboardtemp[x][y - 3] == JudgeId \
and checkerboardtemp[x][y - 4] == JudgeId:
winneridtemp = JudgeId
winx1temp = x
winx2temp = x
winy1temp = y
winy2temp = y - 4
if y - 3 >= 0 and y + 1 <= boardsizetemp - 1:
if checkerboardtemp[x][y - 3] == JudgeId \
and checkerboardtemp[x][y - 2] == JudgeId \
and checkerboardtemp[x][y - 1] == JudgeId \
and checkerboardtemp[x][y + 1] == JudgeId:
winneridtemp = JudgeId
winx1temp = x
winx2temp = x
winy1temp = y + 1
winy2temp = y - 3
if y - 2 >= 0 and y + 2 <= boardsizetemp - 1:
if checkerboardtemp[x][y - 2] == JudgeId \
and checkerboardtemp[x][y - 1] == JudgeId \
and checkerboardtemp[x][y + 1] == JudgeId \
and checkerboardtemp[x][y + 2] == JudgeId:
winneridtemp = JudgeId
winx1temp = x
winx2temp = x
winy1temp = y + 2
winy2temp = y - 2
if y - 1 >= 0 and y + 3 <= boardsizetemp - 1:
if checkerboardtemp[x][y - 1] == JudgeId \
and checkerboardtemp[x][y + 1] == JudgeId \
and checkerboardtemp[x][y + 2] == JudgeId \
and checkerboardtemp[x][y + 3] == JudgeId:
winneridtemp = JudgeId
winx1temp = x
winx2temp = x
winy1temp = y + 3
winy2temp = y - 1
if y >= 0 and y + 4 <= boardsizetemp - 1:
if checkerboardtemp[x][y + 1] == JudgeId \
and checkerboardtemp[x][y + 2] == JudgeId \
and checkerboardtemp[x][y + 3] == JudgeId \
and checkerboardtemp[x][y + 4] == JudgeId:
winneridtemp = JudgeId
winx1temp = x
winx2temp = x
winy1temp = y + 4
winy2temp = y
# 判断最后一子横排
if x - 4 >= 0 and x <= boardsizetemp - 1:
if checkerboardtemp[x - 1][y] == JudgeId \
and checkerboardtemp[x - 2][y] == JudgeId \
and checkerboardtemp[x - 3][y] == JudgeId \
and checkerboardtemp[x - 4][y] == JudgeId:
winneridtemp = JudgeId
winx1temp = x
winx2temp = x - 4
winy1temp = y
winy2temp = y
if x - 3 >= 0 and x + 1 <= boardsizetemp - 1:
if checkerboardtemp[x - 3][y] == JudgeId \
and checkerboardtemp[x - 2][y] == JudgeId \
and checkerboardtemp[x - 1][y] == JudgeId \
and checkerboardtemp[x + 1][y] == JudgeId:
winneridtemp = JudgeId
winx1temp = x + 1
winx2temp = x - 3
winy1temp = y
winy2temp = y
if x - 2 >= 0 and x + 2 <= boardsizetemp - 1:
if checkerboardtemp[x - 2][y] == JudgeId \
and checkerboardtemp[x - 1][y] == JudgeId \
and checkerboardtemp[x + 1][y] == JudgeId \
and checkerboardtemp[x + 2][y] == JudgeId:
winneridtemp = JudgeId
winx1temp = x + 2
winx2temp = x - 2
winy1temp = y
winy2temp = y
if x - 1 >= 0 and x + 3 <= boardsizetemp - 1:
if checkerboardtemp[x - 1][y] == JudgeId \
and checkerboardtemp[x + 1][y] == JudgeId \
and checkerboardtemp[x + 2][y] == JudgeId \
and checkerboardtemp[x + 3][y] == JudgeId:
winneridtemp = JudgeId
winx1temp = x + 3
winx2temp = x - 1
winy1temp = y
winy2temp = y
if x >= 0 and x + 4 <= boardsizetemp - 1:
if checkerboardtemp[x + 1][y] == JudgeId \
and checkerboardtemp[x + 2][y] == JudgeId \
and checkerboardtemp[x + 3][y] == JudgeId \
and checkerboardtemp[x + 4][y] == JudgeId:
winneridtemp = JudgeId
winx1temp = x + 4
winx2temp = x
winy1temp = y
winy2temp = y
# 判断最后一子45°
if x - 4 >= 0 and y - 4 >= 0 and x <= boardsizetemp - 1 and y <= boardsizetemp - 1:
if checkerboardtemp[x - 1][y - 1] == JudgeId \
and checkerboardtemp[x - 2][y - 2] == JudgeId \
and checkerboardtemp[x - 3][y - 3] == JudgeId \
and checkerboardtemp[x - 4][y - 4] == JudgeId:
winneridtemp = JudgeId
winx1temp = x
winx2temp = x - 4
winy1temp = y
winy2temp = y - 4
if x - 3 >= 0 and y - 3 >= 0 and x + 1 <= boardsizetemp - 1 and y + 1 <= boardsizetemp - 1:
if checkerboardtemp[x + 1][y + 1] == JudgeId \
and checkerboardtemp[x - 1][y - 1] == JudgeId \
and checkerboardtemp[x - 2][y - 2] == JudgeId \
and checkerboardtemp[x - 3][y - 3] == JudgeId:
winneridtemp = JudgeId
winx1temp = x + 1
winx2temp = x - 3
winy1temp = y + 1
winy2temp = y - 3
if x - 2 >= 0 and y - 2 >= 0 and x + 2 <= boardsizetemp - 1 and y + 2 <= boardsizetemp - 1:
if checkerboardtemp[x + 2][y + 2] == JudgeId \
and checkerboardtemp[x + 1][y + 1] == JudgeId \
and checkerboardtemp[x - 1][y - 1] == JudgeId \
and checkerboardtemp[x - 2][y - 2] == JudgeId:
winneridtemp = JudgeId
winx1temp = x + 2
winx2temp = x - 2
winy1temp = y + 2
winy2temp = y - 2
if x - 1 >= 0 and y - 1 >= 0 and x + 3 <= boardsizetemp - 1 and y + 3 <= boardsizetemp - 1:
if checkerboardtemp[x + 3][y + 3] == JudgeId \
and checkerboardtemp[x + 2][y + 2] == JudgeId \
and checkerboardtemp[x + 1][y + 1] == JudgeId \
and checkerboardtemp[x - 1][y - 1] == JudgeId:
winneridtemp = JudgeId
winx1temp = x + 3
winx2temp = x - 1
winy1temp = y + 3
winy2temp = y - 1
if x >= 0 and y >= 0 and x + 4 <= boardsizetemp - 1 and y + 4 <= boardsizetemp - 1:
if checkerboardtemp[x + 1][y + 1] == JudgeId \
and checkerboardtemp[x + 2][y + 2] == JudgeId \
and checkerboardtemp[x + 3][y + 3] == JudgeId \
and checkerboardtemp[x + 4][y + 4] == JudgeId:
winneridtemp = JudgeId
winx1temp = x + 4
winx2temp = x
winy1temp = y + 4
winy2temp = y
# 判断最后一子135°
if x - 4 >= 0 and y + 4 <= boardsizetemp - 1 and x <= boardsizetemp - 1 and y >= 0:
if checkerboardtemp[x - 1][y + 1] == JudgeId \
and checkerboardtemp[x - 2][y + 2] == JudgeId \
and checkerboardtemp[x - 3][y + 3] == JudgeId \
and checkerboardtemp[x - 4][y + 4] == JudgeId:
winneridtemp = JudgeId
winx1temp = x
winx2temp = x - 4
winy1temp = y
winy2temp = y + 4
if x - 3 >= 0 and y + 3 <= boardsizetemp - 1 and x + 1 <= boardsizetemp - 1 and y - 1 >= 0:
if checkerboardtemp[x - 1][y + 1] == JudgeId \
and checkerboardtemp[x - 2][y + 2] == JudgeId \
and checkerboardtemp[x - 3][y + 3] == JudgeId \
and checkerboardtemp[x + 1][y - 1] == JudgeId:
winneridtemp = JudgeId
winx1temp = x + 1
winx2temp = x - 3
winy1temp = y - 1
winy2temp = y + 3
if x - 2 >= 0 and y + 2 <= boardsizetemp - 1 and x + 2 <= boardsizetemp - 1 and y - 2 >= 0:
if checkerboardtemp[x - 1][y + 1] == JudgeId \
and checkerboardtemp[x - 2][y + 2] == JudgeId \
and checkerboardtemp[x + 1][y - 1] == JudgeId \
and checkerboardtemp[x + 2][y - 2] == JudgeId:
winneridtemp = JudgeId
winx1temp = x + 2
winx2temp = x - 2
winy1temp = y - 2
winy2temp = y + 2
if x - 1 >= 0 and y + 1 <= boardsizetemp - 1 and x + 3 <= boardsizetemp - 1 and y - 3 >= 0:
if checkerboardtemp[x - 1][y + 1] == JudgeId \
and checkerboardtemp[x + 1][y - 1] == JudgeId \
and checkerboardtemp[x + 2][y - 2] == JudgeId \
and checkerboardtemp[x + 3][y - 3] == JudgeId:
winneridtemp = JudgeId
winx1temp = x + 3
winx2temp = x - 1
winy1temp = y - 3
winy2temp = y + 1
if x >= 0 and y <= boardsizetemp - 1 and x + 4 <= boardsizetemp - 1 and y - 4 >= 0:
if checkerboardtemp[x + 1][y - 1] == JudgeId \
and checkerboardtemp[x + 2][y - 2] == JudgeId \
and checkerboardtemp[x + 3][y - 3] == JudgeId \
and checkerboardtemp[x + 4][y - 4] == JudgeId:
winneridtemp = JudgeId
winx1temp = x + 4
winx2temp = x
winy1temp = y
winy2temp = y - 4
else:
pass
config.set_value("WinnerId", winneridtemp)
config.set_value("WinX1", winx1temp)
config.set_value("WinX2", winx2temp)
config.set_value("WinY1", winy1temp)
config.set_value("WinY2", winy2temp)
6 项目实际运行展示
结果表明:三个相同算法的AI分不出胜负,可以引入合作-竞争机制、增强学习算法等强化Agent算法,或是两个人类围堵一个AI。
🔥 更多精彩专栏:
🏠 欢迎加入社区和更多志同道合的朋友交流:AI 技术社
- 点赞
- 收藏
- 关注作者
评论(0)