前一段時間完成了蜘蛛紙牌的仿寫,現将過程和思路記錄下來
首先,為了符合複用性,在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 程式設計小菜鳥自我總結,各位大佬可以提出自己的建議和意見,謝謝!!!