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 编程小菜鸟自我总结,各位大佬可以提出自己的建议和意见,谢谢!!!

原文地址:https://www.cnblogs.com/xgmzhna/p/11150262.html

时间: 2024-10-28 18:55:40

Win32小游戏--蜘蛛纸牌的相关文章

简单21点纸牌小游戏纸牌显示的修改

上次写的随笔,简单21点纸牌小游戏,当窗体大小改变时,纸牌画面会出现不显示的问题,因为每一张牌都是用GDI+的方法,从图片中剪裁下来的,没有用到vs提供的控件,如PictureBox控件,这就需要自己在窗体的Paint事件中来重新绘制图片,这样在窗体大小改变时,就能够正常显示纸牌图片了,代码中纸牌父类PKFather中的x,y参数,提供的就是纸牌在窗体中的实际坐标,也就是每张纸牌类要在窗体中显示时,都已经提供了在窗体上的坐标,那么问题解决就简单了,将需要在窗体上显示的那几张牌另外创建个集合,这个

C语言小游戏开发之贪吃蛇

寒假期间一时兴起,想着用C语言能开发出贪吃蛇小游戏应该是一件不错的事!但C语言学习过程中从来都是字符界面的编程,如何能有"图形化"的效果呢?于是小编就在度娘, 谷歌等网站搜索了一番,发现了一些有用的Win32 API函数,于是小编的心一下子激动了起来,有了这些API函数也许能够实现哟!开始吧! 编程之前小编的构思是这样的,贪吃蛇小游戏应该实现的有: 1.贪吃蛇在不进行控制的条件下应该能够自主前进. 2.玩家可以通过WASD键来控制贪吃蛇的前进方向. 3.运行过程中若贪吃蛇撞击到墙壁或自

JS小游戏----猜拳小游戏

这周学习了JS 的基础数据 函数 和 对象 每一个都是很多的属性和方法 下面是对象做的Xmind 有很多方法 创建对象的方法 遍历对象 添加删除对象中的属性 访问对象的方法 点 中括号 访问法 猜拳游戏 实际原理其实很简单  规则大家全世界都通用 所以 这个游戏 短短几行 switch就可以 把简易的原理实现出来 但是要做的 像一个小游戏一样 能应对各种情况 和 前进 和 后退的操作 加了一些switch 语句 让分支语句更多  是考虑到的情况更加全面  然后用 函数包装 功能模块 列如 判断模

js选择颜色小游戏(随机生成不含重复数字的数组,通过数组中的数控制定义好的数组)

<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>js网页版小游戏</title> <style media="screen"> .wrap { width: 577px; outline: 1px solid hotpink; margin: 100px auto; box-shadow: 0 0 5px; } .

使用AxureRP7.0制作经典小游戏"大家来找茬"

本案例是<网站蓝图AxureRP7.0从入门到精通视频教程>中的最后一节,适用于对Axure基础知识掌握比较熟练的同学:教程由axure原型库网站录制,转载请注明出处!相信很多刚接触Axure或者学习了一段时间但并没有深入的同学们,看到这个案例一定很惊讶,使用Axure竟然能做出如此逼真的交互效果!通过此案例也可以激发广大同学们学习Axure的热情和信心!试想一下,如果你有情侣的话,把你们珍藏的合影拿出来处理几张,做成大家来找茬的小游戏,不但锻炼了自己的技能,还能给对方一个惊喜,岂不是一举两得

使用AxureRP7.0制作经典数独小游戏原型,axure游戏原型下载

之前,有同学在Q群中提问,如何使用axure制作经典数独解谜小游戏,当时由于时间关系没有来得及亲手制作,而是给同学们提供了Axure6.5版本的一个数独解谜游戏的原型,本教程由axure原型库网站录制,转载请注明出处!但是那个原型做的太过繁杂,所以仅供大家参考交流:在此,金乌老师特地抽时间给同学们使用AxureRP7.0制作了一下,感觉对实战逻辑分析和axure变量的掌握比较有考验,所以就放出来供大家学习交流使用. 在学习的过程中,如果你仅凭自己现有的对axure的掌握,无法准确分析并组织出原型

【H5小游戏开发教程】如何限制微信游戏只能在微信端打开?

在这行里接触的时间多了,就会发现很多有意思的东西. 比如,很多微信小游戏会限制只能在微信端打开,有木有? 有这样的, 也有这样的, 妈蛋,不能用PC访问,这游戏就没法扒呀..... 其实涛舅舅告诉你,这两种都可以扒 而且是用PC! 但是今天,我不教你扒皮 我要教你的是,怎么让你的微信游戏也能限制PC打不开 很想学吧  准备开始! 1.第一种不提了,因为人家是设置了微信授权登录,从微信那里就拦截住了,只能用微信访问,你可能弄不了这么高级的微信授权这块,如果你真能弄,这一讲你也不用听了,因为你已经能

Chrome自带恐龙小游戏的源码研究(完)

在上一篇<Chrome自带恐龙小游戏的源码研究(七)>中研究了恐龙与障碍物的碰撞检测,这一篇主要研究组成游戏的其它要素. 游戏分数记录 如图所示,分数及最高分记录显示在游戏界面的右上角,每达到100分就会出现闪烁特效,游戏第一次gameover时显示历史最高分.分数记录器由DistanceMeter构造函数实现,以下是它的全部代码: 1 DistanceMeter.dimensions = { 2 WIDTH: 10, //每个字符的宽度 3 HEIGHT: 13, //每个字符的高 4 DE

C语言小游戏设计报告

课程设计名称:贪吃蛇小游戏 专业班级:计科15-2 学号:150809229 姓名:XXX 一.设计目标 通过设计,培养学生对电脑的动手能力,使学生巩固<C语言程序设计>课程学习的内容,掌握编写程序的基本方法,强化对其的动手能力,可以独自完成程序的编写. 二.设计内容和要求 设计内容 编写贪吃蛇的小游戏,使其可以成功运行并且操作玩耍. 设计要求 1)源程序要有适当的注释,使程序便于阅读. 2)要有程序运行结果作为依据 三.程序流程 1.编写地图 运用函数,数组编写地图 2.相同的方法把蛇添加进