C++学习 2019-1-9

最近几天的课程是将之前写的纸牌游戏的牌壳进行重写。之前的纸牌游戏只是单纯的继承了牌壳里的类,从而完成纸牌的规则,进而实现了纸牌游戏,但是并没有对扑克牌类游戏的模版进行学习,接下来需要仔细学习扑克牌类的游戏模版。

1.首先创建一个空的模版,也就是基于Win32应用程序创建的窗口,之前在进行任何游戏的实现时都需要有的WinMain.cpp,这个文件是为了创建一个空的Win32项目。

1.1 首先我们将之前写好的 COObject.h、COObject.cpp、CGameApp.h以及 WinMain.cpp文件拷贝到新的工程中去。

1.2 当我们改完字符集属性之后,发现了一个错误:

1>WinMain.obj : error LNK2019: 无法解析的外部符号 "class CGameApp * __cdecl CreateObject(void)" (?CreateObject@@YAPAVCGameApp@@XZ),该符号在函数 "long __stdcall WndProc(struct HWND__ *,unsigned int,unsigned int,long)" (?WndProc@@YGJPAUHWND__@@IIJ@Z) 中被引用
1>D:\all_works\C\colin\CPP\1-7\Win32Project1\Debug\Win32Project1.exe : fatal error LNK1120: 1 个无法解析的外部命令

问题来了:这是为什么呢?

起始原因很简单,因为CGameApp是一个接口类,其需要一个类来进行继承。我们创建一个继承 CGameApp 的类:CCardsApp 类,并在CCardsApp.cpp文件中动态创建一个对象:IMPLEMENT(CCards)。这样以后,我们就能看到窗口了。

2.创建一个 sys.h 的头文件

2.1 该头文件中装的是以后需要用到的头文件、一个牌的节点、以及一个用来表示牌颜色的联合体(这个联合体的顺序是按照我们添加牌为位图时牌的顺序而定义的)。

sys.h

#pragma once

#include <windows.h>
#include <list>
#include <vector>
#include <algorithm>
#include "resource.h"

using namespace std;

// 牌的颜色
enum {Cards_Flower,Cards_Square,Cards_Red,Cards_Black};

class CCards;
struct Node
{
	bool bflag;   //  标示正反面
	int x;
	int y;
	CCards* pCards;
};

3.完成cards类

1.首先对于 CCards 类,我们考虑:一张牌应该需要几个属性? 答案是三个:1)图像句柄、2)牌的颜色、3)牌的数字。

因此我们给 CCards 类创建三个成员属性:HBITMAP m_hBmpCards; int m_nCardsColor;int m_nCardsNum;
并且添加一个初始化 CCards 类的函数:void InitCards(HINSTANCE hIns, int nBmpID, int nCardsNum, int nColor)。

CCards.h

#pragma once
#include "sys.h"

class CCards
{
public:
	CCards(void);
	~CCards(void);

public:
	HBITMAP m_hBmpCards;
	int m_nCardsColor;
	int m_nCardsNum;

public:
	void InitCards(HINSTANCE hIns, int nBmpID, int nCardsNum, int nColor);
};

2.完成 InitCards 函数

2.1 首先我们来考虑:对于牌的初始化,我们需要进行载入位图,并为卡牌的颜色和数字进行赋值(首先应该在构造函数中将颜色和数字初始化为0;并且在析构函数中,我们需要将载入的位图清空)。

CCards.cpp

#include "Cards.h"

CCards::CCards(void)
{
	m_hBmpCards = 0;
	m_nCardsColor = 0;
	m_nCardsNum = 0;
}

CCards::~CCards(void)
{
	DeleteObject(m_hBmpCards);
	m_hBmpCards = 0;
}

void CCards::InitCards(HINSTANCE hIns, int nBmpID, int nCardsNum, int nColor)
{
	m_hBmpCards = LoadBitmap(hIns, MAKEINTRESOURCE(nBmpID));
	m_nCardsNum = nCardsNum;
	m_nCardsColor = nColor;
}

3. 完成 CPoker 类

3.1 在完成 CCards 类之后,我们可以开始完成 CPoker 类。对于 CPoker 类,我们知道,一副扑克具有13张牌,因此我们在 CPoker 类中使用一个 vector<CCards*> 类型的成员来保存一副扑克中的牌。虽然我们知道一副扑克由13张牌组成,但是在进行定义vector成员时,我们仍旧不可以直接定义为这样的形式:vector<CCards*> m_vecCards(13),而是需要在构造函数中进行初始化。原因:==忘记了=(找到后补充)。

3.2 之后我们仍旧需要一个初始化poker的函数:void InitPoker(HINSTANCE hIns, int nColor)。

3.3 在构造函数的初始化列表中初始化 m_vecCards(13),在析构函数中将每一个牌的指针删除,并赋值为空。

3.4 初始化 CPoker ,起始就是像 vector 类型成员中添加牌的指针。

#include "Poker.h"

CPoker::CPoker(void):m_vecCards(13)
{
}

CPoker::~CPoker(void)
{
	for(int i=0; i<13; i++)
	{
		delete(m_vecCards[i]);
		m_vecCards[i] = 0;
	}
}

void CPoker::InitPoker(HINSTANCE hIns, int nColor)
{
	// 创建13个cards对象
	for(int i=0; i<13; i++)
	{
		m_vecCards[i] = new CCards;
		m_vecCards[i]->InitCards(hIns, IDB_BITMAP1+nColor*13+i, i+1, nColor);		// IDB_BITMAP1 是我程序里的第一张牌位图的ID,各位需要将其换成自己的第一张牌的ID
	}
}

4. 完成 CCardsRank 类

4.1 因为 CCardsRank 类需要进行动态创建对象,因此其需要继承 COObject 类。

4.2 对于CCardsRank类,我们想要这个类能够进行管理poker,并且能够存储多副扑克,因此我们定义其中的成员: CPoker* m_pPoker; HBITMAP m_hBmpCardsBack; HBITMAP m_hBmpWndBack; vector<list<Node*>> m_vecRank;其中的m_hBmpCardsBack表示的是扑克牌的背面位图句柄,m_hBmpWndBack表示的是窗口的位图句柄,m_vecRank表示的是所有的牌。

4.3 对于类中的成员函数,我们考虑:一个初始化Rank的函数、一个显示Rank的函数。

4.4 对于 CCardsRank 类的构造函数,我们定义为带参数的构造函数,将传入的参数传递给 m_vecRank 进行初始化,其余赋值为0;在构造函数里对载入的位图进行删除,对扑克对象进行删除,并将vector中的所有元素进行遍历删除。

4.5 我们将初始化Rank的函数定义为纯虚函数,因为我们不知道以后的扑克游戏是蜘蛛纸牌还是纸牌,因此需要具体问题具体分析,因此在此处定义为纯虚函数;对于显示Rank的函数,首先判断有没有整体游戏的背景,若没有则进行加载,再显示背景图;判断牌背面的句柄是否为空,若是则加载牌背面位图;再遍历所有的节点,对牌进行贴图。

CCardsRank.h

#pragma once
#include "OObject.h"
#include "Poker.h"

class CCardsRank :
	public COObject
{
public:
	CCardsRank(int nCount);
	~CCardsRank(void);

public:
	CPoker* m_pPoker;
	HBITMAP m_hBmpCardsBack;
	HBITMAP m_hBmpWndBack;
	vector<list<Node*>> m_vecRank;

public:
	virtual void InitRank(HINSTANCE hIns)=0;
	void ShowRank(HINSTANCE hIns, HDC hdc);
};

CCardsRank.cpp

#include "CardsRank.h"

CCardsRank::CCardsRank(int nCount):m_vecRank(nCount)
{
	m_pPoker = 0;
	m_hBmpCardsBack = 0;
	m_hBmpWndBack = 0;
}

CCardsRank::~CCardsRank(void)
{
	// 删除图像句柄
	DeleteObject(m_hBmpCardsBack);
	DeleteObject(m_hBmpWndBack);
	// 删除扑克的对象
	delete m_pPoker;
	// 删除链表中所有的节点
	for(size_t i=0; i<m_vecRank.size(); i++)
	{
		list<Node*>::iterator ite = m_vecRank[i].begin();
		while (ite != m_vecRank[i].end())
		{
			delete (*ite);
			ite = m_vecRank[i].erase(ite);
		}
	}
}

void CCardsRank::ShowRank(HINSTANCE hIns, HDC hdc)
{
	// 1.判断是否有背景图,若无则加载
	if(m_hBmpWndBack == 0)
		m_hBmpWndBack = ::LoadBitmap(hIns, MAKEINTRESOURCE(IDB_WIN_BACK));
	HDC backComDC = ::CreateCompatibleDC(hdc);
	::SelectObject(backComDC, m_hBmpWndBack);
	::BitBlt(hdc, 0, 0, 850, 600, backComDC, 0, 0, SRCCOPY);
	::DeleteDC(backComDC);

	// 2.判断背景牌的句柄是否为空,若是则加载背景牌位图
	if(m_hBmpCardsBack == 0)
		m_hBmpCardsBack = ::LoadBitmap(hIns, MAKEINTRESOURCE(IDB_POKER_BACK));

	for(size_t i=0; i<m_vecRank.size(); i++)
	{
		list<Node*>::iterator ite = m_vecRank[i].begin();
		while (ite != m_vecRank[i].end())
		{
			HDC cardComDC = ::CreateCompatibleDC(hdc);
			
			if((*ite)->bflag == false)
				::SelectObject(cardComDC, m_hBmpCardsBack);
			else
				::SelectObject(cardComDC, (*ite)->pCards->m_hBmpCards);
			::BitBlt(hdc, (*ite)->x, (*ite)->y, 71, 96, cardComDC, 0, 0, SRCCOPY);
			::DeleteDC(cardComDC);

			++ite;
		}
	}
}

5. 完成 CCardApp 类

5.1 CCardApp 类首先是继承 GameApp 类的,其次它需要管理poker,因此依赖于 CCardsRank 类,需要有一个CCardsRank类型的指针。

5.2 在实现CCardApp类时,首先需要动态创建一个CCardApp类对象,这个宏定义在CGameApp类中;其次完成 OnCreateGame 函数,首先需要对CCardsRank类型的指针 m_pRank 进行赋值,其值为: (CCardsRank*)COObject::Create(“CCardsRank”) ,判断 m_pRank 是否是空,若是空则表明创建失败,否则进行 m_pRank->InitRank(m_hIns) 操作;再完成重绘函数,在 OnGameDraw 函数中判断 m_pRank 是否是空,不是空则进行 m_pRank->ShowRank(m_hIns, tempDC) 操作。

CCardApp.h

#pragma once
#include "gameapp.h"
#include "CardsRank.h"

class CCardsApp :
	public CGameApp
{
public:
	CCardsApp(void);
	~CCardsApp(void);

public:
	CCardsRank* m_pRank;   //  排列

public:
	virtual void OnCreateGame();                 //  WM_CREATE
	virtual void OnGameDraw();                   //  WM_PAINT
	virtual void OnLButtonDown(POINT point);      //  WM_LBUTTONDOWN
	virtual void OnLButtonUp(POINT point);        //  WM_LBUTTONUP
	virtual void OnMouseMove(POINT point);        //  WM_MOUSEMOVE
};

CCardApp.cpp

#include "CardsApp.h"

IMPLEMENT(CCardsApp)

CCardsApp::CCardsApp(void)
{
	m_pRank = 0;
}

CCardsApp::~CCardsApp(void)
{
	delete m_pRank;
	m_pRank = 0;
}

void CCardsApp::OnCreateGame()                 //  WM_CREATE
{
	m_pRank = (CCardsRank*)COObject::Create("CCardsRank");
	if(m_pRank == 0)
		::MessageBox(m_hMainWnd, "游戏初始化失败", "提示", MB_OK);
	else
	{
		m_pRank->InitRank(m_hIns);
	}
}

void CCardsApp::OnGameDraw()                   //  WM_PAINT
{
	HDC tempDC = GetDC(m_hMainWnd);
	if(m_pRank != 0)
		m_pRank->ShowRank(m_hIns, tempDC);
	ReleaseDC(m_hMainWnd, tempDC);
}

void CCardsApp::OnLButtonDown(POINT point)      //  WM_LBUTTONDOWN
{}

void CCardsApp::OnLButtonUp(POINT point)        //  WM_LBUTTONUP
{}

void CCardsApp::OnMouseMove(POINT point)        //  WM_MOUSEMOVE
{}

6. 完成 CRule 类

6.1 对于 CRule 类,由5个纯虚函数和3个成员函数组成,五个虚函数都是需要具体的游戏来具体重写的,因此我们只需要完成剩余三个不管什么牌类游戏都需要用到的函数即可。

6.2 首先我们完成拿牌的函数:GetCards,这个函数需要我们传入三个参数:POINT point, CCardsRank* pCradsRank, list<Node*>& lstCursorCards。为了确定玩家是否取到了牌,我们需要遍历所有的链表(虽然很笨,但是很有效);我们需要从最下面的一张牌开始遍历,这是因为假设我们从最上面的牌开始遍历时,第一张牌和第二张牌是有重复范围的部分的,若我们从最上面一张牌开始遍历链表的话,我们无法区分玩家点击的是第一张牌还是第二张牌,为了避免这个问题,我们需要从链表的最后开始遍历链表,因此创建的是反向迭代器 reverse_iterator ,当不是 rend 时进行鼠标点击的区域判断,判断时还需判断是否是正面,也就是 bflag 的值是否为 true ,若是正面,则先将反向的迭代器进行反转,转换为正向的,此时需要注意,反转反向迭代器时会得到正向迭代器的上一个,因此需要进行 --;之后使用能否拿牌的规则 IsGetCardsRule 进行判断是否可以进行拿牌,若能拿牌则先记录链表的下标,再将这个链表放到光标的链表上。

6.3 具体的程序

void CRule::GetCards(POINT point, CCardsRank* pCradsRank, list<Node*>& lstCursorCards)
{
	// 1.遍历所有链表
	for(size_t i=0; i<pCradsRank->m_vecRank.size(); i++)
	{
		// 2.从最下面一个牌开始遍历(要是从上面开始遍历的话不知道取的是哪一张牌,会有区域是重复的)
		list<Node*>::reverse_iterator rev_ite = pCradsRank->m_vecRank[i].rbegin();
		while (rev_ite != pCradsRank->m_vecRank[i].rend())
		{
			// 3.判断坐标是否点击到了牌上
			if( point.x >= (*rev_ite)->x && point.x <= (*rev_ite)->x+71
				&& point.y >= (*rev_ite)->y && point.y <= (*rev_ite)->y+96)
			{
				// 4.若点击到了牌上,判断是不是正面,正面可拿起,反面不可拿起
				if((*rev_ite)->bflag == true)
				{
					list<Node*>::iterator ite = --(rev_ite.base());		// 将牌反转过来,从当前的光标位置取牌
					// 5.判断能不能拿牌
					if( this->IsGetCardsRule(pCradsRank, i, ite) == true)
					{
						// 能拿牌
						// 记录这个链表的下标
						m_nGetCardsListID = i;
						// 放到光标的链表上
						lstCursorCards.splice(lstCursorCards.end(), pCradsRank->m_vecRank[i], ite, pCradsRank->m_vecRank[i].end());
					}
				}
				return;
			}
			++rev_ite;
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值