游戏工作笔记

来源:互联网 发布:linux 用户文件 编辑:程序博客网 时间:2024/05/17 04:22

代码流程图

游戏开始类型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_pMyselfIGamePlayer类型,该类型跟游戏数据无关的,跟游戏用户数据相关的要自己定义,只是,此类型结构定义如下

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-910个数

                   }

         }

         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-910个数

                   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()事件提醒用户,程序员在这个当中可以添加自己警告的东西。

原创粉丝点击