C++面向对象编程练习小项目——“斗地主”

概要

本文中的“斗地主”是简易斗地主,因为我们主要的目的是练习面向对象编程的思想,而完整的斗地主逻辑是比较复杂的,我们的重心不在那。本文斗地主规则:三个人各自为战,三个人各有若干手牌,牌面大小与斗地主一致,每次只能出一张,其他规则与斗地主近似,最终牌先出完的获胜。另外,摸牌后需自动理牌(从大到小)。

Start

创建地主类

我们在头文件H中创建LandOwner类,该类有若干变量和方法,为了后续的编程方便,我们将大部分数据写在了公有public中:

#include<iostream>
#include<vector>

using namespace std;

class LandOwner
{
private:
	string m_NickName;                         //玩家昵称
	string m_Sex;                              //玩家性别
	int m_Gold;                                //玩家金币
	long m_Exp;                                //玩家经验
	bool isContains(int cardNum);             //剩余牌中是否包含cardNum这个牌面
public:
	static vector<int> surplusCards;                 //摸牌后剩余的牌
	static vector<int> packCards;                    //默认的一副牌,从1-54
	vector<int> currCards;                     //当前玩家的手牌数组

	LandOwner();                              //默认构造函数
	LandOwner(string);                        //带参构造函数
	LandOwner(string, string, int, long);     //带参构造函数
	~LandOwner();                             //析构函数
	string GetNickName() { return m_NickName; }                //获得昵称方法
	void SetNickName(string val) { m_NickName = val; }         //设置昵称方法
	string GetSex() { return m_Sex; }                          //获得性别方法
	void SetSex(string val) { m_Sex = val; }                   //设置性别方法
	int GetGold() { return m_Gold; }                           //获得金币方法
	void SetGold(int val) { m_Gold = val; }                    //设置金币方法
	long GetExp() { return m_Exp; }                            //获得经验方法
	void SetExp(long val) { m_Exp = val; }                     //设置经验方法
	
	void ShowInfo();                                           //显示玩家信息方法
	void TouchCard(int cardCount);                             //摸牌方法
    static void InitCards();                          //初始化packCards和surplusCards,currCards   
	void ShowCards(vector<int>);                          //显示牌面
	void deleteCard(vector<int>&, int);         //删除集合中的数字
	string getColor(int);                       //获得牌的花色
	string getValue(int);                       //获得牌面

	void Sortfunc(vector<int>&);                //冒泡排序理牌
};

这里简单说一下构造函数和析构函数:构造函数就是实例化类时自动执行的一个函数,可以带参和不带参。析构函数是delete实例化对象后自动运行的函数。这里将玩家的一些信息写在了私由private中,这样外部不能随意访问,但可以通过公有方法设置或者是获取这些信息,这样封装后的设置方法可以防止随意篡改玩家信息。
另外pacCards和surplusCards设置成了static静态vector,这样所有类对象共用相同vector,因为斗地主同时只能拥有一副牌。同时还声明了静态初始化函数InitCards,这种在类中的静态函数不依赖于实例化对象,可以在定义后直接调用:LandOwner::InitCards()。值得注意的是,无论是静态vector还是静态函数都需要在类外定义!

实现各种对象方法

设置和获取玩家信息的方法由于十分简单,已经在类中写好了。下面实现一些稍微复杂点的方法。

构造函数

LandOwner::LandOwner()
{
	SetNickName("默认地主");
	SetSex("保密");
	SetExp(0);
	SetGold(1000);
	currCards.clear();
}
LandOwner::LandOwner(string nickName) :m_NickName(nickName)
{
	SetSex("保密");
	SetExp(0);
	SetGold(1000);
	currCards.clear();
}
LandOwner::LandOwner(string nickName, string sex, int gold, long exp) :m_NickName(nickName), m_Sex(sex), m_Exp(exp), m_Gold(gold)
{
	currCards.clear();
}

这里currCards.clear()是将玩家手牌清零。另外留意第三种构造函数的设置方法。

析构函数

LandOwner::~LandOwner()
{
	cout << GetNickName() << "被释放!" << endl;
}

显示玩家信息

}
void LandOwner::ShowInfo()
{
	cout << "昵称:" <<GetNickName() <<endl;
	cout << "性别:" << GetSex() << endl;
	cout << "金币:" << GetGold() << endl;
	cout << "经验:" << GetExp() << endl;
}

初始化卡牌

void LandOwner::InitCards()
{
	//生成默认的一幅扑克牌
	for (int i = 0; i < 54; i++)
	{
		packCards.push_back(i + 1);
		surplusCards.push_back(packCards.at(i));    //packCards.at(i)近似于packCards[i]
	}
}

这里就是实现了之前在类中的静态方法。代码使用vector来初始化一副牌,surplusCards为还可以抽取的牌。这里说一下牌对应的编码:1-13黑桃,14-26红心,27-39方块,40-52梅花,53小王,54大王,且每组中对应3……A,2。也就是编码减一/13为花色,%13为牌面大小。

获得编码对应花色和大小

string LandOwner::getColor(int card)
{
	if (card == 53) return "";
	if (card == 54) return "";
	string colors[] = {
		"黑桃","红心","方块","梅花"
	};
	return colors[(card-1) / 13];
}
string LandOwner::getValue(int card)
{
	if (card == 53) return "小王";
	if (card == 54) return "大王";
	string values[] = {
		"3","4","5","6","7","8","9","10","J","Q","K","A","2"
	};
	return values[(card - 1) % 13];
}

这一部分使用迭代器遍历surplusCards向量,如果找到相同的,iter指正不会停在vector的end地址,如果停在了end,说明剩余卡组没有这张牌。

删除一张牌

void LandOwner::deleteCard(vector<int>& cardVec, int card)
{
	auto iter = find(cardVec.begin(), cardVec.end(), card);
	if (iter != cardVec.end())
	{
		cardVec.erase(iter);
	}
}

摸牌

void LandOwner::TouchCard(int cardCount)
{
	//随机生成一张剩余牌集合中有的牌,添加到currCards中,从surplusCards中删除这张牌
	srand(time(NULL));
	for (int i = 0; i < cardCount; i++)
	{
		int randIndex = rand() % 54;  //0-53随机数字
		//判断随机生成的这张牌是否在剩余牌集合中
		if (isContains(packCards[randIndex]))
		{
			currCards.push_back(packCards[randIndex]);
			deleteCard(surplusCards, packCards[randIndex]);
		}
		else
		{
			i--;
		}
	}
	cout <<GetNickName()<< "摸牌,当前手牌如下:" << endl;
	Sortfunc(currCards);
	ShowCards(currCards);

}

摸牌这里思路挺简单的,就是随机一张牌看剩余牌组中有没有这张牌,有的话就删除这张牌,加入手牌即可。

主函数

#include<iostream>
#include"标头.h"


using namespace std;

int main()
{
	//初始化人物信息
	LandOwner* ptr_landOwner1 = new LandOwner("小张");
	LandOwner* ptr_landOwner2 = new LandOwner("小王");
	LandOwner* ptr_landOwner3 = new LandOwner("小飞");
	//初始化卡牌
	LandOwner::InitCards();
	//玩家信息打印
	ptr_landOwner1->ShowInfo();
	ptr_landOwner2->ShowInfo();
	ptr_landOwner3->ShowInfo();
	cout << endl;
	//玩家摸牌并显示
	ptr_landOwner1->TouchCard(17);
	cout << endl;
	ptr_landOwner2->TouchCard(20);
	cout << endl;
	ptr_landOwner3->TouchCard(17);
	cout << endl;
    //构造对象数组
	LandOwner* array[] = { ptr_landOwner1,ptr_landOwner2,ptr_landOwner3 };

	int curr = -1;
	int count = 0;
	int privilege = 0;
	bool flag = 1;
	string winner;

	while (flag)
	{
		for (int k = 0; k <= 2; k++)
		{
			int num_cards;
			cout << endl;
			cout << "轮到" << array[k]->GetNickName() << "出牌了!" << endl;
			cout << "请选择出哪张牌(-1代表不出):" << endl;
			cin >> num_cards;
			if (num_cards == -1)
			{
				count++;
				continue;
			}
			if (count == 2)
			{
				count = 0;
				privilege = 1;
				curr = -1;
			}
			if (((num_cards != -1) && (((array[k]->currCards[num_cards] - 1) % 13) > ((curr - 1) % 13))) || (privilege == 1))
			{
				curr = array[k]->currCards[num_cards];
				cout << array[k]->GetNickName() << "打了" << array[k]->getColor(array[k]->currCards[num_cards]) << \
					array[k]->getValue(array[k]->currCards[num_cards]) << endl;
				array[k]->deleteCard(array[k]->currCards, array[k]->currCards[num_cards]);
				cout << array[k]->GetNickName() << "还剩手牌:" << endl;
				array[k]->ShowCards(array[k]->currCards);
				privilege = 0;
			}
			else if (((array[k]->currCards[num_cards] - 1) % 13) <= ((curr - 1) % 13)) 
			{
				cout << "请出比上家更大的牌!" << endl;
				k--;
			}
			if (array[k]->currCards.size() == 0)
			{
				flag = 0;
				winner = array[k]->GetNickName();
				break;
			}
		} 
	}
	cout << "游戏结束!" << winner << "获胜!" << endl;
}

这里的逻辑还是比较简单的,因为没有加入三带一这种牌型,唯一需要注意的是当两家都不要的时候第三方可以出任意的牌而不需要出比上一次牌大的牌。另外主函数中使用了对象数组LandOwner* array[] = { ptr_landOwner1,ptr_landOwner2,ptr_landOwner3 };注意这里的*号。

总结

学习面向对象编程思想时结合一个小例子编程会更快理解一些知识。最近斗地主玩得挺多,而玩家在斗地主中就是对象,因而适合用来练习。

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 共2条
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片