前一段时间完成了蜘蛛纸牌的仿写,现将过程和思路记录下来
首先,为了符合复用性,在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 编程小菜鸟自我总结,各位大佬可以提出自己的建议和意见,谢谢!!!
原文地址:https://www.cnblogs.com/xgmzhna/p/11150262.html