网络斗地主游戏的完整设计与实现(四)游戏状态更新机制与心跳机制

引言

在前一篇文章中讲解了通过入口存储过程动态调用业务过程的原理。下面来说明如何实现游戏状态的更新。

项目的源码可在CSDN资源中下载

实现原理

由于http协议是无状态的请求响应式协议,用户可以主动向服务器发起请求,得到回复。但如果服务器状态发生了变化,却不能直接地推送消息给用户。

在web应用中,消息推送是一个长期讨论的话题。现在已经有了比较成熟的解决方案,比如websocket。

不过本项目使用的是最基本的轮询方式,即客户端定时向服务器发起一个请求,检查是否有留给自已的消息,如果有属于自己的消息,则根据消息更新浏览器上显示的内容。与此同时,定时向服务器发起的请求,也是一个心跳信号,定时向服务器报告自己还活着。如果在指定的时间期限过后,还未收到某客户的心跳信号,服务器则会认为该客户已离线。

具体实现

项目引入了jquery.timers-1.2.js,这是一个可以实现定时功能的jquery扩展。

现在看一下源文件:GameEvents.js


//游戏过程中的各种事件,通过不断查询数据库中的公告信息,形成各种游戏事件处理框架
function GameEvents(deskID,playerNo,onEnterRoom,onReady,onDealCards,onBid,onBidOver,onPlayCard,onGameOver,onExitRoom,onNoneBidOver,onSurrenderonTimeTick)
{    
    //处理各种公告信息
    this.OnReceivedNoteInfo=function(result)
    {
        var resObj=eval("("+result+")");

        //enter room
        if (resObj.noteType == 1)  
        {
            onEnterRoom(resObj.content);
        } 
        
        ......
        
    }
    
    var eventHandler = this.OnReceivedNoteInfo;
    
    this.StartTime=new Date();
    
    this.start=function()
    {
        this.StartTime=new Date();
        //$('body').everyTime   
        //$('body').oneTime 
        $('body').everyTime    
        ('1s','pluse',
        function()
        { 
          callProc("readNote" , deskID +","+playerNo , eventHandler);  
          onTimeTick();
        },
        0,true);
    }  
    
    this.stop = function()
    {
        $('body').stopTime ('pluse');
    }
    
    this.ElapsedMinutes = function()
    {
        var nowTime = new Date();
        return parseInt(Math.abs(nowTime - this.StartTime)/1000/60);

    }
}

其中的this.start方法

this.start=function()
    {
        this.StartTime=new Date();        
        $('body').everyTime    
        ('1s','pluse',
        function()
        { 
          callProc("readNote" , deskID +","+playerNo , eventHandler);  
          onTimeTick();
        },
        0,true);
    }  

实现每秒1次的定时请求,在这个请求中调用 callProc(“readNote” , deskID +","+playerNo , eventHandler);

按照上一篇文章的说明,这里调用了存储过程 readNote。现在看一下readNote存储过程

CREATE                    procedure readNote
(
@deskID int,
@playerNo int,
@outMsg varchar(500) out
)
as
begin
	--更新到访时间
	update deskPlayer set lastTime = getdate()
		where deskID = @deskID and playerNo = @playerNo	
	--接收通告
	declare @noteID int
	select @noteID = min(noteID) from v_note
		where deskID = @deskID and receiverNo = @playerNo

	if @noteID is null
	begin
		set @outMsg='{noteType:0}'
		return 0
	end
	
	declare @noteType int,@content varchar(500)
	select @content = content,@noteType = noteType from note where noteID = @noteID
	
	delete noteRecipient  where noteID = @noteID and playerNo=@playerNo

	delete note where noteID not in(select noteID from noteRecipient)
 
	set @outMsg='{noteType:'+cast(@noteType as varchar)+',content:' +@content + '}'
	return 0
end

从以上代码可知,这个存储过程实现了两个功能,
一是将本次请求的访问时间记录在数据表 deskPlayer当中
二是从note 表中取出属于自己的通告消息

下面先看一下deskPlayer表
在这里插入图片描述
其中的lastTime列记录了每次用户的心跳,也就是调用存储过程readNote的时刻。
现在看一下检查用户是否离线的存储过程

CREATE                        procedure checkOffLine
as
begin
	--检查是否有人离线
	declare @offPlayer table(idx int identity(1,1),roomID int,playerID int)
	insert into @offPlayer(roomID,playerID)
		select roomID,playerID from v_roomPlayer 
		where 	dateDiff(second,lastTime,getdate()) > 5  -- off line seconds
	
	declare @roomID int,@offplayerID int, @offPlayerCnt int
	select @offPlayerCnt = count(*) from @offPlayer
	declare @outMsg varchar(100)	
	declare @i int
	set @i=1
	while @i <= @offPlayerCnt
	begin
		select @roomID = roomID,@offplayerID = playerID from @offPlayer where idx = @i
		set @i = @i +1
		exec exitRoom @roomID,@offplayerID,@outMsg out
	end	
	return 0	
end

其中的核心查询语句是

insert into @offPlayer(roomID,playerID)
		select roomID,playerID from v_roomPlayer 
		where 	dateDiff(second,lastTime,getdate()) > 5  -- off line seconds

这个语句从v_roomPlayer 视图,该视图基于表deskPlayer,查找出当前时间与心跳报告时间之差大于5秒的记录。也就是说超过5秒,服务器都没有收到浏览器的心跳报告,就会被认为已经离线。

针对每一个被判定已经离线的用户,调用exitRoom存储过程,而在exitRoom存储过程向其它应该接收消息的用户留下公告消息,在1秒之后,其它活跃用户将会再次向服务器发起请求,就会得到用户离线的公告消息。

接下来看一下接收公告消息的实现
由playNo得到要接收的消息的noteID.

--接收通告
	declare @noteID int
	select @noteID = min(noteID) from v_note
		where deskID = @deskID and receiverNo = @playerNo

在这里要解释一下,为什么不用用户的ID,而是要用playerNo做为查询的依据。 playerNo是在一张牌桌上用户的编号,取值只能是1,2,3中的一个,分别代表西,南,东三个方位的玩家。而playerID是用户的唯一ID,但这个ID不能代表玩家在牌桌上的方位。 而且比较有趣的一点是,对于每个浏览器前的用户而言,他的方位永远是南方,但是它分配得到的playerNo在用户没有离开时永远保持不变。

下面先看一下note表,此表中记录的所有的公告消息
在这里插入图片描述
以上的公告消息在readNote过程中,如果已经被读取,则会被删除掉。

小结

将心跳机制与公告消息机制结合起来,运用轮询的方式实现游戏状态的定时更新。在未引入websocket的前提下,该方案不失为一种简单方便的解决方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值