GameView.cpp

来源:互联网 发布:孔有德 知乎 编辑:程序博客网 时间:2024/05/01 16:29

#include "stdafx.h"
#include "GameClient.h"
#include "GameView.h"
#include "Site_i.h"
#include "../transStruct.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


template<bool b> struct Complier_Error;
template<> struct Complier_Error<true> { };

class CPaintDCWithUR : public CDC
{
public:
 CPaintDCWithUR(CWnd * pWnd): m_paintDC(pWnd){
  CreateCompatibleDC(&m_paintDC);

  CRect rect = GetUpdateRect();

  m_memBmp.CreateCompatibleBitmap(this, rect.Width(), rect.Height());
  m_pOldMemBmp = SelectObject(&m_memBmp);

  m_pt = SetWindowOrg(rect.TopLeft());
 }
 ~CPaintDCWithUR() {};

 void flush() {
  SetWindowOrg(m_pt);

  CRect rect = GetUpdateRect();
  m_paintDC.BitBlt(rect.left, rect.top, rect.Width(), rect.Height(),
   this, 0, 0, SRCCOPY);

  SelectObject(m_pOldMemBmp);
 };

 CRect GetUpdateRect() {
  return m_paintDC.m_ps.rcPaint;
 }

private:
 CPaintDC m_paintDC;
 CBitmap m_memBmp, *m_pOldMemBmp;
 CPoint m_pt;
};

EXPOSE_GAMECREATOR_SUPPORTSITE(CGameWnd, GAME_ID, MAKEWORD(0, 1), CLIENT_AREA_CX, CLIENT_AREA_CY)

// 头像存放在res.dll
static HMODULE hResDll =::LoadLibrary("res.dll");

// IGameConst
STDMETHODIMP CGameWnd::GetEstimateTime(DWORD * pMillesecond)
{
 // TODO:填写游戏运行的估计时间。如果少于这个时间的1/5,那么系统不会计分。
 // 将这个值设为0可以关闭这个检查

 return E_NOTIMPL;
}

STDMETHODIMP CGameWnd::GetPlayerNum(IGameConst::PLAYERNUMBER * pNumber)
{
 ASSERT(pNumber);

 // TODO:填写玩家的数量
 pNumber->nMax =4;
 pNumber->nStart =4;
 pNumber->nMaintenance =4;
 
 return S_OK;
}

STDMETHODIMP CGameWnd::GetGameIdentifier(char * lpszIdentifier)
{
 lstrcpyn(lpszIdentifier, "Ee", 255);
 return S_OK;
}

STDMETHODIMP CGameWnd::CanQuickStart()
{
 //Complier_Error<false> you_must_read_Readme_first_to_emmit_this_error_CanQuickStart;

 return S_OK;
}

STDMETHODIMP CGameWnd::CanSaveGame()
{
 return E_NOTIMPL;
}

STDMETHODIMP CGameWnd::GetVersion(IGameConst::VERSION_STRUCT * pVS)
{
 ASSERT(pVS);

 // TODO: 填写内部版本号
 // 这个号码用于避免玩家使用老的客户端来连入新的服务器
 pVS->curVersion = MAKEWORD(0,1);
 pVS->maxVersion = MAKEWORD(0,1);
    pVS->minVersion = MAKEWORD(0,1);

 return S_OK;
}

// IGameRender
STDMETHODIMP CGameWnd::OnInitialize()
{
 // 在此处获得自己(myself)的指针,用户列表的指针
 m_pSite->GetMyself(&m_pMyself);
 m_pSite->GetPlayerList(&m_pList);

 return S_OK;
}

STDMETHODIMP CGameWnd::OnWaitingStart()
{
 //调整界面,使界面至少包含开始按钮
 //不用判断自己是不是旁观,(旁观不会收到该事件)
 // TODO:自己处理这个事件

 return S_OK;
}

STDMETHODIMP CGameWnd::OnGameStart()
{
 //在这个里往往不用做什么,大部分工作在OnSceneChanged里完成
 return S_OK;
}

STDMETHODIMP CGameWnd::OnGameEnd(void * scorebuf)
{
 Game_SCORE * scoreArray =(Game_SCORE *)scorebuf;
 // TODO:你必须自己处理这个事件
 //游戏结束一轮之后会调用这个事件,scorebuf是一个数组,按照位置顺序存储所有人的分数
 //下面的scoreArray[0]是坐在0号位置的玩家成绩
 //每个单元都是一个Ee_SCORE的结构。

 int i;
 CString strTemp;
 CString strResult;
 IGamePlayer * pPlayer = NULL;

 //显示出最后的结果
 for(i=0;i<MAX_PLAYER_COUNT;++i){
  if(m_CPlayer[i].m_bRunaway)
   strcat(m_CPlayer[i].m_PlayerName,"[逃跑者]");

  if( NONE_CARD != m_CPlayer[i].m_nCardNum ){
   strTemp.Format("玩家牌数:%d  玩家换牌次数:%d  玩家得分:%d  玩家姓名:%s",
   m_CPlayer[i].m_nCardNum,m_CPlayer[i].m_nChangeNum,scoreArray[i].lScore,m_CPlayer[i].m_PlayerName);
   strResult += strTemp+'/n';
  }
 }
 MessageBox(strResult);

 //对玩家数据进行清理
 //千万千万不要清理完了哦。。。要不然,自己的数据都没有了,咋个显示哇
 for(i=0;i<MAX_PLAYER_COUNT;++i){
  m_pList->GetUser(i, &pPlayer); 
  if( pPlayer->IsValidUser() ) //判断下,是否是有效玩家(逃跑了的不算有效玩家了)
   m_CPlayer[i].ReStart();
  else
   m_CPlayer[i].clear();  //无效玩家直接把数据清空
 }
 m_nGameState = GS_RESTART;

 return S_OK;
}

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;
}

//解析收到的场景包的函数
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);
  }
 }
}

STDMETHODIMP CGameWnd::Stop(char * lpszReason)
{
 // lpszReason是服务器端的dismiss函数中传过来的参数
 // 通常我们只是显示一下而已
 // TODO:你可以在这里使用自己的方式处理这个事件
 ::MessageBox(m_hWnd, lpszReason, "客户端->Stop", MB_OK);

 return S_OK;
}

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;
}

STDMETHODIMP CGameWnd::OnGameOperation(int opID, LPOPDATA oData,int nsize)
{
 if (SUCCEEDED(m_quickStartelper.OnGameOperation(opID, oData))) {
  return S_OK;
 }

 //从服务器发来的命令,这些命令的效果不会被累计到OnSceneChanged中
 if (opID==GAMENOTIFY_CLIENT_SHOULD_CLOSE) {
  // 客户端应当退出
  GN_CLIENT_SHOULD_CLOSE * pInfo =(GN_CLIENT_SHOULD_CLOSE *)oData;
  if (pInfo) {
   MessageBox(pInfo->msg, "ddd");
  }
  g_pFrame->Quit();

  return S_OK;
 }

 // TODO:加入你自己的处理代码

 return S_OK;
}

STDMETHODIMP CGameWnd::RequestConfirmQuit()
{
 //请求用户确认要退出(该事件是由用户试图关闭窗口引起的,这可以给用户一个反悔的机会)
 // 通常是一个对话框,返回S_OK将关闭程序,E_FAIL继续游戏

 return S_OK;
}

STDMETHODIMP CGameWnd::Clear()
{
 //务必清除所有数据,包括用户信息
 //该函数在自己离开椅子是被调用,此时程序中记录的所有信息都无效了
 //第一次进入时不会调用该函数。首次的数据准备应该在其他地方完成
 SAFE_RELEASE(m_pList);
 SAFE_RELEASE(m_pSite);
 SAFE_RELEASE(m_pMyself);

 int i = 0;
 //清理掉双缓冲使用的内存兼容DC和设备兼容位图
 m_CGameMapping.clear();

 //按钮类的清理
 for(i=0;i<BTN_COUNT;++i)
  m_CGameButton[i].clear();
 
 //玩家数据的清理
 for(i=0;i<MAX_PLAYER_COUNT;++i)
  m_CPlayer[i].clear();

 return S_OK;
}

STDMETHODIMP CGameWnd::EnterSinglePlayerMode()
{
 //不是所有游戏都需要写这一块的
 MessageBox("单机版哦~~~一般人我还不告诉他哦~~~");

 return E_NOTIMPL;
}

void CGameWnd::OnButtonSetup()
{
 //打开设置对话框
}

 

/////////////////////////////////////////////////////////////////////////////
// CGameWnd

CGameWnd::CGameWnd() : m_dwRef(0)
{
 m_pSite =NULL;
 m_pList =NULL;

 m_pLink =NULL;

 m_pMyself =NULL;

 // TODO:初始化变量
}

CGameWnd::~CGameWnd()
{
 //使用SAFE_RELEASE宏是一个好习惯,定义在utility.h
 SAFE_RELEASE(m_pList);
 SAFE_RELEASE(m_pSite);
 SAFE_RELEASE(m_pMyself);

 //此处要注意啊!最好不要在这个地方进行资源的释放,因为程序框架本身的原因
 //客户端退出的时候,不会调用到这个析构函数的
 //同样,自己添加的类的析构函数也是不会被调用到的。
 //所以,资源的释放最好放在clear()接口中进行!
 //然后,在OnDestroy()中调用clear()。
 //切忌,切忌!
}

BEGIN_MESSAGE_MAP(CGameWnd, CWnd)
 //{{AFX_MSG_MAP(CGameWnd)
 ON_WM_PAINT()
 ON_WM_ERASEBKGND()
 ON_WM_SIZE()
 ON_WM_CREATE()
 ON_WM_LBUTTONDOWN()
 ON_WM_MOUSEMOVE()
 ON_WM_DESTROY()
 //}}AFX_MSG_MAP
 ON_MESSAGE(WM_RECALCLAYOUT, OnReCalcLayout)
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CGameWnd message handlers

BOOL CGameWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
 HDC hDC=NULL;
 hDC = ::GetDC(m_hWnd);
 if(!hDC){
  ::MessageBox(NULL,"在OnCreate中,初始化hDC失败","Error",0);
  return -1;
 }

 //初始化双缓冲使用的内存兼容DC和设备兼容位图
 if( !m_CGameMapping.InitDBB(hDC) ){ 
  ::MessageBox(NULL,"InitDBB失败","Error",0);
  return -1;
 }
 
 if( !::ReleaseDC(m_hWnd,hDC) ){
  ::MessageBox(NULL,"在OnCreate中,释放hDC失败","Error",0);
  return -1;
 }

 //玩家状态初始化
 m_nGameState = GS_START;
 m_nMySitNum = INVALID_SIT_NUM;

 //初始化按钮
 //按钮坐标
 RECT rc_start =  {10 ,500,60 ,530};
 RECT rc_Over  =  {110,500,160,530};
 RECT rc_change=  {410,500,460,530};
 RECT rc_out   =  {510,500,560,530};

 m_CGameButton[START_BTN].InitButton("开始",rc_start);
 m_CGameButton[QUIT_BTN].InitButton("退出",rc_Over);
 m_CGameButton[CHANGE_CARD_BTN].InitButton("换牌",rc_change);
 m_CGameButton[OUT_CARD_BTN].InitButton("出牌",rc_out);
 
 return 0;
}

void CGameWnd::OnPaint()
{
 //CPaintDCWithUR dc(this); // use this device context for painting
 // TODO:把所有的绘图集中到此处,注意只需要绘制dc.GetUpdateRect()指定的区域
 // 一般而言,我们绘制的顺序如下
 //  背景
 //  牌、或者棋盘
 //  用户头像
 //  其他用户信息
 //dc.flush();    // this is the last sentence in this function
 // Do not call CWnd::OnPaint() for painting messages

 HDC hDC = NULL;
 PAINTSTRUCT ps;
 hDC = ::BeginPaint(m_hWnd,&ps);
 if(!hDC)
  ::MessageBox(NULL,"在OnPaint中,初始化hDC失败","Error",0);
 
 //贴图
 m_CGameMapping.SelectObjectDBB();
 m_CGameMapping.DrawGame(m_CPlayer);

 //按钮的绘制
 //1 根据m_nGameState对按钮的显示与否进行设置
 ChangeBTNEnable(m_nGameState);
 //2 贴出按钮
 for(int i=0;i<BTN_COUNT;++i)
  m_CGameButton[i].DrawButton(m_CGameMapping.GetHdcForDBB());

 //贴出缓冲区中的图
 m_CGameMapping.DrawDBB(hDC);
 
 if( !::EndPaint(m_hWnd,&ps) )
  ::MessageBox(NULL,"在OnPaint中,释放hDC失败","Error",0);
}

BOOL CGameWnd::OnEraseBkgnd(CDC* pDC)
{
 // 为避免闪动,最好屏蔽这个消息
 return TRUE;
}

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);
  }
 }
}

void CGameWnd::OnMouseMove(UINT nFlags, CPoint point)
{
 for (int i=0;i<BTN_COUNT;++i)
  m_CGameButton[i].TouchButton(point,nFlags);

 Invalidate(TRUE);
}

LRESULT CGameWnd::OnReCalcLayout(WPARAM reserved, LPARAM lParam)
{
 ASSERT(lParam);
 RECALCLAYOUTINFO * pInfo =(RECALCLAYOUTINFO *)lParam;

 //当框架大小发生改变时通过该消息通知子窗体改变大小,如果子窗不处理该消息,则框架自动把它放置到默认的位置
 //游戏窗口如果支持变大小应该处理该消息,收到该消息时请根据rcControlWnd的位置来调整自己的位置
 //或者更野蛮的做法,强行改变掉rcControlWnd的大小
 //如果是竖直分割条,框架总是把分割条放置在控制窗口的左边,如果是水平分割条,框架总是把滚动条放在控制窗口的上边

 // TODO:必须处理这个消息以便让Frame能够调整游戏窗口的大小

 return 1;
}

void CGameWnd::OnSize(UINT nType, int cx, int cy)
{
 CWnd::OnSize(nType, cx, cy);

 // TODO: 必须在这个消息中安排界面上的元素
}

void CGameWnd::OnDestroy()
{
 //数据清理
 Clear();
}

//根据游戏状态,改变按钮的显示(起作用)与否
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);
 }
}

//IPlayerIOEvent
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;
}

STDMETHODIMP CGameWnd::OnUserExit(int nIdx, IGamePlayer * pGamePlayer)
{
 //此处,区分下玩家是否是因为逃跑退出,如果是,则不清理
 //该玩家的数据,等OnGameEnd使用完后,由OnGameEnd来清理
 //判断玩家是否逃跑,最好使用下面的方式。
 //再备注:此处pGamePlayer->IsValidUser()所得,一定是FALSE!

 //判断是否是NONE_CARD,如果不是,就表明这个玩家手中是有牌的。
 //那就可以肯定,这个玩家是逃跑者.
 //就不清理它的数据。只清理非逃跑者的数据
 if( NONE_CARD == m_CPlayer[nIdx].m_nCardNum )
  m_CPlayer[nIdx].clear();
   
 return E_NOTIMPL;
}

//断线
STDMETHODIMP CGameWnd::OnUserOffline(int nIdx, IGamePlayer * pGamePlayer)
{
 return E_NOTIMPL;
}

//断线后又回来
STDMETHODIMP CGameWnd::OnUserReplay(int nIdx, IGamePlayer * pGamePlayer)
{
 return E_NOTIMPL;
}