用C语言实现CLI界面的魔塔游戏

article/2025/8/27 19:35:49

简介

本着开源的精神,我分享下我做的数据结构大作业,我当时选择的是游戏设计题目,由于魔塔基础的机制不太复杂,所以就借着大作业设计了个简易的魔塔游戏。
这是游戏界面:
在这里插入图片描述

以下是我当时大作业内容:

我把大作业文档精简下,简单说明下,为了让读者能更好的看懂程序内容。

程序内函数介绍:

(1) main函数用于处理开始界面和调用各种函数
(2) switch_case函数用于返回选择的content_str[]的位置,默认1开始。* case_num:contentstr的数组大小;contentstr[]:选项内容
(3) kbhit_remove函数用于清空按键缓冲区,防止之前的残留按键行为的影响
(4) kbhit_wait_getasc函数用于等待按键并读取按下按键的 ASC码
(5) light_gotoxy函数用于光标移动,x为横坐标,y为纵坐标
(6) light_gotoxy函数用于获取光标位置,x为横坐标,y为纵坐标
(7) maps函数用于判断处于第几层并移交给map_sub函数进行详细处理
(8) map_sub函数用于对相应格子进行具体的输出以及相应的处理,使用平面直角坐标系进行定位
(9) mota_move函数用于处理游戏中每步移动后的情景,1代表上,2代表下,3代表左,4代表右
(10) move_sub函数用于判断相应的楼层并移交给floor_sub函数进行详细处理
(11) floor_sub函数用于根据不同的楼层选择不同的楼层地图,并进行具体的处理
(12) battle函数用于魔塔战斗机制的处理
(13) undefined_1函数用于对话事件的处理
(14) blood_vial函数用于处理碰到血瓶时候恢复的血量
(15) gem_stone函数用于处理碰到宝石时候增加相应的能力值
(16) get_keys函数用于处理碰到钥匙后添加相应的钥匙
(17) open_door函数用于处理开门的事件
(18) cant_door函数用于处理无法打开时,进行的撤回操作
(19) up_down函数用于上下楼的切换并移交给manual_sub函数进行详细处理
(20) monster_manual函数用于判断处于的楼层
(21) manual_sub函数用于怪物手册根据楼层进行具体的处理
(22) monster_sub函数用于怪物手册输出怪物信息
(23) lose_hp函数用于处理丢失血量的算法和显示处理
(24) saving_game函数用于存档功能实现
(25) save_floor函数用于判断处于什么层,并移交给save_sub函数进行详细处理
(26) save_sub函数用于对相应层进行存档的具体处理
(27) loading_game函数用于读档功能实现,读档后因为原来的主角位置会更改,必须对其重新定位
(28) load_floor函数用于判断处于什么层,并移交给load_sub函数进行详细处理
(29) load_sub函数用于对相应层进行读档的具体处理
(30) get_poistion函数用于判断处于什么层,并对每层进行位置搜索
(31) poistion_sub函数用于对每层进行搜索,寻找主角的位置

程序内头文件介绍:

本游戏有3个头文件,actors_info.h用于表示主角的信息,结构体内有15个int变量,结构体别名为actors。创建main_actors结构体变量来指定主角相应的信息。结构体定义和变量定义为:

typedef struct
{int level;int hp;int maxhp;int sp;int maxsp;int str;int dex;int img;int agi;int gold;int exp;int yellow;int blue;int red;int green;
}actors;
actors main_actors={-1,800,5000,-1,-1,1,1,1,-1,0,-1,8,2,1,-1
};

enemy_info.h存放怪物信息变量,通过创建结构体别名enemy,然后创建一个结构体数组enemy_data[]。结构体定义和数组定义为:

typedef struct
{int id;int hp;int str;int dex;int gold;int exp;int pro;char name[20];
}enemy;
enemy enemy_data[AMOUNT]={{0,0,0,0,0,0,0,"保留"},//后面和前面一样也是结构体形式
};

map_info.h存放魔塔里面的地图信息,通过结构体别名,然后一个楼层创建一个结构体矩阵floorXXX[][].里面就两个参数Info是判断对应物体种类,sub是在这个种类之下判断具体种类。结构体定义和矩阵定义如下

typedef struct
{int info;int sub;
}mota;
mota floor001[MAP_L][MAP_L]={{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}},
//后面和前面一样,这样就写出了一行
};

函数调用关系图:

在这里插入图片描述

完整程序如下:

map_info.h(存放地图信息)

//魔塔地图最常见的大小为15*15规模
#define MAP_L 15
//基础倍率*1
int multi=1;
//这里采用结构体,info为物品类型,sub为具体编号(其中0代表不用)
typedef struct
{int info;int sub;
}mota;
/*这里相当于魔塔里面的地图,使用行下标和列下标表示地图位置,里面填的是此格信息
0-地板,1-墙体,2-敌人,3-对话事件,4-血瓶,5-宝石,6-主角,7-钥匙,8-门,9-切换地图,10-其他物品
2敌人编号详情查看enemy.h文件
4血瓶编号:1-红血瓶,2-蓝血瓶,3-黄血瓶,4-绿血瓶
5宝石编号:1-红宝石,2-蓝宝石,3-绿宝石,4-黄宝石
7钥匙编号:1-黄钥匙,2-蓝钥匙,3-红钥匙,4-绿钥匙
8门编号:1-黄门,2-蓝门,3-红门,4-绿门
9切换地图:1-切换前一个地图,2-切换后一个地图
我这里就实现3层的内容,用来代表魔塔游戏核心算法*/
//其实设计魔塔时候,本质就是这样操作的,只不过在RMXP中通过GUI方式通过图层加入到楼层中
/*在RMXP中将生成的地图加入Data文件夹中的MapXXX.rxdata,
对应实现我这里相当于把所有MapXXX.rxdata地图都放在这个头文件中*/
mota floor001[MAP_L][MAP_L]={{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}},{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{4,1},{1,0},{7,1},{7,1},{1,0},{0,0},{5,1},{1,0}},{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{0,0},{8,1},{7,1},{7,1},{8,2},{2,5},{0,0},{1,0}},{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{2,3},{1,0},{0,0},{2,4},{1,0},{0,0},{4,2},{1,0}},{{1,0},{1,0},{1,0},{1,0},{4,1},{0,0},{1,0},{8,1},{1,0},{1,0},{8,1},{1,0},{1,0},{1,0},{1,0}},{{1,0},{5,2},{5,1},{1,0},{0,0},{2,2},{8,1},{0,0},{0,0},{4,1},{0,0},{2,3},{0,0},{9,2},{1,0}},{{1,0},{0,0},{2,4},{1,0},{1,0},{8,1},{1,0},{1,0},{8,1},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}},{{1,0},{1,0},{8,2},{1,0},{0,0},{4,1},{1,0},{1,0},{4,1},{1,0},{1,0},{1,0},{5,1},{7,1},{1,0}},{{1,0},{4,1},{0,0},{1,0},{2,1},{0,0},{1,0},{2,3},{0,0},{1,0},{1,0},{1,0},{0,0},{2,3},{1,0}},{{1,0},{0,0},{2,2},{1,0},{8,1},{1,0},{1,0},{8,1},{1,0},{1,0},{1,0},{1,0},{1,0},{8,1},{1,0}},{{1,0},{1,0},{8,1},{1,0},{0,0},{0,0},{2,1},{0,0},{2,2},{0,0},{0,0},{0,0},{2,2},{0,0},{1,0}},{{1,0},{4,1},{0,0},{2,1},{0,0},{1,0},{1,0},{8,3},{1,0},{1,0},{1,0},{8,1},{1,0},{1,0},{1,0}},{{1,0},{1,0},{1,0},{8,1},{1,0},{1,0},{5,1},{0,0},{5,2},{1,0},{0,0},{2,4},{0,0},{1,0},{1,0}},{{1,0},{7,1},{7,1},{2,2},{1,0},{1,0},{1,0},{0,0},{1,0},{1,0},{5,2},{0,0},{7,2},{1,0},{1,0}},{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}}};
mota floor002[MAP_L][MAP_L]={{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}},{{1,0},{0,0},{2,4},{8,1},{0,0},{7,1},{0,0},{1,0},{5,1},{0,0},{1,0},{0,0},{4,1},{0,0},{1,0}},{{1,0},{9,2},{0,0},{1,0},{4,1},{0,0},{2,3},{1,0},{0,0},{2,5},{1,0},{7,1},{0,0},{7,1},{1,0}},{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{8,1},{1,0},{1,0},{8,1},{1,0},{0,0},{2,5},{0,0},{1,0}},{{1,0},{0,0},{2,4},{8,1},{0,0},{0,0},{2,4},{0,0},{0,0},{0,0},{1,0},{1,0},{8,1},{1,0},{1,0}},{{1,0},{7,2},{0,0},{1,0},{2,3},{1,0},{1,0},{1,0},{1,0},{0,0},{7,1},{7,1},{0,0},{9,1},{1,0}},{{1,0},{1,0},{1,0},{1,0},{7,1},{1,0},{5,2},{2,3},{5,1},{1,0},{1,0},{1,0},{8,1},{1,0},{1,0}},{{1,0},{1,0},{1,0},{1,0},{2,2},{1,0},{1,0},{8,1},{1,0},{0,0},{0,0},{0,0},{2,3},{4,1},{1,0}},{{1,0},{1,0},{1,0},{1,0},{0,0},{0,0},{2,3},{4,1},{2,3},{0,0},{1,0},{1,0},{8,1},{1,0},{1,0}},{{1,0},{1,0},{1,0},{1,0},{8,2},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{2,4},{0,0},{1,0}},{{1,0},{0,0},{0,0},{2,6},{2,6},{2,6},{0,0},{0,0},{1,0},{1,0},{1,0},{1,0},{0,0},{4,1},{1,0}},{{1,0},{4,1},{1,0},{0,0},{0,0},{0,0},{1,0},{4,1},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}},{{1,0},{0,0},{1,0},{1,0},{1,0},{1,0},{1,0},{0,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}},{{1,0},{7,1},{0,0},{5,1},{1,0},{5,2},{0,0},{7,1},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}},{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}}
};
mota floor003[MAP_L][MAP_L]={{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}},{{1,0},{0,0},{7,1},{1,0},{0,0},{5,1},{1,0},{0,0},{4,2},{1,0},{5,1},{0,0},{5,2},{1,0},{1,0}},{{1,0},{9,1},{0,0},{8,1},{2,3},{0,0},{8,1},{2,5},{0,0},{1,0},{0,0},{2,6},{0,0},{1,0},{1,0}},{{1,0},{1,0},{1,0},{1,0},{8,1},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{8,1},{1,0},{1,0},{1,0}},{{1,0},{2,5},{8,1},{0,0},{2,3},{0,0},{0,0},{2,6},{0,0},{2,6},{0,0},{0,0},{4,1},{1,0},{1,0}},{{1,0},{7,1},{1,0},{8,1},{1,0},{8,2},{1,0},{1,0},{1,0},{1,0},{1,0},{8,1},{1,0},{1,0},{1,0}},{{1,0},{7,1},{1,0},{2,7},{0,0},{2,3},{1,0},{1,0},{1,0},{1,0},{4,1},{2,4},{7,1},{1,0},{1,0}},{{1,0},{7,1},{1,0},{1,0},{8,1},{1,0},{1,0},{1,0},{1,0},{1,0},{2,4},{0,0},{2,4},{1,0},{1,0}},{{1,0},{1,0},{1,0},{0,0},{2,4},{0,0},{1,0},{0,0},{7,1},{1,0},{1,0},{0,0},{1,0},{1,0},{1,0}},{{1,0},{1,0},{1,0},{2,7},{1,0},{2,3},{8,1},{2,6},{0,0},{1,0},{4,1},{2,7},{7,1},{1,0},{1,0}},{{1,0},{1,0},{1,0},{5,1},{2,7},{0,0},{1,0},{0,0},{4,1},{1,0},{2,7},{0,0},{2,7},{1,0},{1,0}},{{1,0},{1,0},{1,0},{1,0},{8,2},{1,0},{1,0},{1,0},{8,1},{1,0},{1,0},{0,0},{1,0},{1,0},{1,0}},{{1,0},{1,0},{1,0},{9,2},{2,4},{4,1},{8,1},{0,0},{2,4},{1,0},{0,0},{4,1},{0,0},{1,0},{1,0}},{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{4,1},{0,0},{1,0},{7,1},{0,0},{7,2},{1,0},{1,0}},{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}}
};
//这个是以防前面出错,可以方便将基本结构的备份进行复制
/*
mota floor_backup[MAP_L][MAP_L]={{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}},{{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}}
};
*/

enemy_info.h(存放怪物数据信息)

//有几个怪物就写几个编号+1,使用define定义后续修改很方便
#define AMOUNT 8
/*怪物相应编号:1-绿头怪,2-红头怪,3-蓝蝙蝠,4-蓝法师,5-骷髅人
6-黑头怪,7-大蝙蝠*/
//怪物相应元素有编号,生命,攻击,防御,金钱,经验,属性(例如魔攻,坚固,夹击等)
typedef struct
{int id;int hp;int str;int dex;int gold;int exp;int pro;char name[20];
}enemy;
//0号为保留位
enemy enemy_data[AMOUNT]={{0,0,0,0,0,0,0,"保留"},{1,20,2,1,1,1,0,"绿头怪"},{2,30,3,0,1,1,0,"红头怪"},{3,45,5,1,3,1,0,"蓝蝙蝠"},{4,50,4,2,3,1,0,"蓝法师"},{5,60,8,2,4,1,0,"骷髅人"},{6,60,6,4,4,1,0,"黑头怪"},{7,65,10,4,6,1,0,"大蝙蝠"}
};

actors_info.h(存放主角信息)

/*主角相应元素有等级,生命,最大生命,魔法值,最大魔法值,攻击力,防御力,护盾,攻速,
金钱,经验,黄蓝红绿钥匙数量。在本游戏中用不到等级,经验,魔法值,攻速和绿钥匙(在规
模较大并且机制较复杂的蓝海魔塔中这些属性就都会用到,甚至还要自己添加别的属性)*/
//其实钥匙应该写在另一个结构体,为了简化操作合并在一起了,当然可扩展性相应也变差了
//start变量是游戏是否开始的开关
int start=0;
typedef struct
{int level;int hp;int maxhp;int sp;int maxsp;int str;int dex;int img;int agi;int gold;int exp;int yellow;int blue;int red;int green;
}actors;
//-1代表用不到
actors main_actors={-1,800,5000,-1,-1,1,1,1,-1,0,-1,8,2,1,-1
};

main.cpp(游戏主程序)

#include <bits/stdc++.h>
#include <conio.h>
#include <windows.h>
#include "enemy_info.h"
#include "map_info.h"
#include "actors_info.h"
//设置怪物数量目前最大到1000,地图大小为15*15,单层怪物数量为20
//设置当前楼层数,当前为3
#define MAP_L 15
#define MAX_M 50
#define ENEMY_MAX 1000
#define CURRENT_FLOOR 3
//键盘上下左右健其实是一个双字符,第一个字符固定为-32,第二个字符代表相应的操作
//这里采用16进制表示
#define UP 0X48        //上键第二个字符是72
#define LEFT 0X4B    //左键第二个字符是75
#define RIGHT 0X4D    //右键第二个字符是77
#define DOWN 0X50    //下键第二个字符是80
//没有钥匙时候返回值
#define NO_YELLOW -6
#define NO_BLUE -7
#define NO_RED -8
#define NO_GREEN -9
using namespace std;
//对相应格子进行具体的输出以及相应的处理,使用平面直角坐标系进行定位
void map_sub(mota floor[][MAP_L],int x,int y)
{switch(floor[x][y].info){//地板使用空格表示case 0:cout<<setw(3)<<" ";break;//墙壁使用*表示case 1:cout<<setw(3)<<"*";break;//具体怪物直接使用相应数字编号来表示case 2:for(int i=1;i<ENEMY_MAX;i++){if(i==floor[x][y].sub){cout<<setw(3)<<floor[x][y].sub;break;}}break;//对话事件就暂时用一个图标代替case 3:cout<<setw(3)<<"♂";break;//红血瓶使用□表示,蓝血瓶使用☆表示,黄血瓶使用*表示,绿血瓶使用√表示case 4:switch(floor[x][y].sub){case 1:cout<<setw(3)<<"□";break;case 2:cout<<setw(3)<<"☆";break;case 3:cout<<setw(3)<<"*";break;case 4:cout<<setw(3)<<"√";break;}break;//红宝石使用■表示,蓝宝石使用★表示,绿宝石使用§表示,黄宝石使用◎表示case 5:switch(floor[x][y].sub){case 1:cout<<setw(3)<<"■";break;case 2:cout<<setw(3)<<"★";break;case 3:cout<<setw(3)<<"§";break;case 4:cout<<setw(3)<<"◎";break;}break;//主角直接使用‘主’字来表示case 6:cout<<setw(3)<<"主";break;//黄钥匙使用△表示,蓝钥匙使用▽表示,红钥匙使用○表示,绿钥匙使用◇表示case 7:switch(floor[x][y].sub){case 1:cout<<setw(3)<<"△";break;case 2:cout<<setw(3)<<"▽";break;case 3:cout<<setw(3)<<"○";break;case 4:cout<<setw(3)<<"◇";break;}break;//黄门使用▲表示,蓝门使用▼表示,红门使用●表示,绿门使用◆表示case 8:switch(floor[x][y].sub){case 1:cout<<setw(3)<<"▲";break;case 2:cout<<setw(3)<<"▼";break;case 3:cout<<setw(3)<<"●";break;case 4:cout<<setw(3)<<"◆";break;}break;//传送到上个地图用←表示,传送到下个地图用→表示case 9:switch(floor[x][y].sub){case 1:cout<<setw(3)<<"←";break;case 2:cout<<setw(3)<<"→";break;}break;//由于这个3层内没有其余物品,暂时用&代替case 10:cout<<setw(3)<<"&";break;}
}//判断处于第几层并移交给map_sub进行详细处理
int maps(int floor)
{for(int i=0;i<MAP_L;i++){for(int j=0;j<MAP_L;j++){switch(floor){case 1:map_sub(floor001,i,j);break;case 2:map_sub(floor002,i,j);break;case 3:map_sub(floor003,i,j);break;}}printf("\n");}//显示出当前面板信息,这里一会要变量一会要字符的情况下,本人认为printf比cout方便printf("\n生命:%d,最大生命:%d,力量:%d,灵巧:%d,护盾:%d\n",main_actors.hp,main_actors.maxhp,main_actors.str,main_actors.dex,main_actors.img);printf("黄钥匙:%d,蓝钥匙:%d,红钥匙:%d\n",main_actors.yellow,main_actors.blue,main_actors.red);return floor;
}//处理游戏说明模块,0代表直接全是全部内容,其他值代表按一次出现一部分
void description(int choose)
{if(choose==0){cout<<"本程序是根据RMXP里面我制作的,然后用C从0开始对此魔塔系统核心进行实现"<<endl;cout<<"所以从某种意义上来说,这个main.cpp程序并不能称作为一个游戏"<<endl;cout<<"好了,接下来我说明一下程序内出现的图标对应的含义"<<endl;cout<<endl;cout<<"首先地板模块也就是空格,墙就用*来进行代表"<<endl;cout<<endl;cout<<"然后怪物,由于真正的游戏里面怪物种类众多,而C++的CLI支持的图案字符很有限,"<<endl;cout<<"所以为了清晰,我直接使用数字编号来进行代表"<<endl;cout<<endl;cout<<"对话事件同一使用♂来进行表示"<<endl;cout<<endl;cout<<"红血瓶使用□表示,蓝血瓶使用☆表示,黄血瓶使用*表示,绿血瓶使用√表示"<<endl;cout<<"红宝石使用■表示,蓝宝石使用★表示,绿宝石使用§表示,黄宝石使用◎表示"<<endl;cout<<endl;cout<<"玩家控制的主角直接使用‘主’字符表示,这样清晰明了容易知道你现在在哪"<<endl;cout<<endl;cout<<"黄钥匙使用△表示,蓝钥匙使用▽表示,红钥匙使用○表示,绿钥匙使用◇表示"<<endl;cout<<"黄门使用▲表示,蓝门使用▼表示,红门使用●表示,绿门使用◆表示"<<endl;cout<<endl;cout<<"传送到上个地图用←表示,传送到下个地图用→表示"<<endl;cout<<"最后其他物品在前三层并没有,用了&表示,实际上这个程序根本没用到"<<endl;cout<<endl;cout<<"接下来,我来简单的说明魔塔的游戏玩法"<<endl;cout<<endl;cout<<"首先,黄红蓝绿钥匙分别用来开对应颜色的门,开门之后钥匙会相应的减少"<<endl;cout<<"最常见的是开一个门钥匙减少一个,当然还有其他的例如开某种门减3个"<<endl;cout<<endl;cout<<"第二,不同血瓶颜色对应恢复不同的血量,在本游戏开始部分红血瓶+75hp,"<<endl;cout<<"蓝血瓶+200hp,黄血瓶+500hp,绿血瓶+1000hp,到后面血瓶增加血量会增加"<<endl;cout<<endl;cout<<"第三,不同宝石对应加的能力不一样,在本游戏开始部分红宝石+1攻击,"<<endl;cout<<"蓝宝石+1防御,绿宝石+1护盾,黄宝石攻防*1.1,后面增加数值效果会增加"<<endl;cout<<endl;cout<<"第四,绿海/红海塔基本战斗机制为主角损失血量=敌人血量/(主角攻击-敌人防御)"<<endl;cout<<"*(敌人攻击-主角防御)-主角护盾,其中主角攻击小于敌方防御的时候"<<endl;cout<<",直接算主角死亡当敌人攻击小于主角防御的时候,主角即不受到伤害,当然这套"<<endl;cout<<"公式是在没有算上其他属性和机制的情况下,但是其他机制也是围绕这个核心的"<<endl;cout<<endl;cout<<"第五,说明一下魔塔是一个固定数值的RPG游戏,所以不存在重复刷怪的情况,"<<endl;cout<<"也就是说结果不存在任何的随机性,你的拆塔思路决定游戏中出现的结果"<<endl;cout<<endl;cout<<"最后说明下快捷键,按'X'键查看怪物手册,按'S'键进行存档,按'L'键进行读档"<<endl;cout<<"通过上下左右键控制角色的移动"<<endl;cout<<endl;cout<<"具体的基本机制了解可以参考“操作说明及所需软件\\魔塔样板7630•改”"<<endl;cout<<"文件夹中的Game.exe文件(太阳图标)进行了解"<<endl;cout<<endl;getch();}else{cout<<"本程序是根据RMXP里面我制作的,然后用C从0开始对此魔塔系统核心进行实现"<<endl;cout<<"所以从某种意义上来说,这个main.cpp程序并不能称作为一个游戏"<<endl;cout<<"好了,接下来我说明一下程序内出现的图标对应的含义"<<endl;cout<<endl;getch();cout<<"首先地板模块也就是空格,墙就用*来进行代表"<<endl;cout<<endl;getch();cout<<"然后怪物,由于真正的游戏里面怪物种类众多,而C++的CLI支持的图案字符很有限,"<<endl;cout<<"所以为了清晰,我直接使用数字编号来进行代表"<<endl;cout<<endl;getch();cout<<"对话事件同一使用♂来进行表示"<<endl;cout<<endl;getch();cout<<"红血瓶使用□表示,蓝血瓶使用☆表示,黄血瓶使用*表示,绿血瓶使用√表示"<<endl;cout<<"红宝石使用■表示,蓝宝石使用★表示,绿宝石使用§表示,黄宝石使用◎表示"<<endl;cout<<endl;getch();cout<<"玩家控制的主角直接使用‘主’字符表示,这样清晰明了容易知道你现在在哪"<<endl;cout<<endl;getch();cout<<"黄钥匙使用△表示,蓝钥匙使用▽表示,红钥匙使用○表示,绿钥匙使用◇表示"<<endl;cout<<"黄门使用▲表示,蓝门使用▼表示,红门使用●表示,绿门使用◆表示"<<endl;cout<<endl;getch();cout<<"传送到上个地图用←表示,传送到下个地图用→表示"<<endl;cout<<"最后其他物品在前三层并没有,用了&表示,实际上这个程序根本没用到"<<endl;cout<<endl;getch();cout<<"接下来,我来简单的说明魔塔的游戏玩法"<<endl;cout<<endl;getch();cout<<"首先,黄红蓝绿钥匙分别用来开对应颜色的门,开门之后钥匙会相应的减少"<<endl;cout<<"最常见的是开一个门钥匙减少一个,当然还有其他的例如开某种门减3个"<<endl;cout<<endl;getch();cout<<"第二,不同血瓶颜色对应恢复不同的血量,在本游戏开始部分红血瓶+75hp,"<<endl;cout<<"蓝血瓶+200hp,黄血瓶+500hp,绿血瓶+1000hp,到后面血瓶增加血量会增加"<<endl;cout<<endl;getch();cout<<"第三,不同宝石对应加的能力不一样,在本游戏开始部分红宝石+1攻击,"<<endl;cout<<"蓝宝石+1防御,绿宝石+100体力上限,黄宝石攻防*1.1,后面数值效果会增加"<<endl;cout<<endl;getch();cout<<"第四,绿海/红海塔基本战斗机制为主角损失血量=敌人血量/(主角攻击-敌人防御)"<<endl;cout<<"*(敌人攻击-主角防御)-主角护盾,其中主角攻击小于敌方防御的时候"<<endl;cout<<",直接算主角死亡;当敌人攻击小于主角防御的时候,主角即不受到伤害,当然这套"<<endl;cout<<"公式是在没有算上其他属性和机制的情况下,但是其他机制也是围绕这个核心的"<<endl;cout<<endl;getch();cout<<"第五,说明一下魔塔是一个固定数值的RPG游戏,所以不存在重复刷怪的情况,"<<endl;cout<<"也就是说结果不存在任何的随机性,你的拆塔思路决定游戏中出现的结果"<<endl;cout<<endl;getch();cout<<"最后说明下快捷键,按'X'键查看怪物手册,按'S'键进行存档,按'L'键进行读档"<<endl;cout<<"通过上下左右键控制角色的移动"<<endl;cout<<endl;getch();cout<<"具体的基本机制了解可以参考“操作说明及所需软件\\魔塔样板7630•改”"<<endl;cout<<"文件夹中的Game.exe文件(太阳图标)进行了解"<<endl;cout<<endl;getch();}
}//处理光标选定的函数非本人所写,摘自https://www.cnblogs.com/coolight7/p/14792049.html
namespace coolfun
{//清空按键缓冲区,防止之前的残留按键行为的影响void kbhit_remove(){while (_kbhit())_getch();}//等待按键并读取按下按键的 ASC码int kbhit_wait_getasc(){int num;do{num = _getch();} while (_kbhit());return num;}/*光标移动函数
* x为横坐标,y为纵坐标
*/inline void light_gotoxy(int x, int y){COORD pos = { (short)x,(short)y };HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleCursorPosition(hOut, pos);}/*获取光标位置* 同时获取xy,需要传入接受的xy变量引用* x为横坐标,y为纵坐标*/inline void light_getxy(int& x, int& y){HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_SCREEN_BUFFER_INFO csbi;GetConsoleScreenBufferInfo(hConsole, &csbi);x = csbi.dwCursorPosition.X;y = csbi.dwCursorPosition.Y;}/*选择函数
* 返回选择的content_str[]的位置,默认1开始
* case_num:contentstr的数组大小;contentstr[]:选项内容
* >支持偏移<
*/template<typename T>int switch_case(int case_num, const T* content_str){int nowi = 1, nowx, nowy;//nowi 记录选择的选项编号coolfun::light_getxy(nowx, nowy);coolfun::light_gotoxy(nowx, nowy + 1);const T* p = content_str;cout << ">>> " << *(p++);for (int i = 2; i <= case_num; ++i)  //先打印选项内容{coolfun::light_gotoxy(nowx, nowy + i);cout << "    " << *(p++);}coolfun::light_gotoxy(nowx, nowy + case_num + 1);coolfun::kbhit_remove();//清除残留按键for (int getnum;;) //等待按键按下,getnum记录按键asc{getnum = coolfun::kbhit_wait_getasc();coolfun::light_gotoxy(nowx, nowy + nowi); //移动到原来的编号选项前cout << "   ";  //覆盖掉它的">>>"switch (getnum) //获取按下的按键的ask2值{case 72: //上{if (nowi > 1)   //在第一个再按上键会到最后一个--nowi;elsenowi = case_num;}break;case 80: //下{if (nowi < case_num)  //在最后一个按下键会到第一个++nowi;elsenowi = 1;}break;case 13:  //Enter键确定{coolfun::light_gotoxy(nowx, nowy + nowi);cout << "-->";coolfun::light_gotoxy(nowx, nowy + case_num + 1);return nowi;  //返回当前选项编号}break;}coolfun::light_gotoxy(nowx, nowy + nowi);//移动到修改后的位置cout << ">>>";coolfun::light_gotoxy(nowx, nowy + case_num + 1); //把光标移动回输出的最后}return 0;}}//魔塔战斗机制的描述
void battle(int id)
{/*这里相当于将地图表和对应战斗怪物表结合了起来,这个实现操作就是数据库join语句,类似于select * from floorXXX join enemy_data on floorXXX.sub=enemy_data.id*///敌人攻击<=主角防御if(enemy_data[id].str<=main_actors.dex){main_actors.hp-=0;}//主角攻击<=敌人防御else if(main_actors.str<=enemy_data[id].dex){main_actors.hp-=pow(2,31);}//正常情况下,主角血量-=敌人血量/(主角攻击-敌人防御)*(敌人攻击-主角防御)-主角护盾//但是要注意如果正好整除情况下,也就是你最后一下把对面击败,这回合并不会扣血else{//正好整除的情况下if(enemy_data[id].hp%(main_actors.str-enemy_data[id].dex)==0){//魔塔基本机制里面不存在负数伤害,所以小于0时也就不扣血if((enemy_data[id].hp/(main_actors.str-enemy_data[id].dex)-1)*(enemy_data[id].str-main_actors.dex)-main_actors.img<0){main_actors.hp-=0;goto a;}main_actors.hp-=(enemy_data[id].hp/(main_actors.str-enemy_data[id].dex)-1)*(enemy_data[id].str-main_actors.dex)-main_actors.img;}//其他情况下else{if(enemy_data[id].hp/(main_actors.str-enemy_data[id].dex)*(enemy_data[id].str-main_actors.dex)-main_actors.img<0){main_actors.hp-=0;goto a;}main_actors.hp-=enemy_data[id].hp/(main_actors.str-enemy_data[id].dex)*(enemy_data[id].str-main_actors.dex)-main_actors.img;}}a://当生命值不为正数时候游戏失败if(main_actors.hp<=0){//设置字体为红色SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED);system("cls");cout<<endl;cout<<setw(30)<<"你被怪物打死了!你输了!!!"<<endl;cout<<setw(21)<<"真的是太逊了!!!"<<endl;exit(0);}
}//对话事件的处理
void undefined_1(int id)
{switch(id){case 1:cout<<"这是第一个对话事件";break;case 2:cout<<"这是第二个对话事件";break;case 3:cout<<"测试使用";break;/*case XXX:相应的事件处理;break;不过我这个并非是一个完美的处理方法,在RMXP本质上是通过NPC编号和这个角色第几个对话框进行绑定的.此方法如果事件不超过1万个问题不大,也就是说小规模塔用这种方法没问题,大规模的容易导致游戏卡.此功能实现繁琐而且时间有限就算了*/}
}//处理碰到血瓶时候恢复的血量
void blood_vial(int id)
{/*这里采用的是恢复血量*倍数,方便之后血瓶效果的变化,例如打了10层后血瓶恢复血量翻倍等,这也是魔塔样板自带可通过GUI进行的设置,这样提高了程序的可扩展性*/switch(id){case 1:main_actors.hp+=75*multi;break;case 2:main_actors.hp+=200*multi;break;case 3:main_actors.hp+=500*multi;break;case 4:main_actors.hp+=1000*multi;break;}//由于这个魔塔我设置了体力上限,所以当增加血量超过了体力上限,要重置血量为体力上限if(main_actors.hp>main_actors.maxhp){main_actors.hp=main_actors.maxhp;}
}//处理碰到宝石时候增加相应的能力值
void gem_stone(int id)
{//和处理血瓶一样要考虑到可扩展性switch(id){case 1:main_actors.str+=1*multi;break;case 2:main_actors.dex+=1*multi;break;case 3:main_actors.maxhp+=100*multi;break;case 4:main_actors.str*=1.1;main_actors.dex*=1.1;break;}
}//处理碰到钥匙后添加相应的钥匙
void get_keys(int id)
{/*获得钥匙则相应钥匙+1,后期有钥匙串要额外写成新的事件,这个没法用倍率表示,因为不是一个恒定的变量*/switch(id){case 1:main_actors.yellow+=1;break;case 2:main_actors.blue+=1;break;case 3:main_actors.red+=1;break;case 4:main_actors.green+=1;break;}
}//处理无法打开时,进行的撤回操作
void cant_door(int *action,int *x,int *y)
{switch(*action){case 1:*x+=1;break;case 2:*x-=1;break;case 3:*y+=1;break;case 4:*y-=1;break;}
}//处理开门的事件
int open_door(int id,int *action,int *x,int *y)
{/*这个也是基本的4类门,如果需要多个钥匙的,需要重新写事件.这里和前面不同的是,如果钥匙不够就和墙一样无法通行,也就是和碰墙一样做相应的撤回*/switch(id){case 1://返回相应的没有钥匙的值if(main_actors.yellow<=0){cant_door(action,x,y);return NO_YELLOW;}else{main_actors.yellow-=1;}break;case 2:if(main_actors.blue<=0){cant_door(action,x,y);return NO_BLUE;}else{main_actors.blue-=1;}break;case 3:if(main_actors.red<=0){cant_door(action,x,y);return NO_RED;}else{main_actors.red-=1;}break;case 4:if(main_actors.green<=0){cant_door(action,x,y);return NO_GREEN;}else{main_actors.green-=1;}break;}return 1;
}//上下楼的切换
int up_down(int floor,int id)
{//判断当前在几楼switch(floor){case 1://判断是上楼还是下楼(切换上个地图还是下个地图)switch(id){case 2:floor+=1;floor002[5][12].info=6;floor002[5][12].sub=0;break;}break;case 2:switch(id){case 1:floor-=1;floor001[5][12].info=6;floor001[5][12].sub=0;break;case 2:floor+=1;floor003[2][2].info=6;floor003[2][2].sub=0;break;}break;case 3:switch(id){case 1:floor-=1;floor002[2][2].info=6;floor002[2][2].sub=0;break;case 2://设置字体为绿色SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN);system("cls");cout<<endl;cout<<"CLI界面的测试版核心功能结束!"<<endl;cout<<"恭喜你通关了,真正的游戏GUI界面体验请打开游戏文件(RMXP工具实现)"<<endl;cout<<"文件夹中的Game.exe(太阳图标)"<<endl;exit(0);}break;}return floor;
}//刷新第1-10层传送事件
void updown_refresh01()
{floor001[5][13].info=9;floor001[5][13].sub=2;floor002[5][13].info=9;floor002[5][13].sub=1;floor002[2][1].info=9;floor002[2][1].sub=2;floor003[2][1].info=9;floor003[2][1].sub=1;floor003[12][3].info=9;floor003[12][3].sub=2;
}/*备注:其实在JS,Py等语言里面使用嵌套定义函数是很容易的事情,但是C/C++函数不允许嵌套定义,
只能通过地址传值的方法来进行传递,注意点就是小心使用指针层级关系*/
//根据不同的楼层选择不同的楼层地图
int floor_sub(int *action,int *x,int *y,int f,mota floor[][MAP_L])
{int tips=0;//将原来主角的位置置为0,前提是没有碰到墙if(floor[*x][*y].info!=1){switch(*action){case 1://将原来是主角的点重新变为地板floor[*x+1][*y].info=0;floor[*x+1][*y].sub=0;break;case 2:floor[*x-1][*y].info=0;floor[*x-1][*y].sub=0;break;case 3:floor[*x][*y+1].info=0;floor[*x][*y+1].sub=0;break;case 4:floor[*x][*y-1].info=0;floor[*x][*y-1].sub=0;break;}}//如果行走中碰到墙也就是不走动,相当于对原本走动情况下做相反的动作switch(floor[*x][*y].info){case 1:switch(*action){case 1:*x+=1;break;case 2:*x-=1;break;case 3:*y+=1;break;case 4:*y-=1;break;}break;//当主角碰到怪物时候的战斗处理case 2:battle(floor[*x][*y].sub);break;//对话事件处理case 3://由于前三层不涉及对话人物推进剧情,此函数不会被执行到undefined_1(floor[*x][*y].sub);break;//血瓶处理case 4:blood_vial(floor[*x][*y].sub);break;//宝石处理case 5:gem_stone(floor[*x][*y].sub);break;//钥匙处理case 7:get_keys(floor[*x][*y].sub);break;//开门处理case 8://这里必须要传送动作和坐标,门无法打开时撤回操作需要对动作和坐标进行判断tips=open_door(floor[*x][*y].sub,action,x,y);break;//切换地图处理case 9:/*这里直接手工指定每个楼层直接定位就行,使用for循环遍历只有使得时间复杂度提高,规模一大很容易卡顿*/f=up_down(f,floor[*x][*y].sub);}system("cls");//新的点作为主角所在点,然后重新刷新地图floor[*x][*y].info=6;floor[*x][*y].sub=0;//每10层上下楼刷新图标,这样可以有效预防规模大的时候卡顿(虽然这里用不到)//而且这里不适合if..else if结构,因为这样会增加寻找次数switch(f/10){case 0:updown_refresh01();break;}maps(f);//其实在游戏里面前5个战斗层都为入学测试,这里为了方便查看楼层是否切换,表示为第几层printf("当前你所在楼层为:%d层\n",f);/*其实魔塔里面钥匙不够并不会提示,因为GUI界面很清晰的看到门颜色,无需提示,但CLI界面看着不清晰所以我额外增加了提示*/switch(tips){case NO_YELLOW:printf("\n你的黄钥匙不够,无法开门\n");break;case NO_BLUE:printf("\n你的蓝钥匙不够,无法开门\n");break;case NO_RED:printf("\n你的红钥匙不够,无法开门\n");break;case NO_GREEN:printf("\n你的绿钥匙不够,无法开门\n");break;}return f;
}//再次将指针传递给move_sub函数处理楼层
int move_sub(int action,int *x,int *y,int floor)
{switch(floor){case 1:floor=floor_sub(&action,x,y,floor,floor001);break;case 2:floor=floor_sub(&action,x,y,floor,floor002);break;case 3:floor=floor_sub(&action,x,y,floor,floor003);break;}return floor;
}//处理游戏中每步移动后的情景
//其中参数action表示移动结果,1代表上,2代表下,3代表左,4代表右
int mota_move(int action,int *x,int *y,int floor)
{switch(action){//处理按键盘上键触发的事件case 1:*x-=1;floor=move_sub(1,x,y,floor);break;//处理按键盘下键触发的事件case 2:*x+=1;floor=move_sub(2,x,y,floor);break;//处理按键盘左键触发的事件case 3:*y-=1;floor=move_sub(3,x,y,floor);break;//处理按键盘右键触发的事件case 4:*y+=1;floor=move_sub(4,x,y,floor);break;}return floor;
}//丢失血量其实和战斗过程处理方法类似
string lose_hp(int id)
{int tmp;if(enemy_data[id].str<=main_actors.dex){return "0";}//主角攻击<=敌人防御else if(main_actors.str<=enemy_data[id].dex){return "????";}//正常情况下,主角血量-=敌人血量/(主角攻击-敌人防御)*(敌人攻击-主角防御)-主角护盾//但是要注意如果正好整除情况下,也就是你最后一下把对面击败,这回合并不会扣血else{//正好整除的情况下if(enemy_data[id].hp%(main_actors.str-enemy_data[id].dex)==0){//魔塔基本机制里面不存在负数伤害,所以小于0时也就不扣血if((enemy_data[id].hp/(main_actors.str-enemy_data[id].dex)-1)*(enemy_data[id].str-main_actors.dex)-main_actors.img<0){return "0";}tmp=(enemy_data[id].hp/(main_actors.str-enemy_data[id].dex)-1)*(enemy_data[id].str-main_actors.dex)-main_actors.img;return to_string(tmp);}//其他情况下else{if(enemy_data[id].hp/(main_actors.str-enemy_data[id].dex)*(enemy_data[id].str-main_actors.dex)-main_actors.img<0){return "0";}tmp=main_actors.hp-=enemy_data[id].hp/(main_actors.str-enemy_data[id].dex)*(enemy_data[id].str-main_actors.dex)-main_actors.img;return to_string(tmp);}}
}//将所有可能出现的怪物用switch...case的方法列出来
void monster_sub(int id)
{cout<<enemy_data[id].name<<"\t\t编号:"<<id<<",生命:"<<enemy_data[id].hp<<endl;cout<<"攻击:"<<enemy_data[id].str<<",防御:"<<enemy_data[id].dex<<",金币:"<<enemy_data[id].gold<<",经验:"<<enemy_data[id].exp<<",伤害:"<<lose_hp(id)<<endl;cout<<endl;
}//怪物手册根据楼层进行具体的处理
int manual_sub(int f,mota floor[][MAP_L])
{//通常一个楼层里面怪物数量不超过50;int item[MAX_M],count=0;/*这里看似3层循环,但是因为一般楼层地图都是15*15,然后怪物数量一般为10以下,循环执行次数所以一般不超过2000次*/for(int i=0;i<MAP_L;i++){for(int j=0;j<MAP_L;j++){//地图上的点,查看是否有相应相应类型的怪物,加入到item中if(floor[i][j].info==2){item[count]=floor[i][j].sub;count++;}}}//排序并且进行去重操作sort(item,item+count);int n=unique(item,item+count)-item;system("cls");//遍历本层出现的怪物,进行查看for(int i=0;i<n;i++){monster_sub(item[i]);}char ch=getch();switch(ch){case 'x':    case 'X':system("cls");maps(f);printf("当前你所在楼层为:%d层\n",f);break;}return f;
}
//怪物手册处理
int monster_manual(int floor)
{switch(floor){case 1:floor=manual_sub(floor,floor001);break;case 2:floor=manual_sub(floor,floor002);break;case 3:floor=manual_sub(floor,floor003);break;}return floor;
}//对相应层进行存档的具体处理
void save_sub(mota floor[][MAP_L],FILE *fp)
{//所谓的存档就是将当前地图信息保存下来for(int i=0;i<MAP_L;i++){for(int j=0;j<MAP_L;j++){fprintf(fp,"%d,%d ",floor[i][j].info,floor[i][j].sub);}fprintf(fp,"\n");}
}//判断处于什么层,并对每层进行存档
void save_floor(int floor,FILE *fp)
{switch(floor){case 1:save_sub(floor001,fp);break;case 2:save_sub(floor002,fp);break;case 3:save_sub(floor003,fp);break;}
}//存档功能实现
void saving_game(int floor)
{int n;char save[4],mosa[16]="motaSave",txt[5]=".txt";FILE *fp;system("cls");cout<<"你需要存到第几个位置?(1-100):";cin>>n;if(n<1 || n>100){cout<<"没有相应的位置,无法存档,按任意键返回游戏"<<endl;system("pause");}else{//数字转换为字符串后,然后与mosa连接,之后mosa再和后缀名连接itoa(n,save,10);strcat(mosa,save);fp=fopen(strcat(mosa,txt),"w");if(fp==NULL){cout<<"当前文件夹没有写入权限,请开启文件夹的写权限!"<<endl;}for(int i=1;i<=CURRENT_FLOOR;i++){save_floor(i,fp);}//不要忘记保存主角当前状态,因为这是个变量fprintf(fp,"%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d",main_actors.level,main_actors.hp,main_actors.maxhp,main_actors.sp,main_actors.maxsp,main_actors.str,main_actors.dex,main_actors.img,main_actors.agi,main_actors.gold,main_actors.exp,main_actors.yellow,main_actors.blue,main_actors.red,main_actors.green);fclose(fp);}system("cls");maps(floor);printf("当前你所在楼层为:%d层\n",floor);
}//对相应层进行读档的具体处理
void load_sub(mota floor[][MAP_L],FILE *fp)
{//所谓的存档就是将当前地图信息保存下来for(int i=0;i<MAP_L;i++){for(int j=0;j<MAP_L;j++){fscanf(fp,"%d,%d ",&floor[i][j].info,&floor[i][j].sub);}}
}//判断处于什么层,并对每层进行读档
void load_floor(int floor,FILE *fp)
{switch(floor){case 1:load_sub(floor001,fp);break;case 2:load_sub(floor002,fp);break;case 3:load_sub(floor003,fp);break;}
}//对每层进行搜索,寻找主角的位置
void poistion_sub(mota floor[][MAP_L],int *x,int *y)
{//寻找位置,如果找到将对应二维数组下标赋值给x,y坐标轴for(int i=0;i<MAP_L;i++){for(int j=0;j<MAP_L;j++){if(floor[i][j].info==6){*x=i;*y=j;goto a;}}}a:;
}//判断处于什么层,并对每层进行位置搜索
void get_poistion(int floor,int *x,int *y)
{switch(floor){case 1:poistion_sub(floor001,x,y);break;case 2:poistion_sub(floor002,x,y);break;case 3:poistion_sub(floor003,x,y);break;}
}//读档功能实现,读档后因为原来的主角位置会更改,必须对其重新定位
int loading_game(int floor,int *x,int *y)
{int n;char load[4],mosa[16]="motaSave",txt[5]=".txt";FILE *fp;system("cls");cout<<"你需要读第几个位置?(1-100):";cin>>n;//数字转换为字符串后,然后与mosa连接,之后mosa再和后缀名连接itoa(n,load,10);strcat(mosa,load);fp=fopen(strcat(mosa,txt),"r");if(fp==NULL){cout<<"此位置没有相应存档,无法读档,按任意键返回游戏"<<endl;system("pause");}else{for(int i=1;i<CURRENT_FLOOR;i++){load_floor(i,fp);}/*由于fscanf是不记录换行符的,所以需要换个思路.之前通过固定长度保存主角信息,然后就可以通过fseek将指针跳转到末尾然后往前移动8*数据项即可*/fseek(fp,-120,SEEK_END);fscanf(fp,"%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d",&main_actors.level,&main_actors.hp,&main_actors.maxhp,&main_actors.sp,&main_actors.maxsp,&main_actors.str,&main_actors.dex,&main_actors.img,&main_actors.agi,&main_actors.gold,&main_actors.exp,&main_actors.yellow,&main_actors.blue,&main_actors.red,&main_actors.green);//寻找主角当前位置,找到后将相应坐标轴赋值给x,y;for(int i=1;i<CURRENT_FLOOR;i++){get_poistion(i,x,y);}}system("cls");//游戏开始后才会执行地图刷新,或者找到对应的读档,调入相应读档if(start==1 ||(start==0 && fp!=NULL)){maps(floor);printf("当前你所在楼层为:%d层\n",floor);start=1;return 1;}fclose(fp);return 0;
}int main(void)
{int choose,sub_choose1,floor=1,x,y,res;char ch;//处理游戏开始界面设计a:cout<<"****************************************"<<endl;cout<<"*"<<setw(38)<<" "<<"*"<<endl;cout<<"*"<<setw(38)<<"龙族(Dragon Raja)           "<<"*"<<endl;cout<<"*"<<setw(38)<<" "<<"*"<<endl;cout<<"****************************************"<<endl;string str[] ={"龙的世界","传奇继续","退出游戏","游戏说明",};//通过coolfun命名空间内的子函数返回的值判断选择第几项choose=coolfun::switch_case(4, str);//这里加载完初始地图后,设置主角初始位置后,跳转到游戏开始处if(choose==1){x=13,y=7;system("cls");floor001[x][y].info=6;floor001[x][y].sub=0;maps(floor);//游戏开始标记start=1;cout<<"当前你所在楼层为:1层";}else if(choose==2){res=loading_game(floor,&x,&y);if(!res){goto a;}}//退出游戏else if(choose==3){exit(0);}//游戏说明else if(choose==4){system("cls");cout<<"是否需要直接显示说明?(0代表一次行显示完毕,其他代表按一次出现一部分)";cin>>sub_choose1;system("cls");description(sub_choose1);system("cls");//显示完毕后回到开始处goto a;}//在计算机中所谓的动态本质是重新刷新屏幕内容,这里就是每走一步刷新一次while(true){ch=getch();if (ch==-32){ch=getch();switch (ch){case UP://这里必须使用地址,通过传递地址方法来改变主函数横坐标纵坐标floor=mota_move(1,&x,&y,floor);break;case DOWN:floor=mota_move(2,&x,&y,floor);break;case LEFT:floor=mota_move(3,&x,&y,floor);break;case RIGHT:floor=mota_move(4,&x,&y,floor);break;default:break;}}switch(ch){//按'X'键调出怪物手册查看case 'x':    case 'X':floor=monster_manual(floor);break;//按'S'键进行存档,按'L'键进行读档case 's':    case 'S':saving_game(floor);break;    case 'l':    case 'L':loading_game(floor,&x,&y);break;}}
}

test.cpp(魔塔中怪物特殊属性简易实现)

using namespace std;
/*由于是伪代码做大概的实现,会大量使用全局变量,怎么方便怎么来,不考虑程序健壮性,
可扩展性等.在真正的程序中这样做容易因为冲突造成bug的,我游戏主文件main.cpp就没
这么来.后面的注释会比较少,因为和主文件相同,具体含义对照主文件main.cpp*/
int x=1,y=1;
char ch;
int zhongdu=0,suairuo=0,jiangu=3,xiangong=4,lianji=1;
int wudi=1,wudinum=8,mofang=9,chouhen=0;
typedef struct
{int id;int hp;int str;int dex;int gold;int exp;int pro;//怪物拥有的状态,0为没有状态,其实可以有多个状态,由于采用伪代码无需考虑int state;char name[20];
}enemy_test;
actors main_tmp={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
enemy_test enemy_data[AMOUNT_TEST]={{0,66,1,1,1,0,0,1,"中毒怪"},{1,66,1,1,1,0,0,2,"衰弱怪"},{2,66,1,1,1,0,0,3,"吸血怪"},{3,66,850,1,1,0,0,4,"坚固怪"},{4,66,888,1,1,0,0,5,"先攻怪"},{5,999,9999,1,1,0,0,6,"魔攻怪"},{6,999,9999,1,1,0,0,7,"连击怪"},{7,66,1,1,1,0,0,8,"破甲怪"},{8,66,1,1,1,0,0,9,"无敌怪"},{9,9999,1,1,1,0,0,10,"模仿怪"},{10,66,1,1,1,0,0,11,"反击怪"},{11,66,1,1,1,0,0,12,"净化怪"},{12,66,1,1,1,0,0,13,"自爆怪"},{13,66,1,1,1,0,0,14,"退化怪"},{14,66,1,1,1,0,0,15,"仇恨怪"}
};
mota floortest[MAP_TEST][MAP_TEST]={{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}},{{1,0},{6,0},{0,0},{0,0},{0,0},{2,0},{0,0},{0,0},{0,0},{1,0}},{{1,0},{0,0},{2,1},{0,0},{0,0},{0,0},{2,2},{0,0},{0,0},{1,0}},{{1,0},{0,0},{0,0},{2,3},{0,0},{0,0},{0,0},{2,4},{0,0},{1,0}},{{1,0},{0,0},{0,0},{0,0},{2,5},{0,0},{0,0},{0,0},{2,6},{1,0}},{{1,0},{2,7},{0,0},{0,0},{0,0},{2,8},{0,0},{0,0},{0,0},{1,0}},{{1,0},{0,0},{2,9},{0,0},{0,0},{0,0},{2,10},{0,0},{0,0},{1,0}},{{1,0},{0,0},{0,0},{2,11},{0,0},{0,0},{0,0},{2,12},{0,0},{1,0}},{{1,0},{0,0},{0,0},{0,0},{2,13},{0,0},{0,0},{0,0},{2,14},{1,0}},{{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0},{1,0}}
};//由于这里仅做怪物属性测试,这里把原来的很多功能砍掉了,具体主文件map_sub
void maps()
{for(int i=0;i<MAP_TEST;i++){for(int j=0;j<MAP_TEST;j++){switch(floortest[i][j].info){case 0:cout<<setw(3)<<" ";break;case 1:cout<<setw(3)<<"*";break;case 2:for(int k=0;k<AMOUNT_TEST;k++){if(k==floortest[i][j].sub){cout<<setw(3)<<floortest[i][j].sub;break;}}break;case 6:cout<<setw(3)<<"主";break;}}printf("\n");}printf("\n生命:%d,最大生命:%d,力量:%d,灵巧:%d,护盾:%d\n",main_actors.hp,main_actors.maxhp,main_actors.str,main_actors.dex,main_actors.img);
}//处理打怪时候碰到的怪物属性处理
void enemy_state(int state,int count)
{switch(state){//中毒属性,每走一步损失一定血量case 1:zhongdu=1;break;//衰弱状态,使得主角攻防和护盾损失一定比例,这里设定为减少25%case 2:if(!suairuo && count==1){main_tmp.str=main_actors.str*0.75;main_tmp.dex=main_actors.dex*0.75;main_tmp.img=main_actors.img*0.75;suairuo=1;}else if(suairuo && count==2){main_actors.str=main_tmp.str;main_actors.dex=main_tmp.dex;main_actors.img=main_tmp.img;}break;//吸血状态,战斗开始时吸取一定体力,这里设定吸取主角血量的30%case 3:if(count==1){main_actors.hp*=0.7;}break;//坚固状态,意思是当怪物防御<勇士攻击的时候,怪物防御变为勇士攻击-1case 4:if(enemy_data[jiangu].dex+1<main_actors.str){enemy_data[jiangu].dex=main_actors.str-1;}break;//先攻状态,所谓先攻就是怪物首先攻击一次,加一次对应值伤害就行case 5:if(enemy_data[xiangong].str<=main_actors.dex){main_actors.hp-=0;}else{main_actors.hp-=enemy_data[xiangong].str-main_actors.dex;}break;//魔攻状态,所谓魔攻就是无视对手防御,相当于主角防御为0case 6:if(count==1){main_tmp.dex=main_actors.dex;main_actors.dex=0;}else if(count==2){main_actors.dex=main_tmp.dex;}break;//连击状态就是判断每回合多攻击n-1次case 7:if(count==1){lianji=2;}else if(count==2){lianji=1;}break;//破甲状态判定为对主角造成主角防御*倍数,这里设定倍数为1case 8:if(count==1){main_actors.hp-=main_actors.dex;}break;//无敌状态,就是主角在没有获得相应道具时无法击败该怪物case 9:if(wudi==1 && count==1){main_tmp.str=enemy_data[wudinum].str;main_tmp.dex=enemy_data[wudinum].dex;enemy_data[wudinum].str=9999999;enemy_data[wudinum].dex=9999999;}else if(wudi==1 && count==2){enemy_data[wudinum].str=main_tmp.str;enemy_data[wudinum].dex=main_tmp.dex;}break;//模仿状态,当怪物的攻击或防御小于主角时,复制其属性case 10:if(enemy_data[mofang].str<main_actors.str){enemy_data[mofang].str=main_actors.str;}if(enemy_data[mofang].dex<main_actors.dex){enemy_data[mofang].dex=main_actors.dex;}break;//反击状态判定为对主角造成主角攻击*倍数,这里设定倍数为1case 11:if(count==1){main_actors.hp-=main_actors.str;}break;//净化状态判定为对主角造成主角护盾*倍数,这里设定倍数为1case 12:if(count==1){main_actors.hp-=main_actors.img;}break;//自爆状态的意思为战斗后将主角的血量变为1case 13:if(count==2){main_actors.hp=1;}break;//退化状态,战斗后使得主角攻防和护盾相应减少,这里设定攻防-3,护盾-20case 14:if(count==2){main_actors.str-=3;main_actors.dex-=3;main_actors.img-=20;}break;//仇恨状态,额外增加仇恨值作为伤害,然后/2.仇恨值来源这里设定每打一个怪物+3case 15:if(count==1){main_actors.hp-=chouhen;chouhen/=2;}break;}
}//这里是抽象级别高的伪代码,就是大概表示状态在魔塔里面如何消除
/*
if(use_item(解毒药水)){zhongdu(中毒状态)=0;
}
if(use_item(解衰药水)){suairuo(衰弱状态)=0;main_actors.str*=1.333333;main_actors.dex*=1.333333;main_actors.img*=1.333333;
if(use_item(圣十字架)){wudi(无敌状态)=0;
}
*///对应主文件battle
void battle_test(int id)
{enemy_state(enemy_data[id].state,1);//连击次数决定执行几次for(int i=0;i<lianji;i++){if(enemy_data[id].str<=main_actors.dex){main_actors.hp-=0;}else if(main_actors.str<=enemy_data[id].dex){main_actors.hp-=9999999;}else{if(enemy_data[id].hp%(main_actors.str-enemy_data[id].dex)==0){if((enemy_data[id].hp/(main_actors.str-enemy_data[id].dex)-1)*(enemy_data[id].str-main_actors.dex)-main_actors.img<0){main_actors.hp-=0;goto a;}main_actors.hp-=(enemy_data[id].hp/(main_actors.str-enemy_data[id].dex)-1)*(enemy_data[id].str-main_actors.dex)-main_actors.img;}else{if(enemy_data[id].hp/(main_actors.str-enemy_data[id].dex)*(enemy_data[id].str-main_actors.dex)-main_actors.img<0){main_actors.hp-=0;goto a;}main_actors.hp-=enemy_data[id].hp/(main_actors.str-enemy_data[id].dex)*(enemy_data[id].str-main_actors.dex)-main_actors.img;}}}chouhen+=3;enemy_state(enemy_data[id].state,2);a:if(main_actors.hp<=0){system("cls");cout<<"无敌怪确实无敌,测试成功"<<endl;exit(0);}
}//对应主文件floor_sub,也是砍掉了很多处理
void floor_test(int *action)
{int tips=0;if(floortest[x][y].info!=1){switch(*action){case 1:floortest[x+1][y].info=0;floortest[x+1][y].sub=0;break;case 2:floortest[x-1][y].info=0;floortest[x-1][y].sub=0;break;case 3:floortest[x][y+1].info=0;floortest[x][y+1].sub=0;break;case 4:floortest[x][y-1].info=0;floortest[x][y-1].sub=0;break;}}switch(floortest[x][y].info){case 1:switch(*action){case 1:x+=1;break;case 2:x-=1;break;case 3:y+=1;break;case 4:y-=1;break;}break;case 2:battle_test(floortest[x][y].sub);break;}system("cls");floortest[x][y].info=6;floortest[x][y].sub=0;//中毒的时候每走1步掉血,这里设定为10;if(zhongdu){main_actors.hp-=10;}maps();
}//对应主文件mota_move
void mota_move(int action)
{switch(action){case 1:x-=1;floor_test(&action);break;case 2:x+=1;floor_test(&action);break;case 3:y-=1;floor_test(&action);break;case 4:y+=1;floor_test(&action);break;}
}
int main(void)
{main_actors.hp=114514;main_actors.maxhp=1919810;main_actors.str=888;main_actors.dex=666;main_actors.img=8848;maps();while(true){ch=getch();if (ch==-32){ch=getch();switch (ch){case UP:mota_move(1);break;case DOWN:mota_move(2);break;case LEFT:mota_move(3);break;case RIGHT:mota_move(4);break;default:break;}}}return 0;
}

http://chatgpt.dhexx.cn/article/g30MBDID.shtml

相关文章

C语言入门(什么是C语言,C语言的编程机制以及一些基础计算机概念)

目录 一.什么是C语言 1.面向对象&#xff1a; 2.面向过程&#xff1a; 二.C语言特点 三.C语言开发时间 四.环境搭建 1.代码编辑器 2.C编译器 五.C语言标准 六.计算机补充知识 1.计算机构成 2.CPU工作 3.编译器 七.编写程序步骤 八.源文件&#xff0c;目标文件&a…

C语言 09.文件

读写文件与printf、scanf关联 printf – 屏幕 – 标准输出 scanf – 键盘 – 标准输入 perror – 屏幕 – 标准错误 系统文件&#xff1a;&#xff08;打开和关闭由系统自动执行&#xff09; 标准输入 – stdin – 0 一旦关闭了&#xff0c;scanf就不可以使用 标准输出 – s…

C语言完整知识体系总结

C语言的知识体系总结 这里写目录标题 C语言的知识体系总结序言&#xff1a;C语言的概述历史、特点、标准&#xff09;1、嵌入式开发为什么选择C语言&#xff1f;&#xff08;面试题&#xff01;&#xff09;2、为什么内核开发选择C语言&#xff1f;3、C语言的缺点&#xff1a;a…

【C语言篇】初识C语言

友情链接&#xff1a;C/C系列系统学习目录 知识总结顺序参考C Primer Plus&#xff08;第六版&#xff09;和谭浩强老师的C程序设计&#xff08;第五版&#xff09;等&#xff0c;内容以书中为标准&#xff0c;同时参考其它各类书籍以及优质文章&#xff0c;以至减少知识点上的…

C语言学习笔记 1

中央处理器&#xff08;CPU&#xff09;: 包括运算器、控制器、寄存器 Enum&#xff1a; Enum: 枚举类型 System函数&#xff1a; 执行系统命令。如pause、cmd、calc、mspaint、notepad..... System(“pause”);//暂停 System(“calc”);//打开计算机 System(“cls”);//…

C语言:文件操作

标题 文件什么是文件&#xff1a; 文件指针文件缓冲区操作文件1.文件的打开与关闭函数2.文本行输入输出函数3.格式化输入输出函数&#xff08;有格式的数据输入到文件中&#xff09;4.二进制输入输出调整文件指针位置6.ftell与rewind7.文件结束的判定 程序退出&#xff0c;内存…

c语言字节写入文件,C语言文件操作

所谓文件(file)一般指存储在外部介质上数据的集合,比如我们经常使用的mp3、mp4、txt、bmp、jpg、exe、rmvb等等。这些文件各有各的用途,我们通常将它们存放在磁盘或者可移动盘等介质中。那么,为什么这里面又有这么多种格式的文件呢?原因很简单,它们各有各的用途,区分就在…

C语言之编程基础

学自C语言中文网 编程基础 一.编程语言二. C语言的地位三.C语言是菜鸟和大神的分水岭三.C语言和C的关系四.数据在内存中的存储五.载入内存&#xff0c;让程序运行起来六.虚拟内存七. ASCII编码 一.编程语言 通过使用某种“语言”的固定格式和固定词汇来控制计算机的行为, 而这…

matlab中的graythresh函数的实例

一个图足以说明所有问题&#xff0c;代码实例加输出结果&#xff0c;说明一个问题&#xff0c;graythresh()函数还是很不错的&#xff01; 如果还想跟精确&#xff0c;就在该阈值的周围在试几组数组&#xff0c;找到自己觉得最优的阈值

基于MATLAB的数字图像分割的研究与实现

1 绪论 1.1 图像分割的研究背景 在一幅目标图像下&#xff0c;人们往往只是关注其中的一个或者几个目标&#xff0c;而这些目标必然会占据一定的区域&#xff0c;并且与周围其他目标或背景在一些特征上会有相应的差别。但是&#xff0c;很多时候这些差别会非常的细微&#xff0…

一天一个小技巧(4)——利用Python和MATLAB进行图片二值化

转载请注明作者和出处&#xff1a;https://blog.csdn.net/qq_28810395 Python版本&#xff1a; Python3.x 运行平台&#xff1a; Windows 10 IDE&#xff1a; Pycharm profession 2019 Matlab2010a 一、前言 由于在一些软件和处理上都仅要求是黑白图片&#xff0c;那么掌握一种…

matlab nlfilter 填充方式,MatLab实验步骤(超杰版)

图像变换--灰度调整、滤波增强---二值化(阈值分割)--形态学处理--特征提取 一&#xff1a;色彩变换&#xff0c;从颜色上控制思考怎样应用灰度的变换、滤波变换减少地物 直方图均衡化histeq自适应均衡化adapthisteq灰度调节(灰度映射)imadjust【直接变换、非线性变换、灰度映射…

基于Matlab车牌自动识别

灰度变换 灰度图是指将黑色和白色以对数的关系分为256阶的图像。灰度化处理就是将彩色图片通过处理转化为灰度图的过程。彩色图像一般由三个独立色组成&#xff0c;R、G、B三个分量分别显示出红、绿、蓝。灰度化处理就是使R、G、B三个分量相等。灰度值为255的点就是白色&#x…

【MATLAB Image Processing Toolbox 入门教程七】“导入、导出和转换”之“图像类型转换Ⅱ——使用阈值法转换为二值图像”

【MATLAB Image Processing Toolbox 入门教程七】 1 imbinarize函数1.1 imbinarize函数使用语法及说明1.2 imbinarize函数参数说明1.3 imbinarize函数使用示例1.3.1 使用全局阈值和局部自适应阈值对图像二值化1.3.2 检测前景比背景暗的图像 2 graythresh函数3 otsuthresh函数4 …

matlab graythresh3,Matlab—影像分析进阶

在这篇文章里面我们要做的事情全部都围绕两个问题,一个图像当中有多少个 xxx,他们的大小是多少,举个例子 上图是一个米的影像,这张图片里有很多的米,现在我们的问题是,这里面有多少米,他们的大小是多少? graythresh() & im2bw() 要回答上述两个问题,首先要做的是对…

matlab graythresh()函数使用的注意点

用matlab进行图像处理时&#xff0c;经常会遇到设置一个阈值将灰度图处理为二值图的情况。 一般都会这样子 Iimread(1.jpg); levelgraythresh(I); BWim2bw(I,level); 这里就有一个小坑了 比如1.jpg这个读入的图片&#xff0c;每一个像素位置的灰度都是整型。但是你强行转成了…

2.23学习心得 Matlab graythresh函数 形态学开闭操作,腐蚀膨胀的作用

关于graythresh 函数 函数功能&#xff1a;使用 最大类间方差法找到图片的一个合适的阈值&#xff08;threshold&#xff09;。在使用 im2bw函数将灰度图像转换为二值图像时&#xff0c;需要设定一个阈值&#xff0c;这个函数可以帮助我们获得一个合适的阈值。利用这个阈值通常…

graythresh

使用Otsu方法计算全局图像阈值 语法&#xff1a; T graythresh(I) [T,EM] graythresh(I)说明&#xff1a; T graythresh(I) 使用 Otsu 方法 &#xff0c; 根据灰度图像 I 计算全局阈值 T。Otsu 方法选择一个阈值&#xff0c;使阈值化的 黑白像素的类内方差最小化。全局阈值…

软件测试题目

单项选择题&#xff1a;共20小题&#xff0c;每小题1 分&#xff0c;满分20分&#xff1b;请将答案填入题后括号中。 1. 在软件生命周期的哪一个阶段&#xff0c;软件缺陷修复费用最低 &#xff08; A &#xff09; &#xff08;A&#xff…

软件测试题1

单选题 1、系统测试使用&#xff08;C&#xff09;技术, 主要测试被测应用的高级互操作性需求, 而无需考虑被测试应用的内部结构。 A、 单元测试 B、 集成测试 C、 黑盒测试 D、白盒测试 2、单元测试主要的测试技术不包括&#xff08;B &#xff09; A、白…