利用C++的类和对象简单模拟一套修仙体系

1. 灵感来源

         不好意思各位,最近有些托更,很大一部分原因在于迷上了某大型玄幻小说,可毕竟是技术人,又快秋招了,学习压力大,就业压力更大,这个节骨眼染上小说瘾不就死路一条了么。于是在高人的指点下,决定用编程的方式模拟一套修仙系统,既能巩固技术,还能读爽文,简直不要太上头。然后做了一段时间,发现效果还算可以,趁着体测完刚恢复精力,赶紧更一期博文。(PS:大学生还是要好好锻炼身体,今天中午体测完1000米,作者倒在床上差点一口老血喷出来,蒙头睡一下午,晚上才恢复过来,啊~学生时代的痛!)

2. 项目背景&项目介绍 

        如本期博文标题, 主要采用C++里的类和对象技术实现对整个修仙体系的模拟。故事背景是修仙大陆存在着一批神秘的修仙者,他们通过灵石进行交易,灵石不仅可以用来购买妖兽和物品,在战斗中也可以提供一定的战力加成,妖兽是修仙大陆的珍贵的战力资源,实力强大的修仙者可以与妖兽战斗,并在击败妖兽后有概率将其收服进妖兽袋中,修仙者可以在市场贩卖这些妖兽来获取灵石,也可用灵石购买妖兽,如果修仙缺少灵石的话,修仙者也可以通过挖矿来获得少量灵石。综上所述,作者这里把修仙大陆划分为了三个类,修仙者类、妖兽类、灵石类。当然其实还可以增加更多的设定和DIY细节,比如血统传承、家族结亲、妖兽进化等,但是那样整个项目的架构设计起来就有太复杂了,需要涉及到软件工程、UML这些科班技术,所以这里作者尽量从简单出发,后面的优化就留给各位读者大大。

3. 灵界本源——灵石类 

         灵石拥有一定的战力加成,但其主要作用还是为了交易,所以灵石尽量不要设计的太复杂,否则后面耦合性太高不方便功能的拓展和优化。所以灵石类的成员变量我们只设计两个,一个是灵石数量,一个是灵石品阶,灵石品阶分为初阶灵石、中阶灵石和高阶灵石,我们最好用一个枚举类型来表示,所以灵石类ShineStone.h文件代码如下:

#pragma once
#include <string>
#include <iostream>

enum class ShineStoneLevel{
	LEVEL_PRIMARY,
	LEVEL_MIDDLE,
	LEVEL_ADVANCE,
	LEVEL_ALL
};

class ShineStone
{
public:
    // 构造函数
	ShineStone(int count = 0,
		ShineStoneLevel level = ShineStoneLevel::LEVEL_PRIMARY);

    // 经典信息流
	std::string str() const;

	// 加法重载
	ShineStone& operator+(const int number);
	ShineStone& operator+(const ShineStone& stone);

    // 友元声明
	friend std::ostream& operator<<(std::ostream& os,
		const ShineStone& stone);
	friend class Witcher;

private:
	int count; // 灵石的数量多少块
	ShineStoneLevel level; // 灵石的等级

};

std::ostream& operator<<(std:: ostream& os,
	const ShineStone& stone);

可以看到作者声明了几个友元类和运算符重载,这些都是后期完成各种其他功能需要时临时添加上的,不过大家现在直接拿来用就行,没必要纠结原因。有了ShineStone.h的声明以后,我们在ShineStone.cpp里进行进行具体的代码实现即可,代码如下:

#include <sstream>
#include "ShineStone.h"

using namespace std;

ShineStone::ShineStone(int count, ShineStoneLevel level)
{
    this->count = count;
    this->level = level;
}

std::string ShineStone::str() const
{
    stringstream ret;
    ret << count << "块";

    switch (level) {
    case ShineStoneLevel::LEVEL_PRIMARY:
        ret << "初阶灵石";
        break;
    case ShineStoneLevel::LEVEL_MIDDLE:
        ret << "中阶灵石";
        break;
    case ShineStoneLevel::LEVEL_ADVANCE:
        ret << "高阶灵石";
        break;
    defalut:
        ret << "未知灵石";
        break;
    }
    return ret.str();
}

ShineStone& ShineStone::operator+(const int number)
{
    this->count += number;
    return *this;
}

ShineStone& ShineStone::operator+(const ShineStone& stone)
{
    int sum = 0;
    if (stone.level == ShineStoneLevel::LEVEL_ADVANCE) {
        sum += stone.count * 100;
    }
    else if (stone.level == ShineStoneLevel::LEVEL_MIDDLE) {
        sum += stone.count * 10;
    }
    else if(stone.level == ShineStoneLevel::LEVEL_PRIMARY){
        sum += stone.count;
    }

    switch (this->level) {
    case ShineStoneLevel::LEVEL_ADVANCE:
        this->count += sum / 100;
        break;
    case ShineStoneLevel::LEVEL_MIDDLE:
        this->count += sum / 10;
        break;
    case ShineStoneLevel::LEVEL_PRIMARY:
        this->count += sum;
        break;
    default:
        return *this;
    }

    return *this;
}

std::ostream& operator<<(std::ostream& os, const ShineStone& stone)
{
    os << stone.str();
    return os;
    
}

其中除了<sstream>的用法比较特殊以外,实现部分整体代码并没有太多的技术难点 ,然后<<符号重载的时候注意一下直接调用str()方法即可,不需要再额外写重复的代码。

4. 灵界伙伴——妖兽类 

        妖兽是一种特殊的群体,他不仅有类似修仙者的等级和战力体系,更是一种可购买挖掘的经济资源, 所以需要为妖兽定义一些获取价值和战斗力的接口,那么妖兽类Monster.h的代码如下:

#pragma once
#include "ShineStone.h"

// 妖兽战力系数
#define MONSTER_WAILD_BASE 5

class Monster
{
public:
    // 构造函数
	Monster(int level, const std::string species);

	// 获取妖兽的价值
	ShineStone getValue() const;

	// 获取妖兽的战斗力
	int getPower() const;

    // 获取妖兽物种
	std::string getSpecies() const; 

	// 重载判断符
	bool operator==(const Monster& monster);

    // 友元声明
	friend std::ostream& operator<<(std::ostream& os,
		const Monster& monster);

	friend class Witcher;

private:
	std::string species; // 物种
	int level; // 等级1-9

	//天赋值
	int talent;
};

std::ostream& operator<<(std::ostream& os,
	const Monster& monster);

 这里补充说明一下,友元类声明friend class Witcher里的Witcher其实就是修仙者类,由于英语里没有修仙者,所以用巫师、猎魔人的英文来代替了。当然看过玄幻小说的都知道,即便是同等级的妖兽,由于残酷的生存法则和物种之间的差距,战斗力也会天差地别,所以妖兽类我们引入一个天赋值概念,由构造函数在创建对象时使用随机数进行赋值,宏定义里的MONSTER_WAILD_BASE系数也是为了方便计算妖兽的战力,那么Monster.cpp的实现代码如下:

#include "Monster.h"
#include <sstream>

using namespace std;

Monster::Monster(int level, const std::string species)
{
	this->level = level;
	this->species = species;
	this->talent = (rand() % 10) + 6;
}

ShineStone Monster::getValue() const
{
	int count[] = {100, 200, 500, 1000, 2000, 
		5000, 10000, 50000, 100000};
	int index = count[level - 1];
	
	return ShineStone (index,ShineStoneLevel::LEVEL_PRIMARY);
}

int Monster::getPower() const
{
	int temp = pow(talent, level) + talent * (level + 5);
	return pow(MONSTER_WAILD_BASE, level) + temp;
}

std::string Monster::getSpecies() const
{
	return this->species;
}

bool Monster::operator==(const Monster& monster) 
{
	if (this->species == monster.species &&
		this->level == monster.level) {
		return true;
	}
	return false;
}

std::ostream& operator<<(std::ostream& os, 
	const Monster& monster)
{
	os << monster.level << "级" << monster.species;
	return os;
}

实现类一定要注意在明确声明过的const里不要改变成员变量。然后还有可能读者会疑惑为什么不在构造函数里为rand()设置种子,这是因为srand()的作用范围是在一个进程之内,如果在构造函数里设置种子就会导致创建的妖兽天赋值一样,所以srand()最好放在main函数里。

5. 灵界核心——修仙者类

         完成了灵石类和妖兽类这些资源类以后,就要开始完成灵界的核心——修仙者类了。修仙者本质上也应当有天赋值这一概念,但测试起来意外性太高,作者好几次在测试捕捉妖兽功能时居然意外地被妖兽给击杀了,搞得作者很懊恼,所以为了便于管理,修仙者类我们简单粗暴一点直接由等级战力+灵石加成+妖兽助攻组成,但看过修仙的都懂,修仙者在后期每一个境界的提升都是天壤之别,譬如一个练气期的萌新凭借妖兽辅助和灵石buff能跟一个筑基期的老鸟打得有来有回,但一个大成期的至尊强者一巴掌就能拍扁合体期的下位者。所以应当注意不同级别地修仙者战力生成方式。那么修仙者类Witcher.h的代码如下:

#pragma once
#include <vector>
#include "Monster.h"

#define WITCHER_LEVEL_BASE 15
#define SHINESTONE_LEVEL_BASE 50
#define MONSTER_LEVEL_BASE 0.2

using namespace std;

enum class WitcherLevel {
	LEVEL_LianQi,
	LEVEL_ZhuJi,
	LEVEL_JieDan,
	LEVEL_YuanYing,
	LEVEL_HuaShen,
	LEVEL_LianXu,
	LEVEL_HeTi,
	LEVEL_DaCheng,
	LEVEL_DuJie,
	LEVEL_ALL
};

class Witcher
{
public:
	Witcher(const char* name, const char* nation,
		WitcherLevel level);
	// 挖矿
	void mining();

	// 到市场售卖所有的妖兽
	bool trade();
	// 到市场售卖指定的妖兽
	bool trade(const Monster& monster);
	// 购买其他修仙者的指定妖兽
	bool trade(Witcher& other, const Monster& monsterDest);
	// 用自己的妖兽换别人的妖兽
	bool trade(const Monster& monsterSource, Witcher& other,
		Monster& monsterDest);
	// 用自己的妖兽换别人的灵石
	bool trade(const Monster& monsterSource, Witcher& other);

	// 与妖兽战斗
	bool fight(const Monster& monster);

	// 获取战斗力
	int getPower() const; 

	// 友元声明
	friend ostream& operator<<(ostream& os, 
		const Witcher& witcher);
	friend ostream& operator<<(ostream& os, 
		const WitcherLevel& level);

private:
	string name; // 姓名
	string nation; // 宗门
	WitcherLevel level; // 等级
	vector<ShineStone> stone; // 灵石袋子
	vector<Monster> monster; // 妖兽袋子
	bool alive; // 生死状态:在修|阵亡

	// 是否有指定的妖兽
	bool monster_exist(const Monster monster);

	// 从妖兽袋中移除指定的妖兽
	bool monster_remove(const Monster monster);

	// 捕获妖兽
	bool receive(const Monster& monster);

	// 死亡
	void death();
};

ostream& operator<<(ostream& os, const Witcher& witcher);
ostream& operator<<(ostream& os, const WitcherLevel& level);

Witcher类里依旧保留了一些必要的运算符重载,然后可以看到不同的交易方式之间其实是重载的关系,这样有利于提升模块的内聚程度,否则单独创建五个不同的交易方法太复杂了。当然有些私有函数比如death()、monster_exist()都是供类内实现调用的,所以尽量提前创建定义,然后完成所有其他方法的实现后再完善私有函数的实现。那么Witcher.cpp代码如下:

#include "Witcher.h"

Witcher::Witcher(const char* name, const char* nation, WitcherLevel level)
{
	this->name = name;
	this->nation = nation;
	this->level = level;
	this->alive = true;
	for (int i = 0; i < 3; i++) {
		stone.push_back(ShineStone(0,(ShineStoneLevel)i));
	}
}

void Witcher::mining()
{
	stone[(int)ShineStoneLevel::LEVEL_PRIMARY] = 
		stone[(int)ShineStoneLevel::LEVEL_PRIMARY] + 100;
}

bool Witcher::trade()
{
	if (!alive) {
		return false;
	}

	ShineStone temp;
	for (int i = 0; i < monster.size(); i++) {
		temp = temp + monster[i].getValue();
	}

	stone[(int)ShineStoneLevel::LEVEL_PRIMARY] =
		stone[(int)ShineStoneLevel::LEVEL_PRIMARY] + temp.count;
	monster.erase(monster.begin(), monster.end());
	return true;
}

bool Witcher::trade(const Monster& monster)
{
	if (!alive) {
		return false;
	}

	// 判断当前是否存在妖兽
	if (!monster_exist(monster)) {
		cout << this->name << "没有" << monster << endl;
		return false;
	}

	int index = (int)ShineStoneLevel::LEVEL_PRIMARY;
	stone[index] = stone[index] + monster.getValue();

	// 移除妖兽
	this->monster_remove(monster);

	return true;
}

bool Witcher::trade(Witcher& other, const Monster& monsterDest)
{
	if (!alive || !other.alive) {
		return false;
	}

	// 判断对方是否有妖兽
	if (!other.monster_exist(monsterDest)) {
		cout << other.name << "没有" << monsterDest << endl;
		return false;
	}

	// 判断自己是否有足够的灵石
	int ret = 0;
	for (int i = 0; i < stone.size(); i++) {
		ret += stone[i].count * pow(10, i);
	}
	if (ret < monsterDest.getValue().count) {
		cout << this->name << "灵石不够" << endl;
		return false;
	}

	other.monster_remove(monsterDest);
	this->monster.push_back(monsterDest);

	stone[0].count -= monsterDest.getValue().count;
	other.stone[0].count += monsterDest.getValue().count;

	return true;
}

bool Witcher::trade(const Monster& monsterSource, 
	Witcher& other, Monster& monsterDest)
{
	if (!alive || !other.alive) {
		return false;
	}

	if (!monster_exist(monsterSource)
		|| !other.monster_exist(monsterDest)
		|| monsterSource.getValue().count 
		< monsterDest.getValue().count - 1000) {
		cout << "不满足以兽换兽条件" << endl;
		return false;
	}

	// 开始交换妖兽
	this->monster_remove(monsterSource);
	this->monster.push_back(monsterDest);

	other.monster_remove(monsterDest);
	other.monster.push_back(monsterSource);

	return true;
}

bool Witcher::trade(const Monster& monsterSource, Witcher& other)
{
	if (!alive || !other.alive) {
		return false;
	}

	ShineStone tempStone;
	tempStone = other.stone[0] + other.stone[1] + other.stone[2];
	if (!monster_exist(monsterSource)
		|| monsterSource.getValue().count >
		tempStone.count) {

		cout << "不满足购兽条件" << endl;

		return false;
	}

	// 开始交易
	this->monster_remove(monsterSource);
	this->stone[0] = this->stone[0] + monsterSource.getValue();

	other.monster.push_back(monsterSource);
	other.stone[0].count -= monsterSource.getValue().count;
	return true;

}

bool Witcher::receive(const Monster& monster)
{
	int sta = (rand() % 10) + 1;
	
	if (monster.level <= sta) {
		cout << this->name << "已经成功收服"
			<< monster.species << "!" << endl;
		this->monster.push_back(monster);
		return true;
	}
	else {
		cout << monster.species << "在挣扎中逃跑了" << endl;
		return false;
	}
}

void Witcher::death()
{
	alive = false;
	for (int i = 0; i < stone.size(); i++) {
		stone[i].count = 0;
	}
	monster.erase(monster.begin(), monster.end());
}

bool Witcher::fight(const Monster& monster)
{
	if (!alive) {
		return false;
	}

	string line(100, '-');
	cout << line << endl;
	if (this->getPower() >= monster.getPower()) {
		cout << this->name << "已经成功击败" 
			<< monster.species << "!" << endl;

		receive(monster);
		cout << line << endl;
		return true;
	}
	else {
		cout << this->name << "被" << monster.species
			<< "击杀" << endl;
		cout << line << endl;
		//死亡
		death();
		return false;
	}
}


int Witcher::getPower() const 
{
	// 计算修仙者级别的战斗力
	int ret = pow(WITCHER_LEVEL_BASE, (int)level + 1);

	// 计算灵石buff加成
	for (int i = 0; i < stone.size(); i++) {
		ret += stone[i].count * pow(10, i) 
			/ SHINESTONE_LEVEL_BASE;
	}

	// 计算妖兽辅助加成
	for (int i = 0; i < monster.size(); i++) {
		ret += monster[i].getPower()
			* MONSTER_LEVEL_BASE;
	}

	return ret;
}

bool Witcher::monster_exist(const Monster monster)
{
	for (int i = 0; i < this->monster.size(); i++) {
		if (this->monster[i] == monster) {
			return true;
		}
	}

	return false;
}

bool Witcher::monster_remove(const Monster monster)
{
	// 定义一个迭代器
	vector<Monster>::iterator it = this->monster.begin();
	while (it != this->monster.end()) {
		if (*it == monster) {
			it = this->monster.erase(it);
			return true;
		}
		else {
			it++;
		}
	}

	return false;
}

ostream& operator<<(ostream& os, const Witcher& witcher)
{
	string line(100, '-');
	os << line << endl;
	os << "[修仙者]" << witcher.name
		<< (witcher.alive ? ":在修" : ":阵亡")
		<< "\t门派:" << witcher.nation
		<< "\t级别:" << witcher.level << endl;
	os << "\t资产:" << witcher.stone
		[(int)ShineStoneLevel::LEVEL_PRIMARY]
		<< "、" << witcher.stone
		[(int)ShineStoneLevel::LEVEL_MIDDLE]
		<< "、" << witcher.stone
		[(int)ShineStoneLevel::LEVEL_ADVANCE]
		<< endl << "\t宠物:";

	if (witcher.monster.size() == 0) {
		os << "无";
	}
	else {
		for (int i = 0; i < witcher.monster.size(); i++) {
			os << witcher.monster[i] << " ";
		}
	}
	os << endl << "\t战斗力:" << witcher.getPower()
		<< endl << line;
	
	return os;
}

ostream& operator<<(ostream& os, const WitcherLevel& level)
{
	switch (level) {
	case WitcherLevel::LEVEL_LianQi:
		os << "练气期";
		break;
	case WitcherLevel::LEVEL_ZhuJi:
		os << "筑基期";
		break;
	case WitcherLevel::LEVEL_JieDan:
		os << "结丹期";
		break;
	case WitcherLevel::LEVEL_YuanYing:
		os << "元婴期";
		break;
	case WitcherLevel::LEVEL_HuaShen:
		os << "化神期";
		break;
	case WitcherLevel::LEVEL_LianXu:
		os << "炼虚期";
		break;
	case WitcherLevel::LEVEL_HeTi:
		os << "合体期";
		break;
	case WitcherLevel::LEVEL_DaCheng:
		os << "大成期";
		break;
	case WitcherLevel::LEVEL_DuJie:
		os << "渡劫期";
		break;
	}
	return os;
}

 可以看到Witcher.cpp里的实现代码非常多,不过其实大部分在交易类的重载上,逻辑上也没有特别难以理解的地方,所有这里就留给读者们满慢慢看了。

6. 测试&总结

        至此所有的设定都已实现,那么我们简单写个主函数测试一下,main.cpp代码如下:


#include "ShineStone.h"
#include "Monster.h"
#include "Witcher.h"

using namespace std;

// 修仙者
Witcher Akgry("Akgry", "CSDN宗", WitcherLevel::LEVEL_YuanYing);
Witcher Bilbo("Bilbo", "Java帮", WitcherLevel::LEVEL_YuanYing);

// 妖兽
Monster mon1(1, "紫冰虫");
Monster mon2(1, "阳热鼠");
Monster mon3(1, "海银魔蛇");
Monster mon4(6, "邪神王虎");
Monster mon5(7, "上古战龙");
Monster mon6(2, "程序暗猿");

string line(100, '-');

void testMonster() {

	cout << "妖兽信息" << endl;
	cout << line << endl;
	
	cout << mon1 << " 战力:" << mon1.getPower() << endl;
	cout << mon2 << " 战力:" << mon2.getPower() << endl;
	cout << mon3 << " 战力:" << mon3.getPower() << endl;
	cout << mon4 << " 战力:" << mon4.getPower() << endl;
	cout << mon5 << " 战力:" << mon5.getPower() << endl;
	cout << mon6 << " 战力:" << mon6.getPower() << endl;

	cout << line << endl;
}

void testWitcher() {
	for (int i = 0; i < 30; i++) {
		Bilbo.mining();
	}
	cout << "修仙者信息" << endl;
	cout << Akgry << endl;
	cout << Bilbo << endl;
}

int main(void) {
	// 随机化种子
	srand(time(NULL));

	testMonster();

	testWitcher();

	return 0;
}

 编译运行后可得:

结果也是非常的有趣,这里只是测试了一下基本的设定功能,后面的交易功能太多,这里就不单独测试展示了,读者想要看的话自行复制代码并编辑测试函数即可,总的来说用代码来写小说或游戏的设定确实是一件非常快乐的事情,有时候学习并不总是痛苦的。在热爱的领域里,我们总是能将痛苦变为自律,自律变成习惯。希望各位读者也能够走出自己工作或学习中遇到的困境,像修仙者一样不断地打破自己的桎梏。只有超越极限,才有机会攀至顶峰!—— 绯雨剑星

源码下载连接:https://akgry.lanzouj.com/iVgKj1bsiybe

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员Akgry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值