房卡麻将分析系列 "牌局回放" 之 数据设计详解及实例

房卡麻将分析系列 "牌局回放" 之 数据设计

最近几个月,”房卡“棋牌游戏成为了资本追逐的热点,基于微信的广大用户和社交属性,”房卡”棋牌发展迅速。红孩儿团队因为之前几年有过相关项目的经验积累,鉴于未来广阔的地方棋牌市场和”开发间“机制的发展前景,也开始转向基于”开房间“棋牌游戏的项目开发中。为了更好的与开发者进行交流学习,特开设”房卡麻将游戏分析系列“。

红孩儿团队研发的"大赢家"红中麻将

本套麻将分析基于网络上流传的“网狐”房卡麻将源码做为基础,按照功能模块分为"架设指南",”服务器框架","后台系统","胡牌算法","客户端界面",“防作弊功能”等等细节做一些分析和指导,帮助广大的棋牌游戏开发者迅速掌握“房卡”麻将的研发原理和技巧设计。也希望有兴趣的朋友多多关注。

第一次开公众号,挑个简单的下手,先来讲一讲房卡麻将中一个重要功能:“牌局回放”,我们都知道,棋牌类游戏注重公平真实不作弊,如果玩家感觉到游戏的过程有作弊,我相信他一定会对这款游戏失去兴趣。但作弊与否,玩家并不容易进行判断。这时候提供一个“牌局回放”功能给玩家进行分析就尤为重要。

“网狐”等一些长期耕耘在棋牌领域的企业,在这方面都有完整的经验和框架,通过参考,我发现它是通过下面一套流程来完成”牌局回放“功能的。

首先,在游戏服务器的房间类CTableFrameSink里需要有一个GameRecord结构,这个结构对 玩家信息,手牌以及每一步的动作都可以进行相应的记录:

struct GameRecordPlayer { DWORD dwUserID; std::string kHead; std::string kNickName; std::vector<BYTE> cbCardData; void StreamValue(datastream& kData, bool bSend) { Stream_VALUE(dwUserID); Stream_VALUE(kHead); Stream_VALUE(kNickName); Stream_VECTOR(cbCardData); } }; struct GameRecordOperateResult { enum Type { TYPE_NULL, TYPE_OperateResult, TYPE_SendCard, TYPE_OutCard, TYPE_ChiHu, }; GameRecordOperateResult() { cbActionType = 0; wOperateUser = 0; wProvideUser = 0; cbOperateCode = 0; cbOperateCard = 0; } BYTE cbActionType; WORD wOperateUser; //操作用户 WORD wProvideUser; //供应用户 BYTE cbOperateCode; //操作代码 BYTE cbOperateCard; //操作扑克 void StreamValue(datastream& kData, bool bSend) { Stream_VALUE(cbActionType); Stream_VALUE(wOperateUser); Stream_VALUE(wProvideUser); Stream_VALUE(cbOperateCode); Stream_VALUE(cbOperateCard); } }; struct GameRecord { std::vector<GameRecordPlayer> kPlayers; std::vector<GameRecordOperateResult> kAction; void StreamValue(datastream& kData, bool bSend) { StructVecotrMember(GameRecordPlayer, kPlayers); StructVecotrMember(GameRecordOperateResult, kAction); } void CleanUp() { kPlayers.clear(); kAction.clear(); } };

在datastream.h中,有一套set,get数据流的宏,能够将数据放入到数据流中或从中拿出。

#define Stream_VALUE(Name) \ if(bSend) \ { \ kData.pushValue(Name);\ }\ else\ {\ kData.popValue(Name);\ }\

好了,有了这样一个结构,在游戏开始的时候,我们就可以开始记录本局了。

//游戏开始 void CTableFrameSink::GameStart() { ... //填充四个玩家的基础信息 for (int i = 0; i < 4; i++) { GameRecordPlayer tNewRecordPlayer; tagUserInfo * tpUserInfo = m_pITableFrame->GetTableUserItem(i)->GetUserInfo(); tNewRecordPlayer.dwUserID = tpUserInfo->dwUserID; tNewRecordPlayer.kNickName = tpUserInfo->szNickName; //取得手牌信息 BYTE cbCardData[MAX_COUNT]; m_GameLogic.SwitchAllToCardData(m_cbCardIndex[i], cbCardData); for (int j = 0; j < MAX_COUNT ; j++) { tNewRecordPlayer.cbCardData.push_back(cbCardData[j]); } //存储到当前记录结构中的玩家信息容器。 m_sGameRecord.kPlayers.push_back(tNewRecordPlayer); } }

然后我们开始记录操作,分别在玩家出牌,以及玩家应答吃,碰,杠,胡等操作时加入记录。

//用户出牌 bool CTableFrameSink::OnUserOutCard(WORD wChairID, BYTE cbCardData) { ... //记录动作数据 GameRecordOperateResult tNewRecordOperateResult; tNewRecordOperateResult.cbActionType = GameRecordOperateResult::TYPE_OutCard; tNewRecordOperateResult.cbOperateCard = cbCardData; tNewRecordOperateResult.cbOperateCode = WIK_NULL; tNewRecordOperateResult.wOperateUser = wChairID; tNewRecordOperateResult.wProvideUser = wChairID; m_sGameRecord.kAction.push_back(tNewRecordOperateResult); ... }
//用户操作 bool CTableFrameSink::OnUserOperateCard(WORD wChairID, BYTE cbOperateCode, BYTE cbOperateCard) { ... //记录动作数据 GameRecordOperateResult tNewRecordOperateResult; tNewRecordOperateResult.cbActionType = XZDDGameRecordOperateResult::TYPE_OperateResult; tNewRecordOperateResult.cbOperateCard = cbOperateCard; tNewRecordOperateResult.cbOperateCode = cbOperateCode; tNewRecordOperateResult.wOperateUser = wChairID; tNewRecordOperateResult.wProvideUser = m_wProvideUser; m_sGameRecord.kAction.push_back(tNewRecordOperateResult); ... }

就这样,基本的操作记录也完成了。最后当牌局结束时,我们需要将记录提交到数据库中。

//游戏结束 bool CTableFrameSink::OnEventGameConclude(WORD wChairID, IServerUserItem * pIServerUserItem, BYTE cbReason) { switch (cbReason) { case GER_NORMAL: //常规结束 { ... //将记录转化为数据流。 datastream kDataStream; m_sGameRecord.StreamValue(kDataStream, true); //除去写分等处理,这里最后一个参数即是数据流。 m_pITableFrame->WriteTableScore(ScoreInfoArray, CountArray(ScoreInfoArray), kDataStream); ... } } }

在私人场服务器中,会通过WriteTableScore这个函数调用PrivateTableInfo的writeSocre,它将将数的流记录下来。

并最终在牌局结束时DismissRoom(pTableInfo);发给了数据库。

数据库最终会通过一个存储过程的执行完成将数据流入库的工作。具体的代码就不再展示了,大家可以参考

CDataBaseEngineSink::OnRequestPrivateGameRecord()。

这样一套完整的回放数据流程就结束了。

好,今天的分析就到这里,红孩儿欢迎大家下次继续听课哦~

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

时间: 2024-07-31 02:43:57

房卡麻将分析系列 "牌局回放" 之 数据设计详解及实例的相关文章

详解房卡麻将分析系列 "牌局回放" 之 播放处理

详解房卡麻将分析系列 "牌局回放" 之 播放处理 昨天红孩儿给大伙讲了讲"牌局回放"的数据记录处理,有了数据的存储,下面就是数据的显示了. 实话讲,好久没用过 SQL Server 来做数据库了, 网狐的服务器是基于WIN,IOCP,  SQL Server 这套路子.配置好后,可以在QPTreasureDB数据库中看到三个牌局相关的表. 其中dbo.PrivateGameRecord是存储当前游戏的房间及玩家,最终胜负信息的. dbo.PrivateGameRec

dubbo源码分析系列(4)dubbo通信设计

1 系列目录 dubbo源码分析系列(1)扩展机制的实现 dubbo源码分析系列(2)服务的发布 dubbo源码分析系列(3)服务的引用 dubbo源码分析系列(4)dubbo通信设计 2 NIO通信层的抽象 目前dubbo已经集成的有netty.mina.grizzly.先来通过案例简单了解下netty.mina编程(grizzly没有了解过) 2.1 netty和mina的简单案例 netty原本是jboss开发的,后来单独出来了,所以会有两种版本就是org.jboss.netty和io.n

PHP输出缓冲控制Output Control系列函数详解_php实例

概述 以前研究过PHP的输入输出缓冲,不过博客搬家以后,原来文章找不到了,今天看到一篇好文,顺便转载过来. 简介 说到输出缓冲,首先要说的是一个叫做缓冲器(buffer)的东西.举个简单的例子说明他的作用:我们在编辑一篇文档时,在我们没有保存之前,系统是不会向磁盘写入的,而是写到buffer中,当buffer写满或者执行了保存操作,才会将数据写入磁盘.对于PHP来说,每一次像 echo 这样的输出操作,同样是先写入到了 php buffer 里,在脚本执行完毕或者执行了强制输出缓存操作,数据才会

腾讯分析Tencent Analytics怎么用 Tencent Analytics使用详解

无论我们是个人博客.网站站长,还是大型门户.企业网站,知晓网站访客的数据信息是必须的.因为我们可以从访客的信息中获得网站和运营效果的转换分析统计,以及我们可以有针对性的重新部署新的推广策略.所以,无论大小网站,我们都会给网站安装统计工具.   对于大部分中小网站,我们使用较多的还是免费网站统计工具,比如百度统计.CNZZ统计.Google Analytics.量子恒统计,当然我们也有喜欢折腾的朋友可能会用到自定义安装一些开源程序Piwik.从功能和工具软件的背景优势看,肯定是Google Ana

微信 小程序前端源码详解及实例分析_其它综合

微信小程序前端源码逻辑和工作流 看完微信小程序的前端代码真的让我热血沸腾啊,代码逻辑和设计一目了然,没有多余的东西,真的是大道至简. 废话不多说,直接分析前端代码.个人观点,难免有疏漏,仅供参考. 文件基本结构: 先看入口app.js,app(obj)注册一个小程序.接受一个 object 参数,其指定小程序的生命周期函数等.其他文件可以通过全局方法getApp()获取app实例,进而直接调用它的属性或方法,例如(getApp().globalData) //app.js App({ onLau

学DNS系列(十)图、例详解DNS递归和迭代查询原理及过程(1)

上节中提到了一些有关递归查询的内容,但说的很少,也很笼统,本节将会从原理和实例两方面入手分析DNS的递归以及迭代查询. 在此之前,我们需要了解一些背景知识,以便于更好的理解今天的主题内容. 在互联网中,一个域名的顺利解析离不开两类域名服务器,只有由这两类域名服务器可以提供"权威性"的域名解析. 第一类就是国际域名管理机构,也就InterNIC,主要负责国际域名的注册和解析,第二类就是国内域名注册管理机构,在中国就是CNNIC了,主要负责国内域名注册和解析,当然,尽管分为国际和国内,但两

《系统飞狐》系列软件中的“系统监视器”开发详解

自2001年开发系统飞狐系列软件以来,系统飞狐软件只进行过三次升级,现在由于要做毕业设计,更加没有时间来对整个软件,现把整个系统中一个软件--"系统监视器"的源代码拿出来共享,恳请大家指正,希望大家一起把它做得更好.该系统目前支持只支持Window9x,开发平台为VC++6.0,软件界面如图一所示,熟悉windows操作系统的朋友们都知道注册表在windows操作系统中核心地位,系统的许多设置和操作都保存在注册表中,比如添加删除功能.系统的所有设置(桌面.图标.系统文件夹,系统自动运行

解读ASP.NET 5 &amp; MVC6系列教程(6):Middleware详解_自学过程

在第1章项目结构分析中,我们提到Startup.cs作为整个程序的入口点,等同于传统的Global.asax文件,即:用于初始化系统级的信息(例如,MVC中的路由配置).本章我们就来一一分析,在这里如何初始化这些系统级的信息. 新旧版本之间的Pipeline区别 ASP.NET 5和之前版本的最大区别是对HTTP Pipeline的全新重写,在之前的版本中,请求过滤器的通常是以HttpModule为模块组件,这些组件针对HttpApplication里定义的各个周期内的事件进行响应,从而用于实现

用GRC对无线电信号进行安全分析研究后转为数据包详解

介绍 InGuardians作为一家从事信息安全研究和咨询的公司,自创立以来不但关注着web应用的渗透测试,网络取证,嵌入式设备等领域也致力于无线网络的评估方法上面的研究.在期间无线网络评估也从起初单一的企业无线网络部署慢慢地发展到开始涉及一些通用或自定义的蓝牙,zigbee等网络的分析. InGuardians和其它一些企业,安全机构一样会一直通过参考其它人发表的一些研究结果来扩充自己的知识.在利用