房卡麻將分析系列 "牌局回放" 之 數(shù)據(jù)設(shè)計詳解及實例
房卡麻將分析系列 "牌局回放" 之 數(shù)據(jù)設(shè)計
最近幾個月,”房卡“棋牌游戲成為了資本追逐的熱點,基于微信的廣大用戶和社交屬性,”房卡”棋牌發(fā)展迅速。紅孩兒團隊因為之前幾年有過相關(guān)項目的經(jīng)驗積累,鑒于未來廣闊的地方棋牌市場和”開發(fā)間“機制的發(fā)展前景,也開始轉(zhuǎn)向基于”開房間“棋牌游戲的項目開發(fā)中。為了更好的與開發(fā)者進(jìn)行交流學(xué)習(xí),特開設(shè)”房卡麻將游戲分析系列“。

紅孩兒團隊研發(fā)的"大贏家"紅中麻將
本套麻將分析基于網(wǎng)絡(luò)上流傳的“網(wǎng)狐”房卡麻將源碼做為基礎(chǔ),按照功能模塊分為"架設(shè)指南",”服務(wù)器框架","后臺系統(tǒng)","胡牌算法","客戶端界面",“防作弊功能”等等細(xì)節(jié)做一些分析和指導(dǎo),幫助廣大的棋牌游戲開發(fā)者迅速掌握“房卡”麻將的研發(fā)原理和技巧設(shè)計。也希望有興趣的朋友多多關(guān)注。
第一次開公眾號,挑個簡單的下手,先來講一講房卡麻將中一個重要功能:“牌局回放”,我們都知道,棋牌類游戲注重公平真實不作弊,如果玩家感覺到游戲的過程有作弊,我相信他一定會對這款游戲失去興趣。但作弊與否,玩家并不容易進(jìn)行判斷。這時候提供一個“牌局回放”功能給玩家進(jìn)行分析就尤為重要。
“網(wǎng)狐”等一些長期耕耘在棋牌領(lǐng)域的企業(yè),在這方面都有完整的經(jīng)驗和框架,通過參考,我發(fā)現(xiàn)它是通過下面一套流程來完成”牌局回放“功能的。
首先,在游戲服務(wù)器的房間類CTableFrameSink里需要有一個GameRecord結(jié)構(gòu),這個結(jié)構(gòu)對 玩家信息,手牌以及每一步的動作都可以進(jìn)行相應(yīng)的記錄:
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; //供應(yīng)用戶
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數(shù)據(jù)流的宏,能夠?qū)?shù)據(jù)放入到數(shù)據(jù)流中或從中拿出。
#define Stream_VALUE(Name) \
if(bSend) \
{ \
kData.pushValue(Name);\
}\
else\
{\
kData.popValue(Name);\
}\
好了,有了這樣一個結(jié)構(gòu),在游戲開始的時候,我們就可以開始記錄本局了。
//游戲開始
void CTableFrameSink::GameStart()
{
...
//填充四個玩家的基礎(chǔ)信息
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]);
}
//存儲到當(dāng)前記錄結(jié)構(gòu)中的玩家信息容器。
m_sGameRecord.kPlayers.push_back(tNewRecordPlayer);
}
}
然后我們開始記錄操作,分別在玩家出牌,以及玩家應(yīng)答吃,碰,杠,胡等操作時加入記錄。
//用戶出牌
bool CTableFrameSink::OnUserOutCard(WORD wChairID, BYTE cbCardData)
{
...
//記錄動作數(shù)據(jù)
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)
{
...
//記錄動作數(shù)據(jù)
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);
...
}
就這樣,基本的操作記錄也完成了。最后當(dāng)牌局結(jié)束時,我們需要將記錄提交到數(shù)據(jù)庫中。
//游戲結(jié)束
bool CTableFrameSink::OnEventGameConclude(WORD wChairID, IServerUserItem * pIServerUserItem, BYTE cbReason)
{
switch (cbReason)
{
case GER_NORMAL: //常規(guī)結(jié)束
{
...
//將記錄轉(zhuǎn)化為數(shù)據(jù)流。
datastream kDataStream;
m_sGameRecord.StreamValue(kDataStream, true);
//除去寫分等處理,這里最后一個參數(shù)即是數(shù)據(jù)流。
m_pITableFrame->WriteTableScore(ScoreInfoArray, CountArray(ScoreInfoArray), kDataStream);
...
}
}
}
在私人場服務(wù)器中,會通過WriteTableScore這個函數(shù)調(diào)用PrivateTableInfo的writeSocre,它將將數(shù)的流記錄下來。

并最終在牌局結(jié)束時DismissRoom(pTableInfo);發(fā)給了數(shù)據(jù)庫。
數(shù)據(jù)庫最終會通過一個存儲過程的執(zhí)行完成將數(shù)據(jù)流入庫的工作。具體的代碼就不再展示了,大家可以參考
CDataBaseEngineSink::OnRequestPrivateGameRecord()。
這樣一套完整的回放數(shù)據(jù)流程就結(jié)束了。
好,今天的分析就到這里,紅孩兒歡迎大家下次繼續(xù)聽課哦~
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關(guān)文章
Android實現(xiàn)在TextView文字過長時省略部分或滾動顯示的方法
這篇文章主要介紹了Android實現(xiàn)在TextView文字過長時省略部分或滾動顯示的方法,結(jié)合實例形式分析了Android中TextView控件文字顯示及滾動效果相關(guān)操作技巧,需要的朋友可以參考下2016-10-10
詳解OpenGL Shader彩虹條紋效果的實現(xiàn)
這篇文章主要為大家介紹了如何通過OpenGL Shader實現(xiàn)彩虹條紋效果,最后的效果和圖片處理軟件colorow中的彩虹效果濾鏡相似,需要的可以參考一下2022-02-02
Android?shape與selector標(biāo)簽使用詳解
Android中提供一種xml的方式,讓我們可以自由地定義背景,比較常用的就是shape標(biāo)簽和selector標(biāo)簽,這篇文章主要介紹了Android?shape與selector標(biāo)簽使用,需要的朋友可以參考下2022-05-05
Android實現(xiàn)文字和圖片混排(文字環(huán)繞圖片)效果
這篇文章主要介紹了Android實現(xiàn)文字和圖片混排的方法,實例分析了文字環(huán)繞圖片效果的具體功能顯示及頁面布局實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10
Android中調(diào)用另一個Activity并返回結(jié)果(選擇頭像功能為例)
這篇文章主要介紹了Android中調(diào)用另一個Activity并返回結(jié)果,本文以模擬選擇頭像功能為例通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2020-01-01

