简介
本着开源的精神,我分享下我做的数据结构大作业,我当时选择的是游戏设计题目,由于魔塔基础的机制不太复杂,所以就借着大作业设计了个简易的魔塔游戏。
这是游戏界面:
以下是我当时大作业内容:
我把大作业文档精简下,简单说明下,为了让读者能更好的看懂程序内容。
程序内函数介绍:
(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;
else
nowi = case_num;
}break;
case 80: //下
{
if (nowi < case_num) //在最后一个按下键会到第一个
++nowi;
else
nowi = 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;
//坚固状态,意思是当怪物防御<勇士攻击的时候,怪物防御变为勇士攻击-1
case 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;
//魔攻状态,所谓魔攻就是无视对手防御,相当于主角防御为0
case 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;
//破甲状态判定为对主角造成主角防御*倍数,这里设定倍数为1
case 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;
//反击状态判定为对主角造成主角攻击*倍数,这里设定倍数为1
case 11:
if(count==1)
{
main_actors.hp-=main_actors.str;
}
break;
//净化状态判定为对主角造成主角护盾*倍数,这里设定倍数为1
case 12:
if(count==1)
{
main_actors.hp-=main_actors.img;
}
break;
//自爆状态的意思为战斗后将主角的血量变为1
case 13:
if(count==2)
{
main_actors.hp=1;
}
break;
//退化状态,战斗后使得主角攻防和护盾相应减少,这里设定攻防-3,护盾-20
case 14:
if(count==2)
{
main_actors.str-=3;
main_actors.dex-=3;
main_actors.img-=20;
}
break;
//仇恨状态,额外增加仇恨值作为伤害,然后/2.仇恨值来源这里设定每打一个怪物+3
case 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;
}