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