天天围棋AI Server设计
一、服务端框架结构
围棋AI 服务器已有成熟的AI引擎,但引擎只能运行在windows环境下,因而服务器的主要工作就是将客户端的请求转发给AI引擎,并将计算结果反馈给客户端,相当于一个中转站。服务端的框架图如图(1)所示。客户端通过lotus接入层和服务器连接,服务器通过httpout将请求转发给windows环境下的Apache服务器,而AI引擎则作为一个cgi程序提供服务。
图(1)服务器框架图
二、协议设计
服务端和客户端的通信协议主要有以下四条。
1)服务器初始化协议。玩家进入游戏时,客户端会将玩家信息和游戏设置信息发送给服务端,包括玩家uin,终端类型,AI等级,棋盘大小,玩家执子颜色等。服务端收到消息之后,会记录玩家的游戏设置,并返回给客户端一个key用于唯一标示该游戏,key由uin和gametag组成,后面客户端向服务端发送消息时均需携带该key。
2)玩家落子协议。当玩家落子时,客户端向服务端发送该消息,包括游戏key,落子坐标,手数,棋子颜色等。服务端收到消息之后,对落子消息进行合法性检查,然后请求AI引擎,获得AI落子坐标,返回给客户端,至此,一次落子流程完成。
3)请求AI落子协议。有时第一手需AI落子,但服务端无法主动连接客户端,因而需要客户端首先发起请求,客户端请求时只需将游戏key和当前手数发送给服务端即可,服务端会返回AI的落子。
4)玩家离开游戏协议。当玩家离开游戏时,客户端需要通知服务端,服务端收到消息之后,会释放相应的资源。
5)重设服务端游戏信息协议。当服务端因异常导致游戏信息丢失时,在玩家落子时,会检测到游戏信息丢失,此时会在回包中向客户端请求完整的游戏信息。重建信息后可以继续对局。
三、开发模型
服务器采用状态机模型开发,由于协议一和协议三、协议二和协议四大部分流程相同,因而将他们放在同一个Transaction中实现,Server Init Transaction和Player Move Transaction的状态转换图如图(2)、图(3)所示。
服务端和AI引擎采用json格式字符串通信,主要字段有棋盘大小,AI等级,落子序列及下一手棋子颜色。这里有两点需要注意。
1)AI引擎使用数字1表示黑色棋子,2表示白色棋子,0表示无子,不可改变,否则计算结果错误。
2)AI引擎使用的棋盘坐标是1-19,而非0-18。当计算出的落子点为(0,0)时,表示AI停一手,并不是计算出错,一般只在玩家大幅落后AI时发生。
图(2)Server Init Transaction状态转换图
图(3)Player Move Transaction状态转换图
四、AI引擎的使用
1)加载引擎并获得其中的函数接口;
2)判断某一盘面之前先调用NewGame接口,BoardSize是棋盘大小;
NewGame(BoardSize);
3)将形成盘面的落子序列的每一步依次调用SetMove接口,xy为该手坐标,color是该手棋子颜色,nPrisoner是AI返回的提子数,Prisoner是提子坐标;
intnPrisoner; XY xy, xyPrisoner[360];
xy.x = j; // j 为某一步的横坐标(坐标范围1-19)
xy.y = i; // i 为某一步的纵坐标(坐标范围1-19)
SetMove(xy,color,&nPrisoner,xyPrisoner);
4)获得引擎认为的要走的下一步,color为AI执子的颜色,xy中即是AI的落子坐标。
XY xy;
GetNext(color, &xy);
由于每次获取AI落子时,都需要将所有的落子序列发送给引擎,随着对弈的进行,需要发送的数据量越来越大,当对弈进行到后期,每落一子需要发送1KB左右的数据,如果客户端每次都把整个落子序列发送过来,需要消耗很多流量,这是不可接受的。因而在服务器中暂存每个玩家的落子序列,客户端只需在玩家落子时,将当期的落子坐标发送给服务器即可,大大减少了客户端需要发送的数据。
落子序列的存储采用HashSwapMemory,key由服务端在游戏初始化时生成并返回给客户端。玩家离开游戏时,释放相应的资源。对于游戏异常退出,服务端没有收到离开消息的情况,服务端会每隔一段时间,检测所有玩家的活跃情况,如果玩家的不活跃时间超过阈值,则释放其资源。
五、游戏数据管理
考虑到一个玩家可能同时进行多局对弈,在存储落子序列时,key没有采用直接采用uin,而是采用(uin,gamtag)的方式。每个玩家默认最多可以同时进行8局对弈,即有8个gametag,当超过8轮时,会覆盖之前的对局数据。因而需要两个HashSwapMemory,一个存储玩家gametag的使用情况,key即是玩家uin,另一个存储玩家落子序列,key是(uin, gametag)。在游戏初始化时,根据玩家uin找到未使用的gametag,并返回给客户端。在玩家落子时,服务器根据(uin,gametag)存储落子信息,如图(4)所示。
图(4)Gametag和落子序列存储方式