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

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

CGameApp.h

 #pragma once
class CGameApp //接口类
{
public:
virtual void OnCreatGame(){}
virtual void OnGameDraw(){}
virtual void OnGameRun(){}
virtual void OnKeyDown(){}
virtual void OnKeyUp(){}
virtual void OnLButtonDown(){}
virtual void OnLButtonUp(){}
virtual void OnMouseMove(){}
};
 #include<windows.h>
#include"CGameApp.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int CALLBACK WinMain(HINSTANCE hInstance,HINSTANCE hPreInstance,LPSTR pCmdLine,int nCmdShow)
{
// 1. 设计
WNDCLASSEX wndclass;
wndclass.cbClsExtra = ;
wndclass.cbWndExtra = ;
wndclass.cbSize = sizeof(wndclass);
wndclass.hbrBackground = (HBRUSH)COLOR_WINDOW;
wndclass.hCursor = ;
wndclass.hIcon = ;
wndclass.hIconSm = ; // 窗口左上的小图标
wndclass.hInstance = hInstance;
wndclass.lpfnWndProc = WndProc; // 窗口的消息处理函数
wndclass.lpszClassName = "cyc"; // 注册窗口类的名字
wndclass.lpszMenuName = ;
wndclass.style = CS_HREDRAW|CS_VREDRAW; // 2. 注册
if( ::RegisterClassEx(&wndclass) == FALSE)
{
::MessageBox(,"注册失败","提示",MB_OK);
return ;
}
// 3. 创建
HWND hwnd = ::CreateWindow("cyc","游戏壳",WS_OVERLAPPEDWINDOW,,,,,,,hInstance,);
if(hwnd == )
{
::MessageBox(,"创建失败","提示",MB_OK);
return ;
} // 4. 显示窗口
::ShowWindow(hwnd,SW_SHOW); // 5. 消息循环
MSG msg;
while(::GetMessage(&msg,,,))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg); // 分发, 调用消息的处理函数WndProc
} return ;
} CGameApp *p = ;
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
{
if(p == NULL)
p->OnCreatGame(); }
break;
case WM_PAINT:
{
if(p == NULL)
p->OnGameDraw(); }
break;
case WM_TIMER:
{
if(p == NULL)
p->OnGameRun(); }
break;
case WM_KEYDOWN:
{
if(p == NULL)
p->OnKeyDown(); }
break;
case WM_KEYUP:
{
if(p == NULL)
p->OnKeyUp(); }
break;
case WM_LBUTTONDOWN:
{
if(p == NULL)
p->OnLButtonDown(); }
break;
case WM_LBUTTONUP:
{
if(p == NULL)
p->OnLButtonUp(); }
break;
case WM_MOUSEMOVE:
{
if(p == NULL)
p->OnMouseMove(); }
break;
case WM_CLOSE: // 关闭
::PostQuitMessage(); // 发送一个退出的消息
break;
}
return DefWindowProc( hwnd, uMsg, wParam, lParam);
}

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

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

框架内:

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

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

 void CCardsRank::ShowRank(HDC hdc, HINSTANCE hIns)
{
//1==============================显示窗口的背景图============================
if(m_hBmpWndBack == )
m_hBmpWndBack = ::LoadBitmap(hIns,MAKEINTRESOURCE(IDB_WND_BACK)); HDC hMemDC = ::CreateCompatibleDC(hdc);
::SelectObject(hMemDC,m_hBmpWndBack);
::BitBlt(hdc,,,,,hMemDC,,,SRCCOPY);
::DeleteDC(hMemDC);
//1==============================显示窗口的背景图============================ //2==============================显示牌=====================================
//-------------有没有牌的背景图----------
if(m_hBmpCardsBack == )
m_hBmpCardsBack = ::LoadBitmap(hIns,MAKEINTRESOURCE(IDB_CARDS_BACK));
//-------------有没有牌的背景图----------
for(size_t i=;i<m_vecRank.size();i++)
{
list<Node*>::iterator ite = m_vecRank[i].begin();
while(ite != m_vecRank[i].end())
{
//----------贴图-------------------
HDC hMemDC = ::CreateCompatibleDC(hdc); if((*ite)->bflag == false)
::SelectObject(hMemDC,m_hBmpCardsBack);
else
::SelectObject(hMemDC,(*ite)->pCards->m_hBmpCards); ::BitBlt(hdc,(*ite)->x,(*ite)->y,,,hMemDC,,,SRCCOPY); ::DeleteDC(hMemDC);
//----------贴图------------------
++ite;
}
}
//2==============================显示牌=====================================
}

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

 void CCardsApp::ShowCursorCards(HDC hdc)
{
int X = pointMouseMove.x - pointMouseDown.x;
int Y = pointMouseMove.y - pointMouseDown.y; // 在 移动的距离的位置显示牌
list<Node*>::iterator ite = m_lstCursorCards.begin();
while(ite != m_lstCursorCards.end())
{
HDC hMemDC = ::CreateCompatibleDC(hdc);
::SelectObject(hMemDC,(*ite)->pCards->m_hBmpCards);
::BitBlt(hdc,(*ite)->x+X,(*ite)->y+Y,,,hMemDC,,,SRCCOPY);
::DeleteDC(hMemDC);
++ite;
}
}

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

 bool CRule::ReceiveCards(POINT point, CCardsRank* pCardsRank, list<Node*>& lstCursor)
{
// 遍历 所有的链表
for(size_t i=;i<pCardsRank->m_vecRank.size();i++)
{
// 判断坐标的 交给子类
if(this->IsReceiveCardsRule(point,i,pCardsRank,lstCursor) == true)
{
// 和 i这个链表结合
pCardsRank->m_vecRank[i].splice(pCardsRank->m_vecRank[i].end(),lstCursor);
// 更新位置(对齐)
this->UpDatePos(pCardsRank,i);
// 翻牌
if(pCardsRank->m_vecRank[m_nGetCardsListID].empty() == false)
pCardsRank->m_vecRank[m_nGetCardsListID].back()->bflag = true;
m_nGetCardsListID = -;
return true;
}
}
return false;
}
void CRule::GetCards(POINT point, CCardsRank* pCardsRank, list<Node*>& lstCursor)
{
// 遍历 所有的链表
for(size_t i=;i<pCardsRank->m_vecRank.size();i++)
{
// 遍历 i 个链表的所有节点
list<Node*>::reverse_iterator rev_ite = pCardsRank->m_vecRank[i].rbegin();
while(rev_ite != pCardsRank->m_vecRank[i].rend())
{
// 判断光标是否点击到这个牌上
if(point.x >= (*rev_ite)->x && point.x <= (*rev_ite)->x+
&& point.y >= (*rev_ite)->y && point.y <= (*rev_ite)->y+)
{
// 判断是不是正面
if((*rev_ite)->bflag == true)
{
// 判断能不能拿起来
list<Node*>::iterator ite = --(rev_ite.base());
if( this->IsGetCardsRule(pCardsRank,i,ite) == true)
{
// 记录下标
m_nGetCardsListID = i;
// 放到光标的链表上
lstCursor.splice(lstCursor.end(),pCardsRank->m_vecRank[i],ite,pCardsRank->m_vecRank[i].end());
}
}
return;
}
++rev_ite;
}
}
}
void CRule::RevertCards(CCardsRank* pCardsRank, list<Node*>& lstCursor)
{
if(m_nGetCardsListID != -)
{
// 把光标的链表 放回到 m_nGetCardsListID 这个链表尾部
pCardsRank->m_vecRank[m_nGetCardsListID].splice(pCardsRank->m_vecRank[m_nGetCardsListID].end(),lstCursor);
m_nGetCardsListID = -;
}
}

框架外:

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

CMyCardsRank::CMyCardsRank(void):CCardsRank()

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

 void CCardsApp::OnGameDraw()                 //  WM_PAINT
{
HDC dc = ::GetDC(m_hMainWnd);
HDC hdc = ::CreateCompatibleDC(dc);
HBITMAP hbitmap = ::CreateCompatibleBitmap(dc,,);
::SelectObject(hdc,hbitmap);
//-------------------------------------------------------------
// 显示排列
if(m_pRank != )
m_pRank->ShowRank(hdc,m_hIns);
this->ShowCursorCards(hdc);
//-------------------------------------------------------------
::BitBlt(dc,,,,,hdc,,,SRCCOPY);
::DeleteObject(hbitmap);
::DeleteDC(hdc);
::ReleaseDC(m_hMainWnd,dc);
}

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

 bool CMyRule::IsGetCardsRule(CCardsRank* pCardsRank, int nlstID, list<Node*>::iterator iteCursorPos)
{
int num = (*iteCursorPos)->pCards->m_nCardsNum; while(iteCursorPos != pCardsRank->m_vecRank[nlstID].end())
{
if(num != (*iteCursorPos)->pCards->m_nCardsNum)
return false;
--num;
iteCursorPos++;
}
return true;
}

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

 bool CMyRule::IsOpenCards(POINT point, CCardsRank* pCardsRank)
{
//判断最后一个链表里是否有东西
if(pCardsRank->m_vecRank[].empty() == false)
{
//判断是不是点到最后一张牌
if(point.x >= pCardsRank->m_vecRank[].back()->x && point.x <= pCardsRank->m_vecRank[].back()->x+
&& point.y >= pCardsRank->m_vecRank[].back()->y && point.y <= pCardsRank->m_vecRank[].back()->y+)
{
//前十个有没有空链表
for(int i = ;i < ;i++)
{
if(pCardsRank->m_vecRank[i].empty() == true)
return false;
}
return true;
}
}
return false;
}

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

 bool CMyRule::IsReceiveCardsRule(POINT point, int nlstID, CCardsRank* pCardsRank, list<Node*>& lstCursorCards)
{
if(pCardsRank->m_vecRank[nlstID].empty() == true)
{
if(point.x >= +nlstID* && point.x <= +nlstID*+ && point.y >= && point.y <= +)
{
return true;
}
}
else
{
if(point.x >= pCardsRank->m_vecRank[nlstID].back()->x && point.x <= pCardsRank->m_vecRank[nlstID].back()->x+
&& point.y >= pCardsRank->m_vecRank[nlstID].back()->y && point.y <= pCardsRank->m_vecRank[nlstID].back()->y+)
{
if(lstCursorCards.front()->pCards->m_nCardsNum == pCardsRank->m_vecRank[nlstID].back()->pCards->m_nCardsNum - )
{
return true;
}
}
}
}

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

 void CMyRule::UpDatePos(CCardsRank* pCardsRank, int nlstID)
{
int j = ;
list<Node*>::iterator ite = pCardsRank->m_vecRank[nlstID].begin();
while(ite != pCardsRank->m_vecRank[nlstID].end())
{
(*ite)->x = +nlstID*;
(*ite)->y = +j*;
++j;
++ite;
}
this->DeleteNode(pCardsRank,nlstID);
}

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

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

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

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

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

5.删除后,尾结点翻牌

 void CMyRule::DeleteNode(CCardsRank* pCardsRank, int nlstID)
{
if(pCardsRank->m_vecRank[nlstID].size() >= && pCardsRank->m_vecRank[nlstID].back()->pCards->m_nCardsNum == )
{
int num = ;
list<Node*>::reverse_iterator rite = pCardsRank->m_vecRank[nlstID].rbegin();
for(int i = ;i < ;i++)
{
if((*rite)->bflag == false)
return;
if((*rite)->pCards->m_nCardsNum != num)
return;
++rite;
++num;
}
list<Node*>::iterator ite = rite.base();
while(ite != pCardsRank->m_vecRank[nlstID].end())
{
delete (*ite);
ite = pCardsRank->m_vecRank[nlstID].erase(ite);
}
}
if(pCardsRank->m_vecRank[nlstID].empty() == false)
pCardsRank->m_vecRank[nlstID].back()->bflag = true; }

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

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

最新文章

  1. eclipse 突然 一直在loading descriptor for XXX (XXX为工程名)
  2. [SQL]查询数据库里都有哪些自己建的表
  3. [XAF] How to hide individual navigation items and groups for certain users
  4. 如何实现:录制视频聊天的全过程? 【低调赠送:QQ高仿版GG 4.3 最新源码】
  5. 固定表头/锁定前几列的代码参考[JS篇]
  6. (转)阴影锥(Shadow Volume)
  7. [leetcode]Find Minimum in Rotated Sorted Array II @ Python
  8. thinkphp中limit方法
  9. Android调用系统分享功能以及createChooser的使用
  10. NSSet、NSMutableSet
  11. 关于JS中的constructor与prototype
  12. debian7 更换GCC版本
  13. Mac经常使用快捷键
  14. (简单) POJ 2352 Stars,Treap。
  15. Android Material Design控件使用(四)——下拉刷新 SwipeRefreshLayout
  16. java 初学 英语单词 记录在此 希望全部记住
  17. android listView多层嵌套listView显示不全问题
  18. Arduino IDE for ESP8266 教程(一) 局域网 网页查看数据 不控制
  19. 状压dp的另一种形式
  20. uva11552

热门文章

  1. moost — Last.fm&#39;s collection of C++ utility libraries(功能很多)
  2. WPF旋转的界面实现
  3. 从零开始学习 asp.net core 2.1 web api 后端api基础框架(三)-创建Data Transfer Object
  4. 【转载】MySQL和Keepalived高可用双主复制
  5. VisualSVN-6.0.1Patch just for VS2017补丁原创发布
  6. 利用最小二乘法拟合任意次函数曲线(C#)
  7. jquery 让图片飞
  8. inno setup 打包说明
  9. NBU客户端安装失败
  10. QSqlQueryModel 居然默认是只读的!