• Win32小游戏--蜘蛛纸牌


      前一段时间完成了蜘蛛纸牌的仿写,现将过程和思路记录下来

      首先,为了符合复用性,在win32的基本框架中,把可变的部分用c++封装起来成为一系列虚函数,这样如果再继续写游戏的话,只需要继承这个类就可以了

    CGameApp.h

     1 #pragma once
     2 class CGameApp                    //接口类
     3 {
     4 public:
     5     virtual void OnCreatGame(){}
     6     virtual void OnGameDraw(){}
     7     virtual void OnGameRun(){}
     8     virtual void OnKeyDown(){}
     9     virtual void OnKeyUp(){} 
    10     virtual void OnLButtonDown(){}
    11     virtual void OnLButtonUp(){}
    12     virtual void OnMouseMove(){}
    13 };
      1 #include<windows.h>
      2 #include"CGameApp.h"
      3 
      4 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
      5 
      6 int CALLBACK WinMain(HINSTANCE hInstance,HINSTANCE hPreInstance,LPSTR pCmdLine,int nCmdShow)
      7 {
      8     //  1.  设计
      9     WNDCLASSEX wndclass;
     10     wndclass.cbClsExtra = 0;
     11     wndclass.cbWndExtra = 0;
     12     wndclass.cbSize = sizeof(wndclass);
     13     wndclass.hbrBackground = (HBRUSH)COLOR_WINDOW;
     14     wndclass.hCursor = 0;
     15     wndclass.hIcon = 0;
     16     wndclass.hIconSm = 0;    //  窗口左上的小图标
     17     wndclass.hInstance = hInstance;
     18     wndclass.lpfnWndProc = WndProc;   //  窗口的消息处理函数
     19     wndclass.lpszClassName = "cyc";  //  注册窗口类的名字 
     20     wndclass.lpszMenuName = 0;
     21     wndclass.style = CS_HREDRAW|CS_VREDRAW;
     22 
     23     //  2.  注册
     24     if( ::RegisterClassEx(&wndclass) == FALSE)
     25     {
     26         ::MessageBox(0,"注册失败","提示",MB_OK);
     27         return 0;
     28     }
     29     // 3. 创建
     30     HWND hwnd = ::CreateWindow("cyc","游戏壳",WS_OVERLAPPEDWINDOW,0,0,500,500,0,0,hInstance,0);
     31     if(hwnd == 0)
     32     {
     33         ::MessageBox(0,"创建失败","提示",MB_OK);
     34         return 0;
     35     }
     36 
     37     // 4. 显示窗口
     38     ::ShowWindow(hwnd,SW_SHOW);
     39 
     40     // 5. 消息循环
     41     MSG msg;
     42     while(::GetMessage(&msg,0,0,0))
     43     {
     44         ::TranslateMessage(&msg);
     45         ::DispatchMessage(&msg);   // 分发, 调用消息的处理函数WndProc
     46     }
     47 
     48 
     49     return 0;
     50 }
     51 
     52 
     53 
     54 
     55 
     56 CGameApp *p = 0;
     57 LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
     58 {
     59     switch (uMsg)
     60     {
     61     case WM_CREATE:
     62         {
     63             if(p == NULL)
     64                 p->OnCreatGame();
     65 
     66         }
     67         break;
     68     case WM_PAINT:
     69         {
     70             if(p == NULL)
     71                 p->OnGameDraw();
     72 
     73         }
     74         break;
     75     case WM_TIMER:
     76         {
     77             if(p == NULL)
     78                 p->OnGameRun();
     79 
     80         }
     81         break;
     82     case WM_KEYDOWN:
     83         {
     84             if(p == NULL)
     85                 p->OnKeyDown();
     86 
     87         }
     88         break;
     89     case WM_KEYUP:
     90         {
     91             if(p == NULL)
     92                 p->OnKeyUp();
     93 
     94         }
     95         break;
     96     case WM_LBUTTONDOWN:
     97         {
     98             if(p == NULL)
     99                 p->OnLButtonDown();
    100 
    101         }
    102         break;
    103     case WM_LBUTTONUP:
    104         {
    105             if(p == NULL)
    106                 p->OnLButtonUp();
    107 
    108         }
    109         break;
    110     case WM_MOUSEMOVE:
    111         {
    112             if(p == NULL)
    113                 p->OnMouseMove();
    114 
    115         }
    116         break;
    117     case WM_CLOSE:  //  关闭
    118         ::PostQuitMessage(0);    //  发送一个退出的消息
    119         break;
    120     }
    121     return DefWindowProc( hwnd,  uMsg,  wParam,  lParam);
    122 }

    接下来就是 蜘蛛纸牌建设的过程了,先来分析一下纸牌的功能,因为蜘蛛纸牌里抛去大小王,所以1--K每副牌里有13张牌,由于我想搭建类似与纸牌类游戏框架的东西,所以分为可重写,和不可重写两个部分,不可重写的,所以类就设置为单张牌,一副牌,牌的排列,规则这些类,由于哪种游戏用几副牌,鼠标点击是否取牌,鼠标点击是否拿牌,这些有开发人员自行定义,UML如下,

    在接下来分模块记录的时候也分为框架内,和框架外来进行记录

    框架内:

    在CCards和CPocker两个类中,均是简单的参数赋值,此处也是第一次使用STL中vector组件,在CCardsRank类中,每一张牌的属性都用结构体记录了下来,如图

    并且在贴图的过程中,我多设置了一个判断来加载位图是否进入内存,为开发人员省去了加载位图的过程

     1 void CCardsRank::ShowRank(HDC hdc, HINSTANCE hIns)
     2 {
     3     //1==============================显示窗口的背景图============================
     4     if(m_hBmpWndBack == 0)
     5         m_hBmpWndBack = ::LoadBitmap(hIns,MAKEINTRESOURCE(IDB_WND_BACK));
     6 
     7     HDC hMemDC = ::CreateCompatibleDC(hdc);
     8     ::SelectObject(hMemDC,m_hBmpWndBack);
     9     ::BitBlt(hdc,0,0,850,600,hMemDC,0,0,SRCCOPY);
    10     ::DeleteDC(hMemDC);
    11     //1==============================显示窗口的背景图============================
    12 
    13 
    14     //2==============================显示牌=====================================
    15     //-------------有没有牌的背景图----------
    16     if(m_hBmpCardsBack == 0)
    17         m_hBmpCardsBack = ::LoadBitmap(hIns,MAKEINTRESOURCE(IDB_CARDS_BACK));
    18     //-------------有没有牌的背景图----------
    19     for(size_t i=0;i<m_vecRank.size();i++)
    20     {
    21         list<Node*>::iterator ite = m_vecRank[i].begin();
    22         while(ite != m_vecRank[i].end())
    23         {
    24             //----------贴图-------------------
    25             HDC hMemDC = ::CreateCompatibleDC(hdc);
    26 
    27             if((*ite)->bflag == false)
    28                 ::SelectObject(hMemDC,m_hBmpCardsBack);
    29             else
    30                 ::SelectObject(hMemDC,(*ite)->pCards->m_hBmpCards);
    31 
    32             ::BitBlt(hdc,(*ite)->x,(*ite)->y,71,96,hMemDC,0,0,SRCCOPY);
    33 
    34             ::DeleteDC(hMemDC);
    35             //----------贴图------------------
    36             ++ite;
    37         }
    38     }
    39     //2==============================显示牌=====================================
    40 }

    在CardsApp中,由于创建多少副牌是不确定的,那么就没法创建对象,在这里就使用了博客内记录的动态创建对象,只需要在CardsApp中贴上两个宏,开发人员就可以随意的创建多少副牌,在CardsApp中可以自动的去创建对象,而不用修改代码,并且重点标注的是,由于蜘蛛纸牌有松开鼠标归位的功能,所以在显示移动牌的时候,都是以牌的上一个位置为标准进行移动牌坐标的计算

     1 void CCardsApp::ShowCursorCards(HDC hdc)
     2 {
     3     int X = pointMouseMove.x - pointMouseDown.x;
     4     int Y = pointMouseMove.y - pointMouseDown.y;
     5 
     6     //  在 移动的距离的位置显示牌
     7     list<Node*>::iterator ite = m_lstCursorCards.begin();
     8     while(ite != m_lstCursorCards.end())
     9     {
    10         HDC hMemDC = ::CreateCompatibleDC(hdc);
    11         ::SelectObject(hMemDC,(*ite)->pCards->m_hBmpCards);
    12         ::BitBlt(hdc,(*ite)->x+X,(*ite)->y+Y,850,600,hMemDC,0,0,SRCCOPY);
    13         ::DeleteDC(hMemDC);
    14         ++ite;
    15     }
    16 }

    在CRule中,进行三个判断,第一个接收牌后的操作,利用vector自身的计数函数,以及遍历链表,通过是否接收牌这个规则之后,与链表结合,更新位置,翻牌,第二个是获得鼠标点击牌的坐标,在获得之前也需要进行一系列的判断,是否光标点击在牌上,牌是否是正面,是不是最后一张能否拿起来,这些都为真之后,将牌放入光标移动的链表中,在这一步值得一提的是,运用了反向迭代器,正向迭代器比反向迭代器指向少一个元素,所以在删迭代器指向元素前,反向迭代器++,或者转为正向迭代器后--  ,第三个,如果接收失败的话,将光标链表中的牌放回原先列表的尾部

     1 bool CRule::ReceiveCards(POINT point, CCardsRank* pCardsRank, list<Node*>& lstCursor)
     2 {
     3     //  遍历 所有的链表
     4     for(size_t i=0;i<pCardsRank->m_vecRank.size();i++)
     5     {
     6         //  判断坐标的 交给子类
     7         if(this->IsReceiveCardsRule(point,i,pCardsRank,lstCursor) == true)
     8         {
     9             //  和 i这个链表结合
    10             pCardsRank->m_vecRank[i].splice(pCardsRank->m_vecRank[i].end(),lstCursor);
    11             //  更新位置(对齐)
    12             this->UpDatePos(pCardsRank,i);
    13             //  翻牌
    14             if(pCardsRank->m_vecRank[m_nGetCardsListID].empty() == false)
    15                 pCardsRank->m_vecRank[m_nGetCardsListID].back()->bflag = true;
    16             m_nGetCardsListID = -1;
    17             return true;
    18         }
    19     }
    20     return false;
    21 }
    22 void CRule::GetCards(POINT point, CCardsRank* pCardsRank, list<Node*>& lstCursor)
    23 {
    24     //  遍历 所有的链表
    25     for(size_t i=0;i<pCardsRank->m_vecRank.size();i++)
    26     {
    27         //  遍历 i 个链表的所有节点
    28         list<Node*>::reverse_iterator rev_ite = pCardsRank->m_vecRank[i].rbegin();
    29         while(rev_ite != pCardsRank->m_vecRank[i].rend())
    30         {
    31             //  判断光标是否点击到这个牌上
    32             if(point.x >= (*rev_ite)->x && point.x <= (*rev_ite)->x+71
    33                 && point.y >= (*rev_ite)->y && point.y <= (*rev_ite)->y+96)
    34             {
    35                 //  判断是不是正面
    36                 if((*rev_ite)->bflag == true)
    37                 {
    38                     //  判断能不能拿起来
    39                     list<Node*>::iterator ite = --(rev_ite.base());
    40                     if( this->IsGetCardsRule(pCardsRank,i,ite) == true)
    41                     { 
    42                         //  记录下标
    43                         m_nGetCardsListID = i; 
    44                         //  放到光标的链表上
    45                         lstCursor.splice(lstCursor.end(),pCardsRank->m_vecRank[i],ite,pCardsRank->m_vecRank[i].end());
    46                     }
    47                 }
    48                 return;
    49             }
    50             ++rev_ite;
    51         }
    52     }
    53 }
    54 void CRule::RevertCards(CCardsRank* pCardsRank, list<Node*>& lstCursor)
    55 {
    56     if(m_nGetCardsListID != -1)
    57     {
    58         //  把光标的链表 放回到 m_nGetCardsListID 这个链表尾部
    59         pCardsRank->m_vecRank[m_nGetCardsListID].splice(pCardsRank->m_vecRank[m_nGetCardsListID].end(),lstCursor);
    60         m_nGetCardsListID = -1;
    61     }
    62 }

    框架外:

    针对于蜘蛛纸牌而言,难点在于规则的制定上,在CMyCardsRank中需要注意的点就是,这个类的构造应该使用初始化列表来写,初始化列表的作用1.初始化成员属性 2.先完成指定类的构造,也就是说没有CCardsRank这个类,哪来的CMyCardsRank呢?

    CMyCardsRank::CMyCardsRank(void):CCardsRank(11)

    并且在CCardsApp中的显示应该用双缓冲来完成,因为只要连续贴的图超过一张,就有可能多张图出现在两个显卡刷新周期之内,这样的话就会出现闪屏的问题,所以利用双缓冲再为一个兼容性DC在创建一个兼容性DC,多次贴图在第一个兼容性DC中,最后一次性显示到窗口HDC中,这就是解决窗口闪烁的双缓冲技术

     1 void CCardsApp::OnGameDraw()                 //  WM_PAINT
     2 {
     3     HDC dc = ::GetDC(m_hMainWnd);
     4     HDC hdc = ::CreateCompatibleDC(dc);
     5     HBITMAP hbitmap = ::CreateCompatibleBitmap(dc,850,600);
     6     ::SelectObject(hdc,hbitmap);
     7     //-------------------------------------------------------------
     8     //  显示排列
     9     if(m_pRank != 0)
    10         m_pRank->ShowRank(hdc,m_hIns);
    11     this->ShowCursorCards(hdc);
    12     //-------------------------------------------------------------
    13     ::BitBlt(dc,0,0,850,600,hdc,0,0,SRCCOPY);
    14     ::DeleteObject(hbitmap);
    15     ::DeleteDC(hdc);
    16     ::ReleaseDC(m_hMainWnd,dc);
    17 }

    大部分的重写都在CRule中,第一个拿牌的规则,利用迭代器的移动和首个牌的数字--,来判断参数的一串是否连续,一旦连续就可以拿牌

     1 bool CMyRule::IsGetCardsRule(CCardsRank* pCardsRank, int nlstID, list<Node*>::iterator iteCursorPos)
     2 {
     3     int num = (*iteCursorPos)->pCards->m_nCardsNum;
     4 
     5     while(iteCursorPos != pCardsRank->m_vecRank[nlstID].end())
     6     {
     7         if(num != (*iteCursorPos)->pCards->m_nCardsNum)
     8             return false;
     9         --num;
    10         iteCursorPos++;
    11     }
    12     return true;
    13 }

    第二个,发牌的规则,首先判断在发牌的序列中,也就是最后一个链表中是否有牌了,通过了,再判断点到的是不是发牌序列中的最后一张牌,也就是说是否触发发牌的指令,最后一个判断前十个链表中是否有空的链表

     1 bool CMyRule::IsOpenCards(POINT point, CCardsRank* pCardsRank)
     2 {
     3     //判断最后一个链表里是否有东西
     4     if(pCardsRank->m_vecRank[10].empty() == false)
     5     {
     6         //判断是不是点到最后一张牌
     7         if(point.x >= pCardsRank->m_vecRank[10].back()->x && point.x <= pCardsRank->m_vecRank[10].back()->x+71
     8             && point.y >= pCardsRank->m_vecRank[10].back()->y && point.y <= pCardsRank->m_vecRank[10].back()->y+96)
     9         {
    10             //前十个有没有空链表
    11             for(int i = 0;i < 10;i++)
    12             {
    13                 if(pCardsRank->m_vecRank[i].empty() == true)
    14                     return false;
    15             }
    16             return true;
    17         }
    18     }
    19     return false;
    20 }

    第三个,接收牌的规则,这个就只有两点,是否鼠标坐标在上个牌的坐标范围之内,是否鼠标选中这张牌的数字比该链表的尾结点减一

     1 bool CMyRule::IsReceiveCardsRule(POINT point, int nlstID, CCardsRank* pCardsRank, list<Node*>& lstCursorCards)
     2 {
     3     if(pCardsRank->m_vecRank[nlstID].empty() == true)
     4     {
     5         if(point.x >= 10+nlstID*81 && point.x <= 10+nlstID*81+71 && point.y >= 10 && point.y <= 10+96)
     6         {
     7             return true;
     8         }
     9     }
    10     else
    11     {
    12         if(point.x >= pCardsRank->m_vecRank[nlstID].back()->x && point.x <= pCardsRank->m_vecRank[nlstID].back()->x+71 
    13             && point.y >= pCardsRank->m_vecRank[nlstID].back()->y && point.y <= pCardsRank->m_vecRank[nlstID].back()->y+96)
    14         {
    15             if(lstCursorCards.front()->pCards->m_nCardsNum == pCardsRank->m_vecRank[nlstID].back()->pCards->m_nCardsNum - 1)
    16             {
    17                 return true;
    18             }
    19         }
    20     }
    21 }

    第四个,更新坐标,这里需要注意的就是不更新松手后复位的坐标

     1 void CMyRule::UpDatePos(CCardsRank* pCardsRank, int nlstID)
     2 {
     3     int j = 0;
     4     list<Node*>::iterator ite = pCardsRank->m_vecRank[nlstID].begin();
     5     while(ite != pCardsRank->m_vecRank[nlstID].end())
     6     {
     7         (*ite)->x = 10+nlstID*81;
     8         (*ite)->y = 10+j*20;
     9         ++j;
    10         ++ite;
    11     }
    12     this->DeleteNode(pCardsRank,nlstID);
    13 }

    第五个,当结成连续的13张牌时,进行消除,这个判断要在接收牌时,以及发牌时进行

    1.链表内至少有13个结点,最后一张牌应该是A

    2.反向遍历判断有没有13张连续的正面

    3.不连续或者为背面时结束

    4.反向迭代器删除时要转为正向

    5.删除后,尾结点翻牌

     1 void CMyRule::DeleteNode(CCardsRank* pCardsRank, int nlstID)
     2 {
     3     if(pCardsRank->m_vecRank[nlstID].size() >= 13 && pCardsRank->m_vecRank[nlstID].back()->pCards->m_nCardsNum == 1)
     4     {
     5         int num = 1;
     6         list<Node*>::reverse_iterator rite = pCardsRank->m_vecRank[nlstID].rbegin();
     7         for(int i = 0;i < 13;i++)
     8         {
     9             if((*rite)->bflag == false)
    10                 return;
    11             if((*rite)->pCards->m_nCardsNum != num)
    12                 return;
    13             ++rite;
    14             ++num;
    15         }
    16         list<Node*>::iterator ite = rite.base();
    17         while(ite != pCardsRank->m_vecRank[nlstID].end())
    18         {
    19             delete (*ite);
    20             ite = pCardsRank->m_vecRank[nlstID].erase(ite);
    21         }
    22     }
    23     if(pCardsRank->m_vecRank[nlstID].empty() == false)
    24         pCardsRank->m_vecRank[nlstID].back()->bflag = true;
    25     
    26 }

    从宏观上看,蜘蛛纸牌就是vector-List的应用,这也是我第一次尝试去写一个框架,代码我放在文件里了,希望各位能够指正一下

     

    2019-07-08 11:47:46 编程小菜鸟自我总结,各位大佬可以提出自己的建议和意见,谢谢!!!

  • 相关阅读:
    Java内存模型与共享变量可见性
    CopyOnWriteArraySet源码解析
    CopyOnWriteArrayList源码解析(1)
    CopyOnWriteArrayList源码解析(2)
    CopyOnWriteArrayList源码解析
    企业项目开发--切分配置文件
    常用Java集合类总结
    HashSet源码解析
    Flutter中的普通路由与命名路由(Navigator组件)
    Flutter——BottomNavigationBar组件(底部导航栏组件)
  • 原文地址:https://www.cnblogs.com/xgmzhna/p/11150262.html
Copyright © 2020-2023  润新知