您的位置:首页 > 其它

游戏工作笔记

2010-12-21 09:21 267 查看
代码流程图
游戏开始类型0 = 2人以上举手就开始游戏(比如五张牌、马股),1 = 对家举手就开始游戏(通常只有2个椅子,比如象棋台球),2 = 全部举手才开始游戏(比如麻将、斗地主)
客户端:
IClientSite * m_pSite; // IClientSite接口也是有框架自动填充的
IUserList * m_pList; // 用户列表//在OnInitialize()中赋值
IGamePlayer * m_pMyself; // 代表自己的指针//在OnInitialize()中赋值
RenderHelper m_quickStartelper; //由系统自动填入
先是用户点击进入房间,发生下面事情:
STDMETHODIMP CGameWnd::OnInitialize()
{
// 在此处获得自己(myself)的指针,用户列表的指针
m_pSite->GetMyself(&m_pMyself);
m_pSite->GetPlayerList(&m_pList);

return S_OK;
}
m_pMyself 和m_pList这两个数据结构都是框架提供的
这里的m_pMyself是IGamePlayer类型,该类型跟游戏数据无关的,跟游戏用户数据相关的要自己定义,只是,此类型结构定义如下
IGamePlayer : public IUnknown
{
STDMETHOD(GetPlayer)(void * pPlayerInfo) PURE;

// user data manager

STDMETHOD(put_UserStatus)(int nStatus) PURE; // 设置UserStatus属性
STDMETHOD_(int, get_UserStatus)(int * pStatus=NULL) PURE; // 读取UserStatus属性
STDMETHOD(put_UserData)(int nValue) PURE; // 设置UserData属性
STDMETHOD_(int, get_UserData)(int * pValue=NULL) PURE; // 读取UserData属性
STDMETHOD(put_UserDataBlock)(void * lpdata) PURE; // 设置UserDataBlock属性
STDMETHOD(get_UserDataBlock)(void ** ppdata) PURE; // 读取UserDataBlock属性
STDMETHOD(GetLookOnPeopleList)(IUserList **ppUserList) PURE; // 获取旁观者列表

// helper functions

STDMETHOD_(BOOL, IsValidUser)() PURE; // 用户是否有效
STDMETHOD_(BOOL, IsPlayer)() PURE; // 是不是玩家

STDMETHOD(put_State)(int sState) PURE; // 设置State属性
STDMETHOD_(int, get_State)(int * pState=NULL) PURE; // 读取State属性

STDMETHOD(put_EnableLookOn)(BOOL bEnable) PURE; // 设置EnableLookOn属性
STDMETHOD_(BOOL, get_EnableLookOn)(BOOL * pEnable=NULL) PURE; // 读取EnableLookOn属性

STDMETHOD_(DWORD, get_ID)(DWORD * pID=NULL) PURE; // 读取dwUserID属性
STDMETHOD_(int, get_chair)(int * chair=NULL) PURE; // 读取nChair属性
STDMETHOD(get_UserName)(char * lpName) PURE; // 获取用户名
STDMETHOD_(DWORD, get_FaceID)(DWORD * pFaceID=NULL) PURE; // 获取用户头像ID
STDMETHOD_(DWORD, get_UserRight)(DWORD * pRight=NULL) PURE; // 获取用户权利
STDMETHOD(get_GroupName)(char * lpName) PURE; // 获取用户所属社团名
STDMETHOD(get_ScoreBuf)(long * lpScoreBuf) PURE; // 获取用户成绩缓冲区

STDMETHOD_(BOOL, IsRunAway)() PURE;
};
m_pListIUserList类型
IUserList : public IEnumUser
{
public: //IUserList
STDMETHOD(BeginEnum)() PURE; // 枚举函数,开始枚举
STDMETHOD(EnumNext)(IGamePlayer ** ppPlayer) PURE; // 枚举函数,枚举下一个
STDMETHOD(GetUser)(int nIdx, IGamePlayer ** ppPlayer) PURE; // 索引函数,索引nIdx处的User
STDMETHOD_(int, GetUserNumber)() PURE; // 索引函数,索引范围
STDMETHOD(FindUser)(DWORD dwUserID, IGamePlayer ** ppPlayer) PURE; // 辅助函数,搜索指定的User
STDMETHOD_(int, GetValidUserNumber)() PURE; // 辅助函数,获取当前有效User数量
// added by hhh 20030605
STDMETHOD(ensureClearPlayer)(DWORD dwUserID, long lDBID) PURE; //确保dwUserID指定的用户被清除
};
先是客户端点击开始:客户端通过m_quickStartelper.OnReady(m_pMyself->get_ID())向服务器端发送准备命令

服务器端:
服务器端认为应该开始游戏的时候它会触发:STDMETHODIMP CGameServer::ConstructScene()
{
m_quickStartelper.Reset();
// 在这里构造最初的游戏现场,注意ConstructScene并不意味着游戏开始
// 另外,这个函数的调用顺序是在Start()前面的哈!
// TODO:想清楚自己的数据结构,这个函数在每轮开始的时候被调用。
//此处一定要区分是否是有效玩家,从而来分别赋值哦!一定要哈!
//要不后面很多地方就错了。因为其后好几个地方是根据玩家手上
//的牌数是否是有效牌数来判断东西。
IGamePlayer * pPlayer =NULL;
for(int i=0; i<MAX_PLAYER_COUNT; ++i) {
m_CPlayer[i].clear(); //先进行清理,每个都要清理!
m_pList->GetUser(i, &pPlayer);
if ( pPlayer->IsValidUser() ){ //只对有效玩家赋值
srand(time(NULL));
m_CPlayer[i].m_nCardNum = rand()%10; //牌正好是0-9这10个数
}
}
m_started=false;
return S_OK;
}
在这个里面就是做游戏的初始化操作,比如发牌(斗地主发的第一次牌)
服务器端CGameServer的数据成员,
IServerSite * m_pSite;
CServerBase * m_pServer;
IUserList * m_pList;都是由框架提供并且由框架赋值
接下来是服务器端每次收到客户端的发来的数据都会触发OnGameOperation:
STDMETHODIMP CGameServer::OnGameOperation(int nIndex, DWORD dwUserID, int opID, LPOPDATA oData, int nSize)
{
if (SUCCEEDED(m_quickStartelper.OnGameOperation(nIndex, dwUserID, opID, oData))) {
return S_OK;
}

//出错描述
struct OPERATION_ERROR {
enum {
UNDEFERROR,
USERISNOTPLAYER,
USERISNOTACTIVATE,
OUTCARDISNOTEXIST
};
int nCode;
char szDesc[255];

OPERATION_ERROR(int nCode, char * desc=NULL) {
nCode =nCode;
if (desc) lstrcpyn(szDesc, desc, sizeof(szDesc));
}
};

IGamePlayer * pPlayer =NULL;
try {
//第一步,校验用户
//if (nIndex!=m_nActivePlayer) { //额,为什么要注释掉我也不知道。但是如果不注释掉,就会报错。
// 不是活动用户 //貌似是因为这个游戏没有焦点用户的说法?
//MessageBox(NULL,"非活动用户发出数据","服务端抛出的错误",0);
//throw OPERATION_ERROR(OPERATION_ERROR::USERISNOTACTIVATE, "非活动用户发出数据");
//}

//获得用户数据
if (FAILED(m_pList->FindUser(dwUserID, &pPlayer))) {
//在m_pList中不存在意味着该用户不是player
MessageBox(NULL,"非玩家用户发出数据","服务端抛出的错误",0);
throw OPERATION_ERROR(OPERATION_ERROR::USERISNOTPLAYER, "非玩家用户发出数据");
}

// TODO:第二步,校验传输数据
// TODO:第三步, 改变数据
// TODO:第四步,现场改变/结束

//此处,对收到的玩家操作ID进行处理
if( OperateOPID(opID,dwUserID,nIndex) ){// //对从客户端接收到的opID进行处理,返回值为是否有处理,这个很关键,他在这里面对服务器端每个玩家设计(CPlayer)的数据结构进行更新,便于以后操作,这里面也包括对客户端发过来的对不同命令的处理方式。
//都出牌了
if( JudgeAllOutCard() ){
CalculationScore(); //计算得分
m_pSite->OnSceneChanged();//关键在这里当coder调用这个的时候,框架回去主动调用GetPersonalScene(int nIndex, UINT uflags, IGameLogical::SCENE * pScene),在这个当中填充它pScene,在这个当中发送客户端的需要显示在界面的数据,比如每个人有还剩张牌,还了什么牌啊之类的。
m_pSite->OnGameEnd();//这个也很关键,当调用这个的时候,框架会主动调用GetScore(int nIndex, void * lpScoreBuf),所以在GetScore中填充每个玩家得分信息
return S_OK;
}

m_pSite->OnSceneChanged();
return S_OK;
}

m_pSite->OnSceneChanged();
} catch (OPERATION_ERROR err) {
//输出错误
/* 错误信息如下
* --Error occured in OnGameOperation --- xxxxxx
* ----from
* ------user name : xxxxx
* ------user db ID : xxxx
* ----other information
* ----
*/
Trace("--Error occured in OnGameOperation ---- %s", err.szDesc);
Trace("----from");
char playername[255];
if (!pPlayer) m_pList->FindUser(dwUserID, &pPlayer);
if (!pPlayer) strcpy(playername, "unknown player");
else pPlayer->get_UserName(playername);
Trace("------user name : %s", playername);
if (pPlayer) {
TABLE_USER_INFO userInfo;
pPlayer->GetPlayer(&userInfo);
Trace("------user db ID : %d", userInfo.lDBID);
}

//对于不同的错误,处理不一样
switch (err.nCode) {
case OPERATION_ERROR::USERISNOTPLAYER:
// 非玩家发包,直接断线
MessageBox(NULL,"非玩家发包,直接断线","服务端对错误的处理",0);
m_pSite->GetOff(dwUserID);
break;
case OPERATION_ERROR::USERISNOTACTIVATE:
//非活动用户发包,忽略
MessageBox(NULL,"非活动用户发包,忽略","服务端对错误的处理",0);
break;
case OPERATION_ERROR::OUTCARDISNOTEXIST:
//包数据错,断线/回复到上一次
MessageBox(NULL,"包数据错,断线或回复到上一次","服务端对错误的处理",0);
m_pSite->GetOff(dwUserID);
break;
// TODO: 加入其他的处理代码
default:
// 其他问题。断线总是保险的做法
MessageBox(NULL,"其他问题。断线总是保险的做法","服务端对错误的处理",0);
m_pSite->GetOff(dwUserID);
}

Trace("-----");
}
catch(...) {
//未知错误发生
Trace("--Unknown error occured in OnGameOperation");
}

SAFE_RELEASE(pPlayer);
return S_OK;
}

BOOL CGameServer::OperateOPID(int opID,DWORD dwUserID,int nIndex)
{
//对于不同的命令处理方式不同。
switch(opID)
{
case CLICK_OUTCARD_BTN:
//出牌
m_CPlayer[nIndex].m_bOutCard = TRUE;
return TRUE;
case CLICK_CHANGECARD_BTN:
//换牌
srand(time(NULL));
m_CPlayer[nIndex].m_nCardNum = rand()%10; //牌正好是0-9这10个数
m_CPlayer[nIndex].m_nChangeNum++;
return TRUE;
default:
return FALSE;//假.没有对opID找寻到适合的处理
}
}

现在又来整理客户端:
每当有用户进入房间后立刻触发OnUserEnter事件,然后在这里面初始化玩家的游戏数据如下
STDMETHODIMP CGameWnd::OnUserEnter(int nIdx, IGamePlayer * pGamePlayer)
{
m_CPlayer[nIdx].clear(); //玩家进来时,先对数据清理下吧。这样可以避免很多无谓的错误。
// 获得触发这个事件的玩家姓名
pGamePlayer->get_UserName(m_CPlayer[nIdx].m_PlayerName);
//看是否是自己坐下触发的,是的话,增加个标示的
if( pGamePlayer == m_pMyself ){
m_nMySitNum = nIdx; //此处把自己做的座位号号码存储起来,方便将来使用滴。
strcat(m_CPlayer[nIdx].m_PlayerName,"[自己]");
}

// 获得触发这个事件的玩家的总分
long lTotalScore[5] = {0};
pGamePlayer->get_ScoreBuf(lTotalScore);
m_CPlayer[nIdx].m_nTotalScore = lTotalScore[0];

// 获得触发事件的玩家头像
int nFaceID = pGamePlayer->get_FaceID();
HBITMAP hFaceBmp =::LoadBitmap(hResDll, MAKEINTRESOURCE(IDR_FACE_FIRST+nFaceID));
if (!hFaceBmp)
{
//不能确切的知道该资源是否能加载
// 如果失败,用第一张图代替之
hFaceBmp =::LoadBitmap(hResDll, MAKEINTRESOURCE(IDR_FACE_FIRST));
}
ASSERT(hFaceBmp);
m_CPlayer[nIdx].m_hFaceBmp = hFaceBmp;

return S_OK;
}
顺便替一下框架为我们提供的事件接口IPlayerIOEvent(框架自动调用,你只需添加在里面做你要完成的工作)
IPlayerIOEvent : public IUnknown
{
public: //IPlayerIOEvent
STDMETHOD(OnUserEnter)(int nIdx, IGamePlayer * pGamePlayer) PURE;
STDMETHOD(OnUserExit)(int nIdx, IGamePlayer * pGamePlayer) PURE;
STDMETHOD(OnUserOffline)(int nIdx, IGamePlayer * pGamePlayer) PURE;
STDMETHOD(OnUserReplay)(int nIdx, IGamePlayer * pGamePlayer) PURE;
};
接下来玩家点击准备按钮会触发以下事件void CGameWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
char lpButtonText[1024];

//开始
if( m_CGameButton[START_BTN].ClickButton(lpButtonText,point,nFlags) ){ //首先判断是否有点击按钮
if( 0 == strcmp("开始",lpButtonText) ){ //接着,在判断是点击的哪个按钮
m_nGameState = GS_SHOWHAND;
m_quickStartelper.OnReady(m_pMyself->get_ID());
}
}
}
在这个事件当中用户点击ready按钮,客户端发送一个m_quickStartelper.OnReady(m_pMyself->get_ID());给服务器,然后会触发STDMETHODIMP CGameWnd::OnPlayerStateChanged(int nIdx, int state, IGamePlayer * pPlayer)事件,用户状态改变的事件
STDMETHODIMP CGameWnd::OnPlayerStateChanged(int nIdx, int state, IGamePlayer * pPlayer)
{
// 每当用户的状态有改变时,包括举手、断线都会触发这个事件。
// 一般而言,我们应该重画界面以反映更新
// TODO:你可以在这里使用自己的方式处理这个事件
switch(state)
{
case sSit: //玩家刚坐下
m_nGameState = GS_START;
break;
case sReady: //有玩家举手。
m_CPlayer[nIdx].m_nPlayerState = sReady;
break;
case sPlaying:
m_CPlayer[nIdx].m_nPlayerState = sPlaying;
m_nGameState = GS_PLAYING;
//此时,游戏开始了。这个时候,需要给所有玩家先预先分配一个假的牌数
//用于表明这个玩家有牌,方便其后在OnUserExit中判断逃跑
//程序会先给所有玩家(包括)自己分配假牌,然后从场景包中,获得真牌.
m_CPlayer[nIdx].m_nCardNum = FALSE_CARD;
break;
default:
break;
}
Invalidate(FALSE);
return S_OK;
}在这个事件里面用户自己维护自己的状态,同样当所有玩家都准备的时候,服务器会真正开始游戏也会引起OnPlayerStateChanged事件,触发这个事件的原因是正在游戏(开始游戏),同样玩家自己坐下也会触发:OnPlayerStateChanged,这里面state参数为sSit。
接下来每当有服务器端传来的数据的时候都会触发OnSceneChanged STDMETHODIMP CGameWnd::OnSceneChanged(SCENE * pScene)
{
Game_SCENE * p_APP_Scene =(Game_SCENE *)pScene;
// TODO:你必须自己处理这个事件
//最重要的函数啦,在写之前先考虑清楚数据怎么组织
//一般而言,现场改变意味着开始新一轮
// 如果轮到自己动作,准备接受用户操作,同时启动时钟
//轮到自己动作,必须确认自己是player
//服务器的数据通常是可以信赖的,不需要太多的校验
//调用解析函数进行解析
ParseScene(p_APP_Scene,pScene);
Invalidate(FALSE);
return S_OK;
}
然后在这个事件当中解析场景包ParseScene,服务器端发来的数据包都把数据填充在struct SCENE{
int cbSize;//数据包长度
char lpdata[];//自己定义的真实的数据
} ;这个结构体中
void CGameWnd::ParseScene(Game_SCENE * p_APP_Scene,SCENE * pScene)
{
BYTE *pBuf = (BYTE*)pScene->lpdata; //一个BYTE指针,指向lpdata
pBuf += sizeof(struct Game_SCENE);
if( !p_APP_Scene->bALLOutCard ){
//()当游戏进行中,只能看到自己的牌的时候的解析方式
int num = p_APP_Scene->nScentNum; //循环解析次数
for(int i=0;i<num;++i){
//取得座位号
int nSitNum = *(int*)pBuf;
pBuf += sizeof(int);
//出牌与否
m_CPlayer[nSitNum].m_bOutCard = *(BOOL*)pBuf;
pBuf += sizeof(BOOL);
//换牌次数
m_CPlayer[nSitNum].m_nChangeNum = *(int*)pBuf;
pBuf += sizeof(int);
//如果这个是发送给自己的,则取得玩家自己的牌数
IGamePlayer* pPlayer = NULL;
m_pList->GetUser(nSitNum, &pPlayer);
if( pPlayer == m_pMyself ){
m_CPlayer[nSitNum].m_nCardNum = *(int*)pBuf;
pBuf += sizeof(int);
}
}
}
else{
//()当所有玩家都出牌后的解析方式
int num = p_APP_Scene->nScentNum; //循环解析次数
for(int i=0;i<num;++i){
//取得座位号
int nSitNum = *(int*)pBuf;
pBuf += sizeof(int);
//换牌次数
m_CPlayer[nSitNum].m_nChangeNum = *(int*)pBuf;
pBuf += sizeof(int);
//玩家牌数
m_CPlayer[nSitNum].m_nCardNum = *(int*)pBuf;
pBuf += sizeof(int);
//玩家是否逃跑
m_CPlayer[nSitNum].m_bRunaway = *(BOOL*)pBuf;
pBuf += sizeof(BOOL);
//玩家得分
m_CPlayer[nSitNum].m_nScore = *(int*)pBuf;
pBuf += sizeof(int);
}
}
}

接下来又是客户端向服务器端发送数据通过点击按钮方式
void CGameWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
char lpButtonText[1024];
//开始
if( m_CGameButton[START_BTN].ClickButton(lpButtonText,point,nFlags) ){ //首先判断是否有点击按钮
if( 0 == strcmp("开始",lpButtonText) ){ //接着,在判断是点击的哪个按钮
m_nGameState = GS_SHOWHAND;
m_quickStartelper.OnReady(m_pMyself->get_ID());
}
}
//退出
else if( m_CGameButton[QUIT_BTN].ClickButton(lpButtonText,point,nFlags) ){
if( 0 == strcmp("退出",lpButtonText) )
g_pFrame->Quit();
}
//换牌。需要先判断下,是否已经到了换牌的最大次数了
else if( m_CGameButton[CHANGE_CARD_BTN].ClickButton(lpButtonText,point,nFlags) ){
if( 0 == strcmp("换牌",lpButtonText) ){
//判断换牌的次数是否已经到了最大值
if( MAX_CHANGE_CARD_NUM == m_CPlayer[m_nMySitNum].m_nChangeNum )
MessageBox("SORRY,换牌次数已经到了最大值了哦");
else
m_pSite->SendMsg(CLICK_CHANGECARD_BTN, NULL, 0);
}
}
//出牌
else if( m_CGameButton[OUT_CARD_BTN].ClickButton(lpButtonText,point,nFlags) ){
if( 0 == strcmp("出牌",lpButtonText) ){
m_nGameState = GS_OUTCARD;
m_pSite->SendMsg(CLICK_OUTCARD_BTN, NULL, 0);
}
}
}
客户端向服务器端发送数据通过m_pSite->SendMsg(CLICK_OUTCARD_BTN, NULL, 0);第一个是命令号,第二个为一个buffer (void*类型的),第三个为buffer长度。

接着是按钮状态的维护,不同的游戏状态,按钮的状态也不同
void CGameWnd::ChangeBTNEnable(int nGameState)
{
//把所有按钮的显示与否都放入这个函数中,方便将来的修改和维护
if( (GS_START == m_nGameState) || ( GS_RESTART == m_nGameState ) ){
m_CGameButton[START_BTN].SetButtonEnable(TRUE);
m_CGameButton[QUIT_BTN].SetButtonEnable(TRUE);
m_CGameButton[CHANGE_CARD_BTN].SetButtonEnable(FALSE);
m_CGameButton[OUT_CARD_BTN].SetButtonEnable(FALSE);
}
else if( (GS_SHOWHAND == m_nGameState) || (GS_OUTCARD == m_nGameState ) ){
m_CGameButton[START_BTN].SetButtonEnable(FALSE);
m_CGameButton[QUIT_BTN].SetButtonEnable(FALSE);
m_CGameButton[CHANGE_CARD_BTN].SetButtonEnable(FALSE);
m_CGameButton[OUT_CARD_BTN].SetButtonEnable(FALSE);
}
else if( GS_PLAYING == m_nGameState ){
m_CGameButton[START_BTN].SetButtonEnable(FALSE);
m_CGameButton[QUIT_BTN].SetButtonEnable(FALSE);
m_CGameButton[CHANGE_CARD_BTN].SetButtonEnable(TRUE);
m_CGameButton[OUT_CARD_BTN].SetButtonEnable(TRUE);
}
}
ChangeBTNEnable(int nGameState)这个是在void CGameWnd::OnPaint()中调用的,ChangeBTNEnable();而这个m_nGameState是需要自己维护的

接下来经过游戏的几个来回,没个来回都要判赢决定这局游戏是否结束
在服务器端CGameServer::OnGameOperation(int nIndex, DWORD dwUserID, int opID, LPOPDATA oData, int nSize)
{
if( OperateOPID(opID,dwUserID,nIndex) ){//对服务器端各个游戏玩家数据进行维护
//都出牌了
if( JudgeAllOutCard() ){//判断这局游戏是否结束然后算分
CalculationScore(); //计算得分
m_pSite->OnSceneChanged();//这个时候要讨论这局游戏是否结束,如果结束,则每个玩家相应的数据会发送给客户端,如果没结束则会发每个玩家相应的数据,但是不会发每个玩家比较敏感的数据,不然被客户截取了咋个办嘛,这些数据都是程序员自己填充
m_pSite->OnGameEnd();//这局游戏结束,然后框架会自动调用GetScore(int nIndex, void * lpScoreBuf),程序员在GetScore中为每个玩家算分,然后框架会写入数据库
return S_OK;
}

m_pSite->OnSceneChanged();
return S_OK;
}
}
STDMETHODIMP CGameServer::GetScore(int nIndex, void * lpScoreBuf)
{
//此处,会对每个座位进行算分,不管座位上是否有玩家
ASSERT(lpScoreBuf);
Game_SCORE * pScore =(Game_SCORE *)lpScoreBuf;
// TODO:填充分数结构
pScore->Clear();
int nScore = m_CPlayer[nIndex].m_nScore; //分数
((long*)pScore)[0] = nScore;
//接下来,再处理输赢问题
if( 0 < nScore )
((long*)pScore)[1] = 1; //赢了
else if( 0 > nScore )
((long*)pScore)[2] = 1; //输了
else if( 0 == nScore )
((long*)pScore)[3] = 1; //平了
return S_OK;
}
服务器端有个变量m_started标志游戏是否开始,这个是由框架自动维护,程序员只试探这个变量处于什么状态即可。

另外对于异常情况,服务器端游戏要结束
STDMETHODIMP CGameServer::Stop(UINT uflags)
{
int i;
m_quickStartelper.Reset();

//应该仔细的处理stopuflags,不同的flag指示了停止的理由
switch (uflags) {
case STOP_SERVERERROR:
m_pSite->Dismiss("服务器停机或者网管解散");
// TODO:如果要有额外处理,加入代码
break;
case STOP_NOENOUGHPLAYER:
if (m_started)
{
//找到引起该事件的人,此人逃跑
IGamePlayer * pPlayer =NULL;
int nRunOut = -1; //逃跑的人
for (i=0; i<MAX_PLAYER_COUNT; ++i) {
m_pList->GetUser(i, &pPlayer);
//是无效玩家,但是手中的牌却有牌,就是这个人跑了三。//
if ( !(pPlayer->IsValidUser()) && ( NONE_CARD != m_CPlayer[i].m_nCardNum) ){
nRunOut = i;
break;
}
}
//标示出此人逃跑了
m_CPlayer[nRunOut].m_bRunaway = TRUE;
// 如果不是valid,此人逃跑。注意,只可能是一个人引起的停机
// TODO: 计算赢输分
for(i=0;i<MAX_PLAYER_COUNT;++i){
if( NONE_CARD != m_CPlayer[i].m_nCardNum ){
//让所有玩家都出牌,包括逃跑玩家
m_CPlayer[i].m_bOutCard = TRUE;
//逃跑者扣分 = 所有玩家(包括自己)牌数之和
m_CPlayer[nRunOut].m_nScore -= m_CPlayer[i].m_nCardNum;
//剩余玩家的分数 = 自己的牌数
if( i != nRunOut )
m_CPlayer[i].m_nScore = m_CPlayer[i].m_nCardNum;
}
}
//给逃跑者算分
long score[5];
ZeroMemory(score, sizeof(score));
score[0] = m_CPlayer[nRunOut].m_nScore;
score[2] = 0; //逃跑不算局数
score[4] = 1;
m_pSite->WriteScore(nRunOut,(char*)score);

m_pSite->OnSceneChanged();
m_pSite->OnGameEnd();
m_pSite->Dismiss("有人逃跑了啊!无限鄙视中!");
}
else {
m_pSite->Dismiss(NULL);
}
break;
}
//清理数据
for(i=0;i<MAX_PLAYER_COUNT;++i)
m_CPlayer[i].clear();
return S_OK;
}
关于逃跑啰嗦几局:
搞清第一个概念,游戏真正开始是在服务器端来确定的,客户端很被动,只是负责显示,当客户端人都ready了,服务器端收到ready消息,然后启动开始,构造场景包,这个时候游戏才是真正开始了,这个以后如果用户点击关闭游戏,客户端会发送逃跑的一个命令给服务器端,只要服务器端游戏开始了,这个时候服务器端会触发STDMETHODIMP CGameWnd::RequestConfirmQuit()事件提醒用户,程序员在这个当中可以添加自己警告的东西。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: